@wix/himalaya-cli 0.1188.0 → 0.1190.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.
package/dist/cli.mjs CHANGED
@@ -25225,6 +25225,7 @@ function shouldRevealError(args) {
25225
25225
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
25226
25226
  var init_validation = __esm({
25227
25227
  "../../core/ts/src/validation.ts"() {
25228
+ "use strict";
25228
25229
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
25229
25230
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
25230
25231
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -25884,6 +25885,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
25884
25885
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
25885
25886
  var init_worker_subtree = __esm({
25886
25887
  "../../core/ts/src/worker-subtree.ts"() {
25888
+ "use strict";
25887
25889
  init_worker_subtree_safe_types_generated();
25888
25890
  WORKER_SUBTREE_CEILINGS = Object.freeze({
25889
25891
  maxEncodedBytes: 65536,
@@ -31656,6 +31658,26 @@ function encodeDateFieldValue(raw) {
31656
31658
  const ms = Date.parse(raw);
31657
31659
  return Number.isFinite(ms) ? String(Math.trunc(ms / 1e3)) : "";
31658
31660
  }
31661
+ function clampDateFieldValue(encoded2, minUnix, maxUnix, mode) {
31662
+ if (!/^-?\d+$/.test(encoded2)) return encoded2;
31663
+ return String(clampDateUnix(Number(encoded2), minUnix, maxUnix, mode));
31664
+ }
31665
+ function clampDateUnix(unix, minUnix, maxUnix, mode) {
31666
+ const onDay = (bound) => {
31667
+ if (typeof bound !== "number") return null;
31668
+ if (mode !== "time") return bound;
31669
+ const b = new Date(bound * 1e3);
31670
+ const d = new Date(unix * 1e3);
31671
+ d.setHours(b.getHours(), b.getMinutes(), b.getSeconds(), 0);
31672
+ return Math.floor(d.getTime() / 1e3);
31673
+ };
31674
+ const lo = onDay(minUnix);
31675
+ const hi = onDay(maxUnix);
31676
+ let held = unix;
31677
+ if (hi != null && held > hi) held = hi;
31678
+ if (lo != null && held < lo) held = lo;
31679
+ return held;
31680
+ }
31659
31681
  var ISO_INTERNET_DATE_TIME;
31660
31682
  var init_form_bag = __esm({
31661
31683
  "../../core/ts/src/form-bag.ts"() {
@@ -37484,6 +37506,10 @@ var init_renderNode = __esm({
37484
37506
  InteractiveDatePicker = withExtras("InteractiveDatePicker", ({ props, ctx, wrap }) => {
37485
37507
  const C = ctx.color;
37486
37508
  const timeMode = props.mode === "time";
37509
+ const dateTimeMode = props.mode === "datetime";
37510
+ const minUnix = typeof props.minUnix === "number" ? props.minUnix : null;
37511
+ const maxUnix = typeof props.maxUnix === "number" ? props.maxUnix : null;
37512
+ const clampUnix = (unix) => clampDateUnix(unix, minUnix, maxUnix, props.mode);
37487
37513
  const boundRaw = useBoundValue(ctx, typeof props.valuePath === "string" && isBinding(props.valuePath) ? props.valuePath : "");
37488
37514
  const seedUnix = () => {
37489
37515
  if (typeof boundRaw === "number") return boundRaw;
@@ -37497,16 +37523,22 @@ var init_renderNode = __esm({
37497
37523
  if (unix == null) return "";
37498
37524
  const d = new Date(unix * 1e3);
37499
37525
  const p = (n) => String(n).padStart(2, "0");
37500
- return timeMode ? `${p(d.getHours())}:${p(d.getMinutes())}` : `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
37526
+ const day = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
37527
+ const time = `${p(d.getHours())}:${p(d.getMinutes())}`;
37528
+ return timeMode ? time : dateTimeMode ? `${day}T${time}` : day;
37529
+ };
37530
+ const shownSeed = () => {
37531
+ const s = seedUnix();
37532
+ return s == null ? null : clampUnix(s);
37501
37533
  };
37502
- const [val, setVal] = React7.useState(ctx.empty ? "" : toInput(seedUnix()));
37534
+ const [val, setVal] = React7.useState(ctx.empty ? "" : toInput(shownSeed()));
37503
37535
  const dateServiceBound = typeof props.valuePath === "string" && props.valuePath.startsWith("$service:");
37504
37536
  const bagSource = () => dateServiceBound ? boundRaw : boundRaw !== void 0 && boundRaw !== null && boundRaw !== "" ? boundRaw : props.valueUnix;
37505
- const [dateBagVal, setDateBagVal] = React7.useState(ctx.empty ? "" : encodeDateFieldValue(bagSource()));
37537
+ const [dateBagVal, setDateBagVal] = React7.useState(ctx.empty ? "" : clampDateFieldValue(encodeDateFieldValue(bagSource()), props.minUnix, props.maxUnix, props.mode));
37506
37538
  React7.useEffect(() => {
37507
37539
  if (ctx.empty) return;
37508
- setVal(toInput(seedUnix()));
37509
- setDateBagVal(encodeDateFieldValue(bagSource()));
37540
+ setVal(toInput(shownSeed()));
37541
+ setDateBagVal(clampDateFieldValue(encodeDateFieldValue(bagSource()), props.minUnix, props.maxUnix, props.mode));
37510
37542
  }, [boundRaw, props.valueUnix, props.valuePath, ctx.empty]);
37511
37543
  const dateBag = React7.useContext(FormValuesContext);
37512
37544
  const dateFieldId = props.__formFieldId;
@@ -37523,7 +37555,7 @@ var init_renderNode = __esm({
37523
37555
  let label2 = "Select date";
37524
37556
  if (!ctx.empty && props.valueUnix) {
37525
37557
  const d = new Date(props.valueUnix * 1e3);
37526
- label2 = timeMode ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
37558
+ label2 = timeMode ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : dateTimeMode ? d.toLocaleString(void 0, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
37527
37559
  }
37528
37560
  return /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: ctx.radius("md", 8), padding: "12px 14px", boxSizing: "border-box", fontFamily: bodyFont(ctx), fontSize: pxToRem(15), color: C("textPrimary", "#111"), ...wrap }, children: [
37529
37561
  /* @__PURE__ */ jsx8("span", { children: label2 }),
@@ -37537,12 +37569,14 @@ var init_renderNode = __esm({
37537
37569
  setDateBagVal("");
37538
37570
  return;
37539
37571
  }
37540
- const unix = timeMode ? (() => {
37572
+ const picked = timeMode ? (() => {
37541
37573
  const [h, m] = raw.split(":").map(Number);
37542
37574
  const d = /* @__PURE__ */ new Date();
37543
37575
  d.setHours(h || 0, m || 0, 0, 0);
37544
37576
  return Math.floor(d.getTime() / 1e3);
37545
- })() : Math.floor(Date.parse(`${raw}T00:00:00`) / 1e3);
37577
+ })() : dateTimeMode ? Math.floor(Date.parse(raw) / 1e3) : Math.floor(Date.parse(`${raw}T00:00:00`) / 1e3);
37578
+ const unix = Number.isFinite(picked) ? clampUnix(picked) : NaN;
37579
+ if (Number.isFinite(unix) && unix !== picked) setVal(toInput(unix));
37546
37580
  const value = Number.isFinite(unix) ? String(unix) : "";
37547
37581
  setDateBagVal(value);
37548
37582
  if (!onChange || !ctx.onAction) return;
@@ -37551,10 +37585,12 @@ var init_renderNode = __esm({
37551
37585
  return /* @__PURE__ */ jsx8(
37552
37586
  "input",
37553
37587
  {
37554
- type: timeMode ? "time" : "date",
37588
+ type: timeMode ? "time" : dateTimeMode ? "datetime-local" : "date",
37555
37589
  value: val,
37556
37590
  onChange: (e) => emit(e.currentTarget.value),
37557
- "aria-label": timeMode ? "Select time" : "Select date",
37591
+ min: minUnix != null ? toInput(minUnix) : void 0,
37592
+ max: maxUnix != null ? toInput(maxUnix) : void 0,
37593
+ "aria-label": timeMode ? "Select time" : dateTimeMode ? "Select date and time" : "Select date",
37558
37594
  style: { width: "100%", boxSizing: "border-box", background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: ctx.radius("md", 8), padding: "12px 14px", fontFamily: bodyFont(ctx), fontSize: pxToRem(15), color: C("textPrimary", "#111"), ...wrap }
37559
37595
  }
37560
37596
  );
@@ -61120,21 +61156,21 @@ var init_preview_assets_generated = __esm({
61120
61156
  "../../packages/serve/src/preview-assets.generated.ts"() {
61121
61157
  init_preview_assets_format();
61122
61158
  PREVIEW_ASSET_MANIFEST = [
61123
- { key: "assets/index-CuJ-kgj4.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1134765, sha256: "a9096277792ac97bb0ef720bee2022174e7819c53db0d22ae6be2710e9412c1e" },
61124
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1134773, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
61125
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1139383, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
61126
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1627967, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
61127
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1718891, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
61128
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1809875, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
61129
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1900855, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
61130
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1991727, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
61131
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2082707, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
61132
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2191343, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
61133
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2300427, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
61134
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2409475, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
61135
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2517175, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
61136
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2605283, length: 4947, sha256: "2bd45a6fb7d319fcfb7bb44c472decfd2fb6780a7ad852fde57e144fb4f97c86" },
61137
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2610230, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
61159
+ { key: "assets/index-C_AIgKp4.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1135680, sha256: "dec95bd7419620a5edfb4e9cdcf7a027a81cce9009bc214cf03578d384fc1724" },
61160
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1135688, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
61161
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1140298, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
61162
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1628882, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
61163
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1719806, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
61164
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1810790, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
61165
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1901770, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
61166
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1992642, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
61167
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2083622, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
61168
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2192258, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
61169
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2301342, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
61170
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2410390, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
61171
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2518090, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
61172
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2606198, length: 4947, sha256: "c3f476b436d5ba5d2c7e928ba8da0472201b6353f25d6f73fb6cce9f5326c965" },
61173
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2611145, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
61138
61174
  ];
61139
61175
  }
61140
61176
  });
Binary file
@@ -4,12 +4,16 @@
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
5
  "componentCount": 84,
6
6
  "notImplementedComponents": [
7
- "FieldError",
7
+ "Text",
8
8
  "Picker",
9
- "DatePicker",
10
9
  "Tabs",
10
+ "Carousel",
11
+ "CollapsingHeader",
12
+ "Banner",
11
13
  "Snackbar",
14
+ "WebView",
12
15
  "Slider",
16
+ "SelectionControl",
13
17
  "ActivityIndicator",
14
18
  "BottomSheet",
15
19
  "Menu",
@@ -17,7 +21,12 @@
17
21
  "Popover",
18
22
  "ChipsInput",
19
23
  "OtpInput",
20
- "Video"
24
+ "Map",
25
+ "Video",
26
+ "PdfView",
27
+ "MediaPlayer",
28
+ "TimeGrid",
29
+ "Agenda"
21
30
  ],
22
31
  "components": [
23
32
  {
@@ -101,6 +110,17 @@
101
110
  "bindings": [
102
111
  "text"
103
112
  ],
113
+ "notImplemented": {
114
+ "props": [
115
+ "textDirection",
116
+ "animateReveal"
117
+ ],
118
+ "platforms": [
119
+ "web"
120
+ ],
121
+ "severity": "styling",
122
+ "note": "`textDirection` sets no `dir`. `animateReveal` does not animate."
123
+ },
104
124
  "propsSource": "export type TextProps = TextContent & {\n style?: TokenName;\n /** Foreground color token (default \"textPrimary\"). */\n color?: TokenName;\n /** Tail-truncate after N lines (absent = unlimited). */\n maxLines?: number;\n /** Lever D — a live time value (countdown/relative) the OS ticks with no refresh.\n * When present it OVERRIDES `text`. Widget-safe render only. */\n timeRef?: WidgetTimeRef;\n /** Render `text` as a constrained Markdown subset. */\n markdown?: boolean;\n /** Capability enrichments. */\n textAlign?: \"leading\" | \"center\" | \"trailing\" | \"justify\";\n /** Base writing direction. \"auto\" (or \"rtl\") makes a bidi paragraph resolve +\n * align by its own strong-directional content (Hebrew/Arabic → right-aligned,\n * RTL layout), matching per-message direction in chat bubbles. Absent/\"ltr\" =\n * the current left-to-right behavior. Both renderers honor it (parity). */\n textDirection?: \"auto\" | \"ltr\" | \"rtl\";\n decoration?: \"underline\" | \"strikethrough\";\n /** EXTRA SPACE BETWEEN LINES, in points/px — added ON TOP of the line height the `style`\n * token already sets. It is NOT a multiple: `lineSpacing: 6` means six points of extra\n * leading, not six times the font size, and every renderer agrees on that. To\n * change the line height as a ratio of the font size, set the typography token's `line`\n * instead; that is where a body style's 1.4/1.7 lives. Absent/0 = the token's own line\n * height, which is the right answer for almost every screen. */\n lineSpacing?: number;\n /** Per-item gate (usually an `$item.` field pre-substituted per row): when it\n * resolves empty, the text collapses to zero size instead of taking layout. */\n collapseWhenEmpty?: boolean;\n /** Motion grammar (Batch 1, item 4) — count-up tween for a numeric value on\n * change; non-numeric values render instantly. Gated by reduced motion. */\n animateValue?: boolean;\n /** Motion grammar — reveal the (post-substitution literal) text word-by-word,\n * once per distinct content, like a chat assistant \"typing\" its reply. Each\n * content animates a single time (progress is remembered), so list re-renders\n * and scrolls don't restart it. Gated by reduced motion (snaps to full).\n * Both renderers honor it (parity). Default nil/false. */\n animateReveal?: boolean;\n};"
105
125
  },
106
126
  {
@@ -296,16 +316,6 @@
296
316
  "bindings": [
297
317
  "textPath"
298
318
  ],
299
- "notImplemented": {
300
- "props": [
301
- "severity"
302
- ],
303
- "platforms": [
304
- "ios"
305
- ],
306
- "severity": "styling",
307
- "note": "`severity: \"warning\"` still paints `danger` red."
308
- },
309
319
  "propsSource": "export interface FieldErrorProps {\n /** LITERAL - every renderer paints this verbatim. Bind `textPath` instead. */\n text: string;\n severity?: \"error\" | \"warning\";\n /** `$service:`/`$item.` binding -> the error text. Overrides `text`; resolving\n * to empty COLLAPSES the node, so one `FieldError` can sit permanently in the\n * tree and appear only when it has something to say (the same contract as\n * `TextProps.collapseWhenEmpty`). Before this, `FieldError` could not show\n * worker state at all, so apps hand-rolled a danger-colored `Text` instead. */\n textPath?: string;\n /** Explicit visibility override; `false` collapses the node. */\n visible?: boolean;\n}"
310
320
  },
311
321
  {
@@ -340,7 +350,7 @@
340
350
  "ios"
341
351
  ],
342
352
  "severity": "styling",
343
- "note": "`optionsPath` does not bind options; `placeholder` is not shown."
353
+ "note": "Standalone Picker: `optionsPath` does not bind (it does inside a FormField); `placeholder` is only the accessible name, never shown."
344
354
  },
345
355
  "propsSource": "export interface PickerProps {\n options: PickerOption[];\n selectedId?: string;\n onChange?: Action;\n placeholder?: string;\n /** \"$service:\" sentinel → worker array of {id,label}; `options` is the fallback. */\n optionsPath?: string;\n}"
346
356
  },
@@ -348,22 +358,16 @@
348
358
  "name": "DatePicker",
349
359
  "category": "Form primitives",
350
360
  "propsType": "DatePickerProps",
351
- "bindings": [],
361
+ "bindings": [
362
+ "valuePath"
363
+ ],
352
364
  "dispatches": [
353
365
  {
354
366
  "callback": "onChange",
355
367
  "key": "value",
356
368
  "encoding": "unix-seconds-string",
357
- "note": "Unix SECONDS, string-encoded so the params mirror stays legal. iOS ships `valueUnix` as an int instead; RN ships nothing. The normative key is `value` because that is what three of four arms already use for every other scalar control.",
369
+ "note": "Unix SECONDS, string-encoded so the params mirror stays legal, clamped into [minUnix, maxUnix]. iOS, Android and web conform (iOS shipped `valueUnix` as an int until the SwiftUI-parity DatePicker fix); RN ships nothing. The normative key is `value` because that is what the arms use for every other scalar control.",
358
370
  "divergences": [
359
- {
360
- "arms": [
361
- "ios"
362
- ],
363
- "kind": "wrong-key",
364
- "actual": "sends `valueUnix` as an int instead of the contract's `value`.",
365
- "reason": "Only web conforms. This is the widest split in the vocabulary and the clearest argument for the file."
366
- },
367
371
  {
368
372
  "arms": [
369
373
  "rn"
@@ -382,19 +386,6 @@
382
386
  ]
383
387
  }
384
388
  ],
385
- "notImplemented": {
386
- "props": [
387
- "mode",
388
- "minUnix",
389
- "maxUnix",
390
- "valuePath"
391
- ],
392
- "platforms": [
393
- "ios"
394
- ],
395
- "severity": "styling",
396
- "note": "The picker itself renders as a native date wheel — these four props are inert: `mode: \"time\"` still renders a date wheel; `minUnix`/`maxUnix` do not clamp; `valuePath` does not bind."
397
- },
398
389
  "propsSource": "export interface DatePickerProps {\n valueUnix?: number;\n minUnix?: number;\n maxUnix?: number;\n mode?: \"date\" | \"time\" | \"datetime\";\n onChange?: Action;\n /** \"$service:\" sentinel → ISO-8601 string (or unix seconds); prefills + tracks. */\n valuePath?: string;\n}"
399
390
  },
400
391
  {
@@ -484,7 +475,6 @@
484
475
  "propsType": "TabsProps",
485
476
  "bindings": [
486
477
  "optionsPath",
487
- "activeColorPath",
488
478
  "selectedId"
489
479
  ],
490
480
  "dispatches": [
@@ -494,16 +484,39 @@
494
484
  "encoding": "string"
495
485
  }
496
486
  ],
497
- "notImplemented": {
498
- "props": [
499
- "style"
500
- ],
501
- "platforms": [
502
- "ios"
503
- ],
504
- "severity": "styling",
505
- "note": "The tab bar itself renders — only `style` is inert: `swipe` still renders as top tabs."
506
- },
487
+ "notImplemented": [
488
+ {
489
+ "props": [
490
+ "style"
491
+ ],
492
+ "platforms": [
493
+ "ios"
494
+ ],
495
+ "severity": "styling",
496
+ "note": "`style: \"swipe\"` renders as plain top tabs."
497
+ },
498
+ {
499
+ "props": [
500
+ "activeColorPath"
501
+ ],
502
+ "platforms": [
503
+ "android"
504
+ ],
505
+ "severity": "styling",
506
+ "note": "`activeColorPath` does not tint the active tab."
507
+ },
508
+ {
509
+ "props": [
510
+ "activeColorPath",
511
+ "style"
512
+ ],
513
+ "platforms": [
514
+ "web"
515
+ ],
516
+ "severity": "styling",
517
+ "note": "`activeColorPath` does not tint the active tab. `style: \"swipe\"` renders as plain top tabs."
518
+ }
519
+ ],
507
520
  "propsSource": "export interface TabsProps {\n tabs: TabItem[];\n /** Literal id, or a binding-aware `$service:`/`$item.` live selection. */\n selectedId: string;\n onChange?: Action;\n style?: \"top\" | \"swipe\";\n /** `$service:` binding → a live tabs array (overrides `tabs`). */\n optionsPath?: string;\n /** `$service:` binding → a token name for the active-tab color. */\n activeColorPath?: string;\n}"
508
521
  },
509
522
  {
@@ -530,6 +543,17 @@
530
543
  "bindings": [
531
544
  "itemsPath"
532
545
  ],
546
+ "notImplemented": {
547
+ "props": [
548
+ "autoplayIntervalMs"
549
+ ],
550
+ "platforms": [
551
+ "android",
552
+ "web"
553
+ ],
554
+ "severity": "styling",
555
+ "note": "No autoplay."
556
+ },
533
557
  "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}"
534
558
  },
535
559
  {
@@ -551,6 +575,30 @@
551
575
  "category": "Navigation primitives",
552
576
  "propsType": "CollapsingHeaderProps",
553
577
  "bindings": [],
578
+ "notImplemented": [
579
+ {
580
+ "props": [
581
+ "imageAsset",
582
+ "collapsedHeight",
583
+ "parallaxFactor"
584
+ ],
585
+ "platforms": [
586
+ "web"
587
+ ],
588
+ "severity": "styling",
589
+ "note": "A static hero: `imageAsset`, `collapsedHeight` and `parallaxFactor` are inert (only `imageUrl` is read)."
590
+ },
591
+ {
592
+ "props": [
593
+ "imageAsset"
594
+ ],
595
+ "platforms": [
596
+ "android"
597
+ ],
598
+ "severity": "styling",
599
+ "note": "`imageAsset` (a baked asset name) is not resolved — only `imageUrl` paints the hero."
600
+ }
601
+ ],
554
602
  "propsSource": "export interface CollapsingHeaderProps {\n /** Hero image URL (`$service:`-resolvable upstream). */\n imageUrl?: string;\n /** Bundle asset fallback. */\n imageAsset?: string;\n /** Required; scales & fades with collapse. */\n title: string;\n subtitle?: string;\n /** Initial height in points (default 280). */\n expandedHeight?: number;\n /** Final height when fully scrolled (default 64). */\n collapsedHeight?: number;\n /** Body content that scrolls under the header. */\n children?: Component[];\n /** 0.0 = static, 1.0 = full parallax (default 0.5). */\n parallaxFactor?: number;\n}"
555
603
  },
556
604
  {
@@ -570,6 +618,16 @@
570
618
  "title",
571
619
  "message"
572
620
  ],
621
+ "notImplemented": {
622
+ "props": [
623
+ "dismissible"
624
+ ],
625
+ "platforms": [
626
+ "web"
627
+ ],
628
+ "severity": "styling",
629
+ "note": "No dismiss control."
630
+ },
573
631
  "propsSource": "export interface BannerProps {\n severity: Severity;\n /** Banner headline — binding-aware (\"$item.\"/\"$service:\") on web, iOS and Android. */\n title: string;\n /** Banner body — binding-aware (\"$item.\"/\"$service:\") on web, iOS and Android. */\n message?: string;\n action?: Action;\n dismissible?: boolean;\n /** #1059 — action label; overridable for localized hosts. Default \"Take action\". */\n actionLabel?: string;\n}"
574
632
  },
575
633
  {
@@ -582,10 +640,12 @@
582
640
  "durationMs"
583
641
  ],
584
642
  "platforms": [
585
- "ios"
643
+ "ios",
644
+ "android",
645
+ "web"
586
646
  ],
587
647
  "severity": "styling",
588
- "note": "`durationMs` does not control the auto-dismiss timeout."
648
+ "note": "The Snackbar component renders inline, so `durationMs` does nothing (the `snackbar` presentation verb is the working toast)."
589
649
  },
590
650
  "propsSource": "export interface SnackbarProps {\n message: string;\n action?: Action;\n durationMs?: number;\n severity?: Severity;\n /** #1059 — action label; overridable for localized hosts. Default \"Take action\". */\n actionLabel?: string;\n}"
591
651
  },
@@ -608,6 +668,17 @@
608
668
  "category": "Embeddable WebView (web-in-SDUI)",
609
669
  "propsType": "WebViewProps",
610
670
  "bindings": [],
671
+ "notImplemented": {
672
+ "props": [
673
+ "scrollEnabled",
674
+ "config"
675
+ ],
676
+ "platforms": [
677
+ "web"
678
+ ],
679
+ "severity": "styling",
680
+ "note": "The iframe always scrolls. `config` (the native WebView options: cookies, bridge, zoom…) is not applied to the iframe."
681
+ },
611
682
  "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}"
612
683
  },
613
684
  {
@@ -661,10 +732,11 @@
661
732
  "debounceMs"
662
733
  ],
663
734
  "platforms": [
664
- "ios"
735
+ "ios",
736
+ "web"
665
737
  ],
666
738
  "severity": "styling",
667
- "note": "`debounceMs` does not throttle the onChange stream."
739
+ "note": "`debounceMs` does not throttle onChange."
668
740
  },
669
741
  "propsSource": "export interface SliderProps {\n value?: number;\n min?: number;\n max?: number;\n /** 0 = continuous; >0 snaps. */\n step?: number;\n onChange?: Action;\n /** Two-thumb range mode. */\n range?: boolean;\n upperValue?: number;\n /** \"$service:\"/\"$item.\" binding → number (overrides `value`). */\n valuePath?: string;\n tint?: TokenName;\n debounceMs?: number;\n disabled?: boolean;\n}"
670
742
  },
@@ -700,6 +772,17 @@
700
772
  ]
701
773
  }
702
774
  ],
775
+ "notImplemented": {
776
+ "props": [
777
+ "groupId"
778
+ ],
779
+ "platforms": [
780
+ "android",
781
+ "web"
782
+ ],
783
+ "severity": "styling",
784
+ "note": "Radios sharing a `groupId` are not mutually exclusive."
785
+ },
703
786
  "propsSource": "export interface SelectionControlProps {\n label?: string;\n value?: boolean;\n kind?: \"checkbox\" | \"radio\";\n /** Radios sharing a `groupId` are mutually exclusive. */\n groupId?: string;\n onChange?: Action;\n /** \"$service:\"/\"$item.\" binding → bool (overrides `value`). */\n valuePath?: string;\n disabled?: boolean;\n /** Checkbox tri-state (renders a dash); ignored for radio. */\n indeterminate?: boolean;\n}"
704
787
  },
705
788
  {
@@ -714,10 +797,12 @@
714
797
  "ring"
715
798
  ],
716
799
  "platforms": [
717
- "ios"
800
+ "ios",
801
+ "android",
802
+ "web"
718
803
  ],
719
804
  "severity": "styling",
720
- "note": "The `ring` style is decoded but never selected."
805
+ "note": "The `ring` style is never selected (determinate vs indeterminate follows `value`)."
721
806
  },
722
807
  "propsSource": "export interface ActivityIndicatorProps {\n /** 0..1 (determinate); ignored when `indeterminate`. */\n value?: number;\n /** `$service:`/`$item.` binding → a live 0..1 fraction (overrides `value`). */\n valuePath?: string;\n indeterminate?: boolean;\n size?: number;\n tint?: TokenName;\n /** Determinate ring vs. arc spinner. */\n ring?: boolean;\n}"
723
808
  },
@@ -725,22 +810,45 @@
725
810
  "name": "BottomSheet",
726
811
  "category": "Overlays & menus",
727
812
  "propsType": "BottomSheetProps",
728
- "bindings": [],
729
- "notImplemented": {
730
- "props": [
731
- "detents",
732
- "dismissible",
733
- "onDismiss",
734
- "presented",
735
- "presentedPath",
736
- "edge"
737
- ],
738
- "platforms": [
739
- "ios"
740
- ],
741
- "severity": "category",
742
- "note": "Presentation is not implemented — BottomSheet renders as an always-visible inline surface, never a dismissible sheet. All six presentation props are dead. Use the WORKING alternative instead: present the content as its own screen with `presentation: \"sheet\"` on the navigating `Action` — that is a real dismissible sheet on every platform. Reach for BottomSheet only when an always-visible inline surface is what you actually want."
743
- },
813
+ "bindings": [
814
+ "presentedPath"
815
+ ],
816
+ "notImplemented": [
817
+ {
818
+ "props": [
819
+ "edge"
820
+ ],
821
+ "platforms": [
822
+ "ios"
823
+ ],
824
+ "severity": "category",
825
+ "note": "`edge` (side sheet) is ignored — it always opens from the bottom."
826
+ },
827
+ {
828
+ "props": [
829
+ "detents",
830
+ "edge"
831
+ ],
832
+ "platforms": [
833
+ "android"
834
+ ],
835
+ "severity": "category",
836
+ "note": "`detents` are ignored (opens full height). `edge` (side sheet) is ignored — it always opens from the bottom."
837
+ },
838
+ {
839
+ "props": [
840
+ "detents",
841
+ "dismissible",
842
+ "onDismiss",
843
+ "edge"
844
+ ],
845
+ "platforms": [
846
+ "web"
847
+ ],
848
+ "severity": "category",
849
+ "note": "`detents` are ignored (opens full height). No scrim tap / Escape to close, so `onDismiss` never fires — for a dismissible sheet here, present the content as its own screen with `presentation: \"sheet\"` on the navigating `Action`. `edge` (side sheet) is ignored — it always opens from the bottom."
850
+ }
851
+ ],
744
852
  "propsSource": "export interface BottomSheetProps {\n /** Place the sheet as a root-payload sibling (normally the final child), not\n * inside the trigger row, so its presented overlay can cover the whole screen. */\n content?: Component;\n /** Fractional heights, e.g. [0.3, 0.9]. */\n detents?: number[];\n dismissible?: boolean;\n showGrabber?: boolean;\n onDismiss?: Action;\n presented?: boolean;\n /** \"$service:\" binding → bool (overrides `presented`). */\n presentedPath?: string;\n edge?: \"bottom\" | \"leading\" | \"trailing\";\n /** The overlay traps focus and hides the rest of the tree from AT while\n * presented (iOS `accessibilityViewIsModal`, Android `isTraversalGroup`, ARIA\n * `aria-modal`). TRI-STATE: absent takes this component's default (**true**), an\n * explicit `false` opts it out. */\n modal?: boolean;\n}"
745
853
  },
746
854
  {
@@ -748,17 +856,30 @@
748
856
  "category": "Overlays & menus",
749
857
  "propsType": "MenuProps",
750
858
  "bindings": [],
751
- "notImplemented": {
752
- "props": [
753
- "style",
754
- "itemsPath"
755
- ],
756
- "platforms": [
757
- "ios"
758
- ],
759
- "severity": "category",
760
- "note": "`action_sheet`/`context` render as an always-open inline list; `itemsPath` does not bind items."
761
- },
859
+ "notImplemented": [
860
+ {
861
+ "props": [
862
+ "itemsPath"
863
+ ],
864
+ "platforms": [
865
+ "ios"
866
+ ],
867
+ "severity": "category",
868
+ "note": "`itemsPath` does not bind items."
869
+ },
870
+ {
871
+ "props": [
872
+ "style",
873
+ "itemsPath"
874
+ ],
875
+ "platforms": [
876
+ "android",
877
+ "web"
878
+ ],
879
+ "severity": "category",
880
+ "note": "`style` is ignored — `context`/`action_sheet` render as a dropdown. `itemsPath` does not bind items."
881
+ }
882
+ ],
762
883
  "propsSource": "export interface MenuProps {\n anchor?: Component;\n items?: MenuItem[];\n style?: \"dropdown\" | \"context\" | \"action_sheet\";\n /** \"$service:\" binding → MenuItem[]. */\n itemsPath?: string;\n /** The overlay traps focus and hides the rest of the tree from AT while\n * presented (iOS `accessibilityViewIsModal`, Android `isTraversalGroup`, ARIA\n * `aria-modal`). TRI-STATE: absent takes the Menu's default — MODAL,\n * from the one shared overlay default table — and an explicit `false` opts out. */\n modal?: boolean;\n}"
763
884
  },
764
885
  {
@@ -773,10 +894,12 @@
773
894
  "onDismiss"
774
895
  ],
775
896
  "platforms": [
776
- "ios"
897
+ "ios",
898
+ "android",
899
+ "web"
777
900
  ],
778
901
  "severity": "category",
779
- "note": "Not an overlay — renders always-expanded inline; `trigger` and dismissal are inert."
902
+ "note": "Not an overlay — renders always-expanded inline; `edge`, `trigger` and dismissal are inert."
780
903
  },
781
904
  "propsSource": "export interface TooltipProps {\n anchor?: Component;\n text?: string;\n /** Present → rich tooltip. */\n title?: string;\n edge?: \"top\" | \"bottom\" | \"leading\" | \"trailing\" | \"auto\";\n trigger?: \"tap\" | \"long_press\" | \"auto\";\n onDismiss?: Action;\n}"
782
905
  },
@@ -795,7 +918,7 @@
795
918
  "ios"
796
919
  ],
797
920
  "severity": "category",
798
- "note": "Presentation follows `presented` / `presentedPath` (a gated popover shows its bubble only while presented, and is modal then), but the bubble is drawn in the screen's flow under its anchor, not floating over it: `edge` placement is not implemented. It closes when its gate closes, or on the VoiceOver escape gesture, which fires `onDismiss` so a worker can clear the gate."
921
+ "note": "The bubble is drawn in flow under its anchor; `edge` placement is not implemented."
799
922
  },
800
923
  "propsSource": "export interface PopoverProps {\n anchor?: Component;\n content?: Component;\n edge?: \"top\" | \"bottom\" | \"leading\" | \"trailing\" | \"auto\";\n presented?: boolean;\n presentedPath?: string;\n onDismiss?: Action;\n /** The overlay traps focus and hides the rest of the tree from AT while\n * presented (iOS `accessibilityViewIsModal`, Android `isTraversalGroup`, ARIA\n * `aria-modal`). TRI-STATE: absent takes the Popover's default — MODAL,\n * from the one shared overlay default table — and an explicit `false` opts out. Applies to a\n * popover presented through `presented` / `presentedPath`; one with neither is an\n * inline callout, and never modal. */\n modal?: boolean;\n}"
801
924
  },
@@ -838,9 +961,11 @@
838
961
  "onAdd"
839
962
  ],
840
963
  "platforms": [
841
- "ios"
964
+ "ios",
965
+ "android",
966
+ "web"
842
967
  ],
843
- "severity": "styling",
968
+ "severity": "category",
844
969
  "note": "`onAdd` never dispatches."
845
970
  },
846
971
  "propsSource": "export interface ChipsInputProps {\n values?: string[];\n /** \"$service:\" binding → string[]. */\n valuesPath?: string;\n placeholder?: string;\n onAdd?: Action;\n onRemove?: Action;\n disabled?: boolean;\n}"
@@ -1159,14 +1284,16 @@
1159
1284
  ],
1160
1285
  "notImplemented": {
1161
1286
  "props": [
1162
- "onChange",
1163
- "onComplete"
1287
+ "onComplete",
1288
+ "onChange"
1164
1289
  ],
1165
1290
  "platforms": [
1166
- "ios"
1291
+ "ios",
1292
+ "android",
1293
+ "web"
1167
1294
  ],
1168
- "severity": "styling",
1169
- "note": "Neither callback dispatches."
1295
+ "severity": "category",
1296
+ "note": "Neither `onChange` nor `onComplete` dispatches."
1170
1297
  },
1171
1298
  "propsSource": "export interface OtpInputProps {\n length?: number;\n /** LITERAL code. A `$service:` sentinel here paints the raw token into the\n * boxes — bind `valuePath` instead. */\n value?: string;\n /** `$service:`/`$item.` binding → the live code (overrides `value`), so a\n * worker that clears it after submit empties the boxes. */\n valuePath?: string;\n onChange?: Action;\n onComplete?: Action;\n secure?: boolean;\n}"
1172
1299
  },
@@ -1187,6 +1314,17 @@
1187
1314
  "cameraPath",
1188
1315
  "shapesPath"
1189
1316
  ],
1317
+ "notImplemented": {
1318
+ "props": [
1319
+ "followUserLocation"
1320
+ ],
1321
+ "platforms": [
1322
+ "android",
1323
+ "web"
1324
+ ],
1325
+ "severity": "styling",
1326
+ "note": "`followUserLocation` does not track."
1327
+ },
1190
1328
  "propsSource": "export interface MapProps {\n latitude?: number;\n longitude?: number;\n zoom?: number;\n markers?: MapMarker[];\n /** `$service:` binding → a live markers array (overrides `markers`). */\n markersPath?: string;\n height?: number;\n interactive?: boolean;\n /**\n * Polyline waypoints — a recorded GPS track, or a delivery route. Drawn in order, so\n * pass the fixes chronologically (`@himalaya/location`'s `watch()` yields them that way).\n */\n route?: MapMarker[];\n /**\n * How a `route` is drawn. Default `\"tile\"` — the polyline over REAL map tiles, which is\n * what a track/navigation screen wants. `\"schematic\"` keeps the normalized,\n * projection-free mini-map (no tile fetch, so no flicker) that suits a small\n * delivery/route CARD. Strict enum: a `$service:` binding here fails at render time,\n * not validation.\n */\n routeMode?: \"tile\" | \"schematic\";\n /** Draw the live device-position dot. Silently inert without location permission. */\n showUserLocation?: boolean;\n /** Keep the camera following the user. Implies `showUserLocation`. */\n followUserLocation?: boolean;\n /** Frame the viewport to `markers` ∪ `route` instead of `latitude`/`longitude` + `zoom`. */\n fitToContent?: boolean;\n /**\n * Imagery style: `\"standard\"` (default) | `\"satellite\"` | `\"hybrid\"` (satellite + labels).\n * Strict enum — a `$service:` binding here fails at render, not validation.\n *\n * HONORED ON THE MAPKIT SURFACES (iOS + macOS). MapKit ships first-party satellite/hybrid\n * imagery (MKMapType), and the tile-route arm has a macOS representable, so a Mac gets it too. The Android + web tile arms draw OSM raster tiles, and OSM has no satellite\n * layer — serving one would mean bundling a third-party imagery provider with its own\n * attribution terms and usage limits, which is an app's licensing decision rather than the\n * platform's. Those arms normalize the value and keep drawing standard tiles.\n *\n * For satellite/topo on EVERY surface, use the app-scoped `TrailMap` component\n * and point its `styleUrl` at a vector style you have rights to.\n */\n mapStyle?: \"standard\" | \"satellite\" | \"hybrid\";\n /**\n * Tap on the map surface. The event payload is merged into the Action's `args` — exactly\n * like Slider/SelectionControl merge `value` — so a Tier-5 reducer reads\n * `{latitude, longitude}` with no new dispatch contract.\n *\n * Honored on the TILE arms, which have real geography. The schematic mini-map draws a\n * normalized, projection-free polyline with no meaningful coordinate to report, so it\n * fires `onMarkerTap` only. `interactive` governs pan/zoom, not taps: a deliberately\n * non-interactive map still reports them.\n */\n onMapTap?: Action;\n /**\n * Tap on a marker. `args`: `{index, label?, latitude, longitude}`, where `index` is into\n * the RESOLVED marker array (`markersPath` when bound, else `markers`) so the worker can\n * look the item back up in its own state. `label` is OMITTED for an unlabeled pin rather\n * than sent as `\"\"`. A tap that landed on a pin is a marker tap only — never also a map tap.\n */\n onMarkerTap?: Action;\n /**\n * Camera settled after a pan/zoom — fired on gesture END, never per frame.\n * `args`: `{latitude, longitude, zoom, north, south, east, west}`, so a worker can fetch\n * \"trails in view\" without re-deriving the viewport. Programmatic camera moves\n * (e.g. a `fitToContent` re-frame) do NOT fire it, or a worker that refetches on region\n * change would loop against its own camera.\n */\n onRegionChange?: Action;\n /**\n * `$service:` binding → a live route array (overrides `route`), mirroring `markersPath`.\n * Without it a growing recorded track had to re-push the whole descriptor for every fix;\n * with it the polyline follows worker state. Defaults to the TILE arm, not the schematic\n * one — a bound route is a recorded track, which is why the binding exists.\n */\n routePath?: string;\n /**\n * `$service:` binding → `{latitude, longitude, zoom}`: the camera, driven by the WORKER.\n *\n * `latitude`/`longitude`/`zoom` are numbers, so a `$service:` sentinel cannot go in them —\n * which meant a worker could not move the camera at all (\"fly to this search result\" was\n * inexpressible without re-pushing the descriptor).\n *\n * Precedence: a resolved `cameraPath` wins, then `fitToContent`, then the static\n * `latitude`/`longitude`/`zoom`. A NEW value also overrides the user's own pan; an unchanged\n * one leaves it alone. Applying it is programmatic, so it never echoes back through\n * `onRegionChange`. Partial objects are honored — `{zoom}` re-zooms in place.\n */\n cameraPath?: string;\n /** Overlays beyond the single `route`: lines, polygons, circles. */\n shapes?: MapShape[];\n /** `$service:` binding → a live shapes array (overrides `shapes`). */\n shapesPath?: string;\n /**\n * Tap on a drawn shape. `args`: `{id?, index, kind}`. Hit-tested before the surface and after\n * markers — a pin on top of a polygon belongs to the pin.\n */\n onShapeTap?: Action;\n /** App-supplied basemap tiles. Absent ⇒ each arm's default (OSM raster, or Apple's imagery). */\n tileSource?: MapTileSource;\n}"
1191
1329
  },
1192
1330
  {
@@ -1202,7 +1340,7 @@
1202
1340
  "ios"
1203
1341
  ],
1204
1342
  "severity": "styling",
1205
- "note": "`poster` (pre-play frame) is not shown."
1343
+ "note": "`poster` is not shown."
1206
1344
  },
1207
1345
  "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}"
1208
1346
  },
@@ -1211,6 +1349,31 @@
1211
1349
  "category": "Cross-cutting & media",
1212
1350
  "propsType": "PdfViewProps",
1213
1351
  "bindings": [],
1352
+ "notImplemented": [
1353
+ {
1354
+ "props": [
1355
+ "showPageControls",
1356
+ "startPage"
1357
+ ],
1358
+ "platforms": [
1359
+ "android"
1360
+ ],
1361
+ "severity": "styling",
1362
+ "note": "No page controls. `startPage` is ignored."
1363
+ },
1364
+ {
1365
+ "props": [
1366
+ "showPageControls",
1367
+ "zoomable",
1368
+ "allowShare"
1369
+ ],
1370
+ "platforms": [
1371
+ "web"
1372
+ ],
1373
+ "severity": "styling",
1374
+ "note": "No page controls. The browser PDF viewer ignores `zoomable` and `allowShare`."
1375
+ }
1376
+ ],
1214
1377
  "propsSource": "export interface PdfViewProps {\n url?: string;\n height?: number;\n showPageControls?: boolean;\n /** 1-based; 0/absent ⇒ page 1. */\n startPage?: number;\n zoomable?: boolean;\n allowShare?: boolean;\n}"
1215
1378
  },
1216
1379
  {
@@ -1220,6 +1383,56 @@
1220
1383
  "bindings": [
1221
1384
  "itemsPath"
1222
1385
  ],
1386
+ "notImplemented": [
1387
+ {
1388
+ "props": [
1389
+ "autoplayVisibilityThreshold",
1390
+ "preload",
1391
+ "playbackRates",
1392
+ "showQueue",
1393
+ "showChapters",
1394
+ "showTranscript"
1395
+ ],
1396
+ "platforms": [
1397
+ "ios"
1398
+ ],
1399
+ "severity": "category",
1400
+ "note": "`autoplayVisibilityThreshold` is ignored (whenVisible = once on appear). `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render."
1401
+ },
1402
+ {
1403
+ "props": [
1404
+ "externalPlayback",
1405
+ "appearance",
1406
+ "allowCellular",
1407
+ "preload",
1408
+ "playbackRates",
1409
+ "showQueue",
1410
+ "showChapters",
1411
+ "showTranscript"
1412
+ ],
1413
+ "platforms": [
1414
+ "android"
1415
+ ],
1416
+ "severity": "category",
1417
+ "note": "`externalPlayback` is forced off. `appearance` is ignored. `allowCellular` is ignored. `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render."
1418
+ },
1419
+ {
1420
+ "props": [
1421
+ "allowCellular",
1422
+ "forwardSkipSeconds",
1423
+ "backwardSkipSeconds",
1424
+ "playbackRates",
1425
+ "showQueue",
1426
+ "showChapters",
1427
+ "showTranscript"
1428
+ ],
1429
+ "platforms": [
1430
+ "web"
1431
+ ],
1432
+ "severity": "category",
1433
+ "note": "`allowCellular` is ignored. Skip buttons ignore `forwardSkipSeconds`/`backwardSkipSeconds`. `playbackRates` is ignored, and the queue/chapters/transcript panels never render."
1434
+ }
1435
+ ],
1223
1436
  "propsSource": "export interface MediaPlayerProps {\n sessionId?: string;\n items?: MediaItem[];\n startIndex?: number;\n startPositionSeconds?: number;\n presentation?: MediaPresentation;\n controls?: MediaControls;\n autoplay?: MediaAutoplay;\n autoplayVisibilityThreshold?: number;\n muted?: boolean;\n loop?: MediaLoop;\n fullscreen?: Exclude<MediaPolicy, \"automatic\" | \"audioOnly\">;\n pictureInPicture?: Extract<MediaPolicy, \"disabled\" | \"allowed\" | \"automatic\">;\n backgroundPlayback?: Extract<MediaPolicy, \"disabled\" | \"allowed\" | \"audioOnly\">;\n externalPlayback?: Exclude<MediaPolicy, \"automatic\" | \"audioOnly\">;\n playbackRates?: number[];\n forwardSkipSeconds?: number;\n backwardSkipSeconds?: number;\n showQueue?: boolean;\n showChapters?: boolean;\n showTranscript?: boolean;\n height?: number;\n aspectRatio?: number;\n contentMode?: \"fit\" | \"fill\";\n posterUrl?: string;\n appearance?: MediaPlayerAppearance;\n slots?: MediaPlayerSlots;\n onReady?: Action;\n onPlay?: Action;\n onPause?: Action;\n onEnd?: Action;\n onError?: Action;\n onProgress?: Action;\n onFullscreenChange?: Action;\n onItemChange?: Action;\n allowCellular?: boolean;\n preload?: boolean;\n /**\n * `$service:` binding → a live `MediaItem[]`: the worker-driven QUEUE, resolved\n * through the same list-binding seam as `LazyList.itemsPath` / `Map.markersPath`.\n * `items` is `MediaItem[]`, so a value binding there is a type mismatch — this\n * companion string carries the binding and OVERRIDES the literal `items` when set.\n * Each resolved item's own scalar fields (url/title/artist/artworkUrl) still resolve\n * per-field through the media-source seam.\n */\n itemsPath?: string;\n}"
1224
1437
  },
1225
1438
  {
@@ -1255,10 +1468,20 @@
1255
1468
  "category": "Rich vector & celebration (motion grammar Batch 4)",
1256
1469
  "propsType": "TimeGridProps",
1257
1470
  "bindings": [
1258
- "anchorDatePath",
1259
1471
  "eventsPath",
1260
1472
  "resourcesPath"
1261
1473
  ],
1474
+ "notImplemented": {
1475
+ "props": [
1476
+ "anchorDatePath",
1477
+ "onRangeChange"
1478
+ ],
1479
+ "platforms": [
1480
+ "android"
1481
+ ],
1482
+ "severity": "category",
1483
+ "note": "`anchorDatePath` does not bind and `onRangeChange` never fires."
1484
+ },
1262
1485
  "propsSource": "export interface TimeGridProps {\n mode?: \"day\" | \"3day\" | \"week\";\n anchorDate?: string;\n anchorDatePath?: string;\n events?: CalendarEvent[];\n eventsPath?: string;\n startHour?: number;\n endHour?: number;\n firstWeekday?: number;\n showNowLine?: boolean;\n onSelectEvent?: Action;\n onRangeChange?: Action;\n /** Per-event body, `$item.*`-substituted per event. Absent ⇒ the built-in block. */\n eventTemplate?: Component;\n /** RESOURCE lanes: staff, rooms, chairs, courts. Overlap packing already splits\n * concurrent events into ANONYMOUS sub-columns; lanes make a column MEAN\n * something. Single-day modes only — a week of resource columns is a 2-D grid\n * that does not fit a phone, so `mode: \"week\"` ignores them. */\n resources?: TimeGridResource[];\n resourcesPath?: string;\n /** EDITING. Opt-in, default off, so no existing read-only\n * grid can regress into one that moves bookings when a user meant to scroll. */\n editable?: boolean;\n /** Snap grid in minutes (default 15). A drag landing on 10:07 is almost never\n * what anyone meant, and no scheduling system accepts it. */\n snapMinutes?: number;\n /** Moved or resized → args{id, start, end}. The component REPORTS; the worker\n * decides. An edit that must be rejected has to be able to fail. */\n onEventChange?: Action;\n /** Empty space long-pressed → args{start, end} for one `snapMinutes` slot. */\n onCreate?: Action;\n /** Labels for the non-drag equivalents. Drag is unreachable by VoiceOver,\n * TalkBack and keyboard, so these are how editing stays operable at all. */\n moveActionLabel?: string;\n resizeActionLabel?: string;\n}"
1263
1486
  },
1264
1487
  {
@@ -1269,6 +1492,16 @@
1269
1492
  "eventsPath",
1270
1493
  "scrollToDatePath"
1271
1494
  ],
1495
+ "notImplemented": {
1496
+ "props": [
1497
+ "stickyHeaders"
1498
+ ],
1499
+ "platforms": [
1500
+ "android"
1501
+ ],
1502
+ "severity": "styling",
1503
+ "note": "Day headers do not pin."
1504
+ },
1272
1505
  "propsSource": "export interface AgendaProps {\n events?: CalendarEvent[];\n eventsPath?: string;\n scrollToDate?: string;\n /** Worker-driven jump target (a literal `scrollToDate` is frozen in the descriptor). */\n scrollToDatePath?: string;\n stickyHeaders?: boolean;\n emptyText?: string;\n onSelectEvent?: Action;\n /** Per-event row body, `$item.*`-substituted per event. Closes the gap that made\n * `agendaList()` (the LazyList-backed PRESET, which takes a `rowTemplate`) more\n * expressive than this component. Absent ⇒ the built-in row. */\n eventTemplate?: Component;\n /** Newest-first. Absent/false ⇒ ascending. Applies to the day GROUPS and to the\n * events within each group. Without it a list whose default sort is \"Newest\"\n * (an owner's orders, recent activity) could not become an Agenda without\n * silently inverting itself — every arm sorted ascending unconditionally. */\n descending?: boolean;\n}"
1273
1506
  }
1274
1507
  ],
@@ -52,14 +52,14 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
52
52
  | `FormField` | Form primitives | — |
53
53
  | `FieldError` | Form primitives | `textPath` |
54
54
  | `Picker` | Form primitives | — |
55
- | `DatePicker` | Form primitives | — |
55
+ | `DatePicker` | Form primitives | `valuePath` |
56
56
  | `StatefulSection` | State envelopes | — |
57
57
  | `Skeleton` | State envelopes | — |
58
58
  | `FragmentSlot` | Descriptor-shipped dynamic structure | `casePath` |
59
59
  | `WorkerSubtree` | Bounded worker-authored component tree | `treeJsonPath` |
60
60
  | `SearchField` | Search & filter | — |
61
61
  | `FilterChips` | Search & filter | `optionsPath`, `selectedIds` |
62
- | `Tabs` | Navigation primitives | `optionsPath`, `activeColorPath`, `selectedId` |
62
+ | `Tabs` | Navigation primitives | `optionsPath`, `selectedId` |
63
63
  | `SegmentedControl` | Navigation primitives | `optionsPath`, `selectedId` |
64
64
  | `Carousel` | Navigation primitives | `itemsPath` |
65
65
  | `Pager` | Navigation primitives | — |
@@ -75,7 +75,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
75
75
  | `Slider` | Controls & cells | `valuePath` |
76
76
  | `SelectionControl` | Controls & cells | `valuePath` |
77
77
  | `ActivityIndicator` | Controls & cells | `valuePath` |
78
- | `BottomSheet` | Overlays & menus | — |
78
+ | `BottomSheet` | Overlays & menus | `presentedPath` |
79
79
  | `Menu` | Overlays & menus | — |
80
80
  | `Tooltip` | Overlays & menus | — |
81
81
  | `Popover` | Overlays & menus | `presentedPath` |
@@ -109,34 +109,52 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
109
109
  | `Lottie` | Rich vector & celebration (motion grammar Batch 4) | — |
110
110
  | `Rive` | Rich vector & celebration (motion grammar Batch 4) | — |
111
111
  | `MonthCalendar` | Rich vector & celebration (motion grammar Batch 4) | `selectedDatePath`, `decorationsPath`, `yearPath`, `monthPath`, `selectedStartDatePath`, `selectedEndDatePath` |
112
- | `TimeGrid` | Rich vector & celebration (motion grammar Batch 4) | `anchorDatePath`, `eventsPath`, `resourcesPath` |
112
+ | `TimeGrid` | Rich vector & celebration (motion grammar Batch 4) | `eventsPath`, `resourcesPath` |
113
113
  | `Agenda` | Rich vector & celebration (motion grammar Batch 4) | `eventsPath`, `scrollToDatePath` |
114
114
 
115
115
  ## Not implemented (decoded but never read)
116
116
 
117
117
  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.
118
118
 
119
- **Scope: 14 of 84 components, all of them ios-only** — every other component/platform pair reads every prop it decodes. The list splits in two, and the halves mean different things:
119
+ **Scope: 23 of 84 components, all of them web / ios / android-only** — every other component/platform pair reads every prop it decodes. The list splits in two, and the halves mean different things:
120
120
 
121
- - **`category` (4: `BottomSheet`, `Menu`, `Tooltip`, `Popover`)** — 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 — for a dismissible sheet it is `presentation: "sheet"` on the navigating `Action`, not `BottomSheet`.
122
- - **`styling` (10: `FieldError`, `Picker`, `DatePicker`, `Tabs`, `Snackbar`, `Slider`, `ActivityIndicator`, `ChipsInput`, `OtpInput`, `Video`)** — the component itself **works**; only the listed prop is inert. `Slider` on iOS is a working slider whose `debounceMs` does not throttle — not a broken `Slider`. Use the component; just do not depend on that one prop.
121
+ - **`category` (8: `BottomSheet`, `Menu`, `Tooltip`, `Popover`, `ChipsInput`, `OtpInput`, `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 — for a dismissible sheet it is `presentation: "sheet"` on the navigating `Action`, not `BottomSheet`.
122
+ - **`styling` (15: `Text`, `Picker`, `Tabs`, `Carousel`, `CollapsingHeader`, `Banner`, `Snackbar`, `WebView`, `Slider`, `SelectionControl`, `ActivityIndicator`, `Map`, `Video`, `PdfView`, `Agenda`)** — the component itself **works**; only the listed prop is inert. `Slider` on iOS is a working slider whose `debounceMs` does not throttle — not a broken `Slider`. Use the component; just do not depend on that one prop.
123
123
 
124
124
  | Component | Unread props | Platform | Kind | Note |
125
125
  |---|---|---|---|---|
126
- | `FieldError` | `severity` | ios | styling | `severity: "warning"` still paints `danger` red. |
127
- | `Picker` | `optionsPath`, `placeholder` | ios | styling | `optionsPath` does not bind options; `placeholder` is not shown. |
128
- | `DatePicker` | `mode`, `minUnix`, `maxUnix`, `valuePath` | ios | styling | The picker itself renders as a native date wheel — these four props are inert: `mode: "time"` still renders a date wheel; `minUnix`/`maxUnix` do not clamp; `valuePath` does not bind. |
129
- | `Tabs` | `style` | ios | styling | The tab bar itself renders — only `style` is inert: `swipe` still renders as top tabs. |
130
- | `Snackbar` | `durationMs` | ios | styling | `durationMs` does not control the auto-dismiss timeout. |
131
- | `Slider` | `debounceMs` | ios | styling | `debounceMs` does not throttle the onChange stream. |
132
- | `ActivityIndicator` | `ring` | ios | styling | The `ring` style is decoded but never selected. |
133
- | `BottomSheet` | `detents`, `dismissible`, `onDismiss`, `presented`, `presentedPath`, `edge` | ios | category | Presentation is not implemented — BottomSheet renders as an always-visible inline surface, never a dismissible sheet. All six presentation props are dead. Use the WORKING alternative instead: present the content as its own screen with `presentation: "sheet"` on the navigating `Action` — that is a real dismissible sheet on every platform. Reach for BottomSheet only when an always-visible inline surface is what you actually want. |
134
- | `Menu` | `style`, `itemsPath` | ios | category | `action_sheet`/`context` render as an always-open inline list; `itemsPath` does not bind items. |
135
- | `Tooltip` | `edge`, `trigger`, `onDismiss` | ios | category | Not an overlay — renders always-expanded inline; `trigger` and dismissal are inert. |
136
- | `Popover` | `edge` | ios | category | Presentation follows `presented` / `presentedPath` (a gated popover shows its bubble only while presented, and is modal then), but the bubble is drawn in the screen's flow under its anchor, not floating over it: `edge` placement is not implemented. It closes when its gate closes, or on the VoiceOver escape gesture, which fires `onDismiss` so a worker can clear the gate. |
137
- | `ChipsInput` | `onAdd` | ios | styling | `onAdd` never dispatches. |
138
- | `OtpInput` | `onChange`, `onComplete` | ios | styling | Neither callback dispatches. |
139
- | `Video` | `poster` | ios | styling | `poster` (pre-play frame) is not shown. |
126
+ | `Text` | `textDirection`, `animateReveal` | web | styling | `textDirection` sets no `dir`. `animateReveal` does not animate. |
127
+ | `Picker` | `optionsPath`, `placeholder` | ios | styling | Standalone Picker: `optionsPath` does not bind (it does inside a FormField); `placeholder` is only the accessible name, never shown. |
128
+ | `Tabs` | `style` | ios | styling | `style: "swipe"` renders as plain top tabs. |
129
+ | `Tabs` | `activeColorPath` | android | styling | `activeColorPath` does not tint the active tab. |
130
+ | `Tabs` | `activeColorPath`, `style` | web | styling | `activeColorPath` does not tint the active tab. `style: "swipe"` renders as plain top tabs. |
131
+ | `Carousel` | `autoplayIntervalMs` | android, web | styling | No autoplay. |
132
+ | `CollapsingHeader` | `imageAsset`, `collapsedHeight`, `parallaxFactor` | web | styling | A static hero: `imageAsset`, `collapsedHeight` and `parallaxFactor` are inert (only `imageUrl` is read). |
133
+ | `CollapsingHeader` | `imageAsset` | android | styling | `imageAsset` (a baked asset name) is not resolved — only `imageUrl` paints the hero. |
134
+ | `Banner` | `dismissible` | web | styling | No dismiss control. |
135
+ | `Snackbar` | `durationMs` | ios, android, web | styling | The Snackbar component renders inline, so `durationMs` does nothing (the `snackbar` presentation verb is the working toast). |
136
+ | `WebView` | `scrollEnabled`, `config` | web | styling | The iframe always scrolls. `config` (the native WebView options: cookies, bridge, zoom…) is not applied to the iframe. |
137
+ | `Slider` | `debounceMs` | ios, web | styling | `debounceMs` does not throttle onChange. |
138
+ | `SelectionControl` | `groupId` | android, web | styling | Radios sharing a `groupId` are not mutually exclusive. |
139
+ | `ActivityIndicator` | `ring` | ios, android, web | styling | The `ring` style is never selected (determinate vs indeterminate follows `value`). |
140
+ | `BottomSheet` | `edge` | ios | category | `edge` (side sheet) is ignored — it always opens from the bottom. |
141
+ | `BottomSheet` | `detents`, `edge` | android | category | `detents` are ignored (opens full height). `edge` (side sheet) is ignored — it always opens from the bottom. |
142
+ | `BottomSheet` | `detents`, `dismissible`, `onDismiss`, `edge` | web | category | `detents` are ignored (opens full height). No scrim tap / Escape to close, so `onDismiss` never fires — for a dismissible sheet here, present the content as its own screen with `presentation: "sheet"` on the navigating `Action`. `edge` (side sheet) is ignored — it always opens from the bottom. |
143
+ | `Menu` | `itemsPath` | ios | category | `itemsPath` does not bind items. |
144
+ | `Menu` | `style`, `itemsPath` | android, web | category | `style` is ignored — `context`/`action_sheet` render as a dropdown. `itemsPath` does not bind items. |
145
+ | `Tooltip` | `edge`, `trigger`, `onDismiss` | ios, android, web | category | Not an overlay — renders always-expanded inline; `edge`, `trigger` and dismissal are inert. |
146
+ | `Popover` | `edge` | ios | category | The bubble is drawn in flow under its anchor; `edge` placement is not implemented. |
147
+ | `ChipsInput` | `onAdd` | ios, android, web | category | `onAdd` never dispatches. |
148
+ | `OtpInput` | `onComplete`, `onChange` | ios, android, web | category | Neither `onChange` nor `onComplete` dispatches. |
149
+ | `Map` | `followUserLocation` | android, web | styling | `followUserLocation` does not track. |
150
+ | `Video` | `poster` | ios | styling | `poster` is not shown. |
151
+ | `PdfView` | `showPageControls`, `startPage` | android | styling | No page controls. `startPage` is ignored. |
152
+ | `PdfView` | `showPageControls`, `zoomable`, `allowShare` | web | styling | No page controls. The browser PDF viewer ignores `zoomable` and `allowShare`. |
153
+ | `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. |
154
+ | `MediaPlayer` | `externalPlayback`, `appearance`, `allowCellular`, `preload`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` | android | category | `externalPlayback` is forced off. `appearance` is ignored. `allowCellular` is ignored. `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render. |
155
+ | `MediaPlayer` | `allowCellular`, `forwardSkipSeconds`, `backwardSkipSeconds`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` | web | category | `allowCellular` is ignored. Skip buttons ignore `forwardSkipSeconds`/`backwardSkipSeconds`. `playbackRates` is ignored, and the queue/chapters/transcript panels never render. |
156
+ | `TimeGrid` | `anchorDatePath`, `onRangeChange` | android | category | `anchorDatePath` does not bind and `onRangeChange` never fires. |
157
+ | `Agenda` | `stickyHeaders` | android | styling | Day headers do not pin. |
140
158
 
141
159
  ## Token slots: `TokenRef` object vs bare `TokenName` string
142
160
 
@@ -433,6 +451,8 @@ export type TextProps = TextContent & {
433
451
  };
434
452
  ```
435
453
 
454
+ > ⚠️ **Implemented, but these props are inert on web:** `textDirection`, `animateReveal` — `textDirection` sets no `dir`. `animateReveal` does not animate.
455
+
436
456
  #### Image
437
457
 
438
458
  ```ts
@@ -780,8 +800,6 @@ export interface FieldErrorProps {
780
800
  }
781
801
  ```
782
802
 
783
- > ⚠️ **Implemented, but these props are inert on ios:** `severity` — `severity: "warning"` still paints `danger` red.
784
-
785
803
  #### Picker
786
804
 
787
805
  ```ts
@@ -795,7 +813,7 @@ export interface PickerProps {
795
813
  }
796
814
  ```
797
815
 
798
- > ⚠️ **Implemented, but these props are inert on ios:** `optionsPath`, `placeholder` — `optionsPath` does not bind options; `placeholder` is not shown.
816
+ > ⚠️ **Implemented, but these props are inert on ios:** `optionsPath`, `placeholder` — Standalone Picker: `optionsPath` does not bind (it does inside a FormField); `placeholder` is only the accessible name, never shown.
799
817
 
800
818
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
801
819
 
@@ -821,17 +839,13 @@ export interface DatePickerProps {
821
839
  }
822
840
  ```
823
841
 
824
- > ⚠️ **Implemented, but these props are inert on ios:** `mode`, `minUnix`, `maxUnix`, `valuePath` — The picker itself renders as a native date wheel — these four props are inert: `mode: "time"` still renders a date wheel; `minUnix`/`maxUnix` do not clamp; `valuePath` does not bind.
825
-
826
842
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
827
843
 
828
844
  | callback | key | type |
829
845
  |---|---|---|
830
846
  | `onChange` | `value` | unix-seconds-string |
831
847
 
832
- > `onChange` — Unix SECONDS, string-encoded so the params mirror stays legal. iOS ships `valueUnix` as an int instead; RN ships nothing. The normative key is `value` because that is what three of four arms already use for every other scalar control.
833
-
834
- > ⚠️ **`onChange` on ios — DIVERGES:** sends `valueUnix` as an int instead of the contract's `value`. Only web conforms. This is the widest split in the vocabulary and the clearest argument for the file.
848
+ > `onChange` — Unix SECONDS, string-encoded so the params mirror stays legal, clamped into [minUnix, maxUnix]. iOS, Android and web conform (iOS shipped `valueUnix` as an int until the SwiftUI-parity DatePicker fix); RN ships nothing. The normative key is `value` because that is what the arms use for every other scalar control.
835
849
 
836
850
  > ⚠️ **`onChange` on rn — DIVERGES:** dispatches the bare action with no date at all.
837
851
 
@@ -957,7 +971,11 @@ export interface TabsProps {
957
971
  }
958
972
  ```
959
973
 
960
- > ⚠️ **Implemented, but these props are inert on ios:** `style` — The tab bar itself renders — only `style` is inert: `swipe` still renders as top tabs.
974
+ > ⚠️ **Implemented, but these props are inert on ios:** `style` — `style: "swipe"` renders as plain top tabs.
975
+
976
+ > ⚠️ **Implemented, but these props are inert on android:** `activeColorPath` — `activeColorPath` does not tint the active tab.
977
+
978
+ > ⚠️ **Implemented, but these props are inert on web:** `activeColorPath`, `style` — `activeColorPath` does not tint the active tab. `style: "swipe"` renders as plain top tabs.
961
979
 
962
980
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
963
981
 
@@ -997,6 +1015,8 @@ export interface CarouselProps {
997
1015
  }
998
1016
  ```
999
1017
 
1018
+ > ⚠️ **Implemented, but these props are inert on android, web:** `autoplayIntervalMs` — No autoplay.
1019
+
1000
1020
  #### Pager
1001
1021
 
1002
1022
  ```ts
@@ -1040,6 +1060,10 @@ export interface CollapsingHeaderProps {
1040
1060
  }
1041
1061
  ```
1042
1062
 
1063
+ > ⚠️ **Implemented, but these props are inert on web:** `imageAsset`, `collapsedHeight`, `parallaxFactor` — A static hero: `imageAsset`, `collapsedHeight` and `parallaxFactor` are inert (only `imageUrl` is read).
1064
+
1065
+ > ⚠️ **Implemented, but these props are inert on android:** `imageAsset` — `imageAsset` (a baked asset name) is not resolved — only `imageUrl` paints the hero.
1066
+
1043
1067
  #### AvatarStrip
1044
1068
 
1045
1069
  ```ts
@@ -1086,6 +1110,8 @@ export interface BannerProps {
1086
1110
  }
1087
1111
  ```
1088
1112
 
1113
+ > ⚠️ **Implemented, but these props are inert on web:** `dismissible` — No dismiss control.
1114
+
1089
1115
  #### Snackbar
1090
1116
 
1091
1117
  ```ts
@@ -1099,7 +1125,7 @@ export interface SnackbarProps {
1099
1125
  }
1100
1126
  ```
1101
1127
 
1102
- > ⚠️ **Implemented, but these props are inert on ios:** `durationMs` — `durationMs` does not control the auto-dismiss timeout.
1128
+ > ⚠️ **Implemented, but these props are inert on ios, android, web:** `durationMs` — The Snackbar component renders inline, so `durationMs` does nothing (the `snackbar` presentation verb is the working toast).
1103
1129
 
1104
1130
  #### AlertDialog
1105
1131
 
@@ -1153,6 +1179,8 @@ export interface WebViewProps {
1153
1179
  }
1154
1180
  ```
1155
1181
 
1182
+ > ⚠️ **Implemented, but these props are inert on web:** `scrollEnabled`, `config` — The iframe always scrolls. `config` (the native WebView options: cookies, bridge, zoom…) is not applied to the iframe.
1183
+
1156
1184
  ### Controls & cells
1157
1185
 
1158
1186
  #### ListItem
@@ -1203,7 +1231,7 @@ export interface SliderProps {
1203
1231
  }
1204
1232
  ```
1205
1233
 
1206
- > ⚠️ **Implemented, but these props are inert on ios:** `debounceMs` — `debounceMs` does not throttle the onChange stream.
1234
+ > ⚠️ **Implemented, but these props are inert on ios, web:** `debounceMs` — `debounceMs` does not throttle onChange.
1207
1235
 
1208
1236
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
1209
1237
 
@@ -1235,6 +1263,8 @@ export interface SelectionControlProps {
1235
1263
  }
1236
1264
  ```
1237
1265
 
1266
+ > ⚠️ **Implemented, but these props are inert on android, web:** `groupId` — Radios sharing a `groupId` are not mutually exclusive.
1267
+
1238
1268
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
1239
1269
 
1240
1270
  | callback | key | type |
@@ -1263,7 +1293,7 @@ export interface ActivityIndicatorProps {
1263
1293
  }
1264
1294
  ```
1265
1295
 
1266
- > ⚠️ **Implemented, but these props are inert on ios:** `ring` — The `ring` style is decoded but never selected.
1296
+ > ⚠️ **Implemented, but these props are inert on ios, android, web:** `ring` — The `ring` style is never selected (determinate vs indeterminate follows `value`).
1267
1297
 
1268
1298
  ### Overlays & menus
1269
1299
 
@@ -1291,7 +1321,11 @@ export interface BottomSheetProps {
1291
1321
  }
1292
1322
  ```
1293
1323
 
1294
- > ⚠️ **Not implemented on ios:** `detents`, `dismissible`, `onDismiss`, `presented`, `presentedPath`, `edge` — Presentation is not implemented — BottomSheet renders as an always-visible inline surface, never a dismissible sheet. All six presentation props are dead. Use the WORKING alternative instead: present the content as its own screen with `presentation: "sheet"` on the navigating `Action` — that is a real dismissible sheet on every platform. Reach for BottomSheet only when an always-visible inline surface is what you actually want.
1324
+ > ⚠️ **Not implemented on ios:** `edge` — `edge` (side sheet) is ignored — it always opens from the bottom.
1325
+
1326
+ > ⚠️ **Not implemented on android:** `detents`, `edge` — `detents` are ignored (opens full height). `edge` (side sheet) is ignored — it always opens from the bottom.
1327
+
1328
+ > ⚠️ **Not implemented on web:** `detents`, `dismissible`, `onDismiss`, `edge` — `detents` are ignored (opens full height). No scrim tap / Escape to close, so `onDismiss` never fires — for a dismissible sheet here, present the content as its own screen with `presentation: "sheet"` on the navigating `Action`. `edge` (side sheet) is ignored — it always opens from the bottom.
1295
1329
 
1296
1330
  #### Menu
1297
1331
 
@@ -1310,7 +1344,9 @@ export interface MenuProps {
1310
1344
  }
1311
1345
  ```
1312
1346
 
1313
- > ⚠️ **Not implemented on ios:** `style`, `itemsPath` — `action_sheet`/`context` render as an always-open inline list; `itemsPath` does not bind items.
1347
+ > ⚠️ **Not implemented on ios:** `itemsPath` — `itemsPath` does not bind items.
1348
+
1349
+ > ⚠️ **Not implemented on android, web:** `style`, `itemsPath` — `style` is ignored — `context`/`action_sheet` render as a dropdown. `itemsPath` does not bind items.
1314
1350
 
1315
1351
  #### Tooltip
1316
1352
 
@@ -1326,7 +1362,7 @@ export interface TooltipProps {
1326
1362
  }
1327
1363
  ```
1328
1364
 
1329
- > ⚠️ **Not implemented on ios:** `edge`, `trigger`, `onDismiss` — Not an overlay — renders always-expanded inline; `trigger` and dismissal are inert.
1365
+ > ⚠️ **Not implemented on ios, android, web:** `edge`, `trigger`, `onDismiss` — Not an overlay — renders always-expanded inline; `edge`, `trigger` and dismissal are inert.
1330
1366
 
1331
1367
  #### Popover
1332
1368
 
@@ -1348,7 +1384,7 @@ export interface PopoverProps {
1348
1384
  }
1349
1385
  ```
1350
1386
 
1351
- > ⚠️ **Not implemented on ios:** `edge` — Presentation follows `presented` / `presentedPath` (a gated popover shows its bubble only while presented, and is modal then), but the bubble is drawn in the screen's flow under its anchor, not floating over it: `edge` placement is not implemented. It closes when its gate closes, or on the VoiceOver escape gesture, which fires `onDismiss` so a worker can clear the gate.
1387
+ > ⚠️ **Not implemented on ios:** `edge` — The bubble is drawn in flow under its anchor; `edge` placement is not implemented.
1352
1388
 
1353
1389
  ### Chips & selection
1354
1390
 
@@ -1388,7 +1424,7 @@ export interface ChipsInputProps {
1388
1424
  }
1389
1425
  ```
1390
1426
 
1391
- > ⚠️ **Implemented, but these props are inert on ios:** `onAdd` — `onAdd` never dispatches.
1427
+ > ⚠️ **Not implemented on ios, android, web:** `onAdd` — `onAdd` never dispatches.
1392
1428
 
1393
1429
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
1394
1430
 
@@ -1788,7 +1824,7 @@ export interface OtpInputProps {
1788
1824
  }
1789
1825
  ```
1790
1826
 
1791
- > ⚠️ **Implemented, but these props are inert on ios:** `onChange`, `onComplete` — Neither callback dispatches.
1827
+ > ⚠️ **Not implemented on ios, android, web:** `onComplete`, `onChange` — Neither `onChange` nor `onComplete` dispatches.
1792
1828
 
1793
1829
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
1794
1830
 
@@ -1924,6 +1960,8 @@ export interface MapProps {
1924
1960
  }
1925
1961
  ```
1926
1962
 
1963
+ > ⚠️ **Implemented, but these props are inert on android, web:** `followUserLocation` — `followUserLocation` does not track.
1964
+
1927
1965
  #### Video
1928
1966
 
1929
1967
  ```ts
@@ -1939,7 +1977,7 @@ export interface VideoProps {
1939
1977
  }
1940
1978
  ```
1941
1979
 
1942
- > ⚠️ **Implemented, but these props are inert on ios:** `poster` — `poster` (pre-play frame) is not shown.
1980
+ > ⚠️ **Implemented, but these props are inert on ios:** `poster` — `poster` is not shown.
1943
1981
 
1944
1982
  #### PdfView
1945
1983
 
@@ -1955,6 +1993,10 @@ export interface PdfViewProps {
1955
1993
  }
1956
1994
  ```
1957
1995
 
1996
+ > ⚠️ **Implemented, but these props are inert on android:** `showPageControls`, `startPage` — No page controls. `startPage` is ignored.
1997
+
1998
+ > ⚠️ **Implemented, but these props are inert on web:** `showPageControls`, `zoomable`, `allowShare` — No page controls. The browser PDF viewer ignores `zoomable` and `allowShare`.
1999
+
1958
2000
  #### MediaPlayer
1959
2001
 
1960
2002
  ```ts
@@ -2007,6 +2049,12 @@ export interface MediaPlayerProps {
2007
2049
  }
2008
2050
  ```
2009
2051
 
2052
+ > ⚠️ **Not implemented on ios:** `autoplayVisibilityThreshold`, `preload`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` — `autoplayVisibilityThreshold` is ignored (whenVisible = once on appear). `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render.
2053
+
2054
+ > ⚠️ **Not implemented on android:** `externalPlayback`, `appearance`, `allowCellular`, `preload`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` — `externalPlayback` is forced off. `appearance` is ignored. `allowCellular` is ignored. `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render.
2055
+
2056
+ > ⚠️ **Not implemented on web:** `allowCellular`, `forwardSkipSeconds`, `backwardSkipSeconds`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` — `allowCellular` is ignored. Skip buttons ignore `forwardSkipSeconds`/`backwardSkipSeconds`. `playbackRates` is ignored, and the queue/chapters/transcript panels never render.
2057
+
2010
2058
  ### Rich vector & celebration (motion grammar Batch 4)
2011
2059
 
2012
2060
  #### Lottie
@@ -2114,6 +2162,8 @@ export interface TimeGridProps {
2114
2162
  }
2115
2163
  ```
2116
2164
 
2165
+ > ⚠️ **Not implemented on android:** `anchorDatePath`, `onRangeChange` — `anchorDatePath` does not bind and `onRangeChange` never fires.
2166
+
2117
2167
  #### Agenda
2118
2168
 
2119
2169
  ```ts
@@ -2138,6 +2188,8 @@ export interface AgendaProps {
2138
2188
  }
2139
2189
  ```
2140
2190
 
2191
+ > ⚠️ **Implemented, but these props are inert on android:** `stickyHeaders` — Day headers do not pin.
2192
+
2141
2193
  ## Screen descriptor shape
2142
2194
 
2143
2195
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1188.0",
3
+ "version": "0.1190.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -67,5 +67,5 @@
67
67
  "artifactId": "himalaya-cli"
68
68
  }
69
69
  },
70
- "falconPackageHash": "67d96922cd1adeed2becc3bef5c4d027397fd441f83346cd018cb252"
70
+ "falconPackageHash": "9372e1bc93fb23ce8d6d5db9c0d7574d95632af38bcce8830a656813"
71
71
  }