@wix/himalaya-cli 0.1617.0 → 0.1619.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-config.d.ts +13 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +15 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +30 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/index.d.ts +2 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/one-app-section.d.ts +39 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/one-app-visibility.d.ts +22 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/oneapp.d.ts +3 -2
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +1 -0
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +151 -24
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +8 -0
  11. package/dist/cli.mjs +2171 -407
  12. package/dist/preview-assets.generated.bin +0 -0
  13. package/guides/android-ux.md +66 -29
  14. package/guides/component-reference.json +15 -3
  15. package/guides/component-reference.md +28 -3
  16. package/guides/designing-himi-screens.md +46 -11
  17. package/guides/getting-started.md +2 -1
  18. package/guides/hhp.md +3 -1
  19. package/guides/himi-json-output.md +11 -1
  20. package/guides/himi-skill.md +2 -1
  21. package/guides/index.json +1 -1
  22. package/guides/ios-ux.md +110 -31
  23. package/guides/mobile-ux.md +22 -5
  24. package/guides/one-app-section-mappings.md +32 -0
  25. package/guides/one-app-to-himi-app.md +133 -9
  26. package/guides/skill.md +25 -1
  27. package/guides/tier-5-add-a-screen.md +2 -0
  28. package/package.json +2 -2
  29. package/templates/brandy/dev/index.ts +4 -3
Binary file
@@ -1,39 +1,76 @@
1
1
  # Designing a Himi App for Android
2
2
 
3
- Apply the official [Android design guidance](https://developer.android.com/design) throughout
4
- planning, implementation, and review. Keep the product intent shared with iOS; use Android-native
5
- expectations when they materially change navigation, back behaviour, feedback, or adaptation.
3
+ Read this before you write `MOBILE-UX.md`, and again before you compose a screen. Each pattern
4
+ below is a decision, the Himi field that implements it, and the Android page it comes from. Read
5
+ it with `himi docs ios-ux` (https://apps.wix.com/_api/himi-serve/docs/ios-ux.md): iPhone and Android
6
+ render the **same** descriptor, so a decision you make here is made for iPhone too.
6
7
 
7
- ## Plan
8
+ ## What Android does for you, and what it cannot do
8
9
 
9
- - Define the Android back path for every flow, especially sheets, search, selection, and forms.
10
- - Design for compact and expanded windows from the start; Android's
11
- [adaptive layout guidance](https://developer.android.com/design/ui/mobile/guides/layout-and-content/adapt-layout)
12
- is the reference.
13
- - Make loading, empty, error, permission, offline, and destructive states actionable.
14
- - Plan TalkBack, scalable text, contrast, reduced motion, RTL, and touch targets. Use Android's
15
- [accessibility guidance](https://developer.android.com/design/ui/mobile/guides/foundations/accessibility)
16
- for the current requirements.
10
+ A Himi screen is one descriptor that the Android shell renders with Jetpack Compose and
11
+ Material 3. Three things are native without any work from you, and you should not redraw them:
17
12
 
18
- ## Author
13
+ - **Chrome the shell draws:** the navigation bar at the bottom, the top app bar and its title,
14
+ system and predictive Back, the search bar, bottom sheets and dialogs.
15
+ - **Controls the renderer draws:** `Toggle` is a Material switch, `SegmentedControl` a
16
+ segmented button, `Picker`/`DatePicker`/`Menu`/`BottomSheet`/`AlertDialog` the Material ones.
17
+ - **Your type scale:** typography tokens are emitted in `sp` and follow the user's font scale.
19
18
 
20
- - Express intent through Himi semantic components and tokens; let the Android renderer supply the
21
- native interaction model rather than recreating Material chrome in the descriptor.
22
- - Prefer responsive grids, split views, and shell navigation. Use `defineView(surface)` only when
23
- the screen's job differs by surface.
24
- - Treat `@himalaya/app-info` as an exceptional, asynchronous behavioural fallback—not a layout or
25
- theme selector. Use capability availability for platform feature support.
19
+ One thing it **cannot** do: give Android a different layout from iPhone. Lists, cards and inset
20
+ sections draw the same on every renderer. Choose one structure and say so
21
+ in `MOBILE-UX.md`. Per-platform *look* — type face, corner radius, spacing — is the
22
+ `material` token overlay (see **Idiom overlay** in `himi docs ios-ux`), not a second screen.
23
+ The overlay may change type face, radius and spacing; it may not change a brand colour.
24
+ `himi render --platform android` changes the switch, the segmented button, and the type face.
25
+ A list or a grid looks the same in the iPhone PNG. That is one descriptor, not a failed render.
26
26
 
27
- ## Audit
27
+ ## Patterns
28
+
29
+ | Decision | Do this on Android | Himi mapping | Android source |
30
+ | --- | --- | --- | --- |
31
+ | Top-level destinations | Navigation bar of three to five destinations on a phone. | `navigation` tabs in `config.ts` (`himi docs app-config`); the shell draws the bar. | [Navigation bar](https://m3.material.io/components/navigation-bar/guidelines) |
32
+ | Screen title | Top app bar with the screen title. | Set the screen's `title`. Do not repeat it, or the app name, as Text in the body in any style. Today's Android shell leaves the top-bar title slot empty, so a body title would show twice on iPhone and still not as a bar title here. A group's name is the `Section` `header`. A render with no large heading at the top is correct when `title` is set. `himi render --platform android` draws the Material switch and segmented button. | [Top app bar](https://m3.material.io/components/top-app-bar/guidelines) |
33
+ | Back | System Back, including the predictive back gesture, closes transient UI before it leaves the screen. | The shell handles Back. Plan the order in `MOBILE-UX.md`: an open sheet, search or selection closes first. | [Predictive back](https://developer.android.com/design/ui/mobile/guides/patterns/predictive-back) |
34
+ | Completing a flow (Book, Pay, Confirm) | One filled button at the bottom of the content. | `stickyFooter` holding one `Button` with `style: "primary"` and `fullWidth: true`. The same footer serves iPhone. | [Buttons](https://m3.material.io/components/buttons/guidelines) |
35
+ | Creating, adding, editing, saving | An action in the top app bar. | `topBar.items: [{ id, placement: "trailing", label, icon, onTap, role: "confirm" }]`. It is a top-app-bar action here and a bar button on iPhone. | [Top app bar](https://m3.material.io/components/top-app-bar/guidelines) |
36
+ | Floating action button | Material's create affordance, but a shared screen also draws it on iPhone. | Prefer the `topBar.items` action. Use `Fab` only when the app is deliberately Android-first, and write down that iPhone shows it too. | [FAB](https://m3.material.io/components/floating-action-button/guidelines) |
37
+ | Collections people choose by photo (products, books, portfolio, projects) | A lazy grid of image tiles, or a carousel of covers; the tile is the tap target. | `Grid` `columns: 2` (`columnsMedium: 3`, `columnsExpanded: 4`); a gallery `columns: 3`, `spacing: 2`; a shelf a horizontal `LazyList` sized so the next cover peeks at 360dp. Each tile is an `Image` (`fillWidth`, fixed `aspectRatio`, `contentMode: "fill"`, `placeholder`, `contentDescription`) with the name and price under it and `modifiers.onTap` on the tile; no `Card` or button per tile. The same grid renders on iPhone. | [Lazy grids](https://developer.android.com/develop/ui/compose/lists#lazy-grids), [Carousel](https://m3.material.io/components/carousel/guidelines) |
38
+ | Lists and grouped content | List rows with supporting text; group related settings under a header. | `LazyList` of `ListItem` rows for a schedule or a service menu; `Section` (`header`, optional `inset: true`) for a group. Do not wrap each row in a `Card`. One featured object may be a single `Card`. Choices that may not fit the width, such as times, go in a `LazyList` with `horizontal: true`. The same structure renders on iPhone. | [Lists](https://m3.material.io/components/lists/guidelines) |
39
+ | On/off, one of a few, pick a date | Switch, segmented button, menu or dialog picker. | `Toggle` (`label`, `value`): the `label` is the row drawn beside the switch. `SegmentedControl` with `options: [{ id, label }]` — `label` is required, and `title` on an option fails native decode and drops the whole screen. `Picker`, `DatePicker`. Group the rows in a `Section`, not a `Card`. | [Switch](https://m3.material.io/components/switch/guidelines), [Segmented buttons](https://m3.material.io/components/segmented-buttons/guidelines) |
40
+ | A short task on top of a screen | A modal bottom sheet, dismissed by Back or the scrim. | `BottomSheet`. A new destination is a navigation instead. | [Bottom sheets](https://m3.material.io/components/bottom-sheets/guidelines) |
41
+ | Destructive or blocking decision | A dialog that names the action. | `AlertDialog`; confirm or offer undo. | [Dialogs](https://m3.material.io/components/dialogs/guidelines) |
42
+ | Success | A snackbar above the navigation bar. | `ui.snackbar` from the worker. | [Snackbar](https://m3.material.io/components/snackbar/guidelines) |
43
+ | Search | A search bar in the top app bar, or a dedicated search screen. | `topBar.searchable` plus the page's own `SearchField` (or the row that holds it) with `hiddenPath: "$env:topBarSearch"`. The standard app shell draws that field; only a host with a native search handler hides it for the bar. A dedicated screen when search starts a new task (`himi docs recipe-search`). | [Search](https://m3.material.io/components/search/guidelines) |
44
+ | Tablets, foldables, wide windows | List and detail side by side from the expanded width class (840dp and wider); a readable column for reading. | `SplitView`, `Grid` `columnsMedium`/`columnsExpanded`, `contentMaxWidth`. Render at 840px or wider and record the choice. | [Adaptive layouts](https://developer.android.com/design/ui/mobile/guides/layout-and-content/adapt-layout) |
45
+ | Type and shape | The Material type scale and shape scale, following the user's font size. | Typography tokens from the built-in ramp, no raw sizes; a `material` overlay in `tokens.json` `idiom` with `"$system"` (Roboto) and Material radii, turned on by `idiom: { policy: "follow-surface" }` in `config.ts`. | [Typography](https://m3.material.io/styles/typography/applying-type) |
46
+ | Touch targets | At least 48×48dp. | Use `Button`/`ListItem`; do not shrink a hit area. | [Accessibility](https://developer.android.com/design/ui/mobile/guides/foundations/accessibility) |
47
+ | Edge to edge | Content draws under the system bars; nothing important sits behind them. | The shell applies the insets; keep the last row clear of `stickyFooter`. | [Edge-to-edge](https://developer.android.com/design/ui/mobile/guides/layout-and-content/edge-to-edge) |
48
+
49
+ ## Settings, copied as props
50
+
51
+ A settings screen is one `Section` (`inset: true`, `header`), not a stack of `Card`s, and it has no `title1` Text in the body. A `Toggle`'s `label` is the row the renderer draws beside the switch. A `ListItem` `title` with the same word paints it a second time. A row that drills in stays a `ListItem`.
28
52
 
29
- - Drive the installed Android app—not only a phone-width render—through system Back from transient
30
- UI, typed input and keyboard submit, completion, touch exploration/TalkBack, enlarged text, dark
31
- scheme, error recovery, and compact plus expanded width. Record the result for every declared
32
- state in `MOBILE-UX.md`'s acceptance matrix.
33
- - Check each documented Android variance against `MOBILE-UX.md` and its official source.
53
+ ## Plan
54
+
55
+ In `MOBILE-UX.md` → `## Platform decisions`, fill the Android column of each row from the table
56
+ above: navigation, title, back order, primary action, create/add, lists and grouped content, photo collections,
57
+ controls, sheets and dialogs, search, expanded width, and type/idiom. Give the official URL.
58
+
59
+ Also plan TalkBack labels on every icon and image, font scale, contrast in light and dark,
60
+ reduced motion, right-to-left, and permission, offline and error states.
61
+
62
+ ## Audit
34
63
 
35
- ## Route to official guidance
64
+ Render each primary screen with `--platform android` at a compact width (`--width 360`) and at
65
+ an expanded width (`--width 840`), in light and with `--scheme dark`. Check: the primary action
66
+ in the footer or the top bar, no floating button unless the plan chose it, no hand-drawn
67
+ controls, no heading and no app name added to stand in for the empty top-bar title, a schedule
68
+ made of `ListItem` rows rather than a `Card` per row, products, books and photos as image tiles
69
+ with every image filled and none stretched, a rail that shows part of its next item, a horizontal
70
+ choice row that still shows its last item, and an expanded layout that is either adaptive or recorded as an
71
+ intentional single column.
36
72
 
37
- - [Android design](https://developer.android.com/design)
38
- - [Adaptive layouts](https://developer.android.com/design/ui/mobile/guides/layout-and-content/adapt-layout)
39
- - [Accessibility](https://developer.android.com/design/ui/mobile/guides/foundations/accessibility)
73
+ On an installed Android app, drive system Back from every transient UI, typed input and
74
+ keyboard submit, completion, TalkBack, enlarged text, dark theme, error recovery, and compact
75
+ plus expanded width. Record the result in the `MOBILE-UX.md` acceptance matrix; with no device,
76
+ record `himi ux record --status unavailable` with the reason.
@@ -2,7 +2,7 @@
2
2
  "_comment": "GENERATED by core/web-embed/authoring/gen-reference.mjs from core/ts/src/schema.ts — do not hand-edit. The schema is the source of truth; regenerate on schema changes.",
3
3
  "schemaSource": "core/ts/src/schema.ts",
4
4
  "bindingGrammar": "$service:<service>.<path> (worker state) · $item.<field> (list row) · $flag:<key> (feature flag) · $env:<key> (client environment — #1056). Bindings resolve in the text/value props marked 'binding-aware' below — INCLUDING Modifiers token refs — but only `background` and `border.color`, which are the two whose token a binding actually fills: a worker can drive per-item theming (e.g. a game board's per-square color), not just text. `shadow` is NOT binding-aware on any renderer, and a gradient is driven by its own `backgroundGradientPath` prop resolving to a gradient token NAME, not by a binding inside a token ref — so `shadow: { token: \"$item.x\" }` and `backgroundGradient: { token: \"$item.x\" }` keep the sentinel and silently lose the per-item styling. Colour slots split by LEVEL, not by component: inside modifiers a colour is a TokenRef OBJECT ({ token: \"…\" }), inside props it is a bare TokenName STRING — there is no modifiers.color or modifiers.tint (see the 'Token slots' section in component-reference.md). A few input props render the LITERAL and are NOT binding-aware — notably SearchField.placeholder and Chip.label; SearchField.value is live and binding-aware like other content props. Bindings also CANNOT target strict-enum fields (style, size, alignment, severity, presentation, contentMode, …) — those fail at render, not validation. $env: is client-resolved (no worker) and valid in modifiers.hiddenPath and FragmentSlot.casePath ONLY — keys reduceMotion/colorScheme/sizeClass/isRTL/keyboardVisible, e.g. hiddenPath:\"$env:reduceMotion\" or hiddenPath:\"$env:colorScheme==light\", casePath:\"$env:colorScheme\"; unknown key fails open (shown / defaultCase).",
5
- "componentCount": 88,
5
+ "componentCount": 89,
6
6
  "notImplementedComponents": [
7
7
  "Text",
8
8
  "Icon",
@@ -97,6 +97,15 @@
97
97
  ],
98
98
  "propsSource": "export interface ErrorStateProps {\n /** Default `\"server\"`. An unknown value is treated as `\"server\"`. */\n variant?: \"server\" | \"offline\" | \"permission\" | \"signIn\";\n /** `$service:` binding that selects the variant at runtime; wins over `variant`. */\n variantPath?: string;\n title?: string;\n body?: string;\n /** The CTA's action. Absent → no button. */\n cta?: Action;\n ctaLabel?: string;\n /** `\"screen\"` (default, ~96px from the top) or `\"section\"` (~24px). */\n placement?: \"screen\" | \"section\";\n}"
99
99
  },
100
+ {
101
+ "name": "QrCode",
102
+ "category": "Core",
103
+ "propsType": "QrCodeProps",
104
+ "bindings": [
105
+ "valuePath"
106
+ ],
107
+ "propsSource": "export interface QrCodeProps {\n /** Literal payload (UTF-8). One of `value` / `valuePath` is required. */\n value?: string;\n /** `$service:` / `$item.` binding for the payload; wins over `value` once it resolves to a\n * non-empty string (until then — no worker yet, an empty field — `value` draws). */\n valuePath?: string;\n /** Side length (pt/dp/px), quiet zone included. Default `160`. */\n size?: number;\n /** Default `\"M\"`. A literal over the level's capacity is a validator error. */\n errorCorrection?: \"L\" | \"M\" | \"Q\" | \"H\";\n /** Module colour token. Default `\"textPrimary\"`. */\n color?: string;\n /** Background colour token. Default `\"surfacePrimary\"`. */\n background?: string;\n /** Light margin, in modules. Default `4` (the spec minimum). */\n quietZone?: number;\n}"
108
+ },
100
109
  {
101
110
  "name": "Text",
102
111
  "category": "Core",
@@ -780,8 +789,11 @@
780
789
  "name": "Section",
781
790
  "category": "Structure & disclosure",
782
791
  "propsType": "SectionProps",
783
- "bindings": [],
784
- "propsSource": "export interface SectionProps {\n header?: string;\n footer?: string;\n children?: Component[];\n inset?: boolean;\n}"
792
+ "bindings": [
793
+ "header",
794
+ "footer"
795
+ ],
796
+ "propsSource": "export interface SectionProps {\n /** Section caption — binding-aware (\"$service:\") on web, iOS and Android. */\n header?: string;\n /** Section caption — binding-aware (\"$service:\") on web, iOS and Android. */\n footer?: string;\n children?: Component[];\n inset?: boolean;\n}"
785
797
  },
786
798
  {
787
799
  "name": "DisclosureGroup",
@@ -3,7 +3,7 @@
3
3
  > GENERATED from `core/ts/src/schema.ts` by `core/web-embed/authoring/gen-reference.mjs`.
4
4
  > The schema is the source of truth — regenerate on schema changes; do not hand-edit.
5
5
 
6
- **88 core component types.** Every component body is `{ "package": "himalaya", "type": "<Type>", "props": { … } }`.
6
+ **89 core component types.** Every component body is `{ "package": "himalaya", "type": "<Type>", "props": { … } }`.
7
7
  Binding grammar: `$service:<service>.<path>` (worker state) · `$item.<field>` (list row template) · `$flag:<key>` (feature flag) · `$env:<key>` (client environment — #1056).
8
8
  Bindings resolve in the text/value props marked **binding-aware** below — **including two `Modifiers` token refs**, `background` and `border.color`, which are the only two whose token a binding actually fills: a worker can drive per-item **theming** (e.g. a game board's per-square color, `background: { token: "$item.bg" }`), not just text content. `shadow` and `backgroundGradient` are `TokenRef`s but are **not** binding-aware on any renderer — `shadow: { token: "$item.x" }` keeps the sentinel as a literal token name and silently loses the per-item styling, and a gradient is driven by its own `backgroundGradientPath` prop resolving to a gradient token NAME, not by a binding inside a token ref.
9
9
  `$env:` is **client-resolved** (no worker) and valid in `modifiers.hiddenPath` and `FragmentSlot.casePath` **only** — keys `reduceMotion` / `colorScheme` / `sizeClass` / `isRTL` / `keyboardVisible` (an on-screen keyboard is up — `hiddenPath: "$env:keyboardVisible"` hides a header while typing), with an optional `==`/`!=` comparison (e.g. `hiddenPath: "$env:reduceMotion"`, `hiddenPath: "$env:colorScheme==light"`, `casePath: "$env:colorScheme"`). An unknown key fails open (shown / `defaultCase`).
@@ -38,6 +38,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
38
38
  | `Spacer` | Core | — |
39
39
  | `EmptyState` | Core | — |
40
40
  | `ErrorState` | Core | `variantPath` |
41
+ | `QrCode` | Core | `valuePath` |
41
42
  | `Text` | Core | `text` |
42
43
  | `Image` | Core | `url`, `initials`, `contentDescription` |
43
44
  | `Icon` | Core | — |
@@ -82,7 +83,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
82
83
  | `Popover` | Overlays & menus | `presentedPath` |
83
84
  | `Chip` | Chips & selection | `selectedPath`, `tokenPath` |
84
85
  | `ChipsInput` | Chips & selection | `valuesPath` |
85
- | `Section` | Structure & disclosure | — |
86
+ | `Section` | Structure & disclosure | `header`, `footer` |
86
87
  | `DisclosureGroup` | Structure & disclosure | `expandedPath` |
87
88
  | `LabeledContent` | Structure & disclosure | — |
88
89
  | `Label` | Structure & disclosure | — |
@@ -120,7 +121,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
120
121
 
121
122
  These props are accepted by the schema and pass validation, but the named platform's renderer never reads them — they silently do nothing (no warning, no log). Prefer a prop NOT listed here, or drive the behaviour from a worker.
122
123
 
123
- **Scope: 7 of 88 components, all of them android / web / ios-only** — every other component/platform pair reads every prop it decodes. The list splits in two, and the halves mean different things:
124
+ **Scope: 7 of 89 components, all of them android / web / ios-only** — every other component/platform pair reads every prop it decodes. The list splits in two, and the halves mean different things:
124
125
 
125
126
  - **`category` (2: `MediaPlayer`, `TimeGrid`)** — the prop changes the component's *kind*. These genuinely are **not implemented**: an overlay renders always-visible inline, so the component does not do the job its name implies. Check the row's note for a working alternative.
126
127
  - **`styling` (5: `Text`, `Icon`, `WebView`, `Video`, `PdfView`)** — the component itself **works**; only the listed prop is inert. Use the component; just do not depend on that one prop.
@@ -438,6 +439,28 @@ export interface ErrorStateProps {
438
439
  }
439
440
  ```
440
441
 
442
+ #### QrCode
443
+
444
+ ```ts
445
+ export interface QrCodeProps {
446
+ /** Literal payload (UTF-8). One of `value` / `valuePath` is required. */
447
+ value?: string;
448
+ /** `$service:` / `$item.` binding for the payload; wins over `value` once it resolves to a
449
+ * non-empty string (until then — no worker yet, an empty field — `value` draws). */
450
+ valuePath?: string;
451
+ /** Side length (pt/dp/px), quiet zone included. Default `160`. */
452
+ size?: number;
453
+ /** Default `"M"`. A literal over the level's capacity is a validator error. */
454
+ errorCorrection?: "L" | "M" | "Q" | "H";
455
+ /** Module colour token. Default `"textPrimary"`. */
456
+ color?: string;
457
+ /** Background colour token. Default `"surfacePrimary"`. */
458
+ background?: string;
459
+ /** Light margin, in modules. Default `4` (the spec minimum). */
460
+ quietZone?: number;
461
+ }
462
+ ```
463
+
441
464
  #### Text
442
465
 
443
466
  ```ts
@@ -1542,7 +1565,9 @@ export interface ChipsInputProps {
1542
1565
 
1543
1566
  ```ts
1544
1567
  export interface SectionProps {
1568
+ /** Section caption — binding-aware ("$service:") on web, iOS and Android. */
1545
1569
  header?: string;
1570
+ /** Section caption — binding-aware ("$service:") on web, iOS and Android. */
1546
1571
  footer?: string;
1547
1572
  children?: Component[];
1548
1573
  inset?: boolean;
@@ -18,6 +18,35 @@ prescriptive layer that sits **above** the wiring guide: `himi docs tier-5-add-a
18
18
  > `.agents/skills/himalaya/examples/stocks-dashboard.descriptor.json` into the app — a
19
19
  > fully-composed screen to study.
20
20
 
21
+ **A phone screen, before anything below.** Read `himi docs ios-ux` and `himi docs android-ux` too;
22
+ they decide what a phone screen looks like, and this guide decides how to compose it.
23
+
24
+ - **The title lives in the bar, not the body.** Set the screen's `title`; the shell draws it. Do
25
+ not open the body with the screen title, the app name or a greeting as `largeTitle`/`title1`
26
+ text. `himi render` does not draw the bar, so a PNG that starts with content is correct.
27
+ - **A collection is rows, not a card per item.** A schedule, a service menu, a session list or a
28
+ settings group is `ListItem` rows — in a root `LazyList`, or in one `Section` with `inset: true`
29
+ and a `header`. Time, price, coach or duration go in the row's `subtitle`/`trailing`; a small
30
+ action (Book, Full) goes in `trailing`. One featured object may be a single `Card`.
31
+ - **When people choose by the photo, the photo leads.** Products, books, a portfolio and projects
32
+ are image tiles, not rows with a small thumbnail: a `Grid` with `columns: 2` (`columnsMedium: 3`,
33
+ `columnsExpanded: 4`) for products and projects, `columns: 3` with `spacing: 2` and no captions
34
+ for a photo gallery, and a horizontal `LazyList` of covers for each shelf. A tile is one `Image`
35
+ (`fillWidth: true`, a fixed `aspectRatio`, `contentMode: "fill"`, `placeholder`,
36
+ `contentDescription`) with the name and price as text under it, and the whole tile is the tap
37
+ target (`modifiers.onTap`). No `Card` and no button on each tile. A collection without photos
38
+ stays rows.
39
+ - **Choices that may not fit go sideways.** Days and times are a `LazyList` with
40
+ `horizontal: true`, so the last chip stays reachable at 390 points. Size a rail of covers or
41
+ cards so part of the next one shows at 390 points (a cover about 110 points wide, a card about
42
+ 160), or nothing tells the reader the rail scrolls.
43
+ - **A detail screen opens with its photos.** A product, a book or a project starts with its image
44
+ (a paged `Carousel` when there are several), edge to edge with a fixed height: the root `VStack`
45
+ has no padding, the image is its first child, and the name, price and options follow in a
46
+ `VStack` that carries the 16-point gutter.
47
+ - **The step that finishes the task sits in `stickyFooter`** — one primary `Button`, not a button
48
+ inside the last card.
49
+
21
50
  ---
22
51
 
23
52
  ## 1. The materials at a glance
@@ -58,7 +87,8 @@ Screen root gets consistent padding — `padding: { leading: 16, trailing: 16,
58
87
  `surfacePrimary` root reads well. Tight, even spacing is most of what makes a screen look "designed."
59
88
 
60
89
  ### Typography — use the scale, don't invent
61
- `title1` for the one screen heading, `title3` for a sub-heading/stat, `body` for content, `caption`
90
+ `title1` for a heading inside the body that is not the screen title (a featured card, an empty
91
+ state, an onboarding page — the screen title itself is the screen's `title` field), `title3` for a sub-heading/stat, `body` for content, `caption`
62
92
  for secondary/meta, `bodyEmphasis` when you need weight inside body copy. Do **not** introduce new
63
93
  sizes or weights inline to "make it bigger" — that's a token change, deliberated once, not a per-screen
64
94
  decision.
@@ -190,10 +220,10 @@ tiles; `Card` to group a related cluster onto a raised `surfaceSecondary`; `Sect
190
220
  group; `Divider` for a hairline separation; `Spacer` to push apart within a row; `SplitView` for
191
221
  responsive master/detail. *Reach for `Card` to cluster, not to wrap every single element.*
192
222
 
193
- **Lists & collections** — `LazyList` for long/unbounded scrolls (virtualized); `Grid` for a 2-up gallery
194
- (`columns: 1, columnsMedium: 2`); `Table` for tabular rows; `Carousel`/`Pager` for swipeable panels;
223
+ **Lists & collections** — `LazyList` for long/unbounded scrolls (virtualized); `Grid` for photo tiles
224
+ (`columns: 2, columnsMedium: 3` for products, `columns: 3` for a photo gallery); `Table` for tabular rows; `Carousel`/`Pager` for swipeable panels;
195
225
  `Timeline` for chronological events. Compose rows as `ListItem` / `LabeledContent` / `DisclosureGroup`
196
- cells inside a `Card([VStack(spacing: 0)])`.
226
+ cells in one `Section` with `inset: true` (or directly in a root `LazyList`) — never one `Card` per row.
197
227
  > For a feed/list *screen*, put a vertical worker-bound `LazyList` directly in a plain root
198
228
  > `VStack`, as the screen's only bound list: the root carries padding and a literal background at
199
229
  > most, is leading-aligned, starts with a child that is always shown, and holds no `Spacer` or
@@ -233,7 +263,8 @@ long-form Ricos content rather than flattening it into text rows.*
233
263
  **Inputs & forms** — field controls: `TextField` (with `keyboard`/`autoCapitalization`/`secure`),
234
264
  `SearchField`, `Toggle`, `Stepper`, `Slider`, `SelectionControl` (checkbox/radio),
235
265
  `Picker`/`WheelPicker`, `DatePicker`, `ColorPicker`, `OtpInput`, `ChipsInput`. *Reach for `Toggle` for
236
- on/off, `SelectionControl` for pick-from-set — not a row of `Button`s.*
266
+ on/off, `SelectionControl` for pick-from-set — not a row of `Button`s.* A `Toggle`'s `label` is the
267
+ row drawn beside the switch; a `ListItem` `title` with the same word paints it a second time.
237
268
 
238
269
  **Search** — use a dedicated search screen by default when search starts from a broader context
239
270
  (such as Home or a catalog); keep search inline when it only filters the list already on screen.
@@ -293,7 +324,7 @@ Use a live `title` binding to announce “Adding to cart” while pending; `load
293
324
 
294
325
  **Navigation** — `Tabs` / `SegmentedControl` to switch peer views; `Pager` + `PageControl` for
295
326
  full-width swipe; `StepIndicator` for a wizard's progress; `CollapsingHeader` for a large-title-to-inline
296
- scroll. *Reach for `SegmentedControl` for 2–4 in-content modes; `Tabs`/tab-bar for top-level sections.*
327
+ scroll. *Reach for `SegmentedControl` for 2–4 in-content modes; `Tabs`/tab-bar for top-level sections.* Each option is `{ id, label }`. `label` is required — `title` on an option fails native decode and drops the whole screen. `title` belongs on `Button` and `ListItem`.
297
328
  > **`Pager` sizes each page to its natural content height** (it doesn't stretch to fill), and multi-line
298
329
  > text inside a page **truncates** — so onboarding/carousel slides need *short* titles + an explicit
299
330
  > `maxLines`, and you can't vertically center a slide with a `spacer()` inside the page (put the spacer
@@ -317,7 +348,9 @@ dense row); `ProgressBar` for determinate progress, `ActivityIndicator` for inde
317
348
  a single dial metric. *Use a `Badge` for a count, a `Tag` for a category — they aren't interchangeable.*
318
349
 
319
350
  **Media** — `Image` (`fillWidth`, `height`, always `contentDescription`); `Video`/`Lottie`/`Rive` for
320
- motion media; `Map` for location; `Avatar` for a person; `WebView` only when native can't express it.
351
+ motion media; `Map` for location; `Avatar` for a person; `QrCode` to share a link, ticket or invite
352
+ as a code generated on the device (bind `valuePath`; keep the payload short, since a literal over the
353
+ level's capacity fails validation); `WebView` only when native can't express it.
321
354
  *Fill-width media gets a `cornerRadius` so it doesn't fight the card edges.*
322
355
  For audio apps, keep playback UI in SDUI and call `@himalaya/media-playback` from the Tier-5 worker:
323
356
  compose artwork with `Image`, transport controls with icon buttons, scrubbing with `Slider`, buffering
@@ -337,9 +370,10 @@ Concrete patterns lifted from real usage:
337
370
  - **Row with a leading label and trailing detail** — `hstack({ spacing: 8, children: [ icon, text, spacer(), trailingText ] })`. The `spacer()` does the push; don't hand-pad.
338
371
  - **Fill-width controls share a row** — put fill-width controls in **one** `HStack` and let them share it (weight/flex), rather than stacking `fillMaxWidth` elements. This is a real iOS↔Android parity requirement, not a preference — a full-width control alone in an HStack renders differently across platforms.
339
372
  - **A screen that owns its top bar (search beside back)** — set `topBar: { leading: "none" }` and put the row in `stickyHeader`: `hstack([ iconButton({ icon: "arrow_left", accessibilityLabel: "Back", action: { actionId: "nav.back", presentation: "back" } }), withModifiers(textField({ placeholder: "Search", … }), { weight: 1 }) ])`. With **both** set, every shell draws no bar and no back control of its own (iOS hides the system bar, Android floats no chevron and reserves no band, web hides its bar on every look), so the row is the only top bar. `leading: "none"` without a `stickyHeader` keeps the bar's title and trailing actions but drops the back control (and the hamburger, for that screen). System back — hardware, gesture, edge swipe, browser — still pops. Any other `leading` value keeps today's chrome; demo: `gallery_search_topbar`, linked from the gallery's "Lists & data".
340
- - **Cell list** — `card([ vstack({ spacing: 0, alignment: "leading", children: [ …ListItem cells… ] }) ])`. `spacing: 0` because the cells own their own dividers/padding.
373
+ - **Cell list** — one `Section` with `inset: true` and a `header`, its children the `ListItem` cells; the cells own their own dividers and padding. A schedule, a service menu and a session list are this shape too, not a `Card` around each item.
374
+ - **Photo tile** — `vstack({ spacing: 6, alignment: "leading", children: [ image({ url, fillWidth: true, aspectRatio: 0.8, contentMode: "fill", placeholder: "surfaceSecondary", contentDescription }) + cornerRadius 8, text(name, "body"), text(price, "callout", { color: "textSecondary" }) ] })` with `modifiers.onTap` on the stack, inside `grid({ columns: 2, columnsMedium: 3, columnsExpanded: 4, spacing: 12 })`. A shelf is the same tile at a fixed `width` (about 110) in `lazyList({ horizontal: true, spacing: 12 })`.
341
375
  - **Overlay** — `zstack({ alignment: "center", children: [ backdropImage(cornerRadius 12), withModifiers(label, { padding, background: { token: "accentPrimary" }, cornerRadius: 10 }) ] })`.
342
- - **Responsive** — `grid({ columns: 1, columnsMedium: 2, spacing: 12 })` for 1-up on phone / 2-up on tablet; `splitView({ primary, detail, primaryMaxWidth: 280 })` for master/detail that stacks on a phone; set `contentMaxWidth` in tokens so wide surfaces keep a readable column instead of running edge-to-edge.
376
+ - **Responsive** — `grid({ columns: 1, columnsMedium: 2, spacing: 12 })` for wide cards, 1-up on phone / 2-up on tablet (photo tiles start at 2 on a phone); `splitView({ primary, detail, primaryMaxWidth: 280 })` for master/detail that stacks on a phone; set `contentMaxWidth` in tokens so wide surfaces keep a readable column instead of running edge-to-edge.
343
377
  - **Centered separator, not a flanked divider** — the classic "—— or ——" needs each `divider()` to take equal remaining width, and the HStack row-share only kicks in for controls with an intrinsic fill. **A bare `divider()` inside an HStack is a column separator, not a flanking line**: iOS, macOS, Android, web and React Native all draw it as a thin vertical rule the height of the row that takes no share of the width, which is right for a stats row (`12 | 340 | 87`) and wrong for "—— or ——". For "—— or ——", use a **centered caption**: `hstack({ children: [spacer(), text("or continue with", "caption", { color: "textSecondary" }), spacer()] })`, optionally over a single full-width `divider()`.
344
378
 
345
379
  > **Intrinsic-fill controls grab the row.** `Button` and `TextField` have an intrinsic FILL, so the next four idioms are about NOT letting that surprise you (they were the papercuts every off-repo app hit):
@@ -588,7 +622,7 @@ nicety. Don't animate everything: if every element moves, nothing reads as impor
588
622
  A screen full of `id`s and no `accessibility_label`s is testable but unusable with VoiceOver/TalkBack.
589
623
  It may be bound to worker state with `$service:<service>.<path>`; the web renderer keeps the
590
624
  resulting `aria-label` current as that state changes.
591
- - **Tap targets** stay comfortably large (don't shrink `button`/`icon` hit areas below ~44pt).
625
+ - **Tap targets** stay comfortably large: at least 44pt on iOS and 48dp on Android — don't shrink `button`/`icon` hit areas below that.
592
626
  - **Contrast** comes from the token roles — pair `textPrimary`/`textSecondary` with the right surface,
593
627
  and provide the `highContrast` scheme value for load-bearing colors.
594
628
  - **Reduced motion** — §6.
@@ -670,6 +704,7 @@ Run this punch list on the screen (it's also a good reviewer checklist):
670
704
  - [ ] Verified in **both** light and dark (and highContrast for load-bearing color)?
671
705
  - [ ] RTL-safe — leading/trailing alignment + logical insets throughout?
672
706
  - [ ] Kit-clean — no hardcoded look a kit would override; `material`/gradient/background precedence respected?
707
+ - [ ] Platform-clean — the shell draws the tab bar, title and Back (none redrawn in the body); the primary action sits in `stickyFooter` or a trailing `topBar.items` entry, not a `Fab` an iPhone would also show; switches, segmented choices, pickers, sheets and alerts are the native components (`himi docs ios-ux`, `himi docs android-ux`)?
673
708
  - [ ] Fast (§7b) — long or paged lists at the scroll root, stable row ids, `nav` on taps whose destination is fixed, short rails, fixed-size row images?
674
709
 
675
710
  ---
@@ -679,7 +714,7 @@ Run this punch list on the screen (it's also a good reviewer checklist):
679
714
  The demo gallery's `layout()` is a compact model of the
680
715
  principles:
681
716
 
682
- - **Type scale, not sizes** — `title1` heading, `body` content, `caption` secondary, colored via
717
+ - **Type scale, not sizes** — `title1` for a heading inside a card, `body` content, `caption` secondary, colored via
683
718
  `textPrimary`/`textSecondary`/`accentPrimary` tokens (never hex), `maxLines: 1` to truncate.
684
719
  - **Card = cluster** — related copy grouped in `card([ vstack({ spacing: 4, alignment: "leading" }) ])`
685
720
  on `surfaceSecondary`, not scattered.
@@ -33,7 +33,8 @@ and template-preview journey below. Write the reconstruction plan, `SPEC.md`,
33
33
  same-site One App visitor binding, checks and hosted draft. Obtain the
34
34
  site owner's agreement before creating or updating an OAuth client for optional
35
35
  member flows; visitor reads and native email/password sign-in need no client.
36
- Before composing content, read `himi docs designing-himi-screens` and apply the
36
+ Before composing content, read `himi docs ios-ux`, `himi docs android-ux` and
37
+ `himi docs designing-himi-screens`, and apply the
37
38
  migration guide's screen-content contract: readable detail sections, stable rail
38
39
  and image sizing, grouped icon rows for More, native bottom clearance and distinct
39
40
  loading/empty/error states. Put the relevant cases in `MOBILE-UX.md` and verify
package/guides/hhp.md CHANGED
@@ -213,10 +213,12 @@ A screen may also carry `params` (the navigation input a device opens it with, u
213
213
  himi fidelity --content <dir> [--screen a,b] [--variant light,dark,rtl,tablet] [--ios <udid>] [--android <serial>] [--ios-tablet <udid>] [--android-tablet <serial>] [--app-id <id>] [--release <id>] [--serve <port>] [--no-native] [--json] [--post-pr <n>]
214
214
  ```
215
215
 
216
+ `rtl` runs only when config.ts `locale.default` or `locale.translations` names a right-to-left language the Player supports (today `he`), and `locale.pin` does not pin a left-to-right default. A language picked inside the app also outranks the device, and the command cannot read or reset it: on a device where the app's language was changed in the Player, reinstall the Player before an `rtl` run. The Player follows the app's locale, so an app that ships no such language stays left-to-right on a Hebrew device, and comparing it with a mirrored page would only report that. Otherwise every `rtl` row is reported `skipped` with the reason.
217
+
216
218
  For each screen and variant (`light`, `dark`, `rtl`, `tablet`), the command does four things:
217
219
 
218
220
  1. **Captures the mockup.** It draws the page in Chromium at the variant's width. Dark mode is drawn by emulating `prefers-color-scheme`. RTL is drawn by setting `dir=rtl` on the page.
219
- 2. **Captures the web render** from the app's recorded data, as `himi render` does by default. Native frames come from Himi Player on the devices you pass: `--ios <udid>` and `--android <serial>`, plus `--ios-tablet` and `--android-tablet` for the tablet variant. Only those devices are touched. For each variant the command sets dark or light appearance and, for `rtl`, the Hebrew language (iOS `AppleLanguages`, Android per-app locale, API 33+). It opens `himi://app/<app>/screen/<screen>` and captures once two frames match. Every setting it changed is restored afterwards, also on Ctrl-C. A variant with no device is reported `skipped`, never passed. Without `--release <id>` or `--serve <port>` the Player shows the app's **live** release, not your working copy. `--serve <port>` serves the working copy on `127.0.0.1:<port>` as `himi run` does, so native and web render the same release. The Player reads a local origin only from a Debug build that compiled it in (see [View your package on a device](himi-player-implementation.md#view-your-package-on-a-device)): build it with `HIMALAYA_API_BASE_URL_INFO=http://127.0.0.1:<port>` on iOS and `-PhimalayaDevServeBaseUrl=http://10.0.2.2:<port>` on Android. An iOS device whose Player reads another origin is skipped with the rebuild to run. Android's origin cannot be read back from the installed app, so check that build yourself. A screen that declares `params` is skipped natively for now, because the Player's deep link does not carry them yet. On a fresh iOS simulator, open one `himi://` link by hand first and accept iOS's "Open in Himi Player?" prompt: the command cannot answer it, and until it is accepted every iOS capture shows the prompt.
221
+ 2. **Captures the web render** from the app's recorded data, as `himi render` does by default. Native frames come from Himi Player on the devices you pass: `--ios <udid>` and `--android <serial>`, plus `--ios-tablet` and `--android-tablet` for the tablet variant. Only those devices are touched. For each variant the command sets dark or light appearance and, for `rtl`, the app's right-to-left language (iOS `AppleLanguages`, Android per-app locale, API 33+). It opens `himi://app/<app>/screen/<screen>` and captures once two frames match. Every setting it changed is restored afterwards, also on Ctrl-C. A variant with no device is reported `skipped`, never passed. Without `--release <id>` or `--serve <port>` the Player shows the app's **live** release, not your working copy. `--serve <port>` serves the working copy on `127.0.0.1:<port>` as `himi run` does, so native and web render the same release. The Player reads a local origin only from a Debug build that compiled it in (see [View your package on a device](himi-player-implementation.md#view-your-package-on-a-device)): build it with `HIMALAYA_API_BASE_URL_INFO=http://127.0.0.1:<port>` on iOS and `-PhimalayaDevServeBaseUrl=http://10.0.2.2:<port>` on Android. An iOS device whose Player reads another origin is skipped with the rebuild to run. Android's origin cannot be read back from the installed app, so check that build yourself. A screen that declares `params` is skipped natively for now, because the Player's deep link does not carry them yet. On a fresh iOS simulator, open one `himi://` link by hand first and accept iOS's "Open in Himi Player?" prompt: the command cannot answer it, and until it is accepted every iOS capture shows the prompt.
220
222
  3. **Scores mockup↔web** with ImageMagick SSIM, globally and per tile. The worst tile says where the screens differ most.
221
223
  4. **Asks a fresh model** two questions, using the repo's fidelity judge prompt. The model never ran in the session that wrote the app.
222
224
  - **Design:** does WEB look like the MOCKUP? This judges the port.
@@ -83,10 +83,20 @@ publication readiness or bound metadata not yet supplied to the final push.
83
83
  | --- | --- |
84
84
  | `invalid_usage` | Missing, repeated or unsupported checker arguments. |
85
85
  | `payment_review_unreadable` | The collected source evidence or site-bound payment review cannot be read or verified. |
86
- | `payment_decision_required` | Payment intent, readiness evidence or an explicit owner decision remains unresolved before scaffolding. Selecting connect-provider waits for setup and a new review. |
86
+ | `payment_decision_required` | Payment intent, site-bound evidence, target checkout compatibility or a required owner decision remains unresolved before scaffolding. Selecting connect-provider waits for setup and a new review. Unknown payment readiness alone does not refuse a reviewed, supported existing checkout. |
87
87
 
88
88
  The checker validates recorded evidence and decisions; it does not independently
89
89
  verify merchant readiness or create transactions. General publication remains unchanged.
90
+ Newly collected schema-2 payment reviews require checkout compatibility evidence
91
+ for every online-paid flow. Legacy schema-1 completed ready reviews remain accepted
92
+ without that field.
93
+
94
+ Successful checks include a `warnings` array. When an existing paid flow proceeds
95
+ with unverified payment readiness and supported target checkout, it contains
96
+ `{ code: "payment_readiness_unverified", flowId, message }` for that flow. This is
97
+ advisory: it does not mean payments are disconnected, does not require an owner
98
+ decision and does not certify a successful purchase. Other successful reviews
99
+ return an empty array.
90
100
 
91
101
  ## `himi one-app validate-identity` codes
92
102
 
@@ -56,7 +56,8 @@ and template-preview journey below. Write the reconstruction plan, `SPEC.md`,
56
56
  same-site One App visitor binding, checks and hosted draft. Obtain the
57
57
  site owner's agreement before creating or updating an OAuth client for optional
58
58
  member flows; visitor reads and native email/password sign-in need no client.
59
- Before composing content, read `himi docs designing-himi-screens` and apply the
59
+ Before composing content, read `himi docs ios-ux`, `himi docs android-ux` and
60
+ `himi docs designing-himi-screens`, and apply the
60
61
  migration guide's screen-content contract: readable detail sections, stable rail
61
62
  and image sizing, grouped icon rows for More, native bottom clearance and distinct
62
63
  loading/empty/error states. Put the relevant cases in `MOBILE-UX.md` and verify
package/guides/index.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_comment": "AUTO-GENERATED by tools/authoring-kit/bundle.mjs — DO NOT EDIT. Regenerate with: npm run authoring:gen",
3
3
  "schemaStamp": {
4
- "componentCount": 88,
4
+ "componentCount": 89,
5
5
  "schemaSource": "core/ts/src/schema.ts"
6
6
  },
7
7
  "portalUrl": "https://apps.wix.com/_api/himi-serve/docs",