@wix/himalaya-cli 0.942.0 → 0.944.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.
@@ -101,7 +101,7 @@
101
101
  "bindings": [
102
102
  "text"
103
103
  ],
104
- "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 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};"
104
+ "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
105
  },
106
106
  {
107
107
  "name": "Image",
@@ -139,6 +139,44 @@
139
139
  "focusPath",
140
140
  "errorPath"
141
141
  ],
142
+ "dispatches": [
143
+ {
144
+ "callback": "onChange",
145
+ "key": "value",
146
+ "encoding": "string",
147
+ "note": "The typed text, clamped to maxLength by the renderer.",
148
+ "divergences": [
149
+ {
150
+ "arms": [
151
+ "ios",
152
+ "android",
153
+ "web"
154
+ ],
155
+ "kind": "additive",
156
+ "actual": "additionally injects `fieldId` when the text field is inside a Form, so one worker action can serve several fields.",
157
+ "reason": "Additive, not conflicting, and shipped bundles already duck-type it. Every arm guards on the field actually being registered in a Form, so a standalone TextField still sends no `fieldId`. Still a divergence rather than a promoted contract key because RN does not send it and the contract does not require it."
158
+ }
159
+ ]
160
+ },
161
+ {
162
+ "callback": "onSubmit",
163
+ "key": "value",
164
+ "encoding": "string",
165
+ "note": "The field's current text at return-key time.",
166
+ "divergences": [
167
+ {
168
+ "arms": [
169
+ "ios",
170
+ "android",
171
+ "web"
172
+ ],
173
+ "kind": "additive",
174
+ "actual": "additionally injects `fieldId` when the text field is inside a Form, so one worker action can serve several fields.",
175
+ "reason": "Additive, not conflicting, and shipped bundles already duck-type it. Every arm guards on the field actually being registered in a Form, so a standalone TextField still sends no `fieldId`. Still a divergence rather than a promoted contract key because RN does not send it and the contract does not require it."
176
+ }
177
+ ]
178
+ }
179
+ ],
142
180
  "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}"
143
181
  },
144
182
  {
@@ -148,6 +186,23 @@
148
186
  "bindings": [
149
187
  "valuePath"
150
188
  ],
189
+ "dispatches": [
190
+ {
191
+ "callback": "onChange",
192
+ "key": "value",
193
+ "encoding": "boolean",
194
+ "note": "The NEXT state, not the previous one.",
195
+ "divergences": [
196
+ {
197
+ "arms": [
198
+ "ios"
199
+ ],
200
+ "kind": "additive",
201
+ "actual": "additionally injects `fieldId` when the control is inside a Form — via bindingForBool, for a Toggle nested in a FormField."
202
+ }
203
+ ]
204
+ }
205
+ ],
151
206
  "propsSource": "export interface ToggleProps {\n label?: string;\n value: boolean;\n /** `$service:`/`$item.` binding → a live bool (overrides `value`). */\n valuePath?: string;\n onChange?: Action;\n /** #1047 — ON-state track color token (default: `accentPrimary`). Setting any\n * appearance prop switches to a custom-drawn control so a host can match its own\n * switch and keep the screen OTA (vs the `intents:` escape hatch). */\n trackOn?: TokenName;\n /** #1047 — OFF-state track color token (default: system/`surfaceTertiary`). */\n trackOff?: TokenName;\n /** #1047 — thumb color token (default: white). */\n thumb?: TokenName;\n /** #1047 — track width (pt/dp/px). Absent = system size. */\n width?: number;\n /** #1047 — track height (pt/dp/px). Absent = system size. */\n height?: number;\n}"
152
207
  },
153
208
  {
@@ -157,6 +212,25 @@
157
212
  "bindings": [
158
213
  "valuePath"
159
214
  ],
215
+ "dispatches": [
216
+ {
217
+ "callback": "onChange",
218
+ "key": "value",
219
+ "encoding": "int",
220
+ "note": "The clamped next value. A no-change step must not dispatch.",
221
+ "divergences": [
222
+ {
223
+ "arms": [
224
+ "web",
225
+ "rn"
226
+ ],
227
+ "kind": "params-mirror",
228
+ "actual": "mirrors the value into `params` as a NUMBER.",
229
+ "reason": "Violates rule 2 — `Action.params` is `map<string,string>`. Harmless through the JS hosts, wrong on the wire."
230
+ }
231
+ ]
232
+ }
233
+ ],
160
234
  "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}"
161
235
  },
162
236
  {
@@ -238,6 +312,24 @@
238
312
  "category": "Form primitives",
239
313
  "propsType": "PickerProps",
240
314
  "bindings": [],
315
+ "dispatches": [
316
+ {
317
+ "callback": "onChange",
318
+ "key": "selectedId",
319
+ "encoding": "string",
320
+ "note": "The chosen option's id — an enum the reducer switches on, never an index.",
321
+ "divergences": [
322
+ {
323
+ "arms": [
324
+ "ios"
325
+ ],
326
+ "kind": "probe",
327
+ "actual": "inside a FormField, the non-text kinds store into the form bag and RETURN without dispatching — the renderer's own code.",
328
+ "reason": "A formField({kind:'picker'}) onChange is a dead action on iOS while every other arm fires it."
329
+ }
330
+ ]
331
+ }
332
+ ],
241
333
  "notImplemented": {
242
334
  "props": [
243
335
  "optionsPath",
@@ -256,6 +348,39 @@
256
348
  "category": "Form primitives",
257
349
  "propsType": "DatePickerProps",
258
350
  "bindings": [],
351
+ "dispatches": [
352
+ {
353
+ "callback": "onChange",
354
+ "key": "value",
355
+ "encoding": "unix-seconds-string",
356
+ "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.",
357
+ "divergences": [
358
+ {
359
+ "arms": [
360
+ "ios"
361
+ ],
362
+ "kind": "wrong-key",
363
+ "actual": "sends `valueUnix` as an int instead of the contract's `value`.",
364
+ "reason": "Only web conforms. This is the widest split in the vocabulary and the clearest argument for the file."
365
+ },
366
+ {
367
+ "arms": [
368
+ "rn"
369
+ ],
370
+ "kind": "no-payload",
371
+ "actual": "dispatches the bare action with no date at all."
372
+ },
373
+ {
374
+ "arms": [
375
+ "ios"
376
+ ],
377
+ "kind": "probe",
378
+ "actual": "inside a FormField, the non-text kinds store into the form bag and RETURN without dispatching — the renderer's own code.",
379
+ "reason": "A formField({kind:'picker'}) onChange is a dead action on iOS while every other arm fires it."
380
+ }
381
+ ]
382
+ }
383
+ ],
259
384
  "notImplemented": {
260
385
  "props": [
261
386
  "mode",
@@ -308,6 +433,20 @@
308
433
  "category": "Search & filter",
309
434
  "propsType": "SearchFieldProps",
310
435
  "bindings": [],
436
+ "dispatches": [
437
+ {
438
+ "callback": "onChange",
439
+ "key": "value",
440
+ "encoding": "string",
441
+ "note": "Debounced by `debounceMs` before dispatch."
442
+ },
443
+ {
444
+ "callback": "onSubmit",
445
+ "key": "value",
446
+ "encoding": "string",
447
+ "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."
448
+ }
449
+ ],
311
450
  "propsSource": "export interface SearchFieldProps {\n placeholder?: string;\n value?: string;\n onChange?: Action;\n onSubmit?: Action;\n debounceMs?: number;\n}"
312
451
  },
313
452
  {
@@ -318,6 +457,24 @@
318
457
  "optionsPath",
319
458
  "selectedIds"
320
459
  ],
460
+ "dispatches": [
461
+ {
462
+ "callback": "onChange",
463
+ "key": "selectedIds",
464
+ "encoding": "string-array",
465
+ "note": "The full selection AFTER the toggle, not the chip that moved.",
466
+ "divergences": [
467
+ {
468
+ "arms": [
469
+ "rn"
470
+ ],
471
+ "kind": "params-mirror",
472
+ "actual": "mirrors `selectedIds` into `params` as an ARRAY.",
473
+ "reason": "Rule-2 violation."
474
+ }
475
+ ]
476
+ }
477
+ ],
321
478
  "propsSource": "export interface FilterChipsProps {\n chips: FilterChip[];\n /** Literal ids, or a binding-aware `$service:`/`$item.` live selection. */\n selectedIds?: string[];\n multiSelect?: boolean;\n onChange?: Action;\n /** `$service:` binding → a live options array (overrides `chips`). */\n optionsPath?: string;\n}"
322
479
  },
323
480
  {
@@ -329,6 +486,13 @@
329
486
  "activeColorPath",
330
487
  "selectedId"
331
488
  ],
489
+ "dispatches": [
490
+ {
491
+ "callback": "onChange",
492
+ "key": "selectedId",
493
+ "encoding": "string"
494
+ }
495
+ ],
332
496
  "notImplemented": {
333
497
  "props": [
334
498
  "style"
@@ -349,6 +513,13 @@
349
513
  "optionsPath",
350
514
  "selectedId"
351
515
  ],
516
+ "dispatches": [
517
+ {
518
+ "callback": "onChange",
519
+ "key": "selectedId",
520
+ "encoding": "string"
521
+ }
522
+ ],
352
523
  "propsSource": "export interface SegmentedControlProps {\n options: TabItem[];\n /** Literal id, or a binding-aware `$service:`/`$item.` live selection. */\n selectedId: string;\n onChange?: Action;\n /** `$service:` binding → a live options array (overrides `options`). */\n optionsPath?: string;\n}"
353
524
  },
354
525
  {
@@ -456,6 +627,40 @@
456
627
  "bindings": [
457
628
  "valuePath"
458
629
  ],
630
+ "dispatches": [
631
+ {
632
+ "callback": "onChange",
633
+ "key": "value",
634
+ "encoding": "double",
635
+ "note": "Range mode additionally sends `upperValue`.",
636
+ "divergences": [
637
+ {
638
+ "arms": [
639
+ "web"
640
+ ],
641
+ "kind": "no-payload",
642
+ "actual": "dispatches the bare action — the renderer's own code — with no injected value."
643
+ },
644
+ {
645
+ "arms": [
646
+ "rn"
647
+ ],
648
+ "kind": "no-payload",
649
+ "actual": "renders a static visual and never dispatches at all.",
650
+ "reason": "RN Slider is unimplemented rather than divergent; listed so the gap is counted, not hidden."
651
+ },
652
+ {
653
+ "arms": [
654
+ "ios",
655
+ "android"
656
+ ],
657
+ "kind": "additive",
658
+ "actual": "range mode additionally sends `upperValue` beside `value`.",
659
+ "reason": "Additive, and the contract's Slider note already describes it. Same problem as groupId: allowed by a hard-coded list rather than declared."
660
+ }
661
+ ]
662
+ }
663
+ ],
459
664
  "notImplemented": {
460
665
  "props": [
461
666
  "debounceMs"
@@ -475,6 +680,38 @@
475
680
  "bindings": [
476
681
  "valuePath"
477
682
  ],
683
+ "dispatches": [
684
+ {
685
+ "callback": "onChange",
686
+ "key": "value",
687
+ "encoding": "boolean",
688
+ "note": "The next checked state. iOS additionally sends `groupId` for radio groups.",
689
+ "divergences": [
690
+ {
691
+ "arms": [
692
+ "web"
693
+ ],
694
+ "kind": "no-payload",
695
+ "actual": "dispatches the bare action — the renderer's own code — with no injected value."
696
+ },
697
+ {
698
+ "arms": [
699
+ "rn"
700
+ ],
701
+ "kind": "params-mirror",
702
+ "actual": "mirrors the value into `params` as a BOOLEAN."
703
+ },
704
+ {
705
+ "arms": [
706
+ "ios"
707
+ ],
708
+ "kind": "additive",
709
+ "actual": "additionally sends `groupId` so a radio group can identify which set changed.",
710
+ "reason": "Additive, not conflicting. It was previously hard-coded as an allowed key in the gate, which put it outside the contract — the one place this PR claims is the single source of truth."
711
+ }
712
+ ]
713
+ }
714
+ ],
478
715
  "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}"
479
716
  },
480
717
  {
@@ -592,6 +829,23 @@
592
829
  "bindings": [
593
830
  "valuesPath"
594
831
  ],
832
+ "dispatches": [
833
+ {
834
+ "callback": "onRemove",
835
+ "key": "value",
836
+ "encoding": "string",
837
+ "note": "The removed chip's value.",
838
+ "divergences": [
839
+ {
840
+ "arms": [
841
+ "web"
842
+ ],
843
+ "kind": "no-payload",
844
+ "actual": "dispatches the bare action — the renderer's own code — with no injected value."
845
+ }
846
+ ]
847
+ }
848
+ ],
595
849
  "notImplemented": {
596
850
  "props": [
597
851
  "onAdd"
@@ -660,6 +914,41 @@
660
914
  "bindings": [
661
915
  "valuePath"
662
916
  ],
917
+ "dispatches": [
918
+ {
919
+ "callback": "onChange",
920
+ "key": "value",
921
+ "encoding": "int",
922
+ "note": "1-based star index.",
923
+ "divergences": [
924
+ {
925
+ "arms": [
926
+ "web",
927
+ "rn"
928
+ ],
929
+ "kind": "params-only",
930
+ "actual": "writes the value into `params` ONLY, never `args`.",
931
+ "reason": "Survives only because the Tier-5 hosts merge params into args. Any consumer reading `args` — which rule 1 says is the payload map — gets nothing."
932
+ },
933
+ {
934
+ "arms": [
935
+ "ios"
936
+ ],
937
+ "kind": "encoding",
938
+ "actual": "sends `value` as .double where the contract says int."
939
+ },
940
+ {
941
+ "arms": [
942
+ "web",
943
+ "rn"
944
+ ],
945
+ "kind": "params-mirror",
946
+ "actual": "stringifies the 1-based index into `params` (`String(i + 1)`) for a control the contract declares `int`.",
947
+ "reason": "Rule 2 permits a params mirror only for string-encoded values, and Rating is int. The earlier expression heuristic missed it because `String(i + 1)` does not look like a literal — it surfaced only once rule 2 keyed off the declared encoding."
948
+ }
949
+ ]
950
+ }
951
+ ],
663
952
  "propsSource": "export interface RatingProps {\n value?: number;\n max?: number;\n interactive?: boolean;\n onChange?: Action;\n color?: TokenName;\n icon?: IconRef;\n valuePath?: string;\n}"
664
953
  },
665
954
  {
@@ -744,6 +1033,31 @@
744
1033
  "optionsPath",
745
1034
  "selectedId"
746
1035
  ],
1036
+ "dispatches": [
1037
+ {
1038
+ "callback": "onChange",
1039
+ "key": "selectedId",
1040
+ "encoding": "string",
1041
+ "divergences": [
1042
+ {
1043
+ "arms": [
1044
+ "rn"
1045
+ ],
1046
+ "kind": "params-wrong-key",
1047
+ "actual": "writes `params.value = String(id)` but `args.selectedId = id` — a different key in each map.",
1048
+ "reason": "Conforms on `args` (rule 1) but the params mirror uses the wrong key, so a params-reading consumer sees a key no other arm sends."
1049
+ },
1050
+ {
1051
+ "arms": [
1052
+ "web"
1053
+ ],
1054
+ "kind": "no-payload",
1055
+ "actual": "renders a static window of options and never dispatches — the case has no onAction.",
1056
+ "reason": "Unimplemented on web rather than divergent. Surfaced by the missing-site assertion; the other four it reported were blind spots in the scan, now fixed."
1057
+ }
1058
+ ]
1059
+ }
1060
+ ],
747
1061
  "propsSource": "export interface WheelPickerProps {\n options?: PickerOption[];\n /** Literal option id, or binding-aware `$service:`/`$item.` live id. */\n selectedId?: string;\n onChange?: Action;\n optionsPath?: string;\n}"
748
1062
  },
749
1063
  {
@@ -753,6 +1067,25 @@
753
1067
  "bindings": [
754
1068
  "valuePath"
755
1069
  ],
1070
+ "dispatches": [
1071
+ {
1072
+ "callback": "onChange",
1073
+ "key": "value",
1074
+ "encoding": "string",
1075
+ "note": "Hex, including the leading '#'.",
1076
+ "divergences": [
1077
+ {
1078
+ "arms": [
1079
+ "web",
1080
+ "rn"
1081
+ ],
1082
+ "kind": "params-only",
1083
+ "actual": "writes the value into `params` ONLY, never `args`.",
1084
+ "reason": "Survives only because the Tier-5 hosts merge params into args. Any consumer reading `args` — which rule 1 says is the payload map — gets nothing."
1085
+ }
1086
+ ]
1087
+ }
1088
+ ],
756
1089
  "propsSource": "export interface ColorPickerProps {\n value?: string;\n onChange?: Action;\n swatches?: string[];\n label?: string;\n valuePath?: string;\n}"
757
1090
  },
758
1091
  {
@@ -762,6 +1095,44 @@
762
1095
  "bindings": [
763
1096
  "valuePath"
764
1097
  ],
1098
+ "dispatches": [
1099
+ {
1100
+ "callback": "onChange",
1101
+ "key": "value",
1102
+ "encoding": "string",
1103
+ "note": "The assembled code. Only RN dispatches today; web, iOS and Android render the boxes read-only.",
1104
+ "divergences": [
1105
+ {
1106
+ "arms": [
1107
+ "web",
1108
+ "ios",
1109
+ "android"
1110
+ ],
1111
+ "kind": "no-payload",
1112
+ "actual": "renders the digit boxes without wiring onChange — the control dispatches nothing.",
1113
+ "reason": "Unimplemented rather than divergent, but it must be declared: leaving OtpInput out of the contract is what let the gate whitelist RN's payload without checking it."
1114
+ }
1115
+ ]
1116
+ },
1117
+ {
1118
+ "callback": "onComplete",
1119
+ "key": "value",
1120
+ "encoding": "string",
1121
+ "note": "Fires once the last box is filled, carrying the same assembled code as onChange. Declared separately because it is a second public callback — leaving it out left one outside the contract.",
1122
+ "divergences": [
1123
+ {
1124
+ "arms": [
1125
+ "web",
1126
+ "ios",
1127
+ "android"
1128
+ ],
1129
+ "kind": "no-payload",
1130
+ "actual": "renders the digit boxes without wiring onChange — the control dispatches nothing.",
1131
+ "reason": "Unimplemented rather than divergent, but it must be declared: leaving OtpInput out of the contract is what let the gate whitelist RN's payload without checking it."
1132
+ }
1133
+ ]
1134
+ }
1135
+ ],
765
1136
  "notImplemented": {
766
1137
  "props": [
767
1138
  "onChange",