@fuaran-ui/conformance 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/corpus/cards/card-bundle.json +1 -0
  2. package/corpus/cards/card-full.json +1 -0
  3. package/corpus/cards/card-minimal.json +1 -0
  4. package/corpus/cards/card-reject-bad-version.json +1 -0
  5. package/corpus/cards/card-reject-bundle-bad-version.json +1 -0
  6. package/corpus/cards/card-reject-bundle-duplicate.json +1 -0
  7. package/corpus/cards/card-reject-empty-enum.json +1 -0
  8. package/corpus/cards/card-reject-hash-not-object.json +1 -0
  9. package/corpus/cards/card-reject-missing-hash.json +1 -0
  10. package/corpus/cards/card-reject-missing-module.json +1 -0
  11. package/corpus/cards/card-reject-payload-no-language.json +1 -0
  12. package/corpus/cards/card-reject-required-not-bool.json +1 -0
  13. package/corpus/cards/card-reject-stray-key.json +1 -0
  14. package/corpus/cards/card-reject-unknown-prop-type.json +1 -0
  15. package/corpus/lenient/lenient-1126-clipboard-literal-envelope.expected.json +1 -0
  16. package/corpus/lenient/lenient-1126-clipboard-literal-envelope.json +1 -0
  17. package/corpus/lenient/lenient-1521-int-slot-integral-float.expected.json +1 -0
  18. package/corpus/lenient/lenient-1521-int-slot-integral-float.json +1 -0
  19. package/corpus/lenient/lenient-1521-payload-integer-beyond-int53.expected.json +1 -0
  20. package/corpus/lenient/lenient-1521-payload-integer-beyond-int53.json +1 -0
  21. package/corpus/lenient/lenient-navigate-target-self.expected.json +1 -0
  22. package/corpus/lenient/lenient-navigate-target-self.json +1 -0
  23. package/corpus/manifest.json +1285 -4
  24. package/corpus/nodes/action-confirm-cancel.json +1 -0
  25. package/corpus/nodes/action-confirm.json +1 -0
  26. package/corpus/nodes/action-focus.json +1 -0
  27. package/corpus/nodes/action-navigate-bound.json +1 -0
  28. package/corpus/nodes/action-navigate-target.json +1 -0
  29. package/corpus/nodes/box-break-before-1.json +1 -0
  30. package/corpus/nodes/box-keep-together-1.json +1 -0
  31. package/corpus/nodes/btn-copy-bound.json +1 -0
  32. package/corpus/nodes/button-print.json +1 -0
  33. package/corpus/nodes/chart-annotation-bands.json +1 -0
  34. package/corpus/nodes/chart-annotation-events.json +1 -0
  35. package/corpus/nodes/chart-annotations.json +1 -0
  36. package/corpus/nodes/custom-nonascii-keys.json +1 -0
  37. package/corpus/nodes/embed-1.json +1 -0
  38. package/corpus/nodes/embed-aspect-1.json +1 -0
  39. package/corpus/nodes/embed-permissions-1.json +1 -0
  40. package/corpus/nodes/expr-params-state-selection.json +1 -0
  41. package/corpus/nodes/expr-scalar.json +1 -0
  42. package/corpus/nodes/filters-rating-colour.json +1 -0
  43. package/corpus/nodes/filters-tokens.json +1 -0
  44. package/corpus/nodes/form-color.json +1 -0
  45. package/corpus/nodes/form-combobox-freetext.json +1 -0
  46. package/corpus/nodes/form-combobox-query.json +1 -0
  47. package/corpus/nodes/form-combobox-static.json +1 -0
  48. package/corpus/nodes/form-local-declared.json +1 -0
  49. package/corpus/nodes/form-rating-halves.json +1 -0
  50. package/corpus/nodes/form-rating.json +1 -0
  51. package/corpus/nodes/form-tokens-freetext.json +1 -0
  52. package/corpus/nodes/form-tokens-query.json +1 -0
  53. package/corpus/nodes/form-tokens-suggested.json +1 -0
  54. package/corpus/nodes/format-since.json +1 -0
  55. package/corpus/nodes/grid-exportable-1.json +1 -0
  56. package/corpus/nodes/grid-keep-rows-together-1.json +1 -0
  57. package/corpus/nodes/grid-repeat-header-1.json +1 -0
  58. package/corpus/nodes/limit-tree-item-depth-at-max.json +1 -0
  59. package/corpus/nodes/media-audio-transcript-1.json +1 -0
  60. package/corpus/nodes/media-video-captions-1.json +1 -0
  61. package/corpus/nodes/media-video-tracks-2.json +1 -0
  62. package/corpus/nodes/metric-float-1e17.json +1 -0
  63. package/corpus/nodes/multiselect-chip-list-param.json +1 -0
  64. package/corpus/nodes/node-visible.json +1 -0
  65. package/corpus/nodes/now-grain.json +1 -0
  66. package/corpus/nodes/popover-anchored-1.json +1 -0
  67. package/corpus/nodes/popover-open-1.json +1 -0
  68. package/corpus/nodes/shared-source-seeded-pair.json +1 -1
  69. package/corpus/nodes/style-direction-isolated-1.json +1 -0
  70. package/corpus/nodes/style-direction-ltr-1.json +1 -0
  71. package/corpus/nodes/switch-carousel-1.json +1 -0
  72. package/corpus/nodes/switch-on-transform-scalar.json +1 -0
  73. package/corpus/nodes/switch-predicate-only.json +1 -0
  74. package/corpus/nodes/switch-predicate.json +1 -0
  75. package/corpus/nodes/tooltip-button-1.json +1 -0
  76. package/corpus/nodes/tooltip-icon-button-1.json +1 -0
  77. package/corpus/nodes/tooltip-metric-1.json +1 -0
  78. package/corpus/nodes/transfer-board.json +1 -0
  79. package/corpus/nodes/tree-1.json +1 -0
  80. package/corpus/nodes/tree-expanded-1.json +1 -0
  81. package/corpus/nodes/tree-selection-1.json +1 -0
  82. package/corpus/nodes/upload-capture-camera-1.json +1 -0
  83. package/corpus/nodes/upload-capture-microphone-1.json +1 -0
  84. package/corpus/nodes/upload-destination-1.json +1 -0
  85. package/corpus/nodes/upload-drop-1.json +1 -0
  86. package/corpus/nodes/upload-paste-1.json +1 -0
  87. package/corpus/ops/op-updatestyle (phase 1472 direction=ltr, the only style member).json +1 -0
  88. package/corpus/reject/reject-action-print-with-payload.json +1 -0
  89. package/corpus/reject/reject-binding-floatseq-sentinel-case.json +1 -0
  90. package/corpus/reject/reject-chart-annotation-date-unparseable.json +1 -0
  91. package/corpus/reject/reject-chart-annotation-nonfinite.json +1 -0
  92. package/corpus/reject/reject-chart-annotation-range-unordered.json +1 -0
  93. package/corpus/reject/reject-color-value-not-hex.json +1 -0
  94. package/corpus/reject/reject-combobox-allowfreetext-nonbool.json +1 -0
  95. package/corpus/reject/reject-confirm-missing-onconfirm.json +1 -0
  96. package/corpus/reject/reject-confirm-nested.json +1 -0
  97. package/corpus/reject/reject-embed-missing-title.json +1 -0
  98. package/corpus/reject/reject-embed-permission-nonstring.json +1 -0
  99. package/corpus/reject/reject-embed-unknown-permission.json +1 -0
  100. package/corpus/reject/reject-expr-col-reference.json +1 -0
  101. package/corpus/reject/reject-expr-unbound-param.json +1 -0
  102. package/corpus/reject/reject-int-slot-fractional.json +1 -0
  103. package/corpus/reject/reject-int-slot-out-of-range.json +1 -0
  104. package/corpus/reject/reject-json-bare-nan.json +1 -0
  105. package/corpus/reject/reject-json-duplicate-key.json +1 -0
  106. package/corpus/reject/reject-json-lone-high-surrogate.json +1 -0
  107. package/corpus/reject/reject-json-lone-low-surrogate.json +1 -0
  108. package/corpus/reject/reject-json-number-empty-exponent.json +1 -0
  109. package/corpus/reject/reject-json-number-leading-plus.json +1 -0
  110. package/corpus/reject/reject-json-number-leading-zero.json +1 -0
  111. package/corpus/reject/reject-json-number-no-integer-part.json +1 -0
  112. package/corpus/reject/reject-json-number-trailing-point.json +1 -0
  113. package/corpus/reject/reject-json-raw-control-char.json +1 -0
  114. package/corpus/reject/reject-json-surrogate-pair-split.json +1 -0
  115. package/corpus/reject/reject-json-trailing-content.json +1 -0
  116. package/corpus/reject/reject-limit-tree-item-depth.json +1 -0
  117. package/corpus/reject/reject-local-codec-no-inverse.json +1 -0
  118. package/corpus/reject/reject-local-oncommit-and-committo.json +1 -0
  119. package/corpus/reject/reject-media-track-default-nonbool.json +1 -0
  120. package/corpus/reject/reject-media-track-missing-srclang.json +1 -0
  121. package/corpus/reject/reject-modal-modality-nonstring.json +1 -0
  122. package/corpus/reject/reject-modal-modality-unknown.json +1 -0
  123. package/corpus/reject/reject-navigate-target-invalid.json +1 -0
  124. package/corpus/reject/reject-now-grain-invalid.json +1 -0
  125. package/corpus/reject/reject-op-updatestyle-direction-nonstring.json +1 -0
  126. package/corpus/reject/reject-op-updatestyle-direction-unknown.json +1 -0
  127. package/corpus/reject/reject-rating-max-zero.json +1 -0
  128. package/corpus/reject/reject-spark-element-nonnumeric.json +1 -0
  129. package/corpus/reject/reject-style-direction-nonstring.json +1 -0
  130. package/corpus/reject/reject-style-direction-unknown.json +1 -0
  131. package/corpus/reject/reject-switch-autoadvance-fractional.json +1 -0
  132. package/corpus/reject/reject-switch-autoadvance-negative.json +1 -0
  133. package/corpus/reject/reject-switch-autoadvance-zero.json +1 -0
  134. package/corpus/reject/reject-switch-case-match-and-when.json +1 -0
  135. package/corpus/reject/reject-switch-case-neither.json +1 -0
  136. package/corpus/reject/reject-tokens-closed-without-suggestions.json +1 -0
  137. package/corpus/reject/reject-tokens-value-not-list.json +1 -0
  138. package/corpus/reject/reject-tooltip-nonstring.json +1 -0
  139. package/corpus/reject/reject-tree-item-missing-id.json +1 -0
  140. package/corpus/reject/reject-tree-item-missing-label.json +1 -0
  141. package/corpus/reject/reject-tree-nested-item-missing-id.json +1 -0
  142. package/corpus/reject/reject-unknown-capture-source.json +1 -0
  143. package/corpus/reject/reject-upload-acceptpaste-nonbool.json +1 -0
  144. package/corpus/reject/reject-upload-destination-empty.json +1 -0
  145. package/corpus/reject/reject-upload-droptarget-nonbool.json +1 -0
  146. package/corpus/reject/reject-wrongtype-box-break-before.json +1 -0
  147. package/corpus/reject/reject-wrongtype-box-keep-together.json +1 -0
  148. package/corpus/reject/reject-wrongtype-clipboard-payload.json +1 -0
  149. package/corpus/reject/reject-wrongtype-grid-exportable-number.json +1 -0
  150. package/corpus/reject/reject-wrongtype-grid-exportable.json +1 -0
  151. package/corpus/reject/reject-wrongtype-grid-keep-rows-together.json +1 -0
  152. package/corpus/reject/reject-wrongtype-grid-repeat-header.json +1 -0
  153. package/corpus/reject/reject-wrongtype-grid-transfer-in-key.json +1 -0
  154. package/corpus/reject/reject-wrongtype-grid-transfer-out-key.json +1 -0
  155. package/corpus/schema.json +1338 -52
  156. package/dist/cli.js +94 -0
  157. package/dist/cli.js.map +1 -1
  158. package/dist/index.cjs +94 -0
  159. package/dist/index.cjs.map +1 -1
  160. package/dist/index.d.cts +18 -3
  161. package/dist/index.d.ts +18 -3
  162. package/dist/index.js +94 -0
  163. package/dist/index.js.map +1 -1
  164. package/package.json +3 -3
@@ -2,7 +2,7 @@
2
2
  "version": 1,
3
3
  "schema": "schema.json",
4
4
  "idl": "idl.json",
5
- "description": "Fuaran canonical wire-format conformance corpus. node-round-trip / op-round-trip fixtures: decode inputFile, re-encode, assert byte-equal to expectedFile. reject fixtures: decode inputFile, assert DecodeError.Code = expectedErrorCode and DecodeError.Path starts with expectedPath. lenient-accept fixtures (WIRE_FORMAT 16): decode the SHORTHAND inputFile, re-encode, assert byte-equal to expectedFile (the verbose canonical form) — a conformant host MUST accept the shorthand and normalise it; rejecting it, or decoding it to different bytes, is non-conformant. envelope-round-trip / envelope-reject fixtures (WIRE_FORMAT 15): read the $profile/$payload envelope, negotiate the authored profile against the host's own core@1.0, then either re-render byte-equal to expectedFile (Current/Behind — unknown kinds preserved verbatim, must-ignore-but-preserve) or refuse a Foreign profile with expectedErrorCode FOREIGN_PROFILE at expectedPath. elicitation-round-trip / elicitation-reject fixtures (WIRE_FORMAT 18): decode with the decoder-named entry point (elicitation = the envelope codec, elicitation-outcome = the outcome codec), then either re-encode byte-equal to expectedFile or assert the structured refusal (expectedErrorCode at a path starting with expectedPath). elicitation-answer-accept / elicitation-answer-reject fixtures (WIRE_FORMAT 18.4): the inputFile pairs {answer, contract}; run the host's answer-conformance validation and assert acceptance or the expected refusal. See fuaran-dotnet/docs/WIRE_FORMAT.md.",
5
+ "description": "Fuaran canonical wire-format conformance corpus. node-round-trip / op-round-trip fixtures: decode inputFile, re-encode, assert byte-equal to expectedFile. reject fixtures: decode inputFile, assert DecodeError.Code = expectedErrorCode and DecodeError.Path starts with expectedPath. lenient-accept fixtures (WIRE_FORMAT 16): decode the SHORTHAND inputFile, re-encode, assert byte-equal to expectedFile (the verbose canonical form) — a conformant host MUST accept the shorthand and normalise it; rejecting it, or decoding it to different bytes, is non-conformant. envelope-round-trip / envelope-reject fixtures (WIRE_FORMAT 15): read the $profile/$payload envelope, negotiate the authored profile against the host's own core@1.0, then either re-render byte-equal to expectedFile (Current/Behind — unknown kinds preserved verbatim, must-ignore-but-preserve) or refuse a Foreign profile with expectedErrorCode FOREIGN_PROFILE at expectedPath. elicitation-round-trip / elicitation-reject fixtures (WIRE_FORMAT 18): decode with the decoder-named entry point (elicitation = the envelope codec, elicitation-outcome = the outcome codec), then either re-encode byte-equal to expectedFile or assert the structured refusal (expectedErrorCode at a path starting with expectedPath). elicitation-answer-accept / elicitation-answer-reject fixtures (WIRE_FORMAT 18.4): the inputFile pairs {answer, contract}; run the host's answer-conformance validation and assert acceptance or the expected refusal. contract-card-round-trip / contract-card-reject fixtures (WIRE_FORMAT 25): decode with the decoder-named entry point (contract-card = the single-card codec, contract-card-bundle = the bundle codec), then either re-encode byte-equal to expectedFile or assert the structured refusal (expectedErrorCode at a path starting with expectedPath). A card is NOT a node — it is the description a host reads to label and prop-validate a Custom node it has no renderer for — so it is its own family and never appears in nodes/. See fuaran-dotnet/docs/WIRE_FORMAT.md.",
6
6
  "kinds": [
7
7
  "Badge",
8
8
  "Box",
@@ -14,6 +14,7 @@
14
14
  "DataGrid",
15
15
  "Disclosure",
16
16
  "Drawing",
17
+ "Embed",
17
18
  "ErrorBoundary",
18
19
  "Fact",
19
20
  "FileUpload",
@@ -44,22 +45,163 @@
44
45
  "SummaryList",
45
46
  "Switch",
46
47
  "Tabs",
47
- "Toast"
48
+ "Toast",
49
+ "Tree"
48
50
  ],
49
51
  "formFieldKinds": [
50
52
  "Checkbox",
51
53
  "Choice",
54
+ "Color",
55
+ "Combobox",
52
56
  "Date",
53
57
  "DateRange",
54
58
  "Number",
55
59
  "Range",
56
60
  "RangedNumber",
61
+ "Rating",
57
62
  "SegmentedChoice",
58
63
  "Text",
59
64
  "TextArea",
60
- "Toggle"
65
+ "Toggle",
66
+ "Tokens"
67
+ ],
68
+ "ops": [
69
+ "Batch",
70
+ "EditNode",
71
+ "InsertChild",
72
+ "MoveNode",
73
+ "RemoveNode",
74
+ "ReorderChildren",
75
+ "ReplaceBinding",
76
+ "ReplaceRoot",
77
+ "UpdateProp",
78
+ "UpdateState",
79
+ "UpdateStyle"
61
80
  ],
62
81
  "fixtures": [
82
+ {
83
+ "id": "card-reject-bad-version",
84
+ "kind": "contract-card-reject",
85
+ "decoder": "contract-card",
86
+ "inputFile": "cards/card-reject-bad-version.json",
87
+ "expectedErrorCode": "UNSUPPORTED_VERSION",
88
+ "expectedPath": "$.$card",
89
+ "description": "a card format version this decoder does not implement (WIRE_FORMAT 25.1)"
90
+ },
91
+ {
92
+ "id": "card-reject-bundle-bad-version",
93
+ "kind": "contract-card-reject",
94
+ "decoder": "contract-card-bundle",
95
+ "inputFile": "cards/card-reject-bundle-bad-version.json",
96
+ "expectedErrorCode": "UNSUPPORTED_VERSION",
97
+ "expectedPath": "$.$cards",
98
+ "description": "a bundle format version this decoder does not implement (WIRE_FORMAT 25.2)"
99
+ },
100
+ {
101
+ "id": "card-reject-bundle-duplicate",
102
+ "kind": "contract-card-reject",
103
+ "decoder": "contract-card-bundle",
104
+ "inputFile": "cards/card-reject-bundle-duplicate.json",
105
+ "expectedErrorCode": "DUPLICATE_CARD",
106
+ "expectedPath": "$.cards[1]",
107
+ "description": "two cards for one identity: a document with no order to appeal to, so which description a reader gets would depend on decoder implementation detail (WIRE_FORMAT 25.2)"
108
+ },
109
+ {
110
+ "id": "card-reject-empty-enum",
111
+ "kind": "contract-card-reject",
112
+ "decoder": "contract-card",
113
+ "inputFile": "cards/card-reject-empty-enum.json",
114
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
115
+ "expectedPath": "$.props[0].type",
116
+ "description": "`enum()` is unrepresentable in the tag spelling — an enum admitting nothing, spelled as though it admitted one empty choice (WIRE_FORMAT 25.1)"
117
+ },
118
+ {
119
+ "id": "card-reject-hash-not-object",
120
+ "kind": "contract-card-reject",
121
+ "decoder": "contract-card",
122
+ "inputFile": "cards/card-reject-hash-not-object.json",
123
+ "expectedErrorCode": "WRONG_TYPE",
124
+ "expectedPath": "$.contentHash",
125
+ "description": "the digest is carried WITH its algorithm; a bare string would make two digests under different algorithms comparable (WIRE_FORMAT 25.1)"
126
+ },
127
+ {
128
+ "id": "card-reject-missing-hash",
129
+ "kind": "contract-card-reject",
130
+ "decoder": "contract-card",
131
+ "inputFile": "cards/card-reject-missing-hash.json",
132
+ "expectedErrorCode": "MISSING_FIELD",
133
+ "expectedPath": "$.contentHash",
134
+ "description": "the content hash is REQUIRED — without it a card can never be more than a name match, and the three-way verdict collapses to its weakest case for every node (WIRE_FORMAT 25.4)"
135
+ },
136
+ {
137
+ "id": "card-reject-missing-module",
138
+ "kind": "contract-card-reject",
139
+ "decoder": "contract-card",
140
+ "inputFile": "cards/card-reject-missing-module.json",
141
+ "expectedErrorCode": "MISSING_FIELD",
142
+ "expectedPath": "$.moduleId",
143
+ "description": "half an identity is not an identity (WIRE_FORMAT 25.1)"
144
+ },
145
+ {
146
+ "id": "card-reject-payload-no-language",
147
+ "kind": "contract-card-reject",
148
+ "decoder": "contract-card",
149
+ "inputFile": "cards/card-reject-payload-no-language.json",
150
+ "expectedErrorCode": "MISSING_FIELD",
151
+ "expectedPath": "$.props[0].payload.language",
152
+ "description": "a gate with no language names a judge for nothing; the language is the declaration and the gate is the annotation on it (WIRE_FORMAT 25.1)"
153
+ },
154
+ {
155
+ "id": "card-reject-required-not-bool",
156
+ "kind": "contract-card-reject",
157
+ "decoder": "contract-card",
158
+ "inputFile": "cards/card-reject-required-not-bool.json",
159
+ "expectedErrorCode": "WRONG_TYPE",
160
+ "expectedPath": "$.props[0].required",
161
+ "description": "requiredness is a boolean, not a spelling (WIRE_FORMAT 25.1)"
162
+ },
163
+ {
164
+ "id": "card-reject-stray-key",
165
+ "kind": "contract-card-reject",
166
+ "decoder": "contract-card",
167
+ "inputFile": "cards/card-reject-stray-key.json",
168
+ "expectedErrorCode": "UNDECLARED_FIELD",
169
+ "expectedPath": "$.renderer",
170
+ "description": "default-deny by shape: an undeclared key is refused, never ignored — a card is a protocol artefact and its evolution is the version tag (WIRE_FORMAT 25.3)"
171
+ },
172
+ {
173
+ "id": "card-reject-unknown-prop-type",
174
+ "kind": "contract-card-reject",
175
+ "decoder": "contract-card",
176
+ "inputFile": "cards/card-reject-unknown-prop-type.json",
177
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
178
+ "expectedPath": "$.props[0].type",
179
+ "description": "a prop type tag this build cannot resolve is refused at the boundary rather than read as permissive — guessing would turn a check into a pass (WIRE_FORMAT 25.3)"
180
+ },
181
+ {
182
+ "id": "card-bundle",
183
+ "kind": "contract-card-round-trip",
184
+ "decoder": "contract-card-bundle",
185
+ "inputFile": "cards/card-bundle.json",
186
+ "expectedFile": "cards/card-bundle.json",
187
+ "description": "a two-card bundle, emitted sorted by (moduleId, componentId) whatever order the cards were supplied in (WIRE_FORMAT 25.2)"
188
+ },
189
+ {
190
+ "id": "card-full",
191
+ "kind": "contract-card-round-trip",
192
+ "decoder": "contract-card",
193
+ "inputFile": "cards/card-full.json",
194
+ "expectedFile": "cards/card-full.json",
195
+ "description": "summary, required + optional props, an enum type tag, and both payload states — gated and ungated (WIRE_FORMAT 25.1)"
196
+ },
197
+ {
198
+ "id": "card-minimal",
199
+ "kind": "contract-card-round-trip",
200
+ "decoder": "contract-card",
201
+ "inputFile": "cards/card-minimal.json",
202
+ "expectedFile": "cards/card-minimal.json",
203
+ "description": "identity + content hash, no props and no summary (WIRE_FORMAT 25.1)"
204
+ },
63
205
  {
64
206
  "id": "elc-ans-accept-full",
65
207
  "kind": "elicitation-answer-accept",
@@ -404,6 +546,30 @@
404
546
  "expectedFile": "lenient/lenient-022-lvr-emphasis-normal.expected.json",
405
547
  "description": "0.2.2 — 'Normal' coerces to false, which is the omitted-when-false canonical form"
406
548
  },
549
+ {
550
+ "id": "lenient-1126-clipboard-literal-envelope",
551
+ "kind": "lenient-accept",
552
+ "decoder": "node",
553
+ "inputFile": "lenient/lenient-1126-clipboard-literal-envelope.json",
554
+ "expectedFile": "lenient/lenient-1126-clipboard-literal-envelope.expected.json",
555
+ "description": "Phase 1126 — the clipboard payload is a TextSource, so §16's transparent-Literal rule reaches it: the explicit {\"$type\":\"Literal\",\"text\":…} envelope normalises to the bare string, which is also the pre-1126 spelling and still the canonical one"
556
+ },
557
+ {
558
+ "id": "lenient-1521-int-slot-integral-float",
559
+ "kind": "lenient-accept",
560
+ "decoder": "node",
561
+ "inputFile": "lenient/lenient-1521-int-slot-integral-float.json",
562
+ "expectedFile": "lenient/lenient-1521-int-slot-integral-float.expected.json",
563
+ "description": "§7.1 — an integer-VALUED number written with a fractional part decodes at an integer slot and canonicalises to the integer spelling. Two hosts refused `3.0` outright, which refuses a document whose intent is unambiguous for its spelling; `2.5` at the same slot stays a WRONG_TYPE, so the accept set widens by exactly the values an integer slot can hold"
564
+ },
565
+ {
566
+ "id": "lenient-1521-payload-integer-beyond-int53",
567
+ "kind": "lenient-accept",
568
+ "decoder": "node",
569
+ "inputFile": "lenient/lenient-1521-payload-integer-beyond-int53.json",
570
+ "expectedFile": "lenient/lenient-1521-payload-integer-beyond-int53.expected.json",
571
+ "description": "§2 rule 5 — integer identity in a rule-12 payload stops at ±(2⁵³−1). `boundary` is at the limit and survives verbatim; `beyond` is one past it, has no representation every host holds exactly, and decodes to the nearest double. Pinned as a lenient NORMALISATION rather than left implicit, because three hosts silently rounded it, one kept an int64 and one an arbitrary-precision integer — the same document, five canonical byte sequences, five hash-chain digests"
572
+ },
407
573
  {
408
574
  "id": "lenient-460-alias-emphasis-muted",
409
575
  "kind": "lenient-accept",
@@ -668,6 +834,14 @@
668
834
  "expectedFile": "lenient/lenient-master-detail-preselected-compact.expected.json",
669
835
  "description": "Phase 719 — the compact authoring twin of `master-detail-preselected`: all-valid columns as bare arrays (Core#88) and the inferable schema omitted; the canonical envelope stays the encoder's output. The pack teaches this form by example — the exemplar/prose contradiction measurably taught the envelope (claude-opus 81% envelope share, 2026-07-28 census)."
670
836
  },
837
+ {
838
+ "id": "lenient-navigate-target-self",
839
+ "kind": "lenient-accept",
840
+ "decoder": "node",
841
+ "inputFile": "lenient/lenient-navigate-target-self.json",
842
+ "expectedFile": "lenient/lenient-navigate-target-self.expected.json",
843
+ "description": "Phase 1536 — an explicit `target: \"Self\"` normalises away; `Self` is omitted at default, so the canonical form carries no `target` member at all"
844
+ },
671
845
  {
672
846
  "id": "lenient-null-static-options",
673
847
  "kind": "lenient-accept",
@@ -956,6 +1130,46 @@
956
1130
  "expectedFile": "nodes/a11y-wrapper-state-bound.json",
957
1131
  "description": "Accessibility (Phase 955 — State-bound label + hidden, mixed-case custom role doc-pageFooter, off)"
958
1132
  },
1133
+ {
1134
+ "id": "action-confirm",
1135
+ "kind": "node-round-trip",
1136
+ "decoder": "node",
1137
+ "inputFile": "nodes/action-confirm.json",
1138
+ "expectedFile": "nodes/action-confirm.json",
1139
+ "description": "Phase 1537 — Input/Button whose Action.Confirm asks a BOUND question (naming the selected row) before a Call; no onCancel, which is how the language spells 'nothing happens'"
1140
+ },
1141
+ {
1142
+ "id": "action-confirm-cancel",
1143
+ "kind": "node-round-trip",
1144
+ "decoder": "node",
1145
+ "inputFile": "nodes/action-confirm-cancel.json",
1146
+ "expectedFile": "nodes/action-confirm-cancel.json",
1147
+ "description": "Phase 1537 — Input/Button whose Action.Confirm carries a LITERAL prompt and both branches; the only fixture in which the recursive encoder descends into a named continuation rather than a Chain list"
1148
+ },
1149
+ {
1150
+ "id": "action-focus",
1151
+ "kind": "node-round-trip",
1152
+ "decoder": "node",
1153
+ "inputFile": "nodes/action-focus.json",
1154
+ "expectedFile": "nodes/action-focus.json",
1155
+ "description": "Phase 1537 — Input/Button whose Action.Focus moves the caret to an addressed node, chained after the write that emptied it"
1156
+ },
1157
+ {
1158
+ "id": "action-navigate-bound",
1159
+ "kind": "node-round-trip",
1160
+ "decoder": "node",
1161
+ "inputFile": "nodes/action-navigate-bound.json",
1162
+ "expectedFile": "nodes/action-navigate-bound.json",
1163
+ "description": "Phase 1536 — Input/Button whose Action.Navigate carries a BOUND route (a TextSource over the grid's selection), resolved at dispatch time and gated on the resolved value"
1164
+ },
1165
+ {
1166
+ "id": "action-navigate-target",
1167
+ "kind": "node-round-trip",
1168
+ "decoder": "node",
1169
+ "inputFile": "nodes/action-navigate-target.json",
1170
+ "expectedFile": "nodes/action-navigate-target.json",
1171
+ "description": "Phase 1536 — Input/Button whose Action.Navigate names a Blank target (opened with noopener,noreferrer); `Self` is omitted, so this is the only shape that puts `target` on the wire"
1172
+ },
959
1173
  {
960
1174
  "id": "badge-1",
961
1175
  "kind": "node-round-trip",
@@ -980,6 +1194,22 @@
980
1194
  "expectedFile": "nodes/boundary-1.json",
981
1195
  "description": "ErrorBoundary (Markdown child + Callout fallback)"
982
1196
  },
1197
+ {
1198
+ "id": "box-break-before-1",
1199
+ "kind": "node-round-trip",
1200
+ "decoder": "node",
1201
+ "inputFile": "nodes/box-break-before-1.json",
1202
+ "expectedFile": "nodes/box-break-before-1.json",
1203
+ "description": "Layout/Box (Phase 1473 breakBefore, the only new member)"
1204
+ },
1205
+ {
1206
+ "id": "box-keep-together-1",
1207
+ "kind": "node-round-trip",
1208
+ "decoder": "node",
1209
+ "inputFile": "nodes/box-keep-together-1.json",
1210
+ "expectedFile": "nodes/box-keep-together-1.json",
1211
+ "description": "Layout/Box (Phase 1473 keepTogether, the only new member)"
1212
+ },
983
1213
  {
984
1214
  "id": "btn-1",
985
1215
  "kind": "node-round-trip",
@@ -988,6 +1218,14 @@
988
1218
  "expectedFile": "nodes/btn-1.json",
989
1219
  "description": "Input/Button"
990
1220
  },
1221
+ {
1222
+ "id": "btn-copy-bound",
1223
+ "kind": "node-round-trip",
1224
+ "decoder": "node",
1225
+ "inputFile": "nodes/btn-copy-bound.json",
1226
+ "expectedFile": "nodes/btn-copy-bound.json",
1227
+ "description": "Phase 1126 — Input/Button whose Action.WriteToClipboard carries a BOUND payload (a TextSource, not a bare string), resolved at dispatch time"
1228
+ },
991
1229
  {
992
1230
  "id": "btn-copy-link",
993
1231
  "kind": "node-round-trip",
@@ -1020,6 +1258,14 @@
1020
1258
  "expectedFile": "nodes/btn-read-workbook.json",
1021
1259
  "description": "Input/Button (Action.ReadFileBody base64)"
1022
1260
  },
1261
+ {
1262
+ "id": "button-print",
1263
+ "kind": "node-round-trip",
1264
+ "decoder": "node",
1265
+ "inputFile": "nodes/button-print.json",
1266
+ "expectedFile": "nodes/button-print.json",
1267
+ "description": "Input/Button (Phase 1124 — Action.Print: the payload-free action, inside a Chain)"
1268
+ },
1023
1269
  {
1024
1270
  "id": "button-setstate-valuefrom",
1025
1271
  "kind": "node-round-trip",
@@ -1060,6 +1306,30 @@
1060
1306
  "expectedFile": "nodes/chart-1.json",
1061
1307
  "description": "Visualisation/Chart"
1062
1308
  },
1309
+ {
1310
+ "id": "chart-annotation-bands",
1311
+ "kind": "node-round-trip",
1312
+ "decoder": "node",
1313
+ "inputFile": "nodes/chart-annotation-bands.json",
1314
+ "expectedFile": "nodes/chart-annotation-bands.json",
1315
+ "description": "Visualisation/Chart (Phase 1492 — annotations: range bands in all three addressing forms, value / category / date)"
1316
+ },
1317
+ {
1318
+ "id": "chart-annotation-events",
1319
+ "kind": "node-round-trip",
1320
+ "decoder": "node",
1321
+ "inputFile": "nodes/chart-annotation-events.json",
1322
+ "expectedFile": "nodes/chart-annotation-events.json",
1323
+ "description": "Visualisation/Chart (Phase 1491 — annotations: event markers in both x-address forms, category and date)"
1324
+ },
1325
+ {
1326
+ "id": "chart-annotations",
1327
+ "kind": "node-round-trip",
1328
+ "decoder": "node",
1329
+ "inputFile": "nodes/chart-annotations.json",
1330
+ "expectedFile": "nodes/chart-annotations.json",
1331
+ "description": "Visualisation/Chart (Phase 1490 — annotations: two data-addressed reference lines, one labelled)"
1332
+ },
1063
1333
  {
1064
1334
  "id": "chart-axis-titles",
1065
1335
  "kind": "node-round-trip",
@@ -1172,6 +1442,14 @@
1172
1442
  "expectedFile": "nodes/custom-bounded-advisory.json",
1173
1443
  "description": "Custom (bounded escape, AdvisoryWarning hash + no exposed-ids)"
1174
1444
  },
1445
+ {
1446
+ "id": "custom-nonascii-keys",
1447
+ "kind": "node-round-trip",
1448
+ "decoder": "node",
1449
+ "inputFile": "nodes/custom-nonascii-keys.json",
1450
+ "expectedFile": "nodes/custom-nonascii-keys.json",
1451
+ "description": "Custom (§2 rule 2 — astral vs BMP payload keys, the UTF-16-order discriminator)"
1452
+ },
1175
1453
  {
1176
1454
  "id": "dash-empty",
1177
1455
  "kind": "node-round-trip",
@@ -1228,6 +1506,30 @@
1228
1506
  "expectedFile": "nodes/drawing-tipped-shapes.json",
1229
1507
  "description": "Display/Drawing (Phase 883 — tipped shapes, incl. hostile text, Bound tip and explicit empty)"
1230
1508
  },
1509
+ {
1510
+ "id": "embed-1",
1511
+ "kind": "node-round-trip",
1512
+ "decoder": "node",
1513
+ "inputFile": "nodes/embed-1.json",
1514
+ "expectedFile": "nodes/embed-1.json",
1515
+ "description": "Display/Embed (Phase 1111 — the minimum: no ratio, no permissions, total denial)"
1516
+ },
1517
+ {
1518
+ "id": "embed-aspect-1",
1519
+ "kind": "node-round-trip",
1520
+ "decoder": "node",
1521
+ "inputFile": "nodes/embed-aspect-1.json",
1522
+ "expectedFile": "nodes/embed-aspect-1.json",
1523
+ "description": "Display/Embed (Phase 1111 — a declared aspect ratio, sharing Image's enum)"
1524
+ },
1525
+ {
1526
+ "id": "embed-permissions-1",
1527
+ "kind": "node-round-trip",
1528
+ "decoder": "node",
1529
+ "inputFile": "nodes/embed-permissions-1.json",
1530
+ "expectedFile": "nodes/embed-permissions-1.json",
1531
+ "description": "Display/Embed (Phase 1111 — one permission, the one that rides `allow` not `sandbox`)"
1532
+ },
1231
1533
  {
1232
1534
  "id": "empty-state-card",
1233
1535
  "kind": "node-round-trip",
@@ -1236,6 +1538,22 @@
1236
1538
  "expectedFile": "nodes/empty-state-card.json",
1237
1539
  "description": "Layout/Box (Phase 767 — the canonical empty state with a CTA: one Card region, no nested Callout)"
1238
1540
  },
1541
+ {
1542
+ "id": "expr-params-state-selection",
1543
+ "kind": "node-round-trip",
1544
+ "decoder": "node",
1545
+ "inputFile": "nodes/expr-params-state-selection.json",
1546
+ "expectedFile": "nodes/expr-params-state-selection.json",
1547
+ "description": "Binding/Expr (Phase 1534 — params from State / Selection / Query / Filter / Now, including an InParam membership test)"
1548
+ },
1549
+ {
1550
+ "id": "expr-scalar",
1551
+ "kind": "node-round-trip",
1552
+ "decoder": "node",
1553
+ "inputFile": "nodes/expr-scalar.json",
1554
+ "expectedFile": "nodes/expr-scalar.json",
1555
+ "description": "Binding/Expr (Phase 1534 — scalar logic with no params: concat, arithmetic, AND/NOT, a null test)"
1556
+ },
1239
1557
  {
1240
1558
  "id": "fact-1",
1241
1559
  "kind": "node-round-trip",
@@ -1276,6 +1594,14 @@
1276
1594
  "expectedFile": "nodes/filters-declarative.json",
1277
1595
  "description": "Input/Filters (declarative — omitted onChange + typed range bounds)"
1278
1596
  },
1597
+ {
1598
+ "id": "filters-rating-colour",
1599
+ "kind": "node-round-trip",
1600
+ "decoder": "node",
1601
+ "inputFile": "nodes/filters-rating-colour.json",
1602
+ "expectedFile": "nodes/filters-rating-colour.json",
1603
+ "description": "Input/Filters (Phase 1130 — Rating + Color chips, declarative and auto-bound)"
1604
+ },
1279
1605
  {
1280
1606
  "id": "filters-segmented",
1281
1607
  "kind": "node-round-trip",
@@ -1284,6 +1610,14 @@
1284
1610
  "expectedFile": "nodes/filters-segmented.json",
1285
1611
  "description": "Input/Filters (SegmentedFilter horizontal)"
1286
1612
  },
1613
+ {
1614
+ "id": "filters-tokens",
1615
+ "kind": "node-round-trip",
1616
+ "decoder": "node",
1617
+ "inputFile": "nodes/filters-tokens.json",
1618
+ "expectedFile": "nodes/filters-tokens.json",
1619
+ "description": "Input/Filters (Phase 1121 — Tokens chip, constrained and auto-bound)"
1620
+ },
1287
1621
  {
1288
1622
  "id": "form-1",
1289
1623
  "kind": "node-round-trip",
@@ -1292,6 +1626,38 @@
1292
1626
  "expectedFile": "nodes/form-1.json",
1293
1627
  "description": "Input/Form (all fields)"
1294
1628
  },
1629
+ {
1630
+ "id": "form-color",
1631
+ "kind": "node-round-trip",
1632
+ "decoder": "node",
1633
+ "inputFile": "nodes/form-color.json",
1634
+ "expectedFile": "nodes/form-color.json",
1635
+ "description": "Input/Form (Phase 1130 — Color: upper-case hex, preserved not normalised)"
1636
+ },
1637
+ {
1638
+ "id": "form-combobox-freetext",
1639
+ "kind": "node-round-trip",
1640
+ "decoder": "node",
1641
+ "inputFile": "nodes/form-combobox-freetext.json",
1642
+ "expectedFile": "nodes/form-combobox-freetext.json",
1643
+ "description": "Input/Form (Phase 1113 — Combobox: allowFreeText, value outside the option set)"
1644
+ },
1645
+ {
1646
+ "id": "form-combobox-query",
1647
+ "kind": "node-round-trip",
1648
+ "decoder": "node",
1649
+ "inputFile": "nodes/form-combobox-query.json",
1650
+ "expectedFile": "nodes/form-combobox-query.json",
1651
+ "description": "Input/Form (Phase 1113 — Combobox: Query-bound option source, declarative, auto-bound value)"
1652
+ },
1653
+ {
1654
+ "id": "form-combobox-static",
1655
+ "kind": "node-round-trip",
1656
+ "decoder": "node",
1657
+ "inputFile": "nodes/form-combobox-static.json",
1658
+ "expectedFile": "nodes/form-combobox-static.json",
1659
+ "description": "Input/Form (Phase 1113 — Combobox: static options, allowFreeText omitted)"
1660
+ },
1295
1661
  {
1296
1662
  "id": "form-date",
1297
1663
  "kind": "node-round-trip",
@@ -1348,6 +1714,14 @@
1348
1714
  "expectedFile": "nodes/form-local-debounce.json",
1349
1715
  "description": "Input/Form (Local-bound text, OnDebounce 250)"
1350
1716
  },
1717
+ {
1718
+ "id": "form-local-declared",
1719
+ "kind": "node-round-trip",
1720
+ "decoder": "node",
1721
+ "inputFile": "nodes/form-local-declared.json",
1722
+ "expectedFile": "nodes/form-local-declared.json",
1723
+ "description": "Input/Form (Fuaran-UI Phase 1538 — a DECLARED Local: codec + commitTo, no closure)"
1724
+ },
1351
1725
  {
1352
1726
  "id": "form-ranged",
1353
1727
  "kind": "node-round-trip",
@@ -1356,6 +1730,22 @@
1356
1730
  "expectedFile": "nodes/form-ranged.json",
1357
1731
  "description": "Input/Form (RangedNumber — all/min-only/no bounds)"
1358
1732
  },
1733
+ {
1734
+ "id": "form-rating",
1735
+ "kind": "node-round-trip",
1736
+ "decoder": "node",
1737
+ "inputFile": "nodes/form-rating.json",
1738
+ "expectedFile": "nodes/form-rating.json",
1739
+ "description": "Input/Form (Phase 1130 — Rating: whole stars, allowHalf omitted, static value)"
1740
+ },
1741
+ {
1742
+ "id": "form-rating-halves",
1743
+ "kind": "node-round-trip",
1744
+ "decoder": "node",
1745
+ "inputFile": "nodes/form-rating-halves.json",
1746
+ "expectedFile": "nodes/form-rating-halves.json",
1747
+ "description": "Input/Form (Phase 1130 — Rating: allowHalf entry + a bound average on a ten-scale, value auto-bound)"
1748
+ },
1359
1749
  {
1360
1750
  "id": "form-segmented",
1361
1751
  "kind": "node-round-trip",
@@ -1372,6 +1762,30 @@
1372
1762
  "expectedFile": "nodes/form-toggle.json",
1373
1763
  "description": "Input/Form (Phase 766 — the Toggle switch affordance beside a Checkbox)"
1374
1764
  },
1765
+ {
1766
+ "id": "form-tokens-freetext",
1767
+ "kind": "node-round-trip",
1768
+ "decoder": "node",
1769
+ "inputFile": "nodes/form-tokens-freetext.json",
1770
+ "expectedFile": "nodes/form-tokens-freetext.json",
1771
+ "description": "Input/Form (Phase 1121 — Tokens: the shortest spelling, allowFreeText omitted (true), value auto-bound)"
1772
+ },
1773
+ {
1774
+ "id": "form-tokens-query",
1775
+ "kind": "node-round-trip",
1776
+ "decoder": "node",
1777
+ "inputFile": "nodes/form-tokens-query.json",
1778
+ "expectedFile": "nodes/form-tokens-query.json",
1779
+ "description": "Input/Form (Phase 1121 — Tokens: Query-bound suggestion source, declarative, value auto-bound)"
1780
+ },
1781
+ {
1782
+ "id": "form-tokens-suggested",
1783
+ "kind": "node-round-trip",
1784
+ "decoder": "node",
1785
+ "inputFile": "nodes/form-tokens-suggested.json",
1786
+ "expectedFile": "nodes/form-tokens-suggested.json",
1787
+ "description": "Input/Form (Phase 1121 — Tokens: allowFreeText false over a static suggestion source, tokens in reader order)"
1788
+ },
1375
1789
  {
1376
1790
  "id": "format-bindings",
1377
1791
  "kind": "node-round-trip",
@@ -1380,6 +1794,14 @@
1380
1794
  "expectedFile": "nodes/format-bindings.json",
1381
1795
  "description": "Binding.Format (number/currency/percent/date/relativeTime across locales)"
1382
1796
  },
1797
+ {
1798
+ "id": "format-since",
1799
+ "kind": "node-round-trip",
1800
+ "decoder": "node",
1801
+ "inputFile": "nodes/format-since.json",
1802
+ "expectedFile": "nodes/format-since.json",
1803
+ "description": "Binding.Format (Phase 1533 — Since: the instant-reading twin of RelativeTime, declared unit and auto)"
1804
+ },
1383
1805
  {
1384
1806
  "id": "frag-decl-1",
1385
1807
  "kind": "node-round-trip",
@@ -1604,6 +2026,14 @@
1604
2026
  "expectedFile": "nodes/grid-editable-state.json",
1605
2027
  "description": "Visualisation/Grid (Phase 663/665 — editable State-sourced grid, typed rows on the wire)"
1606
2028
  },
2029
+ {
2030
+ "id": "grid-exportable-1",
2031
+ "kind": "node-round-trip",
2032
+ "decoder": "node",
2033
+ "inputFile": "nodes/grid-exportable-1.json",
2034
+ "expectedFile": "nodes/grid-exportable-1.json",
2035
+ "description": "Visualisation/Grid (Phase 1125 exportable, the only new member)"
2036
+ },
1607
2037
  {
1608
2038
  "id": "grid-field-named",
1609
2039
  "kind": "node-round-trip",
@@ -1612,6 +2042,14 @@
1612
2042
  "expectedFile": "nodes/grid-field-named.json",
1613
2043
  "description": "Visualisation/Grid (Phase 425 — field-named columns + RowKeyField, closure-free)"
1614
2044
  },
2045
+ {
2046
+ "id": "grid-keep-rows-together-1",
2047
+ "kind": "node-round-trip",
2048
+ "decoder": "node",
2049
+ "inputFile": "nodes/grid-keep-rows-together-1.json",
2050
+ "expectedFile": "nodes/grid-keep-rows-together-1.json",
2051
+ "description": "Visualisation/Grid (Phase 1473 keepRowsTogether, the only new member)"
2052
+ },
1615
2053
  {
1616
2054
  "id": "grid-paged",
1617
2055
  "kind": "node-round-trip",
@@ -1636,6 +2074,14 @@
1636
2074
  "expectedFile": "nodes/grid-reorderable.json",
1637
2075
  "description": "Visualisation/Grid (Phase 934 — declarative row reorder: omit-when-false flag; edits and reorders share one destination)"
1638
2076
  },
2077
+ {
2078
+ "id": "grid-repeat-header-1",
2079
+ "kind": "node-round-trip",
2080
+ "decoder": "node",
2081
+ "inputFile": "nodes/grid-repeat-header-1.json",
2082
+ "expectedFile": "nodes/grid-repeat-header-1.json",
2083
+ "description": "Visualisation/Grid (Phase 1473 repeatHeader, the only new member)"
2084
+ },
1639
2085
  {
1640
2086
  "id": "grid-sort-state-key",
1641
2087
  "kind": "node-round-trip",
@@ -1748,6 +2194,14 @@
1748
2194
  "expectedFile": "nodes/limit-node-depth-at-max.json",
1749
2195
  "description": "§21 max node depth — a tree at EXACTLY the limit (24 levels). Rule 1: every conformant host MUST decode this; refusing it is non-conformance, not conservatism"
1750
2196
  },
2197
+ {
2198
+ "id": "limit-tree-item-depth-at-max",
2199
+ "kind": "node-round-trip",
2200
+ "decoder": "node",
2201
+ "inputFile": "nodes/limit-tree-item-depth-at-max.json",
2202
+ "expectedFile": "nodes/limit-tree-item-depth-at-max.json",
2203
+ "description": "§21.5 the tree-item axis — a tree nesting rows at EXACTLY the limit (24 levels) inside ONE node. Rule 1: every conformant host MUST decode this. Its past-the-bound twin is `reject-limit-tree-item-depth`"
2204
+ },
1751
2205
  {
1752
2206
  "id": "link-1",
1753
2207
  "kind": "node-round-trip",
@@ -1852,6 +2306,14 @@
1852
2306
  "expectedFile": "nodes/media-audio-1.json",
1853
2307
  "description": "Display/Media (Phase 1076 — the Audio variant, whose payload is the discriminator alone)"
1854
2308
  },
2309
+ {
2310
+ "id": "media-audio-transcript-1",
2311
+ "kind": "node-round-trip",
2312
+ "decoder": "node",
2313
+ "inputFile": "nodes/media-audio-transcript-1.json",
2314
+ "expectedFile": "nodes/media-audio-transcript-1.json",
2315
+ "description": "Display/Media (Phase 1110 — the audio transcript floor)"
2316
+ },
1855
2317
  {
1856
2318
  "id": "media-video-1",
1857
2319
  "kind": "node-round-trip",
@@ -1868,6 +2330,14 @@
1868
2330
  "expectedFile": "nodes/media-video-autoplay-1.json",
1869
2331
  "description": "Display/Media (Phase 1076 — autoplay declared, both shared bools off-default)"
1870
2332
  },
2333
+ {
2334
+ "id": "media-video-captions-1",
2335
+ "kind": "node-round-trip",
2336
+ "decoder": "node",
2337
+ "inputFile": "nodes/media-video-captions-1.json",
2338
+ "expectedFile": "nodes/media-video-captions-1.json",
2339
+ "description": "Display/Media (Phase 1110 — one captions track, elected default)"
2340
+ },
1871
2341
  {
1872
2342
  "id": "media-video-poster-1",
1873
2343
  "kind": "node-round-trip",
@@ -1876,6 +2346,14 @@
1876
2346
  "expectedFile": "nodes/media-video-poster-1.json",
1877
2347
  "description": "Display/Media (Phase 1076 — video with a poster frame in the case payload)"
1878
2348
  },
2349
+ {
2350
+ "id": "media-video-tracks-2",
2351
+ "kind": "node-round-trip",
2352
+ "decoder": "node",
2353
+ "inputFile": "nodes/media-video-tracks-2.json",
2354
+ "expectedFile": "nodes/media-video-tracks-2.json",
2355
+ "description": "Display/Media (Phase 1110 — three tracks, authored order, two default elections)"
2356
+ },
1879
2357
  {
1880
2358
  "id": "metric-1",
1881
2359
  "kind": "node-round-trip",
@@ -1900,6 +2378,14 @@
1900
2378
  "expectedFile": "nodes/metric-float-17sig.json",
1901
2379
  "description": "Display/Metric (float divergence-zone — 17 significant digits)"
1902
2380
  },
2381
+ {
2382
+ "id": "metric-float-1e17",
2383
+ "kind": "node-round-trip",
2384
+ "decoder": "node",
2385
+ "inputFile": "nodes/metric-float-1e17.json",
2386
+ "expectedFile": "nodes/metric-float-1e17.json",
2387
+ "description": "Display/Metric (§2 rule 5 — 1e17, one past the fixed-point window)"
2388
+ },
1903
2389
  {
1904
2390
  "id": "metric-float-bigint",
1905
2391
  "kind": "node-round-trip",
@@ -1980,6 +2466,22 @@
1980
2466
  "expectedFile": "nodes/multiselect-1.json",
1981
2467
  "description": "Input/Select (multi-select — list value)"
1982
2468
  },
2469
+ {
2470
+ "id": "multiselect-chip-list-param",
2471
+ "kind": "node-round-trip",
2472
+ "decoder": "node",
2473
+ "inputFile": "nodes/multiselect-chip-list-param.json",
2474
+ "expectedFile": "nodes/multiselect-chip-list-param.json",
2475
+ "description": "Layout/Box (Phase 610 — multi-select chip + DataGrid scoped by a list-valued in/param)"
2476
+ },
2477
+ {
2478
+ "id": "node-visible",
2479
+ "kind": "node-round-trip",
2480
+ "decoder": "node",
2481
+ "inputFile": "nodes/node-visible.json",
2482
+ "expectedFile": "nodes/node-visible.json",
2483
+ "description": "Node envelope (Phase 1535 — visible: removal from flow, three predicate spellings, beside an aria-hidden sibling)"
2484
+ },
1983
2485
  {
1984
2486
  "id": "now-environment-binding",
1985
2487
  "kind": "node-round-trip",
@@ -1988,6 +2490,30 @@
1988
2490
  "expectedFile": "nodes/now-environment-binding.json",
1989
2491
  "description": "Binding/Now (Phase 765 — the host-furnished instant: a text slot + a Transform param feeding dateDiffDays)"
1990
2492
  },
2493
+ {
2494
+ "id": "now-grain",
2495
+ "kind": "node-round-trip",
2496
+ "decoder": "node",
2497
+ "inputFile": "nodes/now-grain.json",
2498
+ "expectedFile": "nodes/now-grain.json",
2499
+ "description": "Binding/Now (Phase 1533 — the declared grain: minute/hour/day text slots + a Day-grain Transform param)"
2500
+ },
2501
+ {
2502
+ "id": "popover-anchored-1",
2503
+ "kind": "node-round-trip",
2504
+ "decoder": "node",
2505
+ "inputFile": "nodes/popover-anchored-1.json",
2506
+ "expectedFile": "nodes/popover-anchored-1.json",
2507
+ "description": "Layout/Modal (Phase 1119 — Popover modality, anchored, state-bound open)"
2508
+ },
2509
+ {
2510
+ "id": "popover-open-1",
2511
+ "kind": "node-round-trip",
2512
+ "decoder": "node",
2513
+ "inputFile": "nodes/popover-open-1.json",
2514
+ "expectedFile": "nodes/popover-open-1.json",
2515
+ "description": "Layout/Modal (Phase 1119 — Popover, statically open: the SSR floor)"
2516
+ },
1991
2517
  {
1992
2518
  "id": "progress-1",
1993
2519
  "kind": "node-round-trip",
@@ -2084,6 +2610,22 @@
2084
2610
  "expectedFile": "nodes/step-1.json",
2085
2611
  "description": "Layout/Stepper"
2086
2612
  },
2613
+ {
2614
+ "id": "style-direction-isolated-1",
2615
+ "kind": "node-round-trip",
2616
+ "decoder": "node",
2617
+ "inputFile": "nodes/style-direction-isolated-1.json",
2618
+ "expectedFile": "nodes/style-direction-isolated-1.json",
2619
+ "description": "Layout/Box (Phase 1472 an Ltr value isolated inside Rtl prose)"
2620
+ },
2621
+ {
2622
+ "id": "style-direction-ltr-1",
2623
+ "kind": "node-round-trip",
2624
+ "decoder": "node",
2625
+ "inputFile": "nodes/style-direction-ltr-1.json",
2626
+ "expectedFile": "nodes/style-direction-ltr-1.json",
2627
+ "description": "Display/Markdown (Phase 1472 Direction=Ltr, the only style member)"
2628
+ },
2087
2629
  {
2088
2630
  "id": "style-role-voice-1",
2089
2631
  "kind": "node-round-trip",
@@ -2108,6 +2650,14 @@
2108
2650
  "expectedFile": "nodes/switch-1.json",
2109
2651
  "description": "Switch (view state → details/summary cases + info default)"
2110
2652
  },
2653
+ {
2654
+ "id": "switch-carousel-1",
2655
+ "kind": "node-round-trip",
2656
+ "decoder": "node",
2657
+ "inputFile": "nodes/switch-carousel-1.json",
2658
+ "expectedFile": "nodes/switch-carousel-1.json",
2659
+ "description": "Meta/Switch (Phase 1122 — an auto-advancing carousel: `autoAdvanceMs` over an index key)"
2660
+ },
2111
2661
  {
2112
2662
  "id": "switch-on-selection",
2113
2663
  "kind": "node-round-trip",
@@ -2116,6 +2666,30 @@
2116
2666
  "expectedFile": "nodes/switch-on-selection.json",
2117
2667
  "description": "Meta/Switch (Phase 768 — the selector widened: `on` takes a Selection, the branch follows the clicked row)"
2118
2668
  },
2669
+ {
2670
+ "id": "switch-on-transform-scalar",
2671
+ "kind": "node-round-trip",
2672
+ "decoder": "node",
2673
+ "inputFile": "nodes/switch-on-transform-scalar.json",
2674
+ "expectedFile": "nodes/switch-on-transform-scalar.json",
2675
+ "description": "Meta/Switch (Phase 1535 — a computed selector: a Transform yielding one cell, read through the scalar path)"
2676
+ },
2677
+ {
2678
+ "id": "switch-predicate",
2679
+ "kind": "node-round-trip",
2680
+ "decoder": "node",
2681
+ "inputFile": "nodes/switch-predicate.json",
2682
+ "expectedFile": "nodes/switch-predicate.json",
2683
+ "description": "Meta/Switch (Phase 1535 — predicate cases: a State bool and an Expr, MIXED with a match case in one ordered list)"
2684
+ },
2685
+ {
2686
+ "id": "switch-predicate-only",
2687
+ "kind": "node-round-trip",
2688
+ "decoder": "node",
2689
+ "inputFile": "nodes/switch-predicate-only.json",
2690
+ "expectedFile": "nodes/switch-predicate-only.json",
2691
+ "description": "Meta/Switch (Phase 1535 — selected entirely by predicate: no selector, nothing to write)"
2692
+ },
2119
2693
  {
2120
2694
  "id": "table-1",
2121
2695
  "kind": "node-round-trip",
@@ -2157,13 +2731,109 @@
2157
2731
  "description": "Display/Toast (Success tone, open)"
2158
2732
  },
2159
2733
  {
2160
- "id": "upload-1",
2734
+ "id": "tooltip-button-1",
2735
+ "kind": "node-round-trip",
2736
+ "decoder": "node",
2737
+ "inputFile": "nodes/tooltip-button-1.json",
2738
+ "expectedFile": "nodes/tooltip-button-1.json",
2739
+ "description": "Tooltip (Phase 1112 — the trait on a forwarding interactive kind; the description rides the button)"
2740
+ },
2741
+ {
2742
+ "id": "tooltip-icon-button-1",
2743
+ "kind": "node-round-trip",
2744
+ "decoder": "node",
2745
+ "inputFile": "nodes/tooltip-icon-button-1.json",
2746
+ "expectedFile": "nodes/tooltip-icon-button-1.json",
2747
+ "description": "Tooltip (Phase 1112 — the icon-only button: accessibility.label NAMES, tooltip DESCRIBES, describedBy merges)"
2748
+ },
2749
+ {
2750
+ "id": "tooltip-metric-1",
2751
+ "kind": "node-round-trip",
2752
+ "decoder": "node",
2753
+ "inputFile": "nodes/tooltip-metric-1.json",
2754
+ "expectedFile": "nodes/tooltip-metric-1.json",
2755
+ "description": "Tooltip (Phase 1112 — the trait on a display kind, I18n hint; description and focus stop on the wrapper)"
2756
+ },
2757
+ {
2758
+ "id": "transfer-board",
2759
+ "kind": "node-round-trip",
2760
+ "decoder": "node",
2761
+ "inputFile": "nodes/transfer-board.json",
2762
+ "expectedFile": "nodes/transfer-board.json",
2763
+ "description": "Layout/Box (Phase 1123 — cross-container transfer: two two-way columns and a one-way archive on one shared key)"
2764
+ },
2765
+ {
2766
+ "id": "tree-1",
2767
+ "kind": "node-round-trip",
2768
+ "decoder": "node",
2769
+ "inputFile": "nodes/tree-1.json",
2770
+ "expectedFile": "nodes/tree-1.json",
2771
+ "description": "Display/Tree (Phase 1120 — two levels, no State key: static and fully expanded)"
2772
+ },
2773
+ {
2774
+ "id": "tree-expanded-1",
2775
+ "kind": "node-round-trip",
2776
+ "decoder": "node",
2777
+ "inputFile": "nodes/tree-expanded-1.json",
2778
+ "expectedFile": "nodes/tree-expanded-1.json",
2779
+ "description": "Display/Tree (Phase 1120 — three levels, the expansion key named; the key IS the affordance)"
2780
+ },
2781
+ {
2782
+ "id": "tree-selection-1",
2783
+ "kind": "node-round-trip",
2784
+ "decoder": "node",
2785
+ "inputFile": "nodes/tree-selection-1.json",
2786
+ "expectedFile": "nodes/tree-selection-1.json",
2787
+ "description": "Display/Tree (Phase 1120 — the selection key AND the handler, both declared)"
2788
+ },
2789
+ {
2790
+ "id": "upload-1",
2161
2791
  "kind": "node-round-trip",
2162
2792
  "decoder": "node",
2163
2793
  "inputFile": "nodes/upload-1.json",
2164
2794
  "expectedFile": "nodes/upload-1.json",
2165
2795
  "description": "Input/FileUpload"
2166
2796
  },
2797
+ {
2798
+ "id": "upload-capture-camera-1",
2799
+ "kind": "node-round-trip",
2800
+ "decoder": "node",
2801
+ "inputFile": "nodes/upload-capture-camera-1.json",
2802
+ "expectedFile": "nodes/upload-capture-camera-1.json",
2803
+ "description": "Input/FileUpload (Phase 1116 — camera capture, image/* filtered)"
2804
+ },
2805
+ {
2806
+ "id": "upload-capture-microphone-1",
2807
+ "kind": "node-round-trip",
2808
+ "decoder": "node",
2809
+ "inputFile": "nodes/upload-capture-microphone-1.json",
2810
+ "expectedFile": "nodes/upload-capture-microphone-1.json",
2811
+ "description": "Input/FileUpload (Phase 1116 — microphone capture, audio/* filtered)"
2812
+ },
2813
+ {
2814
+ "id": "upload-destination-1",
2815
+ "kind": "node-round-trip",
2816
+ "decoder": "node",
2817
+ "inputFile": "nodes/upload-destination-1.json",
2818
+ "expectedFile": "nodes/upload-destination-1.json",
2819
+ "description": "Input/FileUpload (Phase 1117 — streamed to a registered destination)"
2820
+ },
2821
+ {
2822
+ "id": "upload-drop-1",
2823
+ "kind": "node-round-trip",
2824
+ "decoder": "node",
2825
+ "inputFile": "nodes/upload-drop-1.json",
2826
+ "expectedFile": "nodes/upload-drop-1.json",
2827
+ "description": "Input/FileUpload (Phase 1115 — drop target; acceptPaste OMITTED)"
2828
+ },
2829
+ {
2830
+ "id": "upload-paste-1",
2831
+ "kind": "node-round-trip",
2832
+ "decoder": "node",
2833
+ "inputFile": "nodes/upload-paste-1.json",
2834
+ "expectedFile": "nodes/upload-paste-1.json",
2835
+ "description": "Input/FileUpload (Phase 1115 — paste ingestion, image/* filtered; dropTarget OMITTED)"
2836
+ },
2167
2837
  {
2168
2838
  "id": "op-batch",
2169
2839
  "kind": "op-round-trip",
@@ -2340,6 +3010,14 @@
2340
3010
  "expectedFile": "ops/op-updatestyle (phase 147 role=eyebrow + voice=structural).json",
2341
3011
  "description": "UpdateStyle (Phase 147 Role=Eyebrow + Voice=Structural)"
2342
3012
  },
3013
+ {
3014
+ "id": "op-updatestyle (phase 1472 direction=ltr, the only style member)",
3015
+ "kind": "op-round-trip",
3016
+ "decoder": "op",
3017
+ "inputFile": "ops/op-updatestyle (phase 1472 direction=ltr, the only style member).json",
3018
+ "expectedFile": "ops/op-updatestyle (phase 1472 direction=ltr, the only style member).json",
3019
+ "description": "UpdateStyle (Phase 1472 Direction=Ltr, the only style member)"
3020
+ },
2343
3021
  {
2344
3022
  "id": "reject-a11y-hidden-nonbool",
2345
3023
  "kind": "reject",
@@ -2394,6 +3072,15 @@
2394
3072
  "expectedPath": "$.accessibility.role",
2395
3073
  "description": "accessibility.role not a JSON string — the slot is open to any role NAME, not to any value (Phase 955)"
2396
3074
  },
3075
+ {
3076
+ "id": "reject-action-print-with-payload",
3077
+ "kind": "reject",
3078
+ "decoder": "node",
3079
+ "inputFile": "reject/reject-action-print-with-payload.json",
3080
+ "expectedErrorCode": "WRONG_TYPE",
3081
+ "expectedPath": "$.kind.onClick.pageRange",
3082
+ "description": "Print carrying a member — the payload-free action takes none; page range, size, margins and copies are the host's page setup and the reader's dialogue, so a member here is refused rather than dropped (Phase 1124)"
3083
+ },
2397
3084
  {
2398
3085
  "id": "reject-binding-float-bool",
2399
3086
  "kind": "reject",
@@ -2421,6 +3108,15 @@
2421
3108
  "expectedPath": "$.kind.value",
2422
3109
  "description": "Binding<float> Static payload is a non-sentinel string — §7's float accept set is JSON number plus exactly \"NaN\" / \"Infinity\" / \"-Infinity\" (Phase 1064)"
2423
3110
  },
3111
+ {
3112
+ "id": "reject-binding-floatseq-sentinel-case",
3113
+ "kind": "reject",
3114
+ "decoder": "node",
3115
+ "inputFile": "reject/reject-binding-floatseq-sentinel-case.json",
3116
+ "expectedErrorCode": "WRONG_TYPE",
3117
+ "expectedPath": "$.kind.source",
3118
+ "description": "Binding<float seq> ELEMENT is a MIS-CASED sentinel (\"nan\") — the sequence twin of reject-binding-float-sentinel-case: §7's three tokens are exact at an element too, so a host that routes elements through a looser reader than its scalar slots is caught (Phase 1099)"
3119
+ },
2424
3120
  {
2425
3121
  "id": "reject-binding-int-bool",
2426
3122
  "kind": "reject",
@@ -2448,6 +3144,69 @@
2448
3144
  "expectedPath": "$.kind.layout.cols",
2449
3145
  "description": "Masonry cols 0 — refused: a column count names how many columns the children fill, so a non-positive one describes a layout no renderer can realise. Refused rather than canonicalised to `Auto`, unlike a column-less `Grid`: that leniency works because `Auto` already means the browser's own choice, and masonry has no such case for the rewrite to land on"
2450
3146
  },
3147
+ {
3148
+ "id": "reject-chart-annotation-date-unparseable",
3149
+ "kind": "reject",
3150
+ "decoder": "node",
3151
+ "inputFile": "reject/reject-chart-annotation-date-unparseable.json",
3152
+ "expectedErrorCode": "WRONG_TYPE",
3153
+ "expectedPath": "$.kind.annotations[0].at.iso",
3154
+ "description": "chart event marker addressing a date that names no calendar day — the address is what the marker is drawn at, and an unreadable one would place it at 1970-01-01 and drag the axis back with it (Phase 1491)"
3155
+ },
3156
+ {
3157
+ "id": "reject-chart-annotation-nonfinite",
3158
+ "kind": "reject",
3159
+ "decoder": "node",
3160
+ "inputFile": "reject/reject-chart-annotation-nonfinite.json",
3161
+ "expectedErrorCode": "WRONG_TYPE",
3162
+ "expectedPath": "$.kind.annotations[0].value",
3163
+ "description": "chart annotation carrying the §7 NaN sentinel — the value slot is narrowed to the finite numbers (Phase 1490)"
3164
+ },
3165
+ {
3166
+ "id": "reject-chart-annotation-range-unordered",
3167
+ "kind": "reject",
3168
+ "decoder": "node",
3169
+ "inputFile": "reject/reject-chart-annotation-range-unordered.json",
3170
+ "expectedErrorCode": "WRONG_TYPE",
3171
+ "expectedPath": "$.kind.annotations[0].range",
3172
+ "description": "chart range band whose pair runs from its upper value to its lower one — a band names an interval, and swapping the ends silently would draw one the author did not describe (Phase 1492)"
3173
+ },
3174
+ {
3175
+ "id": "reject-color-value-not-hex",
3176
+ "kind": "reject",
3177
+ "decoder": "node",
3178
+ "inputFile": "reject/reject-color-value-not-hex.json",
3179
+ "expectedErrorCode": "WRONG_TYPE",
3180
+ "expectedPath": "$.kind.fields[0].kind.value",
3181
+ "description": "a Color `value` of `\"rebeccapurple\"` — refused rather than coerced. `#rrggbb` is the one shape a native colour input can hold, so a literal outside it names a colour this control could never carry, and a host that narrowed it would show a colour the document did not choose. Only the STATIC case is judged, because it is the only text a decoder has: a bound value is re-checked by the pre-emit validator for an author and by the server-side submission floor for a client, which is one rule checked wherever the value becomes visible"
3182
+ },
3183
+ {
3184
+ "id": "reject-combobox-allowfreetext-nonbool",
3185
+ "kind": "reject",
3186
+ "decoder": "node",
3187
+ "inputFile": "reject/reject-combobox-allowfreetext-nonbool.json",
3188
+ "expectedErrorCode": "WRONG_TYPE",
3189
+ "expectedPath": "$.kind.fields[0].kind.allowFreeText",
3190
+ "description": "a Combobox `allowFreeText` of `\"yes\"` — refused rather than coerced. The slot decides whether values OUTSIDE the option set are admitted, so a lenient truthiness read is a constraint silently relaxed by a string: `\"yes\"`, `\"no\"` and `\"false\"` are all non-empty, and a host that coerced them would widen the field on two of the three. It also omits at `false`, so absence already spells the safe answer and a wrong-typed present value can only mean the emitter meant something it did not say"
3191
+ },
3192
+ {
3193
+ "id": "reject-confirm-missing-onconfirm",
3194
+ "kind": "reject",
3195
+ "decoder": "node",
3196
+ "inputFile": "reject/reject-confirm-missing-onconfirm.json",
3197
+ "expectedErrorCode": "MISSING_FIELD",
3198
+ "expectedPath": "$.kind.onClick",
3199
+ "description": "Confirm with no 'onConfirm' — a question with no answer. The other two members are absent-legal (`onCancel`) or shorthand-legal (`prompt` as a bare string), so this vector is what keeps an emitter from generalising 'optional' across the case (Phase 1537)"
3200
+ },
3201
+ {
3202
+ "id": "reject-confirm-nested",
3203
+ "kind": "reject",
3204
+ "decoder": "node",
3205
+ "inputFile": "reject/reject-confirm-nested.json",
3206
+ "expectedErrorCode": "WRONG_TYPE",
3207
+ "expectedPath": "$.kind.onClick.onConfirm.ops[1]",
3208
+ "description": "A Confirm inside a Confirm's continuation, reached through a Chain — confirmation is bounded at one question, and the check walks the DECODED continuation so a chain cannot hide the nesting (Phase 1537)"
3209
+ },
2451
3210
  {
2452
3211
  "id": "reject-daterange-unordered",
2453
3212
  "kind": "reject",
@@ -2457,6 +3216,33 @@
2457
3216
  "expectedPath": "$.kind.fields[0].kind.value",
2458
3217
  "description": "DateRange literal pair with start after end — the ordered-pair rule (Phase 725)"
2459
3218
  },
3219
+ {
3220
+ "id": "reject-embed-missing-title",
3221
+ "kind": "reject",
3222
+ "decoder": "node",
3223
+ "inputFile": "reject/reject-embed-missing-title.json",
3224
+ "expectedErrorCode": "MISSING_FIELD",
3225
+ "expectedPath": "$.kind.title",
3226
+ "description": "Embed with no `title` — refused. The accessible name is mandatory because a browsing context has no decorative case, and there is no value to default to that would not be a fabricated description of someone else's page"
3227
+ },
3228
+ {
3229
+ "id": "reject-embed-permission-nonstring",
3230
+ "kind": "reject",
3231
+ "decoder": "node",
3232
+ "inputFile": "reject/reject-embed-permission-nonstring.json",
3233
+ "expectedErrorCode": "WRONG_TYPE",
3234
+ "expectedPath": "$.kind.permissions[0]",
3235
+ "description": "an Embed permission of `true` — refused rather than coerced. A host that read a bare boolean as a granted permission would have to invent which one it names, and every answer is a relaxation the document never spelled"
3236
+ },
3237
+ {
3238
+ "id": "reject-embed-unknown-permission",
3239
+ "kind": "reject",
3240
+ "decoder": "node",
3241
+ "inputFile": "reject/reject-embed-unknown-permission.json",
3242
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3243
+ "expectedPath": "$.kind.permissions[0]",
3244
+ "description": "EmbedPermission 'allow-top-navigation' — refused. The set is closed at four, and top-level navigation is excluded by design rather than reserved; the path carries the array index and no `.$type`, because the slot holds bare enum strings"
3245
+ },
2460
3246
  {
2461
3247
  "id": "reject-emptynodeid",
2462
3248
  "kind": "reject",
@@ -2466,6 +3252,24 @@
2466
3252
  "expectedPath": "$.id",
2467
3253
  "description": "Empty top-level id"
2468
3254
  },
3255
+ {
3256
+ "id": "reject-expr-col-reference",
3257
+ "kind": "reject",
3258
+ "decoder": "node",
3259
+ "inputFile": "reject/reject-expr-col-reference.json",
3260
+ "expectedErrorCode": "WRONG_TYPE",
3261
+ "expectedPath": "$.kind.text.binding.expr",
3262
+ "description": "a `col` reference inside a Binding.Expr — an Expr evaluates against its params alone and has no row for a column name to read, so the reference can never resolve. The remedy is a different BINDING, not a different spelling: Binding.Transform supplies the frame `col` reads from. Refused at decode rather than left to fail once per render on every host (Phase 1534)"
3263
+ },
3264
+ {
3265
+ "id": "reject-expr-unbound-param",
3266
+ "kind": "reject",
3267
+ "decoder": "node",
3268
+ "inputFile": "reject/reject-expr-unbound-param.json",
3269
+ "expectedErrorCode": "WRONG_TYPE",
3270
+ "expectedPath": "$.kind.text.binding.expr",
3271
+ "description": "a Binding.Expr whose expression reads `param quantity` while its own `params` binds only `unitPrice`. Unlike Binding.Transform — where an unbound filter param prunes its step, the deliberate unset-chip leniency — an Expr has no step to prune and no rows to fall back on, so the reference has no value it could ever take. Refused at decode, where the missing name can be named (Phase 1534)"
3272
+ },
2469
3273
  {
2470
3274
  "id": "reject-fieldrule-empty",
2471
3275
  "kind": "reject",
@@ -2520,6 +3324,24 @@
2520
3324
  "expectedPath": "$.kind.srcSet",
2521
3325
  "description": "Image srcSet `null` — refused. Absent means the EMPTY LIST (the missing-list-field decode class); a present null is a second spelling for an absence that already has one, and admitting it would let two hosts disagree about the canonical bytes of the same document"
2522
3326
  },
3327
+ {
3328
+ "id": "reject-int-slot-fractional",
3329
+ "kind": "reject",
3330
+ "decoder": "node",
3331
+ "inputFile": "reject/reject-int-slot-fractional.json",
3332
+ "expectedErrorCode": "WRONG_TYPE",
3333
+ "expectedPath": "$.kind.rows",
3334
+ "description": "§7.1 — a fractional value at an integer slot. Three hosts TRUNCATED it to 2, discarding the author's value at a slot the author typed; two refused it. §7.1 refuses, and accepts `2.0` (see the lenient twin). Twin: the `skel-1` node fixture"
3335
+ },
3336
+ {
3337
+ "id": "reject-int-slot-out-of-range",
3338
+ "kind": "reject",
3339
+ "decoder": "node",
3340
+ "inputFile": "reject/reject-int-slot-out-of-range.json",
3341
+ "expectedErrorCode": "WRONG_TYPE",
3342
+ "expectedPath": "$.kind.rows",
3343
+ "description": "§7.1 — a value outside the 32-bit range an integer slot can hold. The cast was implementation-defined: the same bytes became Int32.MinValue on one runtime and 1410065408 on another, which is §20's defect one layer above the syntax. Twin: the `skel-1` node fixture"
3344
+ },
2523
3345
  {
2524
3346
  "id": "reject-invalid-empty",
2525
3347
  "kind": "reject",
@@ -2547,6 +3369,114 @@
2547
3369
  "expectedPath": "$",
2548
3370
  "description": "truncated object"
2549
3371
  },
3372
+ {
3373
+ "id": "reject-json-bare-nan",
3374
+ "kind": "reject",
3375
+ "decoder": "node",
3376
+ "inputFile": "reject/reject-json-bare-nan.json",
3377
+ "expectedErrorCode": "INVALID_JSON",
3378
+ "expectedPath": "$",
3379
+ "description": "§20.2 row 4 — a bare `NaN` literal. RFC 8259 forbids it as a bare token and §7's QUOTED sentinels are the specified representation, which row 8 accepts unchanged; one host accepted the bare form and re-emitted it. Twin: the `markdown-1` node fixture (drop the trailing member)"
3380
+ },
3381
+ {
3382
+ "id": "reject-json-duplicate-key",
3383
+ "kind": "reject",
3384
+ "decoder": "node",
3385
+ "inputFile": "reject/reject-json-duplicate-key.json",
3386
+ "expectedErrorCode": "INVALID_JSON",
3387
+ "expectedPath": "$",
3388
+ "description": "§20.2 row 1 — a repeated object member. THE row that changes what a document MEANS rather than whether it is accepted: this host kept the first occurrence and every other host the last, so a vetting host and a rendering host saw different trees from identical bytes with no error anywhere. Twin: the `markdown-1` node fixture (drop the second `id`)"
3389
+ },
3390
+ {
3391
+ "id": "reject-json-lone-high-surrogate",
3392
+ "kind": "reject",
3393
+ "decoder": "node",
3394
+ "inputFile": "reject/reject-json-lone-high-surrogate.json",
3395
+ "expectedErrorCode": "INVALID_JSON",
3396
+ "expectedPath": "$",
3397
+ "description": "§20.2 row 6 — an unpaired HIGH surrogate. The second row that changes MEANING silently: two hosts lowered it to U+FFFD, two kept it in a UTF-16 string, and one kept it and then raised an uncatchable encoding error at the first canonical-bytes boundary rather than at decode. Twin: the `markdown-1` node fixture (drop the escape)"
3398
+ },
3399
+ {
3400
+ "id": "reject-json-lone-low-surrogate",
3401
+ "kind": "reject",
3402
+ "decoder": "node",
3403
+ "inputFile": "reject/reject-json-lone-low-surrogate.json",
3404
+ "expectedErrorCode": "INVALID_JSON",
3405
+ "expectedPath": "$",
3406
+ "description": "§20.2 row 6 — an unpaired LOW surrogate. Pinned separately from its high twin because a pairing check written as \"a high must be followed by a low\" passes this vector while leaving the class open. Twin: the `markdown-1` node fixture (drop the escape)"
3407
+ },
3408
+ {
3409
+ "id": "reject-json-number-empty-exponent",
3410
+ "kind": "reject",
3411
+ "decoder": "node",
3412
+ "inputFile": "reject/reject-json-number-empty-exponent.json",
3413
+ "expectedErrorCode": "INVALID_JSON",
3414
+ "expectedPath": "$",
3415
+ "description": "§20.2 row 3 — an exponent marker with no digits. Twin: the `skel-1` node fixture (drop the `e`)"
3416
+ },
3417
+ {
3418
+ "id": "reject-json-number-leading-plus",
3419
+ "kind": "reject",
3420
+ "decoder": "node",
3421
+ "inputFile": "reject/reject-json-number-leading-plus.json",
3422
+ "expectedErrorCode": "INVALID_JSON",
3423
+ "expectedPath": "$",
3424
+ "description": "§20.2 row 3 — a leading `+` on a number. RFC 8259 does not permit it; three hosts accepted it because they reached a platform number parser without checking the grammar first. Twin: the `skel-1` node fixture (drop the `+`)"
3425
+ },
3426
+ {
3427
+ "id": "reject-json-number-leading-zero",
3428
+ "kind": "reject",
3429
+ "decoder": "node",
3430
+ "inputFile": "reject/reject-json-number-leading-zero.json",
3431
+ "expectedErrorCode": "INVALID_JSON",
3432
+ "expectedPath": "$",
3433
+ "description": "§20.2 row 3 — a leading zero. RFC 8259's `int` production is `0` or a non-zero digit followed by digits. Twin: the `skel-1` node fixture (drop the leading zero)"
3434
+ },
3435
+ {
3436
+ "id": "reject-json-number-no-integer-part",
3437
+ "kind": "reject",
3438
+ "decoder": "node",
3439
+ "inputFile": "reject/reject-json-number-no-integer-part.json",
3440
+ "expectedErrorCode": "INVALID_JSON",
3441
+ "expectedPath": "$",
3442
+ "description": "§20.2 row 3 — a number with no integer part. Refused as a GRAMMAR violation before the slot's type is ever consulted, which is why the vector sits at a slot that would refuse a number anyway: the point is that `.5` is not a number at all. Twin: the `markdown-1` node fixture (drop the trailing member)"
3443
+ },
3444
+ {
3445
+ "id": "reject-json-number-trailing-point",
3446
+ "kind": "reject",
3447
+ "decoder": "node",
3448
+ "inputFile": "reject/reject-json-number-trailing-point.json",
3449
+ "expectedErrorCode": "INVALID_JSON",
3450
+ "expectedPath": "$",
3451
+ "description": "§20.2 row 3 — a decimal point with no fractional digits. Twin: the `skel-1` node fixture (drop the point)"
3452
+ },
3453
+ {
3454
+ "id": "reject-json-raw-control-char",
3455
+ "kind": "reject",
3456
+ "decoder": "node",
3457
+ "inputFile": "reject/reject-json-raw-control-char.json",
3458
+ "expectedErrorCode": "INVALID_JSON",
3459
+ "expectedPath": "$",
3460
+ "description": "§20.2 row 5 — a RAW U+0009 inside a string. RFC 8259 requires a C0 control character to be escaped and §2 rule 6 requires a conformant encoder to escape it, so accepting the raw byte admits input this host's own encoder cannot produce. The escaped spelling stays legal. Twin: the `markdown-1` node fixture (the tab escaped, or a space)"
3461
+ },
3462
+ {
3463
+ "id": "reject-json-surrogate-pair-split",
3464
+ "kind": "reject",
3465
+ "decoder": "node",
3466
+ "inputFile": "reject/reject-json-surrogate-pair-split.json",
3467
+ "expectedErrorCode": "INVALID_JSON",
3468
+ "expectedPath": "$",
3469
+ "description": "§20.2 row 6 — both halves of a pair present but separated. A host that counts surrogates rather than pairing them adjacently accepts this and reassembles a scalar the author never wrote. Twin: the `markdown-1` node fixture"
3470
+ },
3471
+ {
3472
+ "id": "reject-json-trailing-content",
3473
+ "kind": "reject",
3474
+ "decoder": "node",
3475
+ "inputFile": "reject/reject-json-trailing-content.json",
3476
+ "expectedErrorCode": "INVALID_JSON",
3477
+ "expectedPath": "$",
3478
+ "description": "§20.2 row 2 — content after the root value. §1 makes a wire artefact a single JSON document; two hosts refused this and three accepted it, which is a framing ambiguity rather than a tolerance. Twin: the `markdown-1` node fixture (drop everything after the root object)"
3479
+ },
2550
3480
  {
2551
3481
  "id": "reject-limit-json-depth",
2552
3482
  "kind": "reject",
@@ -2583,6 +3513,33 @@
2583
3513
  "expectedPath": "$",
2584
3514
  "description": "§21.5 the op axis — 25 nested TreeOp.Batch. Counted separately from the node axis; the syntactic bound only LOOKS like cover for it"
2585
3515
  },
3516
+ {
3517
+ "id": "reject-limit-tree-item-depth",
3518
+ "kind": "reject",
3519
+ "decoder": "node",
3520
+ "inputFile": "reject/reject-limit-tree-item-depth.json",
3521
+ "expectedErrorCode": "LIMIT_EXCEEDED",
3522
+ "expectedPath": "$.kind.items[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0]",
3523
+ "description": "§21.5 the tree-item axis — 25 nested TreeItem rows inside ONE node. Counted separately from the node axis, which cannot see it at all (the whole hierarchy is one node) and from the syntactic bound, which at ~50 levels is nowhere near reached. The `TreeOp.Batch` lesson on a third axis"
3524
+ },
3525
+ {
3526
+ "id": "reject-local-codec-no-inverse",
3527
+ "kind": "reject",
3528
+ "decoder": "node",
3529
+ "inputFile": "reject/reject-local-codec-no-inverse.json",
3530
+ "expectedErrorCode": "WRONG_TYPE",
3531
+ "expectedPath": "$.kind.fields[0].kind.value.codec",
3532
+ "description": "a Binding.Local declaring a `Currency` codec. `Binding.Format` carries a LocaleSource because it renders for reading; a Local codec carries none, because whatever it renders it must be able to parse back from the reader's keystrokes — and a currency symbol, a locale decimal mark and a grouping separator are all chosen by the reader's locale, not by the document. `Number` is the one case with a total, locale-independent inverse. Refused at decode rather than left to format one way and parse another (Fuaran-UI Phase 1538)"
3533
+ },
3534
+ {
3535
+ "id": "reject-local-oncommit-and-committo",
3536
+ "kind": "reject",
3537
+ "decoder": "node",
3538
+ "inputFile": "reject/reject-local-oncommit-and-committo.json",
3539
+ "expectedErrorCode": "WRONG_TYPE",
3540
+ "expectedPath": "$.kind.fields[0].kind.value.commitTo",
3541
+ "description": "a Binding.Local carrying BOTH an `onCommit` closure sentinel and a declared `commitTo` key. The SetState value/valueFrom law, applied to the buffer's flush: exactly one commit destination. A decoding host can honour only the declared one — the closure is `\"<closure>\"` and nothing more — so admitting the pair would make the same bytes commit to two different places depending on who read them (Fuaran-UI Phase 1538)"
3542
+ },
2586
3543
  {
2587
3544
  "id": "reject-media-autoplay-nonbool",
2588
3545
  "kind": "reject",
@@ -2601,6 +3558,24 @@
2601
3558
  "expectedPath": "$.kind.label",
2602
3559
  "description": "Media with no `label` — refused. The accessible name is mandatory because a transport has no decorative case, and there is no value to default to that would not be a fabricated name for someone else's recording"
2603
3560
  },
3561
+ {
3562
+ "id": "reject-media-track-default-nonbool",
3563
+ "kind": "reject",
3564
+ "decoder": "node",
3565
+ "inputFile": "reject/reject-media-track-default-nonbool.json",
3566
+ "expectedErrorCode": "WRONG_TYPE",
3567
+ "expectedPath": "$.kind.tracks[0].default",
3568
+ "description": "a track `default` of `\"true\"` — the stringified boolean, refused rather than coerced. Two hosts ruling differently on truthiness would disagree about which caption track opens, which is a difference the reader meets on the first frame"
3569
+ },
3570
+ {
3571
+ "id": "reject-media-track-missing-srclang",
3572
+ "kind": "reject",
3573
+ "decoder": "node",
3574
+ "inputFile": "reject/reject-media-track-missing-srclang.json",
3575
+ "expectedErrorCode": "MISSING_FIELD",
3576
+ "expectedPath": "$.kind.tracks[0].srcLang",
3577
+ "description": "a text track with no `srcLang` — refused. The language tag is what orders a track menu, drives pronunciation and tells two same-labelled tracks apart; there is no value to default to that would not be an invented claim about someone else's recording. The path carries the ARRAY INDEX, so a document with four tracks names the one at fault"
3578
+ },
2604
3579
  {
2605
3580
  "id": "reject-missing-binding-type",
2606
3581
  "kind": "reject",
@@ -2691,6 +3666,33 @@
2691
3666
  "expectedPath": "$.kind.stateKey",
2692
3667
  "description": "Switch missing stateKey"
2693
3668
  },
3669
+ {
3670
+ "id": "reject-modal-modality-nonstring",
3671
+ "kind": "reject",
3672
+ "decoder": "node",
3673
+ "inputFile": "reject/reject-modal-modality-nonstring.json",
3674
+ "expectedErrorCode": "WRONG_TYPE",
3675
+ "expectedPath": "$.kind.modality",
3676
+ "description": "a Modal `modality` of `3` — a separate decoder arm from the unknown-string vector above, and vectored separately for that reason. An ordinal is the shape a host generating from an enum's index is likeliest to emit, and reading one would make the wire's meaning depend on a declaration order the wire never carries"
3677
+ },
3678
+ {
3679
+ "id": "reject-modal-modality-unknown",
3680
+ "kind": "reject",
3681
+ "decoder": "node",
3682
+ "inputFile": "reject/reject-modal-modality-unknown.json",
3683
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3684
+ "expectedPath": "$.kind.modality",
3685
+ "description": "a Modal `modality` of `\"Sheet\"` — outside the closed pair `Modal | Popover`. A sheet, a drawer and a menu are all PRESENTATIONS of one of the two modalities rather than a third one, so the answer to this document is not to widen the enum but to say which of the two it meant. The path is the bare slot, per §6 and the Phase 1073 ruling: a bare enum carries no discriminator on the wire, so there is no `.$type` to name"
3686
+ },
3687
+ {
3688
+ "id": "reject-navigate-target-invalid",
3689
+ "kind": "reject",
3690
+ "decoder": "node",
3691
+ "inputFile": "reject/reject-navigate-target-invalid.json",
3692
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3693
+ "expectedPath": "$.kind.onClick.target",
3694
+ "description": "Action.Navigate's target is the closed enum Self | Blank, not HTML's target attribute. `_blank` is refused rather than aliased: the vocabulary it comes from also contains `_parent` and `_top`, which are frame-busting gestures a hosted tree must not be able to ask for, so accepting any of it would teach an emitter the wrong vocabulary (Phase 1536)"
3695
+ },
2694
3696
  {
2695
3697
  "id": "reject-nearmiss-a11y-aria-hidden",
2696
3698
  "kind": "reject",
@@ -2763,6 +3765,15 @@
2763
3765
  "expectedPath": "$.kind.sortable",
2764
3766
  "description": "grid-level 'sortable' — the staticRows spelling reached for on the bound path, where sortability is sortStateKey + per-column narrowing (Phase 863)"
2765
3767
  },
3768
+ {
3769
+ "id": "reject-now-grain-invalid",
3770
+ "kind": "reject",
3771
+ "decoder": "node",
3772
+ "inputFile": "reject/reject-now-grain-invalid.json",
3773
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3774
+ "expectedPath": "$.kind.value.binding.grain",
3775
+ "description": "Binding.Now's grain is Second | Minute | Hour | Day — `Week` is a RelativeTimeUnit member and not a TimeGrain one. The two vocabularies are deliberately different sizes: a grain truncates a calendar instant, and a week, a month and a year have no truncation every host agrees on. A host that accepted this and rendered at day grain would be answering a question the document did not ask (Phase 1533)"
3776
+ },
2766
3777
  {
2767
3778
  "id": "reject-null-action-aitool-args",
2768
3779
  "kind": "reject",
@@ -2889,6 +3900,33 @@
2889
3900
  "expectedPath": "$.path",
2890
3901
  "description": "UpdateProp missing path"
2891
3902
  },
3903
+ {
3904
+ "id": "reject-op-updatestyle-direction-nonstring",
3905
+ "kind": "reject",
3906
+ "decoder": "op",
3907
+ "inputFile": "reject/reject-op-updatestyle-direction-nonstring.json",
3908
+ "expectedErrorCode": "WRONG_TYPE",
3909
+ "expectedPath": "$.style.direction",
3910
+ "description": "UpdateStyle style.direction not a JSON string (Phase 1472)"
3911
+ },
3912
+ {
3913
+ "id": "reject-op-updatestyle-direction-unknown",
3914
+ "kind": "reject",
3915
+ "decoder": "op",
3916
+ "inputFile": "reject/reject-op-updatestyle-direction-unknown.json",
3917
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3918
+ "expectedPath": "$.style.direction",
3919
+ "description": "UpdateStyle style.direction outside the closed set — the op arm's twin of the node vector above (Phase 1472)"
3920
+ },
3921
+ {
3922
+ "id": "reject-rating-max-zero",
3923
+ "kind": "reject",
3924
+ "decoder": "node",
3925
+ "inputFile": "reject/reject-rating-max-zero.json",
3926
+ "expectedErrorCode": "WRONG_TYPE",
3927
+ "expectedPath": "$.kind.fields[0].kind.max",
3928
+ "description": "a Rating `max` of `0` — refused rather than clamped. This is not a control with a bad number in it: a scale with no positions has nothing to draw, no `aria-valuemax` to announce and no keystroke that could change anything, so the document names a control that cannot exist. Note the asymmetry this vector pins: the SCALE is refused here and the VALUE is not, because a bound value is invisible to a decoder and a rule enforced only on literals would be two rules wearing one name"
3929
+ },
2892
3930
  {
2893
3931
  "id": "reject-setstate-value-and-valuefrom",
2894
3932
  "kind": "reject",
@@ -2898,6 +3936,96 @@
2898
3936
  "expectedPath": "$.kind.onClick.valueFrom",
2899
3937
  "description": "SetState carrying BOTH 'value' and 'valueFrom' — exactly one is allowed; the didactic names both fields and how each is used (Phase 818)"
2900
3938
  },
3939
+ {
3940
+ "id": "reject-spark-element-nonnumeric",
3941
+ "kind": "reject",
3942
+ "decoder": "node",
3943
+ "inputFile": "reject/reject-spark-element-nonnumeric.json",
3944
+ "expectedErrorCode": "WRONG_TYPE",
3945
+ "expectedPath": "$.kind.source",
3946
+ "description": "A Sparkline series element of \"lots\" — a typed float SEQUENCE slot types its ELEMENTS, not just the binding. spark-nonfinite-sentinel proves the §7 sentinels are accepted, which an entirely untyped slot also does by never reading an element, so this is the vector that separates the two (Phase 1099)"
3947
+ },
3948
+ {
3949
+ "id": "reject-style-direction-nonstring",
3950
+ "kind": "reject",
3951
+ "decoder": "node",
3952
+ "inputFile": "reject/reject-style-direction-nonstring.json",
3953
+ "expectedErrorCode": "WRONG_TYPE",
3954
+ "expectedPath": "$.style.direction",
3955
+ "description": "style.direction not a JSON string (Phase 1472)"
3956
+ },
3957
+ {
3958
+ "id": "reject-style-direction-unknown",
3959
+ "kind": "reject",
3960
+ "decoder": "node",
3961
+ "inputFile": "reject/reject-style-direction-unknown.json",
3962
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3963
+ "expectedPath": "$.style.direction",
3964
+ "description": "style.direction outside the closed set — the message names auto | ltr | rtl, and the near miss is the upper-case spelling (Phase 1472)"
3965
+ },
3966
+ {
3967
+ "id": "reject-switch-autoadvance-fractional",
3968
+ "kind": "reject",
3969
+ "decoder": "node",
3970
+ "inputFile": "reject/reject-switch-autoadvance-fractional.json",
3971
+ "expectedErrorCode": "WRONG_TYPE",
3972
+ "expectedPath": "$.kind.autoAdvanceMs",
3973
+ "description": "Switch autoAdvanceMs is fractional — the slot is an integer count of milliseconds, and a decoder truncating where another rounded would leave two hosts disagreeing about a document neither refused (Phase 1122)"
3974
+ },
3975
+ {
3976
+ "id": "reject-switch-autoadvance-negative",
3977
+ "kind": "reject",
3978
+ "decoder": "node",
3979
+ "inputFile": "reject/reject-switch-autoadvance-negative.json",
3980
+ "expectedErrorCode": "WRONG_TYPE",
3981
+ "expectedPath": "$.kind.autoAdvanceMs",
3982
+ "description": "Switch autoAdvanceMs is negative — an interval names how long to wait, so a negative one describes a schedule no renderer can realise (Phase 1122)"
3983
+ },
3984
+ {
3985
+ "id": "reject-switch-autoadvance-zero",
3986
+ "kind": "reject",
3987
+ "decoder": "node",
3988
+ "inputFile": "reject/reject-switch-autoadvance-zero.json",
3989
+ "expectedErrorCode": "WRONG_TYPE",
3990
+ "expectedPath": "$.kind.autoAdvanceMs",
3991
+ "description": "Switch autoAdvanceMs is 0 — refused, never canonicalised to absence. An absent key is already the only spelling of \"does not advance\", so accepting a zero would make two shapes mean one thing and hide the emitter's misreading of the slot (Phase 1122)"
3992
+ },
3993
+ {
3994
+ "id": "reject-switch-case-match-and-when",
3995
+ "kind": "reject",
3996
+ "decoder": "node",
3997
+ "inputFile": "reject/reject-switch-case-match-and-when.json",
3998
+ "expectedErrorCode": "WRONG_TYPE",
3999
+ "expectedPath": "$.kind.cases[0].when",
4000
+ "description": "a Switch case carrying both `match` and `when` — the two ways of selecting a case, in one case. `match` compares the switch's `on` selector against a literal; `when` evaluates a Binding<bool> and consults no selector at all. Refused rather than resolved by precedence: a precedence rule would need specifying, agreeing on five hosts and remembering, for a document nobody meant to write (Phase 1535)"
4001
+ },
4002
+ {
4003
+ "id": "reject-switch-case-neither",
4004
+ "kind": "reject",
4005
+ "decoder": "node",
4006
+ "inputFile": "reject/reject-switch-case-neither.json",
4007
+ "expectedErrorCode": "MISSING_FIELD",
4008
+ "expectedPath": "$.kind.cases[0].match",
4009
+ "description": "a Switch case carrying neither `match` nor `when` — no condition, so no rendering could be right. Refused at decode rather than skipped silently at render: skipping it renders the `default` and reports nothing, which is the exact class of silence this phase's finding was filed about. The message names both fields, so the author is told which two spellings exist (Phase 1535)"
4010
+ },
4011
+ {
4012
+ "id": "reject-tokens-closed-without-suggestions",
4013
+ "kind": "reject",
4014
+ "decoder": "node",
4015
+ "inputFile": "reject/reject-tokens-closed-without-suggestions.json",
4016
+ "expectedErrorCode": "WRONG_TYPE",
4017
+ "expectedPath": "$.kind.fields[0].kind.allowFreeText",
4018
+ "description": "a Tokens field admitting no free text and declaring NO suggestion source — refused. No gesture could ever put a token into it: this is not a control with a bad value in it but a document naming a control that cannot exist, the same line `Rating`'s `max` of zero is refused on. Note the polarity that makes the refusal fair rather than hostile: `allowFreeText` omits at TRUE on this case (unlike `Combobox`, whose option source is required), so the shortest document `{\"$type\":\"Tokens\"}` is the plain open token box and passes — only an emitter that wrote `false` and then omitted the suggestions lands here. Note also what is NOT refused anywhere: duplicate tokens and membership of a token in the suggestion set are properties of the VALUE, invisible to a decoder whenever the slot is bound, and are held by the pre-emit validator and the server-side submission floor instead"
4019
+ },
4020
+ {
4021
+ "id": "reject-tokens-value-not-list",
4022
+ "kind": "reject",
4023
+ "decoder": "node",
4024
+ "inputFile": "reject/reject-tokens-value-not-list.json",
4025
+ "expectedErrorCode": "WRONG_TYPE",
4026
+ "expectedPath": "$.kind.fields[0].kind.value",
4027
+ "description": "a Tokens `value` carrying a bare STRING where the slot is a `Binding<string list>` — refused rather than lifted into a one-element list. The lift is the tempting coercion and it is exactly wrong: a document saying `\"urgent\"` where the wire says a list has a misunderstanding of the slot, and silently agreeing with it would let an emitter ship a token field that can only ever hold one token while every host round-tripped it perfectly"
4028
+ },
2901
4029
  {
2902
4030
  "id": "reject-tonedpill-unknown-tone",
2903
4031
  "kind": "reject",
@@ -2907,6 +4035,42 @@
2907
4035
  "expectedPath": "$.kind.columns[0].kind.map.Delayed",
2908
4036
  "description": "TonedPill tone-map value outside ToneVariant — the message names the seven legal tones (Phase 750)"
2909
4037
  },
4038
+ {
4039
+ "id": "reject-tooltip-nonstring",
4040
+ "kind": "reject",
4041
+ "decoder": "node",
4042
+ "inputFile": "reject/reject-tooltip-nonstring.json",
4043
+ "expectedErrorCode": "WRONG_TYPE",
4044
+ "expectedPath": "$.tooltip",
4045
+ "description": "a node `tooltip` of `42` — refused rather than stringified. A bare STRING at this slot is the canonical encoding of a literal hint (TextSource's transparent case), so a number is one lenient `toString` away from minting hint text out of a JSON type — which no downstream check could ever catch"
4046
+ },
4047
+ {
4048
+ "id": "reject-tree-item-missing-id",
4049
+ "kind": "reject",
4050
+ "decoder": "node",
4051
+ "inputFile": "reject/reject-tree-item-missing-id.json",
4052
+ "expectedErrorCode": "MISSING_FIELD",
4053
+ "expectedPath": "$.kind.items[0].id",
4054
+ "description": "a Tree row with no `id` — refused. Both State keys address rows BY id, so an id-less row can never be expanded, selected or restored; a synthesised positional id would move the reader's open branches the moment a sibling was inserted"
4055
+ },
4056
+ {
4057
+ "id": "reject-tree-item-missing-label",
4058
+ "kind": "reject",
4059
+ "decoder": "node",
4060
+ "inputFile": "reject/reject-tree-item-missing-label.json",
4061
+ "expectedErrorCode": "MISSING_FIELD",
4062
+ "expectedPath": "$.kind.items[0].label",
4063
+ "description": "a Tree row with no `label` — refused. A row's label is the only thing a reader walking the hierarchy has, and there is no value to default to that would not be an invented description of somebody's content"
4064
+ },
4065
+ {
4066
+ "id": "reject-tree-nested-item-missing-id",
4067
+ "kind": "reject",
4068
+ "decoder": "node",
4069
+ "inputFile": "reject/reject-tree-nested-item-missing-id.json",
4070
+ "expectedErrorCode": "MISSING_FIELD",
4071
+ "expectedPath": "$.kind.items[0].children[0].id",
4072
+ "description": "a NESTED Tree row with no `id` — refused, and the path names the row inside the hierarchy. A host whose child walker is looser than its root walker accepts this while passing every top-level case"
4073
+ },
2910
4074
  {
2911
4075
  "id": "reject-unknown-binding",
2912
4076
  "kind": "reject",
@@ -2916,6 +4080,15 @@
2916
4080
  "expectedPath": "$.kind.value.$type",
2917
4081
  "description": "Binding $type 'Bogus'"
2918
4082
  },
4083
+ {
4084
+ "id": "reject-unknown-capture-source",
4085
+ "kind": "reject",
4086
+ "decoder": "node",
4087
+ "inputFile": "reject/reject-unknown-capture-source.json",
4088
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
4089
+ "expectedPath": "$.kind.capture",
4090
+ "description": "a FileUpload `capture` of `\"Screen\"` — outside the closed pair `Camera | Microphone`. The HTML capture attribute cannot ask for a display at all, and a screen capture is a standing grant reaching every window the reader has open rather than a third device behind the same picker permission, so it is refused exactly like a name nobody has proposed: a later admission would be an ADDITION, never a re-meaning of shipped bytes"
4091
+ },
2919
4092
  {
2920
4093
  "id": "reject-unknown-drawing-curve-command",
2921
4094
  "kind": "reject",
@@ -3006,6 +4179,33 @@
3006
4179
  "expectedPath": "$.kind.trendPolarity",
3007
4180
  "description": "TrendPolarity value 'Neutral' — RESERVED, not admitted (§3.6.1 clause 5); refused exactly like a name nobody has proposed, so a later admission is an ADDITION rather than a re-meaning of shipped bytes"
3008
4181
  },
4182
+ {
4183
+ "id": "reject-upload-acceptpaste-nonbool",
4184
+ "kind": "reject",
4185
+ "decoder": "node",
4186
+ "inputFile": "reject/reject-upload-acceptpaste-nonbool.json",
4187
+ "expectedErrorCode": "WRONG_TYPE",
4188
+ "expectedPath": "$.kind.acceptPaste",
4189
+ "description": "a FileUpload `acceptPaste` of `1` — the second gesture slot, refused on the same reasoning and vectored separately because it is a separate decoder arm. A number is the shape a truthiness coercion is likeliest to admit, which is why this vector carries one where its `dropTarget` twin carries a string"
4190
+ },
4191
+ {
4192
+ "id": "reject-upload-destination-empty",
4193
+ "kind": "reject",
4194
+ "decoder": "node",
4195
+ "inputFile": "reject/reject-upload-destination-empty.json",
4196
+ "expectedErrorCode": "WRONG_TYPE",
4197
+ "expectedPath": "$.kind.destination",
4198
+ "description": "a FileUpload `destination` of `\"\"` — refused rather than read as absence. The member names a host-registered destination and the empty string names none, so the document describes an upload that can never stream; reading it as \"no destination\" would silently turn a streaming upload into a client-only one, which is the one failure mode with no visible symptom"
4199
+ },
4200
+ {
4201
+ "id": "reject-upload-droptarget-nonbool",
4202
+ "kind": "reject",
4203
+ "decoder": "node",
4204
+ "inputFile": "reject/reject-upload-droptarget-nonbool.json",
4205
+ "expectedErrorCode": "WRONG_TYPE",
4206
+ "expectedPath": "$.kind.dropTarget",
4207
+ "description": "a FileUpload `dropTarget` of `\"true\"` — refused rather than coerced. The slot decides whether a whole INGRESS ROUTE exists, and it omits at `false`, so absence already spells the plain picker: a present wrong-typed value can only mean the emitter meant something it did not say. A lenient truthiness read would open a drop target on `\"no\"` and `\"false\"` alike, and the reader would find out by dropping a file into a control that never listened for it"
4208
+ },
3009
4209
  {
3010
4210
  "id": "reject-wrongnodekind-sparkler",
3011
4211
  "kind": "reject",
@@ -3024,6 +4224,15 @@
3024
4224
  "expectedPath": "$.kind.$type",
3025
4225
  "description": "Top-level kind 'Widget' is not a recognised node kind"
3026
4226
  },
4227
+ {
4228
+ "id": "reject-wrongtype-box-break-before",
4229
+ "kind": "reject",
4230
+ "decoder": "node",
4231
+ "inputFile": "reject/reject-wrongtype-box-break-before.json",
4232
+ "expectedErrorCode": "WRONG_TYPE",
4233
+ "expectedPath": "$.kind.breakBefore",
4234
+ "description": "Box breakBefore is the NUMBER 1, not a boolean — the truthy-integer spelling a JSON emitter reaches for, refused for the same reason as its sibling (Phase 1473)"
4235
+ },
3027
4236
  {
3028
4237
  "id": "reject-wrongtype-box-children",
3029
4238
  "kind": "reject",
@@ -3033,6 +4242,24 @@
3033
4242
  "expectedPath": "$.kind.children",
3034
4243
  "description": "Box children is object not array"
3035
4244
  },
4245
+ {
4246
+ "id": "reject-wrongtype-box-keep-together",
4247
+ "kind": "reject",
4248
+ "decoder": "node",
4249
+ "inputFile": "reject/reject-wrongtype-box-keep-together.json",
4250
+ "expectedErrorCode": "WRONG_TYPE",
4251
+ "expectedPath": "$.kind.keepTogether",
4252
+ "description": "Box keepTogether is the STRING \"true\", not a boolean — the container arm of the print-break pair. Refused, never coerced: absence is the only spelling of \"not declared\" (Phase 1473)"
4253
+ },
4254
+ {
4255
+ "id": "reject-wrongtype-clipboard-payload",
4256
+ "kind": "reject",
4257
+ "decoder": "node",
4258
+ "inputFile": "reject/reject-wrongtype-clipboard-payload.json",
4259
+ "expectedErrorCode": "WRONG_TYPE",
4260
+ "expectedPath": "$.kind.onClick.text",
4261
+ "description": "Action.WriteToClipboard's payload is a TextSource, so it is a string (the Literal shorthand) or a $type-tagged object — never a number. The refusal matters more here than at an ordinary text slot: the slot WIDENED in 1126 from a bare string, and a host that read the widening as \"anything goes\" would put a JSON literal on the reader's clipboard rather than refusing the document (Phase 1126)"
4262
+ },
3036
4263
  {
3037
4264
  "id": "reject-wrongtype-discriminator",
3038
4265
  "kind": "reject",
@@ -3051,6 +4278,33 @@
3051
4278
  "expectedPath": "$.kind.defaultSort.column",
3052
4279
  "description": "bound-grid defaultSort column -1 — the same non-negative bound the staticRows spelling carries, same record, same message (Phase 861)"
3053
4280
  },
4281
+ {
4282
+ "id": "reject-wrongtype-grid-exportable",
4283
+ "kind": "reject",
4284
+ "decoder": "node",
4285
+ "inputFile": "reject/reject-wrongtype-grid-exportable.json",
4286
+ "expectedErrorCode": "WRONG_TYPE",
4287
+ "expectedPath": "$.kind.exportable",
4288
+ "description": "DataGrid exportable is the STRING \"true\", not a boolean — the shape a stringly-typed emitter produces. Refused, never coerced: coerced to false it would silently withdraw a declared affordance, coerced to true it would grow one the document never asked for (Phase 1125)"
4289
+ },
4290
+ {
4291
+ "id": "reject-wrongtype-grid-exportable-number",
4292
+ "kind": "reject",
4293
+ "decoder": "node",
4294
+ "inputFile": "reject/reject-wrongtype-grid-exportable-number.json",
4295
+ "expectedErrorCode": "WRONG_TYPE",
4296
+ "expectedPath": "$.kind.exportable",
4297
+ "description": "DataGrid exportable is the NUMBER 1 — the truthy-integer spelling, pinned separately because a decoder can reject one non-boolean shape and accept another (Phase 1125)"
4298
+ },
4299
+ {
4300
+ "id": "reject-wrongtype-grid-keep-rows-together",
4301
+ "kind": "reject",
4302
+ "decoder": "node",
4303
+ "inputFile": "reject/reject-wrongtype-grid-keep-rows-together.json",
4304
+ "expectedErrorCode": "WRONG_TYPE",
4305
+ "expectedPath": "$.kind.keepRowsTogether",
4306
+ "description": "DataGrid keepRowsTogether is a string — the GRID arm of the print-break pair, a decoder branch of its own that a container-arm vector says nothing about (Phase 1473)"
4307
+ },
3054
4308
  {
3055
4309
  "id": "reject-wrongtype-grid-page-size-zero",
3056
4310
  "kind": "reject",
@@ -3060,6 +4314,33 @@
3060
4314
  "expectedPath": "$.kind.pageSize",
3061
4315
  "description": "grid pageSize 0 — a page of no rows names no page; the schema says the same with minimum:1 (Phase 862)"
3062
4316
  },
4317
+ {
4318
+ "id": "reject-wrongtype-grid-repeat-header",
4319
+ "kind": "reject",
4320
+ "decoder": "node",
4321
+ "inputFile": "reject/reject-wrongtype-grid-repeat-header.json",
4322
+ "expectedErrorCode": "WRONG_TYPE",
4323
+ "expectedPath": "$.kind.repeatHeader",
4324
+ "description": "DataGrid repeatHeader is an object, not a boolean — the fourth flag, read by its own call and so pinned on its own (Phase 1473)"
4325
+ },
4326
+ {
4327
+ "id": "reject-wrongtype-grid-transfer-in-key",
4328
+ "kind": "reject",
4329
+ "decoder": "node",
4330
+ "inputFile": "reject/reject-wrongtype-grid-transfer-in-key.json",
4331
+ "expectedErrorCode": "WRONG_TYPE",
4332
+ "expectedPath": "$.kind.transferInKey",
4333
+ "description": "DataGrid transferInKey is a number — the slot names the shared State key a transfer travels on, so it is a key name and never an ordinal; a grid identified by position could not be paired with by any other grid (Phase 1123)"
4334
+ },
4335
+ {
4336
+ "id": "reject-wrongtype-grid-transfer-out-key",
4337
+ "kind": "reject",
4338
+ "decoder": "node",
4339
+ "inputFile": "reject/reject-wrongtype-grid-transfer-out-key.json",
4340
+ "expectedErrorCode": "WRONG_TYPE",
4341
+ "expectedPath": "$.kind.transferOutKey",
4342
+ "description": "DataGrid transferOutKey is a boolean — the releasing side names a KEY, never declares a flag; there is deliberately no boolean spelling of \"this grid may release rows\", because a release with no key names no counterpart and would be an affordance with nowhere to go (Phase 1123)"
4343
+ },
3063
4344
  {
3064
4345
  "id": "reject-wrongtype-heading-level",
3065
4346
  "kind": "reject",