@wix/himalaya-cli 0.1538.0 → 0.1540.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 (151) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +5 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/index.d.ts +7 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +7 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/display.d.ts +4 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +23 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +1 -0
  7. package/dist/cli.mjs +2184 -1825
  8. package/dist/preview-assets.generated.bin +0 -0
  9. package/guides/component-reference.json +29 -42
  10. package/guides/component-reference.md +67 -14
  11. package/guides/dynamic-list.service.ts +7 -1
  12. package/guides/form-submit.service.ts +7 -1
  13. package/guides/himi-json-output.md +7 -0
  14. package/guides/index.json +2 -2
  15. package/guides/recipe-four-state.md +10 -1
  16. package/guides/recipe-search.md +9 -7
  17. package/guides/release-notes.md +17 -0
  18. package/guides/sdk-reference.json +966 -46
  19. package/guides/search-list.service.ts +7 -1
  20. package/guides/templates.md +19 -0
  21. package/package.json +2 -2
  22. package/stdlib/design-kits/dark-luxe.json +4 -0
  23. package/templates/beauty-salon/dev/index.ts +5021 -4164
  24. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +9 -3
  25. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +6 -3
  26. package/templates/beauty-salon/tier5-src/_shared/engine-intl.ts +5 -2
  27. package/templates/beauty-salon/tier5-src/_shared/himi-store-categories.ts +18 -12
  28. package/templates/beauty-salon/tier5-src/_shared/store-order-presentation.ts +56 -0
  29. package/templates/beauty-salon/tier5-src/_shared/wix-service-price.ts +4 -2
  30. package/templates/beauty-salon/tier5-src/himi-branded-lite-category/index.ts +196 -0
  31. package/templates/beauty-salon/tier5-src/himi-branded-lite-order/index.ts +29 -6
  32. package/templates/beauty-salon/tier5-src/himi-branded-lite-orders/index.ts +58 -5
  33. package/templates/beauty-salon/tier5-src/wix-spaces-collections/index.ts +37 -88
  34. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +9 -3
  35. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +6 -3
  36. package/templates/beauty-salon-owner/tier5-src/_shared/engine-intl.ts +5 -2
  37. package/templates/branded-lite/dev/index.ts +4933 -4079
  38. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +9 -3
  39. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +6 -3
  40. package/templates/branded-lite/tier5-src/_shared/engine-intl.ts +5 -2
  41. package/templates/branded-lite/tier5-src/_shared/himi-store-categories.ts +18 -12
  42. package/templates/branded-lite/tier5-src/_shared/store-order-presentation.ts +56 -0
  43. package/templates/branded-lite/tier5-src/_shared/wix-service-price.ts +4 -2
  44. package/templates/branded-lite/tier5-src/himi-branded-lite-category/index.ts +196 -0
  45. package/templates/branded-lite/tier5-src/himi-branded-lite-order/index.ts +29 -6
  46. package/templates/branded-lite/tier5-src/himi-branded-lite-orders/index.ts +58 -5
  47. package/templates/branded-lite/tier5-src/wix-spaces-collections/index.ts +37 -88
  48. package/templates/branded-lite/tokens.json +1 -1
  49. package/templates/brandy/tokens.json +1 -1
  50. package/templates/community-club/tier5-src/_shared/collection-contract.ts +9 -3
  51. package/templates/community-club/tier5-src/_shared/collection-service.ts +6 -3
  52. package/templates/community-club/tier5-src/_shared/engine-intl.ts +5 -2
  53. package/templates/community-club/tier5-src/_shared/himi-store-categories.ts +18 -12
  54. package/templates/community-club/tier5-src/_shared/wix-service-price.ts +4 -2
  55. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +9 -3
  56. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +6 -3
  57. package/templates/community-club-owner/tier5-src/_shared/engine-intl.ts +5 -2
  58. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +9 -3
  59. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +6 -3
  60. package/templates/events-meetups/tier5-src/_shared/engine-intl.ts +5 -2
  61. package/templates/events-meetups/tier5-src/_shared/himi-store-categories.ts +18 -12
  62. package/templates/events-meetups/tier5-src/_shared/wix-service-price.ts +4 -2
  63. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +9 -3
  64. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +6 -3
  65. package/templates/events-meetups-owner/tier5-src/_shared/engine-intl.ts +5 -2
  66. package/templates/gym-fitness/dev/index.ts +5018 -4161
  67. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +9 -3
  68. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +6 -3
  69. package/templates/gym-fitness/tier5-src/_shared/engine-intl.ts +5 -2
  70. package/templates/gym-fitness/tier5-src/_shared/himi-store-categories.ts +18 -12
  71. package/templates/gym-fitness/tier5-src/_shared/store-order-presentation.ts +56 -0
  72. package/templates/gym-fitness/tier5-src/_shared/wix-service-price.ts +4 -2
  73. package/templates/gym-fitness/tier5-src/himi-branded-lite-category/index.ts +196 -0
  74. package/templates/gym-fitness/tier5-src/himi-branded-lite-order/index.ts +29 -6
  75. package/templates/gym-fitness/tier5-src/himi-branded-lite-orders/index.ts +58 -5
  76. package/templates/gym-fitness/tier5-src/wix-spaces-collections/index.ts +37 -88
  77. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +9 -3
  78. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +6 -3
  79. package/templates/gym-fitness-owner/tier5-src/_shared/engine-intl.ts +5 -2
  80. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +9 -3
  81. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +6 -3
  82. package/templates/nonprofit-cause/tier5-src/_shared/engine-intl.ts +5 -2
  83. package/templates/nonprofit-cause/tier5-src/_shared/himi-store-categories.ts +18 -12
  84. package/templates/nonprofit-cause/tier5-src/_shared/wix-service-price.ts +4 -2
  85. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +9 -3
  86. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +6 -3
  87. package/templates/nonprofit-cause-owner/tier5-src/_shared/engine-intl.ts +5 -2
  88. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +9 -3
  89. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +6 -3
  90. package/templates/owner-lite/tier5-src/_shared/engine-intl.ts +5 -2
  91. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +9 -3
  92. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +6 -3
  93. package/templates/podcast-creator/tier5-src/_shared/engine-intl.ts +5 -2
  94. package/templates/podcast-creator/tier5-src/_shared/himi-store-categories.ts +18 -12
  95. package/templates/podcast-creator/tier5-src/_shared/wix-service-price.ts +4 -2
  96. package/templates/podcast-creator/tokens.json +4 -4
  97. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +9 -3
  98. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +6 -3
  99. package/templates/podcast-creator-owner/tier5-src/_shared/engine-intl.ts +5 -2
  100. package/templates/podcast-creator-owner/tokens.json +4 -4
  101. package/templates/portfolio-creative/dev/index.ts +5017 -4157
  102. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +9 -3
  103. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +6 -3
  104. package/templates/portfolio-creative/tier5-src/_shared/engine-intl.ts +5 -2
  105. package/templates/portfolio-creative/tier5-src/_shared/himi-store-categories.ts +18 -12
  106. package/templates/portfolio-creative/tier5-src/_shared/store-order-presentation.ts +56 -0
  107. package/templates/portfolio-creative/tier5-src/_shared/wix-service-price.ts +4 -2
  108. package/templates/portfolio-creative/tier5-src/himi-branded-lite-category/index.ts +196 -0
  109. package/templates/portfolio-creative/tier5-src/himi-branded-lite-order/index.ts +29 -6
  110. package/templates/portfolio-creative/tier5-src/himi-branded-lite-orders/index.ts +58 -5
  111. package/templates/portfolio-creative/tier5-src/wix-spaces-collections/index.ts +37 -88
  112. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +9 -3
  113. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +6 -3
  114. package/templates/portfolio-creative-owner/tier5-src/_shared/engine-intl.ts +5 -2
  115. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +9 -3
  116. package/templates/restaurant/tier5-src/_shared/collection-service.ts +6 -3
  117. package/templates/restaurant/tier5-src/_shared/engine-intl.ts +5 -2
  118. package/templates/restaurant/tier5-src/_shared/himi-store-categories.ts +18 -12
  119. package/templates/restaurant/tier5-src/_shared/wix-service-price.ts +4 -2
  120. package/templates/restaurant/tokens.json +4 -4
  121. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +9 -3
  122. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +6 -3
  123. package/templates/restaurant-owner/tier5-src/_shared/engine-intl.ts +5 -2
  124. package/templates/retail-store/dev/index.ts +5159 -4302
  125. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +9 -3
  126. package/templates/retail-store/tier5-src/_shared/collection-service.ts +6 -3
  127. package/templates/retail-store/tier5-src/_shared/engine-intl.ts +5 -2
  128. package/templates/retail-store/tier5-src/_shared/himi-store-categories.ts +18 -12
  129. package/templates/retail-store/tier5-src/_shared/store-order-presentation.ts +56 -0
  130. package/templates/retail-store/tier5-src/_shared/wix-service-price.ts +4 -2
  131. package/templates/retail-store/tier5-src/himi-branded-lite-category/index.ts +196 -0
  132. package/templates/retail-store/tier5-src/himi-branded-lite-order/index.ts +29 -6
  133. package/templates/retail-store/tier5-src/himi-branded-lite-orders/index.ts +58 -5
  134. package/templates/retail-store/tier5-src/wix-spaces-collections/index.ts +37 -88
  135. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +9 -3
  136. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +6 -3
  137. package/templates/retail-store-owner/tier5-src/_shared/engine-intl.ts +5 -2
  138. package/templates/yoga-studio/dev/index.ts +0 -3
  139. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +9 -3
  140. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +6 -3
  141. package/templates/yoga-studio/tier5-src/_shared/engine-intl.ts +5 -2
  142. package/templates/yoga-studio/tier5-src/_shared/himi-store-categories.ts +18 -12
  143. package/templates/yoga-studio/tier5-src/_shared/wix-service-price.ts +4 -2
  144. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +9 -3
  145. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +6 -3
  146. package/templates/yoga-studio-owner/tier5-src/_shared/engine-intl.ts +5 -2
  147. package/templates/beauty-salon/tier5-src/wix-spaces-collection-detail/index.ts +0 -170
  148. package/templates/branded-lite/tier5-src/wix-spaces-collection-detail/index.ts +0 -170
  149. package/templates/gym-fitness/tier5-src/wix-spaces-collection-detail/index.ts +0 -170
  150. package/templates/portfolio-creative/tier5-src/wix-spaces-collection-detail/index.ts +0 -170
  151. package/templates/retail-store/tier5-src/wix-spaces-collection-detail/index.ts +0 -170
Binary file
@@ -2,12 +2,10 @@
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, e.g. hiddenPath:\"$env:reduceMotion\" or hiddenPath:\"$env:colorScheme==light\", casePath:\"$env:colorScheme\"; unknown key fails open (shown / defaultCase).",
5
- "componentCount": 87,
5
+ "componentCount": 88,
6
6
  "notImplementedComponents": [
7
7
  "Text",
8
8
  "Icon",
9
- "Carousel",
10
- "Pager",
11
9
  "WebView",
12
10
  "Video",
13
11
  "PdfView",
@@ -52,7 +50,7 @@
52
50
  "bindings": [
53
51
  "itemsPath"
54
52
  ],
55
- "propsSource": "export interface GridProps {\n items: Component[];\n /** Base column count — used at compact width (<600) and as the fallback for\n * clients that don't understand the responsive overrides below. */\n columns: number;\n /** Responsive column overrides, resolved against the Grid's OWN\n * available width: `columnsMedium` at ≥600, `columnsExpanded` at ≥840. Each\n * falls back downward (expanded → medium → columns). Absent ⇒ the Grid keeps\n * `columns` at every width (back-compat). */\n columnsMedium?: number;\n columnsExpanded?: number;\n spacing?: number;\n /** `$service:`/`$item.` binding → a live array to render as grid cells (overrides `items`). */\n itemsPath?: string;\n /** Per-cell entrance delay (`index * staggerMs`), so a Grid whose cells carry\n * `transition`/`appear` cascades in instead of appearing all at once. Honored on every\n * renderer, on BOTH the static and `itemsPath` paths. The index is FLAT across the grid\n * (reading order), so the cascade runs down the diagonal rather than restarting on each\n * row. Reduced motion collapses it to 0. Absent/0 ⇒ no cascade, which is the back-compat\n * default. */\n staggerMs?: number;\n}"
53
+ "propsSource": "export interface GridProps {\n items: Component[];\n /** Base column count — used at compact width (<600) and as the fallback for\n * clients that don't understand the responsive overrides below. */\n columns: number;\n /** Responsive column overrides, resolved against the Grid's OWN\n * available width: `columnsMedium` at ≥600, `columnsExpanded` at ≥840. Each\n * falls back downward (expanded → medium → columns). Absent ⇒ the Grid keeps\n * `columns` at every width (back-compat). */\n columnsMedium?: number;\n columnsExpanded?: number;\n spacing?: number;\n /** `$service:`/`$item.` binding → a live array to render as grid cells (overrides `items`). */\n itemsPath?: string;\n /** Per-cell entrance delay (`index * staggerMs`), so a Grid whose cells carry\n * `transition`/`appear` cascades in instead of appearing all at once. Honored on every\n * renderer, on BOTH the static and `itemsPath` paths. The index is FLAT across the grid\n * (reading order), so the cascade runs down the diagonal rather than restarting on each\n * row. Reduced motion collapses it to 0. Absent/0 ⇒ no cascade, which is the back-compat\n * default. */\n staggerMs?: number;\n /** #4672 — pull-to-refresh, exactly as on `LazyList`: the screen's refresh gesture dispatches\n * this action (the shells find the first `onPullToRefresh` in the payload, list or grid). */\n onPullToRefresh?: Action;\n /** #4672 — infinite scroll, as on `LazyList`: `onLoadMore` fires once per published non-empty\n * `cursor` (usually a `$service:` binding) when the grid's trailing edge comes into view; a cursor\n * the worker publishes again (a refresh back to page 2) pages again. An empty cursor is\n * exhausted, and nothing fires. Works with `items` and `itemsPath`. */\n pagination?: Pagination;\n}"
56
54
  },
57
55
  {
58
56
  "name": "Card",
@@ -89,6 +87,15 @@
89
87
  "bindings": [],
90
88
  "propsSource": "export interface EmptyStateProps {\n title: string;\n subtitle?: string;\n icon?: IconRef;\n /** The CTA. Drawn as a real `Button` (see `actionStyle` / `actionSize`). */\n action?: Action;\n /** #1059 — CTA label, also its accessibility label. REQUIRED when `action` is set:\n * there is no default, `himi validate` reports an error, and no button is drawn. */\n actionLabel?: string;\n /** `typography` token for the title. Default `\"bodyEmphasis\"`. */\n titleStyle?: string;\n /** `typography` token for the subtitle. Default `\"caption\"`. */\n subtitleStyle?: string;\n /** The CTA's Button `style`. Default `\"secondary\"`. */\n actionStyle?: \"primary\" | \"secondary\" | \"plain\";\n /** The CTA's Button `size`. Default `\"sm\"`. */\n actionSize?: \"sm\" | \"md\";\n}"
91
89
  },
90
+ {
91
+ "name": "ErrorState",
92
+ "category": "Core",
93
+ "propsType": "ErrorStateProps",
94
+ "bindings": [
95
+ "variantPath"
96
+ ],
97
+ "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}"
98
+ },
92
99
  {
93
100
  "name": "Text",
94
101
  "category": "Core",
@@ -198,7 +205,7 @@
198
205
  ]
199
206
  }
200
207
  ],
201
- "propsSource": "export interface TextFieldProps {\n placeholder?: string;\n /** LITERAL initial text. Renderers paint it verbatim — a `$service:` sentinel\n * here shows the raw token to the user. Bind `valuePath` instead. */\n value?: string;\n /** `$service:`/`$item.` binding → live text from the worker (overrides `value`).\n * Adopted while the field is NOT focused; an empty worker value (a draft reset\n * after send/post/submit) is adopted even mid-focus, so a cleared draft empties\n * the field without the worker ever fighting the caret. */\n valuePath?: string;\n onChange?: Action;\n /** Text input enrichments. */\n keyboard?: Keyboard;\n secure?: boolean;\n multilineMaxLines?: number;\n maxLength?: number;\n autoCapitalization?: AutoCapitalization;\n onSubmit?: Action;\n /** Keyboard control. `suggestions: \"off\"` kills autocorrect + the suggestion\n * strip. `submitLabel` paints the return key and, with `onSubmit`, fires it —\n * submitting dismisses the keyboard except for `\"next\"`, which keeps it up so\n * a worker can move focus on. */\n suggestions?: Suggestions;\n submitLabel?: SubmitLabel;\n /** `$service:` binding → a TOKEN the worker changes to focus this field. Focus\n * is requested whenever the resolved value becomes a new NON-EMPTY value\n * (the first seed included, so a non-empty init focuses on open). Use a\n * monotonic nonce — `String(Date.now())` — since a latched boolean cannot\n * re-fire. Blur is the keyboard-dismiss capability, not a token here. */\n focusPath?: string;\n /** Accessory & state enrichments. */\n leadingIcon?: IconRef;\n trailingIcon?: IconRef;\n clearButton?: boolean;\n helperText?: string;\n error?: boolean;\n prefix?: string;\n suffix?: string;\n /** Drop the default rounded-border chrome so a caller background shows (composer pill). */\n borderless?: boolean;\n /** Declarative syntactic rules the RENDERER evaluates, so they hold with no\n * worker running (Tier 0-4) and with no bridge round-trip. Before this,\n * validation was reachable only by wrapping the field in `Form` -> `FormField`\n * - the shape the design guide steers you away from for hero flows. */\n validation?: Validation;\n /** `$service:`/`$item.` binding -> a worker-computed error string. Non-empty =\n * invalid. This is how SEMANTIC and CROSS-FIELD errors (\"passwords don't\n * match\", \"email taken\") reach the same error slot, with the same styling and\n * a11y wiring, as the declarative ones. Wins over `validation.message`. */\n errorPath?: string;\n /** LITERAL error string for a server-rendered screen with no worker to bind.\n * Lower precedence than `errorPath`, higher than the rules. */\n errorText?: string;\n /** Opt-in accessible show/hide button. Only applies to secure fields; initially masked. */\n visibilityToggle?: boolean;\n}"
208
+ "propsSource": "export interface TextFieldProps {\n placeholder?: string;\n /** LITERAL initial text. Renderers paint it verbatim — a `$service:` sentinel\n * here shows the raw token to the user. Bind `valuePath` instead. */\n value?: string;\n /** `$service:`/`$item.` binding → live text from the worker (overrides `value`).\n * Adopted while the field is NOT focused; an empty worker value (a draft reset\n * after send/post/submit) is adopted even mid-focus, so a cleared draft empties\n * the field without the worker ever fighting the caret. */\n valuePath?: string;\n onChange?: Action;\n /** Text input enrichments. */\n keyboard?: Keyboard;\n secure?: boolean;\n multilineMaxLines?: number;\n maxLength?: number;\n autoCapitalization?: AutoCapitalization;\n onSubmit?: Action;\n /** Keyboard control. `suggestions: \"off\"` kills autocorrect + the suggestion\n * strip. `submitLabel` paints the return key and, with `onSubmit`, fires it —\n * submitting dismisses the keyboard except for `\"next\"`, which keeps it up so\n * a worker can move focus on. */\n suggestions?: Suggestions;\n submitLabel?: SubmitLabel;\n /** `$service:` binding → a TOKEN the worker changes to focus this field. Focus\n * is requested whenever the resolved value becomes a new NON-EMPTY value\n * (the first seed included, so a non-empty init focuses on open). Use a\n * monotonic nonce — `String(Date.now())` — since a latched boolean cannot\n * re-fire. Blur is the keyboard-dismiss capability, not a token here. */\n focusPath?: string;\n /** Accessory & state enrichments. */\n leadingIcon?: IconRef;\n trailingIcon?: IconRef;\n clearButton?: boolean;\n helperText?: string;\n error?: boolean;\n prefix?: string;\n suffix?: string;\n /** Drop the default rounded-border chrome so a caller background shows (composer pill). */\n borderless?: boolean;\n /** Declarative syntactic rules the RENDERER evaluates, so they hold with no\n * worker running (Tier 0-4) and with no bridge round-trip. Before this,\n * validation was reachable only by wrapping the field in `Form` -> `FormField`\n * - the shape the design guide steers you away from for hero flows. */\n validation?: Validation;\n /** `$service:`/`$item.` binding -> a worker-computed error string. Non-empty =\n * invalid. This is how SEMANTIC and CROSS-FIELD errors (\"passwords don't\n * match\", \"email taken\") reach the same error slot, with the same styling and\n * a11y wiring, as the declarative ones. Wins over `validation.message`. */\n errorPath?: string;\n /** LITERAL error string for a server-rendered screen with no worker to bind.\n * Lower precedence than `errorPath`, higher than the rules. */\n errorText?: string;\n /** Opt-in accessible show/hide button. Only applies to secure fields; initially masked. */\n visibilityToggle?: boolean;\n /** `onChange` fires after this many ms of idle typing. 0 or unset = every change.\n * A submit fires a pending debounced `onChange` first. */\n debounceMs?: number;\n}"
202
209
  },
203
210
  {
204
211
  "name": "Toggle",
@@ -252,7 +259,7 @@
252
259
  ]
253
260
  }
254
261
  ],
255
- "propsSource": "export interface StepperProps {\n value: number;\n /** `$service:`/`$item.` binding → a live integer (overrides `value`). */\n valuePath?: string;\n min?: number;\n max?: number;\n onChange?: Action;\n}"
262
+ "propsSource": "export interface StepperProps {\n value: number;\n /** `$service:`/`$item.` binding → a live integer (overrides `value`). */\n valuePath?: string;\n min?: number;\n max?: number;\n onChange?: Action;\n /** \"plain\" draws exposed minus/value/plus controls; absent uses the default capsule. */\n presentation?: \"plain\";\n}"
256
263
  },
257
264
  {
258
265
  "name": "Avatar",
@@ -391,7 +398,7 @@
391
398
  "category": "State envelopes",
392
399
  "propsType": "SkeletonProps",
393
400
  "bindings": [],
394
- "propsSource": "export interface SkeletonProps {\n width?: number;\n height?: number;\n cornerRadius?: number;\n count?: number;\n}"
401
+ "propsSource": "export interface SkeletonProps {\n width?: number;\n height?: number;\n cornerRadius?: number;\n count?: number;\n style?: \"static\" | \"shimmer\";\n}"
395
402
  },
396
403
  {
397
404
  "name": "FragmentSlot",
@@ -415,7 +422,9 @@
415
422
  "name": "SearchField",
416
423
  "category": "Search & filter",
417
424
  "propsType": "SearchFieldProps",
418
- "bindings": [],
425
+ "bindings": [
426
+ "focusPath"
427
+ ],
419
428
  "dispatches": [
420
429
  {
421
430
  "callback": "onChange",
@@ -430,7 +439,7 @@
430
439
  "note": "These two rows share a control and key, so they are NOT independently gated: a payload observation cannot say which callback produced it, and a helper-mediated dispatch does not name the callback at the write site at all. The gate proves the callback is still referenced in the component that dispatches, not that this callback carries the payload."
431
440
  }
432
441
  ],
433
- "propsSource": "export interface SearchFieldProps {\n placeholder?: string;\n value?: string;\n onChange?: Action;\n onSubmit?: Action;\n debounceMs?: number;\n}"
442
+ "propsSource": "export interface SearchFieldProps {\n placeholder?: string;\n value?: string;\n onChange?: Action;\n onSubmit?: Action;\n /** `onChange` fires after this many ms of idle typing; 0 or less (or unset) = 250 ms.\n * A submit fires a pending debounced `onChange` first. */\n debounceMs?: number;\n /** `$service:` binding → a focus TOKEN; `TextFieldProps.focusPath` semantics. */\n focusPath?: string;\n}"
434
443
  },
435
444
  {
436
445
  "name": "FilterChips",
@@ -502,18 +511,6 @@
502
511
  "bindings": [
503
512
  "itemsPath"
504
513
  ],
505
- "notImplemented": {
506
- "props": [
507
- "liveWindow"
508
- ],
509
- "platforms": [
510
- "ios",
511
- "android",
512
- "web"
513
- ],
514
- "severity": "styling",
515
- "note": "Declared ahead of its runtime: every page stays mounted until Pager/Carousel live_window lands on each renderer (W8, #4630)."
516
- },
517
514
  "propsSource": "export interface CarouselProps {\n items: Component[];\n currentIndex?: number;\n onChange?: Action;\n autoplayIntervalMs?: number;\n /** `$service:` binding → a live array to render as slides (overrides `items`). */\n itemsPath?: string;\n /** 0 (default) = all slides mounted; N ⇒ mount current ± N, unmount the rest. */\n liveWindow?: number;\n}"
518
515
  },
519
516
  {
@@ -521,18 +518,6 @@
521
518
  "category": "Navigation primitives",
522
519
  "propsType": "PagerProps",
523
520
  "bindings": [],
524
- "notImplemented": {
525
- "props": [
526
- "liveWindow"
527
- ],
528
- "platforms": [
529
- "ios",
530
- "android",
531
- "web"
532
- ],
533
- "severity": "styling",
534
- "note": "Declared ahead of its runtime: every page stays mounted until Pager/Carousel live_window lands on each renderer (W8, #4630)."
535
- },
536
521
  "propsSource": "export interface PagerProps {\n pages: Component[];\n currentIndex?: number;\n onChange?: Action;\n showIndicator?: boolean;\n /** As {@link CarouselProps.liveWindow}. */\n liveWindow?: number;\n}"
537
522
  },
538
523
  {
@@ -606,11 +591,10 @@
606
591
  ],
607
592
  "platforms": [
608
593
  "ios",
609
- "android",
610
- "web"
594
+ "android"
611
595
  ],
612
596
  "severity": "styling",
613
- "note": "The embedded worker-bridged WebView contract is declared ahead of its runtime: no channel, instance identity, fill layout, bound height or active/muted/reload binding until the hosts implement it (iOS/macOS #4624/#4625, Android #4626/#4627, web #4628/#4629)."
597
+ "note": "The embedded worker-bridged WebView contract is declared ahead of its runtime on these platforms: no channel, instance identity, fill layout, bound height or active/muted/reload binding until their hosts implement it. On web all of them work for a cross-origin url page listed in allowedOrigins; an inlineHtml page is not bridged on web yet."
614
598
  },
615
599
  "propsSource": "export interface WebViewProps {\n url?: string;\n /** Inline HTML to load instead of `url` (mutually exclusive). */\n inlineHtml?: string;\n /** Fixed height in points/dp; set this OR `aspectRatio`. */\n height?: number;\n /** width:height ratio; used when `height` is unset. */\n aspectRatio?: number;\n /** Keep the WebView's own scrolling (default false). */\n scrollEnabled?: boolean;\n config?: WebViewConfig;\n capabilitiesRequired?: string[];\n // Embedded worker-bridged WebViews\n /** `\"\"` (default, no channel) | `\"worker\"` — channel to the owning screen's worker. */\n bridge?: \"\" | \"worker\";\n /** Channel + storage identity; default `<screenId>?<args>/<Component.id>` (§4.1, `effectiveWebViewInstanceId`). */\n instanceId?: string;\n /** `$service:` bool — false ⇒ inactive (§5.4); unset ⇒ active. */\n activePath?: string;\n /** `$service:` bool — true ⇒ all page media muted. */\n mutedPath?: string;\n /** `$service:` scalar — any change remounts the document. */\n reloadKeyPath?: string;\n /** Take the parent's proposed size (works with weight); beats `height` / `aspectRatio`. */\n fill?: boolean;\n /** `$service:` number — live height in points/dp; beats `height`. */\n heightPath?: string;\n}"
616
600
  },
@@ -1127,7 +1111,7 @@
1127
1111
  "severity": "styling",
1128
1112
  "note": "`poster` is not shown."
1129
1113
  },
1130
- "propsSource": "export interface VideoProps {\n url?: string;\n poster?: string;\n autoplay?: boolean;\n controls?: boolean;\n loop?: boolean;\n muted?: boolean;\n height?: number;\n aspectRatio?: number;\n}"
1114
+ "propsSource": "export interface VideoProps {\n url?: string;\n poster?: string;\n autoplay?: boolean;\n controls?: boolean;\n loop?: boolean;\n muted?: boolean;\n height?: number;\n aspectRatio?: number;\n /** #4669 — `\"fit\"` (default) letterboxes; `\"fill\"` covers the frame and crops (poster too). */\n contentMode?: \"fit\" | \"fill\";\n}"
1131
1115
  },
1132
1116
  {
1133
1117
  "name": "PdfView",
@@ -1213,8 +1197,10 @@
1213
1197
  "category": "Rich vector & celebration (motion grammar Batch 4)",
1214
1198
  "propsType": "LottieProps",
1215
1199
  "requiresCapability": "animation",
1216
- "bindings": [],
1217
- "propsSource": "export interface LottieProps {\n source?: string;\n loop?: boolean;\n autoplay?: boolean;\n speed?: number;\n height?: number;\n}"
1200
+ "bindings": [
1201
+ "playPath"
1202
+ ],
1203
+ "propsSource": "export interface LottieProps {\n source?: string;\n loop?: boolean;\n autoplay?: boolean;\n speed?: number;\n height?: number;\n /** #4669 — `$service:` binding to a nonce: each CHANGE of the resolved value plays the\n * animation once from frame 0. A Lottie bound this way is a one-shot (`loop` is treated as\n * false). The first value plays only when `autoplay` is true; reduced motion never replays. */\n playPath?: string;\n}"
1218
1204
  },
1219
1205
  {
1220
1206
  "name": "Rive",
@@ -1234,9 +1220,10 @@
1234
1220
  "yearPath",
1235
1221
  "monthPath",
1236
1222
  "selectedStartDatePath",
1237
- "selectedEndDatePath"
1223
+ "selectedEndDatePath",
1224
+ "displayModePath"
1238
1225
  ],
1239
- "propsSource": "export interface MonthCalendarProps {\n year?: number;\n /** 1-12, NOT the JS 0-11 index. */\n month?: number;\n selectedDate?: string;\n selectedDatePath?: string;\n decorations?: DayDecoration[];\n decorationsPath?: string;\n /** 0 = Sunday … 6 = Saturday. Callers supply it; renderers never guess. */\n firstWeekday?: number;\n onSelectDate?: Action;\n onMonthChange?: Action;\n showAdjacentMonths?: boolean;\n /** Worker-driven visible month. `onMonthChange` only reports outward; bind\n * these and the grid FOLLOWS worker state (jump-to-today, open-on-search-hit). */\n yearPath?: string;\n monthPath?: string;\n /** Selectable window as LOCAL \"YYYY-MM-DD\". Replaces expressing a booking\n * horizon as one `disabled` DayDecoration per out-of-window day. */\n minDate?: string;\n maxDate?: string;\n /** Per-day dot ceiling. Absent/0 ⇒ 4, so every surface draws the same count. */\n maxDots?: number;\n /** \"single\" (default) ⇒ `onSelectDate` per tap. \"range\" ⇒ check-in/check-out:\n * the first tap sets the start, the second the end, then `onSelectRange` fires.\n * A separate mode + separate pair, so a descriptor is never ambiguous and the\n * single-date path is byte-identical for existing screens. */\n selectionMode?: \"single\" | \"range\";\n selectedStartDate?: string;\n selectedStartDatePath?: string;\n selectedEndDate?: string;\n selectedEndDatePath?: string;\n /** Fires with args{startDate, endDate} once a range completes. */\n onSelectRange?: Action;\n}"
1226
+ "propsSource": "export interface MonthCalendarProps {\n year?: number;\n /** 1-12, NOT the JS 0-11 index. */\n month?: number;\n selectedDate?: string;\n selectedDatePath?: string;\n decorations?: DayDecoration[];\n decorationsPath?: string;\n /** 0 = Sunday … 6 = Saturday. Callers supply it; renderers never guess. */\n firstWeekday?: number;\n onSelectDate?: Action;\n onMonthChange?: Action;\n showAdjacentMonths?: boolean;\n /** Worker-driven visible month. `onMonthChange` only reports outward; bind\n * these and the grid FOLLOWS worker state (jump-to-today, open-on-search-hit). */\n yearPath?: string;\n monthPath?: string;\n /** Selectable window as LOCAL \"YYYY-MM-DD\". Replaces expressing a booking\n * horizon as one `disabled` DayDecoration per out-of-window day. */\n minDate?: string;\n maxDate?: string;\n /** Per-day dot ceiling. Absent/0 ⇒ 4, so every surface draws the same count. */\n maxDots?: number;\n /** \"single\" (default) ⇒ `onSelectDate` per tap. \"range\" ⇒ check-in/check-out:\n * the first tap sets the start, the second the end, then `onSelectRange` fires.\n * A separate mode + separate pair, so a descriptor is never ambiguous and the\n * single-date path is byte-identical for existing screens. */\n selectionMode?: \"single\" | \"range\";\n selectedStartDate?: string;\n selectedStartDatePath?: string;\n selectedEndDate?: string;\n selectedEndDatePath?: string;\n /** Fires with args{startDate, endDate} once a range completes. */\n onSelectRange?: Action;\n /** \"month\" (default) | \"week\" — the one-row collapsed calendar an agenda sits\n * under. Week mode shows the week holding the selected day (else today), pages\n * by 7 days, and titles the month holding most of that week. */\n displayMode?: \"month\" | \"week\";\n /** Worker-driven mode, applied on CHANGE (the `yearPath` rule). */\n displayModePath?: string;\n /** Draw the week ↔ month toggle: a chevron on the title and a grab handle. */\n expandable?: boolean;\n /** The user switched mode → args{mode}. */\n onDisplayModeChange?: Action;\n /** The visible days changed (a page in either mode, or a mode switch) →\n * args{startDate, endDate}, LOCAL \"YYYY-MM-DD\" of the first and last cell. */\n onVisibleRangeChange?: Action;\n}"
1240
1227
  },
1241
1228
  {
1242
1229
  "name": "TimeGrid",
@@ -1271,7 +1258,7 @@
1271
1258
  }
1272
1259
  ],
1273
1260
  "descriptor": {
1274
- "ScreenDescriptor": "export interface ScreenDescriptor {\n screenId: string;\n renderer: RendererKind;\n title?: string;\n /** Present when `renderer` is `sdui_intent` or `sdui_layout`. */\n payload?: Component;\n /** A component pinned ABOVE the scrolling payload (sticky tab-header / toolbar).\n * Rendered by the host outside the screen scroll, so it stays put as content\n * scrolls. Optional; absent = no sticky header. */\n stickyHeader?: Component;\n /** A component pinned BELOW the scrolling payload (sticky footer — price/confirm\n * bar, peeking bottom-sheet). Rendered outside the screen scroll by the host. */\n stickyFooter?: Component;\n /** When true, the host wraps the screen in a scroll that starts pinned to the\n * bottom and shows a floating \"jump to latest\" button whenever the user scrolls\n * up (a chat transcript). Tapping it animates back to the bottom. Absent/false =\n * a normal top-anchored scroll. Honored by the app host (Router). */\n scrollToBottom?: boolean;\n /** The chat transcript scroll policy. On a send the host pins the\n * named component near the top and reserves the space the reply grows into;\n * when the reply outgrows it the host follows the tail. A drag pauses\n * following, and only then is a jump-to-latest control shown. */\n transcriptScroll?: TranscriptScroll;\n /** The page's title for a host that shows it in its own navigation bar (iOS `himalayaEmbedTitle`, Android\n * `ProvideHimalayaEmbedTitle`): it gets the title as soon as it has the descriptor, before the worker boots, and\n * `replaces` is not drawn. Every other host ignores it, so the in-content header stays. */\n hostTitle?: HostTitle;\n /** Present when `renderer` is `webview_native_bridge` or `webview_ds`. */\n url?: string;\n capabilitiesRequired?: string[];\n schemaVersion: number;\n togglesApplied?: string[];\n /** Per-session flag/experiment assignments baked in for this request. */\n assignments?: import(\"./flags.js\").Assignment[];\n /** Per-screen top bar (trailing actions +\n * leading/style override). Fully replaces `NavigationConfig.topBarDefault`. */\n topBar?: TopBarConfig;\n etag?: string;\n cacheControl?: CacheControl;\n telemetryScreenName?: string;\n bundleSri?: string;\n manifestVersion?: number;\n /**\n * Present on Tier-5 screens. The presence of this field is the tier-5\n * signal — there is no separate `tier: 5` flag.\n */\n logicBundle?: LogicBundleRef;\n /**\n * Static per-screen input injected as `globalThis.himalaya.input` before the\n * Tier-5 worker bundle evaluates — the worker reads it synchronously in `load()`\n * (e.g. `himalaya.input.id` for a detail screen, or a baked catalog like\n * `himalaya.input.cmsCollections`).\n *\n * A push merges its navigation parameters into this object on web, iOS, and Android.\n * For example, `himi.open({ screenId: \"event\", id: \"$item.id\" })` makes the id\n * available as `himalaya.input.id` in the pushed worker; static `input` remains the\n * fallback for values baked into a descriptor or a templated route. The host also\n * supplies `screenId`. See docs/tier-5-add-a-screen.md → \"Navigate to a detail screen\".\n *\n * Arbitrary JSON object; consumed by the web host (ScreenController) and the\n * native JSWorker.\n */\n input?: Record<string, unknown>;\n /**\n * Tier 3 / Tier 4 — behavior of the hosted WebView. Ignored by SDUI tiers.\n * Authored in the descriptor builder (git); served OTA verbatim.\n */\n webViewConfig?: WebViewConfig;\n}",
1261
+ "ScreenDescriptor": "export interface ScreenDescriptor {\n screenId: string;\n renderer: RendererKind;\n title?: string;\n /** Present when `renderer` is `sdui_intent` or `sdui_layout`. */\n payload?: Component;\n /** A component pinned ABOVE the scrolling payload (sticky tab-header / toolbar).\n * Rendered by the host outside the screen scroll, so it stays put as content\n * scrolls. Optional; absent = no sticky header. */\n stickyHeader?: Component;\n /** A component pinned BELOW the scrolling payload (sticky footer — price/confirm\n * bar, peeking bottom-sheet). Rendered outside the screen scroll by the host. */\n stickyFooter?: Component;\n /** When true, the host wraps the screen in a scroll that starts pinned to the\n * bottom and shows a floating \"jump to latest\" button whenever the user scrolls\n * up (a chat transcript). Tapping it animates back to the bottom. Absent/false =\n * a normal top-anchored scroll. Honored by the app host (Router). */\n scrollToBottom?: boolean;\n /** The chat transcript scroll policy. On a send the host pins the\n * named component near the top and reserves the space the reply grows into;\n * when the reply outgrows it the host follows the tail. A drag pauses\n * following, and only then is a jump-to-latest control shown. */\n transcriptScroll?: TranscriptScroll;\n /** The page's title for a host that shows it in its own navigation bar (iOS `himalayaEmbedTitle`, Android\n * `ProvideHimalayaEmbedTitle`): it gets the title as soon as it has the descriptor, before the worker boots, and\n * `replaces` is not drawn. Every other host ignores it, so the in-content header stays. */\n hostTitle?: HostTitle;\n /** Present when `renderer` is `webview_native_bridge` or `webview_ds`. */\n url?: string;\n capabilitiesRequired?: string[];\n schemaVersion: number;\n togglesApplied?: string[];\n /** Per-session flag/experiment assignments baked in for this request. */\n assignments?: import(\"./flags.js\").Assignment[];\n /** Per-screen top bar (trailing actions +\n * leading/style override). Fully replaces `NavigationConfig.topBarDefault`. */\n topBar?: TopBarConfig;\n /** \"\" / \"visible\" (default) | \"hidden\": hide the bottom tab bar while this screen is the\n * visible top of its tab's stack. Static; no effect in a presentation. */\n tabBarVisibility?: string;\n etag?: string;\n cacheControl?: CacheControl;\n telemetryScreenName?: string;\n bundleSri?: string;\n manifestVersion?: number;\n /**\n * Present on Tier-5 screens. The presence of this field is the tier-5\n * signal — there is no separate `tier: 5` flag.\n */\n logicBundle?: LogicBundleRef;\n /**\n * Static per-screen input injected as `globalThis.himalaya.input` before the\n * Tier-5 worker bundle evaluates — the worker reads it synchronously in `load()`\n * (e.g. `himalaya.input.id` for a detail screen, or a baked catalog like\n * `himalaya.input.cmsCollections`).\n *\n * A push merges its navigation parameters into this object on web, iOS, and Android.\n * For example, `himi.open({ screenId: \"event\", id: \"$item.id\" })` makes the id\n * available as `himalaya.input.id` in the pushed worker; static `input` remains the\n * fallback for values baked into a descriptor or a templated route. The host also\n * supplies `screenId`. See docs/tier-5-add-a-screen.md → \"Navigate to a detail screen\".\n *\n * Arbitrary JSON object; consumed by the web host (ScreenController) and the\n * native JSWorker.\n */\n input?: Record<string, unknown>;\n /**\n * Tier 3 / Tier 4 — behavior of the hosted WebView. Ignored by SDUI tiers.\n * Authored in the descriptor builder (git); served OTA verbatim.\n */\n webViewConfig?: WebViewConfig;\n}",
1275
1262
  "Component": "export interface Component {\n id: string;\n body: ComponentBody;\n modifiers?: Modifiers;\n minVersion?: number;\n}",
1276
1263
  "LogicBundleRef": "export interface LogicBundleRef {\n url: string;\n /** \"sha256-...\" SRI string. */\n integrity: string;\n contentType?: string;\n /** SDK capability ids the bundle is permitted to import / invoke. */\n declaredCapabilities: string[];\n /** Bundle refuses to load on a binary older than this (semver). */\n minBinaryVersion?: string;\n /** Bundle's own semver. */\n bundleVersion?: string;\n /** Base64 detached publisher signature over the bundle bytes\n * (authenticity beyond SRI). Verified by a host-injected verifier when present;\n * unsigned bundles load as today. */\n signature?: string;\n /** The publisher key id a host pins, paired with `signature`. */\n keyId?: string;\n /** Ambient refresh (gap G2): the host invokes `action` (a `tier5.<svc>.<action>`\n * path) every `intervalMs` while the screen is visible — workers have no timer\n * of their own, so the host owns the cadence. */\n refresh?: { intervalMs: number; action: string };\n}",
1277
1264
  "Modifiers": "export interface Modifiers {\n padding?: EdgeInsets;\n background?: TokenRef;\n cornerRadius?: number;\n /** Fixed intrinsic width (pt/dp/px). Honored by iOS (`FrameIfPresent`), Android\n * (frame) and web (`modifiers.ts`). With no `ScrollView` in the vocabulary and\n * regions clipped at `maxHeight`, `width`/`height`/`fillMaxWidth` are the main\n * sizing tools for embed/host content. */\n width?: number;\n /** Fixed intrinsic height (pt/dp/px). See `width`. */\n height?: number;\n /** Fill the available cross-axis width (keeps any fixed `height`). Mirrors\n * Compose `fillMaxWidth` / SwiftUI `.frame(maxWidth: .infinity)`. See `width`. */\n fillMaxWidth?: boolean;\n /** CAP the node's laid-out width at this many points. It CONSTRAINS, it\n * does not SET: a node narrower than the cap keeps its intrinsic width, so a\n * short chat bubble stays short while a long one stops at the cap — the\n * asymmetry `width` cannot express and `weight` cannot either (weights divide\n * the *available* space, so every child lands on the same fraction).\n * The cap covers the node's TOTAL width, padding + border included (CSS\n * `max-width` under `box-sizing: border-box`). `fillMaxWidth` grabs the axis but\n * THE CAP WINS — fill + cap is exactly `maxWidth` wide; a literal `width` larger\n * than the cap is clamped to it too. POINTS, not a fraction: a modifier is\n * resolved with no portable notion of \"the row\" on any renderer — compose a\n * percentage from a weighted sibling gap instead. `0`/absent = no cap. */\n maxWidth?: number;\n /** Flex weight along the parent stack's MAIN axis (HStack = width, VStack =\n * height). The stack measures its intrinsic (unweighted) children first, then\n * divides what is LEFT among the weighted ones in proportion to their weights:\n * two children at `1` split the leftover equally, `2` and `1` split it 2:1.\n * Absent (or <= 0) keeps the node at its intrinsic size. Use this — not\n * `fillMaxWidth` — when a node should claim the remaining space *beside* its\n * siblings; a full-bleed fill grabs the whole axis and starves them. Ignored\n * outside an HStack/VStack.\n * CAVEAT — row-sharing controls have NO intrinsic width to measure first: in an\n * HStack a `Button` and a `Toggle` are implicitly weighted, so they DIVIDE the\n * row with your weighted child instead of taking a width ahead of it —\n * `[VStack(weight:1), Button, Button]` is a three-way split, not \"the VStack gets\n * the remainder\". To opt a Button out on EVERY renderer set `fullWidth: false`\n * (an explicit flag wins in both directions; absent falls back to the style\n * default, which is \"share\" for every style but `tertiary`/`link`), or use\n * `iconOnly`. Otherwise give the actions their own row.\n * See `himi docs designing-himi-screens` §4. */\n weight?: number;\n /** RTL — pin the subtree's writing direction: `\"ltr\"` | `\"rtl\"` | `\"locale\"`\n * (inherit). The common RTL pattern is the inverse of `Text.textDirection`: pin a\n * container `ltr` (a logotype, an attachment-tile row, a composer button row)\n * inside an otherwise-RTL screen. Absent = inherit the ambient direction. #1060. */\n layoutDirection?: \"ltr\" | \"rtl\" | \"locale\";\n /** Absolute visual translation (pt/dp/px). Applied as the OUTERMOST transform so\n * it shifts the fully-styled node WITHOUT changing its layout footprint — lets a\n * ZStack place polar/absolute children (e.g. AgentOrbit). Negative allowed. */\n offsetX?: number;\n offsetY?: number;\n onTap?: Action;\n /** Double-tap gesture (e.g. Instagram double-tap-to-like). Distinct from onTap. */\n onDoubleTap?: Action;\n accessibilityLabel?: string;\n hidden?: boolean;\n /** Data-bound conditional visibility: a `\"$service:<svc>.<field>\"`\n * or `\"$item.<field>\"` sentinel resolving to a bool at render; when true the\n * subtree fully COLLAPSES (zero layout, like `hidden`), overriding the\n * literal `hidden`. */\n hiddenPath?: string;\n /** Long-press gesture modifier. */\n onLongPress?: Action;\n /** Fires once when the component first appears on screen. */\n onAppear?: Action;\n /** Scoped token overrides for the subtree. */\n theme?: ThemeOverrides;\n /** Names a token from the app's `shadows` registry. */\n shadow?: TokenRef;\n /** Names a token from the app's `gradients` registry (background fill). */\n backgroundGradient?: TokenRef;\n /** Value-driven gradient: a `$service:`/`$item.` path resolving to a gradient\n * TOKEN NAME used as the live background (e.g. weather sky day→night). */\n backgroundGradientPath?: string;\n /** Value-driven background COLOR: a `$service:`/`$item.` path resolving to a\n * color TOKEN NAME used as the live background. Honored on iOS + Android\n * (`BoundBackgroundIfPresent`); web render support is tracked in #1057. */\n backgroundColorPath?: string;\n /** Value-driven background IMAGE: a `$service:`/`$item.` path resolving to an\n * image URL used as the live background. Honored on iOS + Android; web render\n * support is tracked in #1057. */\n backgroundImagePath?: string;\n /** #1051 — MEASURE (publish): report this node's laid-out size into the renderer's\n * self-contained (worker-free) measure store under the named keys, so a SIBLING can\n * feed the number back into its own layout via a `$measure:<key>` binding. The SDUI\n * equivalent of SwiftUI `.onGeometryChange` / Compose `onGloballyPositioned`; the\n * canonical use is a pinned bottom bar whose measured height becomes a scroll view's\n * bottom inset (sticky-footer + scroll-inset, unrepresentable before — #1051). */\n onMeasure?: Measure;\n /** #1051 — bound height: a `\"$measure:<key>\"` (measure store) or\n * `\"$service:<svc>.<field>\"` / `\"$item.<field>\"` (worker/row state) sentinel resolving\n * to a NUMBER at render. Wins over the literal `height`. String path only, so a\n * binding can never blank the node (never overload the numeric `height`). */\n heightPath?: string;\n /** #1051 — bound width. See `heightPath`. Wins over the literal `width`. */\n widthPath?: string;\n /** #1051 — bound TOP inset (the header-height → top-inset idiom). A `$measure:`/\n * `$service:`/`$item.` sentinel resolving to a number; added as top padding. */\n paddingTopPath?: string;\n /** #1051 — bound BOTTOM inset (the sticky-footer-height → bottom-inset idiom). A\n * `$measure:`/`$service:`/`$item.` sentinel resolving to a number; added as bottom\n * padding so the last scroll row clears a pinned bottom bar. */\n paddingBottomPath?: string;\n /** #1053 — INLINE gradient background paint. Distinct from `backgroundGradient`\n * (which names a `gradients` registry token an embed host can't register): this\n * carries its stops/colours in the descriptor, so it works anywhere. Painted as\n * a surface (behind children), below `material`/`backgroundGradient` if those are\n * also set. */\n gradient?: Gradient;\n /** #1053 — gradient ALPHA MASK: the node shows through where the gradient is\n * opaque and fades where it's transparent (a transparent-token stop = fully\n * clear). The material-under-gradient-scrim / scroll-fade-out idiom. Mirrors iOS\n * `.mask()`, Android `BlendMode.DstIn`, web `mask-image`. */\n mask?: Gradient;\n /** Inline border/stroke. */\n border?: Border;\n /** Subtree alpha 0.0–1.0; absent = opaque. */\n opacity?: number;\n /** #433 — constrain the subtree to a width:height ratio (e.g. `1` = square,\n * `1.777` = 16:9). Width-driven: the height derives from the resolved width.\n * Lets a data-bound Grid cell / photo tile hold its shape at any width. */\n aspectRatio?: number;\n /** Names a token from the app's `motions` registry; the subtree\n * fades + slides up on first appearance. */\n appear?: TokenRef;\n /** Surface material token (flat/glass/frosted). */\n material?: TokenRef;\n /** Per-node construction-language override. */\n variant?: string;\n /** Per-node density / motion override. */\n density?: string;\n motion?: string;\n /** Motion grammar (Batch 1) — press/tap feedback style\n * (auto|none|scale|highlight|scaleHighlight|lift); timing comes from `motion`.\n * Applied only when the node is interactive. Unknown degrades to `auto`. */\n pressFeedback?: string;\n /** Motion grammar (Batch 1) — press-coupled haptic\n * (none|selection|light|medium|heavy|success|warning|error); fires with onTap.\n * Default none (opt-in). Web is a no-op. */\n haptic?: string;\n /** Motion grammar (Batch 1) — animate this container's size changes instead of\n * snapping (iOS `.animation(value:)`, Android `animateContentSize`, web height\n * transition). Curve from the active `motion` personality. */\n animateContentSize?: boolean;\n /** Motion grammar (Batch 1) — enter/exit transition; supersedes `appear` when\n * both are present. */\n transition?: Transition;\n /** Motion grammar (Batch 2, scroll-driven) — defer the `transition` enter until\n * the node first crosses into the scroll viewport (reveal-on-scroll). Gated by\n * reduced motion (shown immediately when reduced). */\n revealOnScroll?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — parallax factor: the node drifts\n * vertically at this fraction of the scroll delta as it moves through the\n * viewport. Gated by reduced motion. Generalizes CollapsingHeader parallaxFactor. */\n parallax?: number;\n /** Motion grammar (Batch 4, rich visual) — when a backgroundGradient is present,\n * continuously drift/rotate it (animated gradient). Gated by reduced motion. */\n animatedGradient?: boolean;\n /** Motion grammar (Batch 3, hero/nav) — shared-element key: a matching\n * transitionId on the source + destination screen morphs that element across\n * navigation (web View Transitions / iOS matchedTransitionSource / Android\n * SharedTransitionLayout). Gated by reduced motion. */\n transitionId?: string;\n /** Motion grammar (Batch 4, rich visual) — render the backgroundGradient as a\n * mesh (overlapping multi-point blend) rather than a linear sweep; combine with\n * animatedGradient to drift it. */\n meshGradient?: boolean;\n /** Motion grammar (Batch 4, celebration) — burst a one-shot confetti overlay\n * within the node on appear. Gated by reduced motion. */\n confetti?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — pin this node to the top of its\n * scroll container (sticky section header). Functional positioning; not gated\n * by reduced motion. */\n sticky?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — turn this thin node into a\n * reading-progress bar that fills left→right with scroll (scaleX). web reads\n * window scroll; native reads the host scroll fraction. Functional. */\n scrollProgress?: boolean;\n /** Motion grammar (Batch 4, rich vector) — a named built-in vector animation\n * (\"loader\" | \"success\" | \"pulse\"), dependency-free. Gated by reduced motion.\n * Arbitrary Lottie/Rive JSON is the lib-backed superset follow-up. */\n vectorAnim?: string;\n /** Motion grammar (Batch 1, choreography) — delay this node's ENTER transition\n * by N ms so a screen can SEQUENCE its entrance beyond list stagger (\"title\n * fades, subtitle slides in 100ms later\"). Adds to any list `staggerMs`. Gated\n * by reduced motion. 0/absent = no extra delay. */\n enterDelayMs?: number;\n /** Motion grammar (Phase-3, per-node MotionSpec) — overrides the PRESS-feedback\n * curve with a `motions`-registry token, mirroring `Transition.motion` for the\n * enter curve. Absent → press uses the global `motion` personality. */\n pressMotion?: TokenRef;\n /** Motion grammar (Phase-3, gesture-driven) — finger-tracked drag-to-dismiss; past\n * threshold along `direction` it animates off and fires `action`, else springs back. */\n swipeDismiss?: SwipeDismiss;\n /** Motion grammar (Phase-4, reduced-motion) — per-node escape from the reduced-motion\n * gate. Default true (respect). false = \"must move anyway\" (transition/press animate\n * even when reduced motion is on). */\n respectReducedMotion?: boolean;\n /** #1052 — uniform scale (1 = no change). */\n scale?: number;\n /** #1052 — rotation in DEGREES. */\n rotation?: number;\n /** #1052 — stacking order within a ZStack/overlay. */\n zIndex?: number;\n /** #1052 — gaussian blur radius (pt/dp/px). */\n blur?: number;\n /** #1052 — `false` = decorative (ignores taps, unlike `hidden` which stays hit-testable);\n * `true` = force hit-testable (a clear tap target). */\n hitTestable?: boolean;\n /** #1052 — explicit drop shadow (colour + radius + offset), for upward / multi-layer\n * shadows the `shadow` token can't express. */\n boxShadow?: Shadow;\n /** The role assistive technology should announce. Absent = the\n * renderer INFERS it from the component type, so an author never restates the\n * obvious. `\"decorative\"` is node-local: the node itself stops being an\n * accessibility element (no role, not a stop) while its children stay\n * traversable — the ARIA `role=\"presentation\"` semantic. To remove the subtree\n * too, use `accessibilityHidden`. */\n accessibilityRole?: AccessibilityRole;\n /** The supplementary \"what happens if I act\" phrase\n * (iOS `accessibilityHint`, Android `onClickLabel`, ARIA `aria-describedby`).\n * Never a substitute for `accessibilityLabel`: AT may not speak a hint at all. */\n accessibilityHint?: string;\n /** Remove this node AND its subtree from the accessibility tree.\n * Distinct from `hidden` (which removes it visually too) and from the\n * `\"decorative\"` role (node-local; children stay reachable). */\n accessibilityHidden?: boolean;\n /** Mark the node as a heading at level 1–6, for rotor / heading\n * navigation. Implies the `\"heading\"` role when `accessibilityRole` is absent.\n * The LEVEL reaches iOS and web only: Android (Compose `heading()`) and React Native\n * (`accessibilityRole: \"header\"`) have no level primitive and mark a plain heading. */\n headingLevel?: number;\n /** An explicit SPOKEN value where the visual and spoken values\n * differ (a rating \"3 of 5\", a gauge \"72 percent\"). Where the value is derivable\n * from a prop the renderer computes it and this override is unnecessary. */\n accessibilityValue?: string;\n /** Additive state that a single role cannot carry. A node may\n * hold several. */\n accessibilityTraits?: AccessibilityTrait[];\n /** Explicit traversal index within the nearest focus group. Absent ⇒\n * the arm's visual / DOM order. The lower index is reached FIRST, which is why iOS\n * (whose `accessibilitySortPriority` is descending) negates it. */\n focusOrder?: number;\n /** This node's subtree is ONE traversal scope: children are ordered\n * relative to each other, then the group takes its parent's slot (iOS\n * `shouldGroupAccessibilityChildren`, Android `isTraversalGroup`, ARIA grouping). */\n focusGroup?: boolean;\n /** AT / keyboard focus lands here when the screen or a newly presented\n * modal appears. At most one per focus scope. */\n initialFocus?: boolean;\n /** A general live-region announcement, on any node and any transition.\n * The generalization of `FragmentSlotCase.accessibilityAnnouncement`, which only ever\n * fired on a fragment-slot case swap. */\n announce?: LiveRegion;\n /** Clip ANY node (content + surface) to a shape:\n * \"rectangle\" | \"roundedRectangle\" | \"capsule\" | \"circle\" | \"ellipse\". Unknown/empty =\n * no shape clip (`cornerRadius` keeps its meaning). When set it replaces the\n * corner-radius-only clip; a \"roundedRectangle\" uses `clipCornerRadii`, else `cornerRadius`. */\n clipShape?: string;\n /** Uneven corner radii for `clipShape: \"roundedRectangle\"`. */\n clipCornerRadii?: CornerRadii;\n /** A recurring, finite, transform-only motion on a decorative node: a\n * living character's idle `hop`, a `blink`, a working `breathe`. Never changes the\n * node's layout footprint. Stopped by reduced motion unless `respectReducedMotion`\n * is false. */\n ambientMotion?: AmbientMotion;\n}",
@@ -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
- **87 core component types.** Every component body is `{ "package": "himalaya", "type": "<Type>", "props": { … } }`.
6
+ **88 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`, with an optional `==`/`!=` comparison (e.g. `hiddenPath: "$env:reduceMotion"`, `hiddenPath: "$env:colorScheme==light"`, `casePath: "$env:colorScheme"`). An unknown key fails open (shown / `defaultCase`).
@@ -37,6 +37,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
37
37
  | `Divider` | Core | — |
38
38
  | `Spacer` | Core | — |
39
39
  | `EmptyState` | Core | — |
40
+ | `ErrorState` | Core | `variantPath` |
40
41
  | `Text` | Core | `text` |
41
42
  | `Image` | Core | `url`, `initials`, `contentDescription` |
42
43
  | `Icon` | Core | — |
@@ -57,7 +58,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
57
58
  | `Skeleton` | State envelopes | — |
58
59
  | `FragmentSlot` | Descriptor-shipped dynamic structure | `casePath` |
59
60
  | `WorkerSubtree` | Bounded worker-authored component tree | `treeJsonPath` |
60
- | `SearchField` | Search & filter | — |
61
+ | `SearchField` | Search & filter | `focusPath` |
61
62
  | `FilterChips` | Search & filter | `optionsPath`, `selectedIds` |
62
63
  | `Tabs` | Navigation primitives | `optionsPath`, `activeColorPath`, `selectedId` |
63
64
  | `SegmentedControl` | Navigation primitives | `optionsPath`, `selectedId` |
@@ -109,9 +110,9 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
109
110
  | `Video` | Cross-cutting & media | — |
110
111
  | `PdfView` | Cross-cutting & media | — |
111
112
  | `MediaPlayer` | Cross-cutting & media | `itemsPath` |
112
- | `Lottie` | Rich vector & celebration (motion grammar Batch 4) | — |
113
+ | `Lottie` | Rich vector & celebration (motion grammar Batch 4) | `playPath` |
113
114
  | `Rive` | Rich vector & celebration (motion grammar Batch 4) | — |
114
- | `MonthCalendar` | Rich vector & celebration (motion grammar Batch 4) | `selectedDatePath`, `decorationsPath`, `yearPath`, `monthPath`, `selectedStartDatePath`, `selectedEndDatePath` |
115
+ | `MonthCalendar` | Rich vector & celebration (motion grammar Batch 4) | `selectedDatePath`, `decorationsPath`, `yearPath`, `monthPath`, `selectedStartDatePath`, `selectedEndDatePath`, `displayModePath` |
115
116
  | `TimeGrid` | Rich vector & celebration (motion grammar Batch 4) | `anchorDatePath`, `eventsPath`, `resourcesPath` |
116
117
  | `Agenda` | Rich vector & celebration (motion grammar Batch 4) | `eventsPath`, `scrollToDatePath` |
117
118
 
@@ -119,18 +120,16 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
119
120
 
120
121
  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.
121
122
 
122
- **Scope: 9 of 87 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:
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:
123
124
 
124
125
  - **`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.
125
- - **`styling` (7: `Text`, `Icon`, `Carousel`, `Pager`, `WebView`, `Video`, `PdfView`)** — the component itself **works**; only the listed prop is inert. Use the component; just do not depend on that one prop.
126
+ - **`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.
126
127
 
127
128
  | Component | Unread props | Platform | Kind | Note |
128
129
  |---|---|---|---|---|
129
130
  | `Text` | `minimumScaleFactor`, `truncation` | android, web | styling | Compose 1.7.5 and web retain tail truncation and their existing text size. Monospaced digits work; Apple supports shrinking and head/middle truncation. React Native maps its native text API, with Android multiline ellipsis limited to tail. |
130
131
  | `Icon` | `renderingMode`, `weight`, `variableValue` | android, web | styling | Fixed catalog vectors have no SF layers, font-weight axis or variable fills. Bounce and pulse animate the whole glyph; variableColor remains static. React Native uses the same catalog fallback. |
131
- | `Carousel` | `liveWindow` | ios, android, web | styling | Declared ahead of its runtime: every page stays mounted until Pager/Carousel live_window lands on each renderer (W8, #4630). |
132
- | `Pager` | `liveWindow` | ios, android, web | styling | Declared ahead of its runtime: every page stays mounted until Pager/Carousel live_window lands on each renderer (W8, #4630). |
133
- | `WebView` | `bridge`, `instanceId`, `activePath`, `mutedPath`, `reloadKeyPath`, `heightPath`, `fill` | ios, android, web | styling | The embedded worker-bridged WebView contract is declared ahead of its runtime: no channel, instance identity, fill layout, bound height or active/muted/reload binding until the hosts implement it (iOS/macOS #4624/#4625, Android #4626/#4627, web #4628/#4629). |
132
+ | `WebView` | `bridge`, `instanceId`, `activePath`, `mutedPath`, `reloadKeyPath`, `heightPath`, `fill` | ios, android | styling | The embedded worker-bridged WebView contract is declared ahead of its runtime on these platforms: no channel, instance identity, fill layout, bound height or active/muted/reload binding until their hosts implement it. On web all of them work for a cross-origin url page listed in allowedOrigins; an inlineHtml page is not bridged on web yet. |
134
133
  | `Video` | `poster` | ios | styling | `poster` is not shown. |
135
134
  | `PdfView` | `showPageControls`, `zoomable`, `allowShare` | web | styling | Interactive PDF.js canvas rendering honours the reader’s page, zoom and export controls. Static previews and PDF.js loading/rendering failures use a native browser-viewer fallback. In that fallback, hiding page or zoom controls is only a viewer hint and cannot reliably hide browser chrome; allowShare hides the reader’s export controls but cannot prevent browser save/print. These props are UI controls, not document access restrictions. |
136
135
  | `MediaPlayer` | `autoplayVisibilityThreshold`, `preload`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` | ios | category | `autoplayVisibilityThreshold` is ignored (whenVisible = once on appear). `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render. |
@@ -324,6 +323,14 @@ export interface GridProps {
324
323
  * row. Reduced motion collapses it to 0. Absent/0 ⇒ no cascade, which is the back-compat
325
324
  * default. */
326
325
  staggerMs?: number;
326
+ /** #4672 — pull-to-refresh, exactly as on `LazyList`: the screen's refresh gesture dispatches
327
+ * this action (the shells find the first `onPullToRefresh` in the payload, list or grid). */
328
+ onPullToRefresh?: Action;
329
+ /** #4672 — infinite scroll, as on `LazyList`: `onLoadMore` fires once per published non-empty
330
+ * `cursor` (usually a `$service:` binding) when the grid's trailing edge comes into view; a cursor
331
+ * the worker publishes again (a refresh back to page 2) pages again. An empty cursor is
332
+ * exhausted, and nothing fires. Works with `items` and `itemsPath`. */
333
+ pagination?: Pagination;
327
334
  }
328
335
  ```
329
336
 
@@ -399,6 +406,24 @@ export interface EmptyStateProps {
399
406
  }
400
407
  ```
401
408
 
409
+ #### ErrorState
410
+
411
+ ```ts
412
+ export interface ErrorStateProps {
413
+ /** Default `"server"`. An unknown value is treated as `"server"`. */
414
+ variant?: "server" | "offline" | "permission" | "signIn";
415
+ /** `$service:` binding that selects the variant at runtime; wins over `variant`. */
416
+ variantPath?: string;
417
+ title?: string;
418
+ body?: string;
419
+ /** The CTA's action. Absent → no button. */
420
+ cta?: Action;
421
+ ctaLabel?: string;
422
+ /** `"screen"` (default, ~96px from the top) or `"section"` (~24px). */
423
+ placement?: "screen" | "section";
424
+ }
425
+ ```
426
+
402
427
  #### Text
403
428
 
404
429
  ```ts
@@ -595,6 +620,9 @@ export interface TextFieldProps {
595
620
  errorText?: string;
596
621
  /** Opt-in accessible show/hide button. Only applies to secure fields; initially masked. */
597
622
  visibilityToggle?: boolean;
623
+ /** `onChange` fires after this many ms of idle typing. 0 or unset = every change.
624
+ * A submit fires a pending debounced `onChange` first. */
625
+ debounceMs?: number;
598
626
  }
599
627
  ```
600
628
 
@@ -657,6 +685,8 @@ export interface StepperProps {
657
685
  min?: number;
658
686
  max?: number;
659
687
  onChange?: Action;
688
+ /** "plain" draws exposed minus/value/plus controls; absent uses the default capsule. */
689
+ presentation?: "plain";
660
690
  }
661
691
  ```
662
692
 
@@ -893,6 +923,7 @@ export interface SkeletonProps {
893
923
  height?: number;
894
924
  cornerRadius?: number;
895
925
  count?: number;
926
+ style?: "static" | "shimmer";
896
927
  }
897
928
  ```
898
929
 
@@ -932,7 +963,11 @@ export interface SearchFieldProps {
932
963
  value?: string;
933
964
  onChange?: Action;
934
965
  onSubmit?: Action;
966
+ /** `onChange` fires after this many ms of idle typing; 0 or less (or unset) = 250 ms.
967
+ * A submit fires a pending debounced `onChange` first. */
935
968
  debounceMs?: number;
969
+ /** `$service:` binding → a focus TOKEN; `TextFieldProps.focusPath` semantics. */
970
+ focusPath?: string;
936
971
  }
937
972
  ```
938
973
 
@@ -1031,8 +1066,6 @@ export interface CarouselProps {
1031
1066
  }
1032
1067
  ```
1033
1068
 
1034
- > ⚠️ **Implemented, but these props are inert on ios, android, web:** `liveWindow` — Declared ahead of its runtime: every page stays mounted until Pager/Carousel live_window lands on each renderer (W8, #4630).
1035
-
1036
1069
  #### Pager
1037
1070
 
1038
1071
  ```ts
@@ -1046,8 +1079,6 @@ export interface PagerProps {
1046
1079
  }
1047
1080
  ```
1048
1081
 
1049
- > ⚠️ **Implemented, but these props are inert on ios, android, web:** `liveWindow` — Declared ahead of its runtime: every page stays mounted until Pager/Carousel live_window lands on each renderer (W8, #4630).
1050
-
1051
1082
  #### StepIndicator
1052
1083
 
1053
1084
  ```ts
@@ -1207,7 +1238,7 @@ export interface WebViewProps {
1207
1238
  }
1208
1239
  ```
1209
1240
 
1210
- > ⚠️ **Implemented, but these props are inert on ios, android, web:** `bridge`, `instanceId`, `activePath`, `mutedPath`, `reloadKeyPath`, `heightPath`, `fill` — The embedded worker-bridged WebView contract is declared ahead of its runtime: no channel, instance identity, fill layout, bound height or active/muted/reload binding until the hosts implement it (iOS/macOS #4624/#4625, Android #4626/#4627, web #4628/#4629).
1241
+ > ⚠️ **Implemented, but these props are inert on ios, android:** `bridge`, `instanceId`, `activePath`, `mutedPath`, `reloadKeyPath`, `heightPath`, `fill` — The embedded worker-bridged WebView contract is declared ahead of its runtime on these platforms: no channel, instance identity, fill layout, bound height or active/muted/reload binding until their hosts implement it. On web all of them work for a cross-origin url page listed in allowedOrigins; an inlineHtml page is not bridged on web yet.
1211
1242
 
1212
1243
  ### Controls & cells
1213
1244
 
@@ -2055,6 +2086,8 @@ export interface VideoProps {
2055
2086
  muted?: boolean;
2056
2087
  height?: number;
2057
2088
  aspectRatio?: number;
2089
+ /** #4669 — `"fit"` (default) letterboxes; `"fill"` covers the frame and crops (poster too). */
2090
+ contentMode?: "fit" | "fill";
2058
2091
  }
2059
2092
  ```
2060
2093
 
@@ -2149,6 +2182,10 @@ export interface LottieProps {
2149
2182
  autoplay?: boolean;
2150
2183
  speed?: number;
2151
2184
  height?: number;
2185
+ /** #4669 — `$service:` binding to a nonce: each CHANGE of the resolved value plays the
2186
+ * animation once from frame 0. A Lottie bound this way is a one-shot (`loop` is treated as
2187
+ * false). The first value plays only when `autoplay` is true; reduced motion never replays. */
2188
+ playPath?: string;
2152
2189
  }
2153
2190
  ```
2154
2191
 
@@ -2205,6 +2242,19 @@ export interface MonthCalendarProps {
2205
2242
  selectedEndDatePath?: string;
2206
2243
  /** Fires with args{startDate, endDate} once a range completes. */
2207
2244
  onSelectRange?: Action;
2245
+ /** "month" (default) | "week" — the one-row collapsed calendar an agenda sits
2246
+ * under. Week mode shows the week holding the selected day (else today), pages
2247
+ * by 7 days, and titles the month holding most of that week. */
2248
+ displayMode?: "month" | "week";
2249
+ /** Worker-driven mode, applied on CHANGE (the `yearPath` rule). */
2250
+ displayModePath?: string;
2251
+ /** Draw the week ↔ month toggle: a chevron on the title and a grab handle. */
2252
+ expandable?: boolean;
2253
+ /** The user switched mode → args{mode}. */
2254
+ onDisplayModeChange?: Action;
2255
+ /** The visible days changed (a page in either mode, or a mode switch) →
2256
+ * args{startDate, endDate}, LOCAL "YYYY-MM-DD" of the first and last cell. */
2257
+ onVisibleRangeChange?: Action;
2208
2258
  }
2209
2259
  ```
2210
2260
 
@@ -2315,6 +2365,9 @@ export interface ScreenDescriptor {
2315
2365
  /** Per-screen top bar (trailing actions +
2316
2366
  * leading/style override). Fully replaces `NavigationConfig.topBarDefault`. */
2317
2367
  topBar?: TopBarConfig;
2368
+ /** "" / "visible" (default) | "hidden": hide the bottom tab bar while this screen is the
2369
+ * visible top of its tab's stack. Static; no effect in a presentation. */
2370
+ tabBarVisibility?: string;
2318
2371
  etag?: string;
2319
2372
  cacheControl?: CacheControl;
2320
2373
  telemetryScreenName?: string;
@@ -87,7 +87,11 @@ function shape(s: Partial<ProductsState>): ProductsState {
87
87
  // under `himi test` / `himi render`.
88
88
  const STATUS = /\b(?:HTTP|Wix) (\d{3})\b/;
89
89
  // No answer at all — what a dropped connection says on every platform.
90
- const TRANSPORT = /NSURLErrorDomain|(?:UnknownHost|Connect|SocketTimeout|NoRouteToHost|SSLHandshake)Exception|Failed to connect|Unable to resolve host|Connection (?:refused|reset)|Network is unreachable|Software caused connection abort|ECONNRESET|ETIMEDOUT|EPIPE|Broken pipe|stream was reset|unexpected end of stream|timed out|^timeout$|Failed to fetch|NetworkError|^(?:TypeError: )?Load failed$|net::ERR_|^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i;
90
+ const TRANSPORT = /(?:UnknownHost|Connect|NoRouteToHost|SSLHandshake)Exception|Unable to resolve host|Connection (?:refused|reset)|Network is unreachable|Software caused connection abort|ECONNRESET|EPIPE|Broken pipe|stream was reset|unexpected end of stream|Failed to fetch|NetworkError|^(?:TypeError: )?Load failed$|net::ERR_|^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i;
91
+ // iOS reports a URLError as `NSURLErrorDomain error <code>`: only the no-route codes are offline. A bad server
92
+ // response (-1011) or a timeout is a server problem, as in the SDK's classifyLoadError.
93
+ const URL_ERROR = /NSURLErrorDomain\D*?(-?\d{3,5})/;
94
+ const URL_OFFLINE = new Set([-1009, -1005, -1004, -1003, -1006, -1020]);
91
95
  // Android reports an unknown host as the bare hostname (`users.wix.com`) — hence the last form.
92
96
  // No session on the device: the native bridge refuses before any request leaves.
93
97
  // A pinned site with no member session: iOS `siteNotPermitted(requested: "account", …)`,
@@ -119,6 +123,8 @@ function classifyError(e: unknown, authState: "signedIn" | "signedOut" | "unknow
119
123
  ? /"detail"\s*:\s*"([^"]+)"/.exec(msg)?.[1] ?? msg
120
124
  : msg;
121
125
  if (status && detail === msg) return "server";
126
+ const urlCode = URL_ERROR.exec(detail)?.[1];
127
+ if (urlCode !== undefined) return URL_OFFLINE.has(Number(urlCode)) ? "offline" : "server";
122
128
  if (TRANSPORT.test(detail)) return "offline";
123
129
  return "server"; // anything else — a bug, a malformed row — is not the user's connection
124
130
  }
@@ -60,7 +60,11 @@ const SAVE_FAILED: Record<Exclude<ErrorKind, "">, string> = {
60
60
  // under `himi test` / `himi render`.
61
61
  const STATUS = /\b(?:HTTP|Wix) (\d{3})\b/;
62
62
  // No answer at all — what a dropped connection says on every platform.
63
- const TRANSPORT = /NSURLErrorDomain|(?:UnknownHost|Connect|SocketTimeout|NoRouteToHost|SSLHandshake)Exception|Failed to connect|Unable to resolve host|Connection (?:refused|reset)|Network is unreachable|Software caused connection abort|ECONNRESET|ETIMEDOUT|EPIPE|Broken pipe|stream was reset|unexpected end of stream|timed out|^timeout$|Failed to fetch|NetworkError|^(?:TypeError: )?Load failed$|net::ERR_|^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i;
63
+ const TRANSPORT = /(?:UnknownHost|Connect|NoRouteToHost|SSLHandshake)Exception|Unable to resolve host|Connection (?:refused|reset)|Network is unreachable|Software caused connection abort|ECONNRESET|EPIPE|Broken pipe|stream was reset|unexpected end of stream|Failed to fetch|NetworkError|^(?:TypeError: )?Load failed$|net::ERR_|^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i;
64
+ // iOS reports a URLError as `NSURLErrorDomain error <code>`: only the no-route codes are offline. A bad server
65
+ // response (-1011) or a timeout is a server problem, as in the SDK's classifyLoadError.
66
+ const URL_ERROR = /NSURLErrorDomain\D*?(-?\d{3,5})/;
67
+ const URL_OFFLINE = new Set([-1009, -1005, -1004, -1003, -1006, -1020]);
64
68
  // Android reports an unknown host as the bare hostname (`users.wix.com`) — hence the last form.
65
69
  // No session on the device: the native bridge refuses before any request leaves.
66
70
  // A pinned site with no member session: iOS `siteNotPermitted(requested: "account", …)`,
@@ -92,6 +96,8 @@ function classifyError(e: unknown, authState: "signedIn" | "signedOut" | "unknow
92
96
  ? /"detail"\s*:\s*"([^"]+)"/.exec(msg)?.[1] ?? msg
93
97
  : msg;
94
98
  if (status && detail === msg) return "server";
99
+ const urlCode = URL_ERROR.exec(detail)?.[1];
100
+ if (urlCode !== undefined) return URL_OFFLINE.has(Number(urlCode)) ? "offline" : "server";
95
101
  if (TRANSPORT.test(detail)) return "offline";
96
102
  return "server"; // anything else — a bug, a malformed row — is not the user's connection
97
103
  }
@@ -80,6 +80,13 @@ The `ux_evidence_*` codes map to the existing typed `uxEvidence.state`; a succes
80
80
  `recorded` state has no refusal code. Successful pushes and warnings do not gain
81
81
  failure codes. A forced push still follows the existing override rules.
82
82
 
83
+ After a full `himi push --live`, push reads the app's rollout rings (`GET /v1/releases`). A ring
84
+ pinned to another release keeps serving that release to its devices, so the success envelope
85
+ then carries a `ring_pinned` entry in `warnings` (also printed to stderr) and
86
+ `ringPins: [{ ring, releaseId, promote }]`, where `promote` is the `himi promote … --ring <ring>`
87
+ that moves the ring to the new release. When the push passed `--control-url`, `promote` repeats it. It stays `ok: true`. When the rings cannot be read, push
88
+ prints neither.
89
+
83
90
  ## `himi cms seed` codes
84
91
 
85
92
  | Code | Meaning |
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": 87,
4
+ "componentCount": 88,
5
5
  "schemaSource": "core/ts/src/schema.ts"
6
6
  },
7
7
  "portalUrl": "https://apps.wix.com/_api/himi-serve/docs",
@@ -25,7 +25,7 @@
25
25
  "Keep structure descriptor-authored: use FragmentSlot for enumerable variants and bound list/grid templates for repetition. Use WorkerSubtree only for runtime-unknowable structure, with an explicit screen allowlist, policy budgets, and shipped fallback.",
26
26
  "Bindings ($service:<svc>.<path>, $item.<field>, $flag:<key>) are plain strings in ANY prop — INCLUDING Modifiers token refs (background, color, tint), so a worker can drive per-item theming, not just text. They CANNOT target strict-enum fields (style, size, alignment, severity, presentation, …) — those fail at render, not validation.",
27
27
  "A screen that loads data asynchronously ships a designed LOADING state with it — a `Skeleton` shaped like the loaded layout when that layout is known (`StatefulSection` or `ActivityIndicator` when they fit the behavior better) — and a REDESIGN of a data screen reviews and updates that loading state in the same change, so it never shows the old layout. Loading is its own state, never the empty or the error state; a static screen gets no loading state (`himi docs designing-himi-screens` §5).",
28
- "CLASSIFY every error before you show it: offline, server failure, sign-in, site membership and permission each get their own `EmptyState` with `title` + `subtitle` and NO `icon` (one node per class, gated by `hiddenPath`, because iOS/Android do not bind `$service:` in its text), user-facing copy — never `e.message`, a status code or an API error string — and an `action` + `actionLabel` ONLY for a real recovery (Try again for offline, server and failed writes; the app's auth flow for sign-in; the site's join flow for `NotSiteMember` when available; none for permission, empty results or field errors). Empty results are the empty state, not an error. A refresh that fails KEEPS the rows and adds a `Banner` with retry; a load-more that fails keeps the rows and ends the list with a \"Couldn't load more\" row that retries the SAME cursor (`himi docs recipe-dynamic-list`), never the Banner, which reloads page 1. A sign-in, membership or permission failure CLEARS the rows instead — they are no longer the user's to see. Field errors stay in the field's slot; a failed action shows next to the action. When an `ErrorState` component ships, only the component changes — these rules stay (`himi docs designing-himi-screens` §5, `himi docs recipe-four-state`).",
28
+ "CLASSIFY every error before you show it: offline, server failure, sign-in, site membership and permission each show an `ErrorState` (gated by `hiddenPath`): `variantPath` bound to the worker's error kind switches the copy and glyph, but `cta`, `title`, `body` and `ctaLabel` apply to every variant, so offline and server share one node with the retry `cta` while sign-in, permission and membership (`variant: \"permission\"` with its own `title`/`body`) keep a node each; user-facing copy — never `e.message`, a status code or an API error string — and a `cta` ONLY for a real recovery (Try again for offline, server and failed writes; the app's auth flow for sign-in; the site's join flow for `NotSiteMember` when available; none for permission, empty results or field errors). Empty results are the empty state, not an error. A refresh that fails KEEPS the rows and adds a `Banner` with retry; a load-more that fails keeps the rows and ends the list with a \"Couldn't load more\" row that retries the SAME cursor (`himi docs recipe-dynamic-list`), never the Banner, which reloads page 1. A sign-in, membership or permission failure CLEARS the rows instead — they are no longer the user's to see. Field errors stay in the field's slot; a failed action shows next to the action. (`himi docs designing-himi-screens` §5, `himi docs recipe-four-state`).",
29
29
  "Workers are sandboxed: no DOM, no timers, no raw fetch. Produce state synchronously in actions; do not rely on an ambient refresh tick.",
30
30
  "Declare every capability a worker uses in TWO places: the calling screen's logicBundle.declaredCapabilities (wix_platform, stream, camera, location, …) — undeclared calls are blocked — AND config.ts `capabilities`, the app-level list a binary is built with. The `capabilities` guide shows each id's import, what the binary needs and which shells serve it; validate flags an unknown id, an undeclared call, and (with `shell` in himalaya.content.json) an id that shell's binary does not serve. A capability with a native footprint (camera, location, push, …) only arrives with a new binary — an over-the-air push cannot add it. EXCEPTION: the Wix REST bridge (wix_api — the @himalaya/wix-* clients / wixApi.call) is ungated today (installed unconditionally), so it works WITHOUT a declaration; declaring wix_api is still recommended — it is where the platform is heading, and it documents what your worker touches.",
31
31
  "`validate`/`render` prove a screen is WELL-FORMED and, by default, paint its INITIAL state — NOT that the app's LOGIC is correct. A wrong reducer (a game that never ends, a bad total) passes both with ok:true. Run `himi test`: it needs NO test files — it boots each screen's real worker and dispatches every action your descriptor already declares, then reports anything that threw, stayed stuck on `loading`, or leaked a NaN/undefined/`$service:` token into visible text. It crawls on HERMETIC DATA by default (zero network): your `dev/net-mocks.json` first, then your config's CMS `seed:` rows, then a canned fixture of frozen real vertical responses shipped with the CLI — so the fetch-SUCCESS mapping path runs too, where data-shape bugs actually live (canned data exercises code paths, not API-contract truth). Two boot-only HOSTILE passes (every list empty; every request 500) then check the empty/outage day — their findings are errors, not warnings. It also runs the worker in a DEVICE-PARITY sandbox (no setTimeout/setInterval, as on JSC/WebView), so a bundle that would only die on the device fails locally instead — e.g. `svc.actions.load(…)` (actions are DIRECT methods; `.actions` is undefined on device). (You are NOT limited to the crawled states either: rendering with `--live` runs the worker's REAL fetches against live Wix — a minted anonymous visitor token from the app's clientId — so a render CAN prove real-data binding locally with no VPN/push; and injecting `state` (`{\"<svc>\":{…}}`) renders a specific mid-flow state without the network.)",
@@ -22,6 +22,9 @@ more than half model error, and the reference apps (`apps/himi-owner-lite`,
22
22
 
23
23
  ## 1. One status field, a classified error, derived flags
24
24
 
25
+ > `classifyError` below ships in the SDK as `classifyLoadError(error, authState)` from `@himalaya/state`
26
+ > (same rules, returns the `ErrorKind` without `""`). Import it instead of pasting the regexes into a new worker.
27
+
25
28
  ```ts
26
29
  type ErrorKind = "" | "offline" | "server" | "signIn" | "membership" | "permission";
27
30
 
@@ -63,7 +66,11 @@ function shape(s: Partial<State>): State {
63
66
  // under `himi test` / `himi render`.
64
67
  const STATUS = /\b(?:HTTP|Wix) (\d{3})\b/;
65
68
  // No answer at all — what a dropped connection says on every platform.
66
- const TRANSPORT = /NSURLErrorDomain|(?:UnknownHost|Connect|SocketTimeout|NoRouteToHost|SSLHandshake)Exception|Failed to connect|Unable to resolve host|Connection (?:refused|reset)|Network is unreachable|Software caused connection abort|ECONNRESET|ETIMEDOUT|EPIPE|Broken pipe|stream was reset|unexpected end of stream|timed out|^timeout$|Failed to fetch|NetworkError|^(?:TypeError: )?Load failed$|net::ERR_|^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i;
69
+ const TRANSPORT = /(?:UnknownHost|Connect|NoRouteToHost|SSLHandshake)Exception|Unable to resolve host|Connection (?:refused|reset)|Network is unreachable|Software caused connection abort|ECONNRESET|EPIPE|Broken pipe|stream was reset|unexpected end of stream|Failed to fetch|NetworkError|^(?:TypeError: )?Load failed$|net::ERR_|^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i;
70
+ // iOS reports a URLError as `NSURLErrorDomain error <code>`: only the no-route codes are offline. A bad server
71
+ // response (-1011) or a timeout is a server problem, as in the SDK's classifyLoadError.
72
+ const URL_ERROR = /NSURLErrorDomain\D*?(-?\d{3,5})/;
73
+ const URL_OFFLINE = new Set([-1009, -1005, -1004, -1003, -1006, -1020]);
67
74
  // Android reports an unknown host as the bare hostname (`users.wix.com`) — hence the last form.
68
75
  // No session on the device: the native bridge refuses before any request leaves.
69
76
  // A pinned site with no member session: iOS `siteNotPermitted(requested: "account", …)`,
@@ -95,6 +102,8 @@ function classifyError(e: unknown, authState: "signedIn" | "signedOut" | "unknow
95
102
  ? /"detail"\s*:\s*"([^"]+)"/.exec(msg)?.[1] ?? msg
96
103
  : msg;
97
104
  if (status && detail === msg) return "server";
105
+ const urlCode = URL_ERROR.exec(detail)?.[1];
106
+ if (urlCode !== undefined) return URL_OFFLINE.has(Number(urlCode)) ? "offline" : "server";
98
107
  if (TRANSPORT.test(detail)) return "offline";
99
108
  return "server"; // anything else — a bug, a malformed row — is not the user's connection
100
109
  }