@fuaran-ui/conformance 0.9.0 → 0.19.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 (196) 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-image-caption-envelope.expected.json +1 -0
  18. package/corpus/lenient/lenient-image-caption-envelope.json +1 -0
  19. package/corpus/lenient/lenient-image-empty-srcset.expected.json +1 -0
  20. package/corpus/lenient/lenient-image-empty-srcset.json +1 -0
  21. package/corpus/lenient/lenient-image-explicit-defaults.expected.json +1 -0
  22. package/corpus/lenient/lenient-image-explicit-defaults.json +1 -0
  23. package/corpus/lenient/lenient-image-explicit-expandable-false.expected.json +1 -0
  24. package/corpus/lenient/lenient-image-explicit-expandable-false.json +1 -0
  25. package/corpus/lenient/lenient-shape-a11y-label-bare-scalar.expected.json +1 -0
  26. package/corpus/lenient/lenient-shape-a11y-label-bare-scalar.json +1 -0
  27. package/corpus/manifest.json +1586 -85
  28. package/corpus/nodes/a11y-alert-assertive.json +1 -0
  29. package/corpus/nodes/a11y-button-named.json +1 -0
  30. package/corpus/nodes/a11y-image-decorative.json +1 -0
  31. package/corpus/nodes/a11y-link-labelled.json +1 -0
  32. package/corpus/nodes/a11y-wrapper-all-slots.json +1 -0
  33. package/corpus/nodes/a11y-wrapper-state-bound.json +1 -0
  34. package/corpus/nodes/box-break-before-1.json +1 -0
  35. package/corpus/nodes/box-keep-together-1.json +1 -0
  36. package/corpus/nodes/btn-copy-bound.json +1 -0
  37. package/corpus/nodes/button-print.json +1 -0
  38. package/corpus/nodes/drawing-nonfinite-sentinels.json +1 -0
  39. package/corpus/nodes/embed-1.json +1 -0
  40. package/corpus/nodes/embed-aspect-1.json +1 -0
  41. package/corpus/nodes/embed-permissions-1.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-field-rules.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/frag-stdlib-confirm-action-pair-ref.json +1 -0
  55. package/corpus/nodes/frag-stdlib-confirm-action-pair.json +1 -0
  56. package/corpus/nodes/frag-stdlib-empty-state-panel-ref.json +1 -0
  57. package/corpus/nodes/frag-stdlib-empty-state-panel.json +1 -0
  58. package/corpus/nodes/frag-stdlib-filter-bar-ref.json +1 -0
  59. package/corpus/nodes/frag-stdlib-filter-bar.json +1 -0
  60. package/corpus/nodes/frag-stdlib-kpi-strip-ref.json +1 -0
  61. package/corpus/nodes/frag-stdlib-kpi-strip.json +1 -0
  62. package/corpus/nodes/frag-stdlib-labelled-metric-row-ref.json +1 -0
  63. package/corpus/nodes/frag-stdlib-labelled-metric-row.json +1 -0
  64. package/corpus/nodes/frag-stdlib-loading-placeholder-ref.json +1 -0
  65. package/corpus/nodes/frag-stdlib-loading-placeholder.json +1 -0
  66. package/corpus/nodes/frag-stdlib-metric-card-ref.json +1 -0
  67. package/corpus/nodes/frag-stdlib-metric-card.json +1 -0
  68. package/corpus/nodes/frag-stdlib-section-header-ref.json +1 -0
  69. package/corpus/nodes/frag-stdlib-section-header.json +1 -0
  70. package/corpus/nodes/grid-exportable-1.json +1 -0
  71. package/corpus/nodes/grid-keep-rows-together-1.json +1 -0
  72. package/corpus/nodes/grid-repeat-header-1.json +1 -0
  73. package/corpus/nodes/image-caption-1.json +1 -0
  74. package/corpus/nodes/image-caption-i18n-1.json +1 -0
  75. package/corpus/nodes/image-expandable-1.json +1 -0
  76. package/corpus/nodes/image-expandable-figure-1.json +1 -0
  77. package/corpus/nodes/image-presentation-1.json +1 -0
  78. package/corpus/nodes/image-srcset-1.json +1 -0
  79. package/corpus/nodes/limit-node-depth-at-max.json +1 -0
  80. package/corpus/nodes/limit-tree-item-depth-at-max.json +1 -0
  81. package/corpus/nodes/masonry-1.json +1 -0
  82. package/corpus/nodes/masonry-gap.json +1 -0
  83. package/corpus/nodes/media-audio-1.json +1 -0
  84. package/corpus/nodes/media-audio-transcript-1.json +1 -0
  85. package/corpus/nodes/media-video-1.json +1 -0
  86. package/corpus/nodes/media-video-autoplay-1.json +1 -0
  87. package/corpus/nodes/media-video-captions-1.json +1 -0
  88. package/corpus/nodes/media-video-poster-1.json +1 -0
  89. package/corpus/nodes/media-video-tracks-2.json +1 -0
  90. package/corpus/nodes/metric-inverted-polarity.json +1 -0
  91. package/corpus/nodes/metric-nonfinite-sentinel.json +1 -0
  92. package/corpus/nodes/multiselect-chip-list-param.json +1 -0
  93. package/corpus/nodes/popover-anchored-1.json +1 -0
  94. package/corpus/nodes/popover-open-1.json +1 -0
  95. package/corpus/nodes/shared-source-seeded-pair.json +1 -0
  96. package/corpus/nodes/spark-nonfinite-sentinel.json +1 -0
  97. package/corpus/nodes/style-direction-isolated-1.json +1 -0
  98. package/corpus/nodes/style-direction-ltr-1.json +1 -0
  99. package/corpus/nodes/switch-carousel-1.json +1 -0
  100. package/corpus/nodes/tooltip-button-1.json +1 -0
  101. package/corpus/nodes/tooltip-icon-button-1.json +1 -0
  102. package/corpus/nodes/tooltip-metric-1.json +1 -0
  103. package/corpus/nodes/transfer-board.json +1 -0
  104. package/corpus/nodes/tree-1.json +1 -0
  105. package/corpus/nodes/tree-expanded-1.json +1 -0
  106. package/corpus/nodes/tree-selection-1.json +1 -0
  107. package/corpus/nodes/upload-capture-camera-1.json +1 -0
  108. package/corpus/nodes/upload-capture-microphone-1.json +1 -0
  109. package/corpus/nodes/upload-destination-1.json +1 -0
  110. package/corpus/nodes/upload-drop-1.json +1 -0
  111. package/corpus/nodes/upload-paste-1.json +1 -0
  112. package/corpus/ops/op-updatestyle (phase 1472 direction=ltr, the only style member).json +1 -0
  113. package/corpus/reject/reject-a11y-hidden-nonbool.json +1 -0
  114. package/corpus/reject/reject-a11y-labelledby-nonstring.json +1 -0
  115. package/corpus/reject/reject-a11y-liveregion-nonstring.json +1 -0
  116. package/corpus/reject/reject-a11y-liveregion-unknown.json +1 -0
  117. package/corpus/reject/reject-a11y-not-object.json +1 -0
  118. package/corpus/reject/reject-a11y-role-nonstring.json +1 -0
  119. package/corpus/reject/reject-action-print-with-payload.json +1 -0
  120. package/corpus/reject/reject-binding-float-bool.json +1 -0
  121. package/corpus/reject/reject-binding-float-sentinel-case.json +1 -0
  122. package/corpus/reject/reject-binding-float-string.json +1 -0
  123. package/corpus/reject/reject-binding-int-bool.json +1 -0
  124. package/corpus/reject/reject-binding-int-sentinel-string.json +1 -0
  125. package/corpus/reject/reject-box-masonry-nonpositive-cols.json +1 -0
  126. package/corpus/reject/reject-color-value-not-hex.json +1 -0
  127. package/corpus/reject/reject-combobox-allowfreetext-nonbool.json +1 -0
  128. package/corpus/reject/reject-embed-missing-title.json +1 -0
  129. package/corpus/reject/reject-embed-permission-nonstring.json +1 -0
  130. package/corpus/reject/reject-embed-unknown-permission.json +1 -0
  131. package/corpus/reject/reject-fieldrule-empty.json +1 -0
  132. package/corpus/reject/reject-fieldrule-length-unordered.json +1 -0
  133. package/corpus/reject/reject-formfield-near-miss-validation.json +1 -0
  134. package/corpus/reject/reject-image-expandable-nonbool.json +1 -0
  135. package/corpus/reject/reject-image-srcset-nonpositive-width.json +1 -0
  136. package/corpus/reject/reject-image-srcset-null.json +1 -0
  137. package/corpus/reject/reject-limit-json-depth-at-max.json +1 -0
  138. package/corpus/reject/reject-limit-json-depth.json +1 -0
  139. package/corpus/reject/reject-limit-node-depth.json +1 -0
  140. package/corpus/reject/reject-limit-op-depth.json +1 -0
  141. package/corpus/reject/reject-limit-tree-item-depth.json +1 -0
  142. package/corpus/reject/reject-media-autoplay-nonbool.json +1 -0
  143. package/corpus/reject/reject-media-missing-label.json +1 -0
  144. package/corpus/reject/reject-media-track-default-nonbool.json +1 -0
  145. package/corpus/reject/reject-media-track-missing-srclang.json +1 -0
  146. package/corpus/reject/reject-modal-modality-nonstring.json +1 -0
  147. package/corpus/reject/reject-modal-modality-unknown.json +1 -0
  148. package/corpus/reject/reject-nearmiss-a11y-aria-hidden.json +1 -0
  149. package/corpus/reject/reject-nearmiss-a11y-arialabel.json +1 -0
  150. package/corpus/reject/reject-nearmiss-a11y-live.json +1 -0
  151. package/corpus/reject/reject-nearmiss-a11y-liveregion-case.json +1 -0
  152. package/corpus/reject/reject-op-insertchild-empty-childid.json +1 -1
  153. package/corpus/reject/reject-op-insertchild-missing-parentid.json +1 -1
  154. package/corpus/reject/reject-op-insertchild-retired-position.json +1 -0
  155. package/corpus/reject/reject-op-movenode-retired-newposition.json +1 -0
  156. package/corpus/reject/reject-op-updatestyle-direction-nonstring.json +1 -0
  157. package/corpus/reject/reject-op-updatestyle-direction-unknown.json +1 -0
  158. package/corpus/reject/reject-rating-max-zero.json +1 -0
  159. package/corpus/reject/reject-style-direction-nonstring.json +1 -0
  160. package/corpus/reject/reject-style-direction-unknown.json +1 -0
  161. package/corpus/reject/reject-switch-autoadvance-fractional.json +1 -0
  162. package/corpus/reject/reject-switch-autoadvance-negative.json +1 -0
  163. package/corpus/reject/reject-switch-autoadvance-zero.json +1 -0
  164. package/corpus/reject/reject-tokens-closed-without-suggestions.json +1 -0
  165. package/corpus/reject/reject-tokens-value-not-list.json +1 -0
  166. package/corpus/reject/reject-tooltip-nonstring.json +1 -0
  167. package/corpus/reject/reject-tree-item-missing-id.json +1 -0
  168. package/corpus/reject/reject-tree-item-missing-label.json +1 -0
  169. package/corpus/reject/reject-tree-nested-item-missing-id.json +1 -0
  170. package/corpus/reject/reject-unknown-capture-source.json +1 -0
  171. package/corpus/reject/reject-unknown-image-aspect.json +1 -0
  172. package/corpus/reject/reject-unknown-media-kind.json +1 -0
  173. package/corpus/reject/reject-unknown-trend-polarity.json +1 -0
  174. package/corpus/reject/reject-upload-acceptpaste-nonbool.json +1 -0
  175. package/corpus/reject/reject-upload-destination-empty.json +1 -0
  176. package/corpus/reject/reject-upload-droptarget-nonbool.json +1 -0
  177. package/corpus/reject/reject-wrongtype-box-break-before.json +1 -0
  178. package/corpus/reject/reject-wrongtype-box-keep-together.json +1 -0
  179. package/corpus/reject/reject-wrongtype-clipboard-payload.json +1 -0
  180. package/corpus/reject/reject-wrongtype-grid-exportable-number.json +1 -0
  181. package/corpus/reject/reject-wrongtype-grid-exportable.json +1 -0
  182. package/corpus/reject/reject-wrongtype-grid-keep-rows-together.json +1 -0
  183. package/corpus/reject/reject-wrongtype-grid-repeat-header.json +1 -0
  184. package/corpus/reject/reject-wrongtype-grid-transfer-in-key.json +1 -0
  185. package/corpus/reject/reject-wrongtype-grid-transfer-out-key.json +1 -0
  186. package/corpus/schema.json +3897 -207
  187. package/dist/cli.js +95 -1
  188. package/dist/cli.js.map +1 -1
  189. package/dist/index.cjs +95 -1
  190. package/dist/index.cjs.map +1 -1
  191. package/dist/index.d.cts +18 -3
  192. package/dist/index.d.ts +18 -3
  193. package/dist/index.js +95 -1
  194. package/dist/index.js.map +1 -1
  195. package/package.json +3 -3
  196. package/corpus/reject/reject-transform-source-empty-wrapper.json +0 -1
@@ -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",
@@ -30,6 +31,7 @@
30
31
  "Map",
31
32
  "Markdown",
32
33
  "Math",
34
+ "Media",
33
35
  "Metric",
34
36
  "Modal",
35
37
  "Mount",
@@ -43,22 +45,163 @@
43
45
  "SummaryList",
44
46
  "Switch",
45
47
  "Tabs",
46
- "Toast"
48
+ "Toast",
49
+ "Tree"
47
50
  ],
48
51
  "formFieldKinds": [
49
52
  "Checkbox",
50
53
  "Choice",
54
+ "Color",
55
+ "Combobox",
51
56
  "Date",
52
57
  "DateRange",
53
58
  "Number",
54
59
  "Range",
55
60
  "RangedNumber",
61
+ "Rating",
56
62
  "SegmentedChoice",
57
63
  "Text",
58
64
  "TextArea",
59
- "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"
60
80
  ],
61
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
+ },
62
205
  {
63
206
  "id": "elc-ans-accept-full",
64
207
  "kind": "elicitation-answer-accept",
@@ -403,6 +546,14 @@
403
546
  "expectedFile": "lenient/lenient-022-lvr-emphasis-normal.expected.json",
404
547
  "description": "0.2.2 — 'Normal' coerces to false, which is the omitted-when-false canonical form"
405
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
+ },
406
557
  {
407
558
  "id": "lenient-460-alias-emphasis-muted",
408
559
  "kind": "lenient-accept",
@@ -627,6 +778,38 @@
627
778
  "expectedFile": "lenient/lenient-grid-transform-param-compact.expected.json",
628
779
  "description": "Phase 719 — the compact authoring twin of `grid-transform-param`: 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)."
629
780
  },
781
+ {
782
+ "id": "lenient-image-caption-envelope",
783
+ "kind": "lenient-accept",
784
+ "decoder": "node",
785
+ "inputFile": "lenient/lenient-image-caption-envelope.json",
786
+ "expectedFile": "lenient/lenient-image-caption-envelope.expected.json",
787
+ "description": "Image — the enveloped `{\"$type\":\"Literal\"}` TextSource form on `caption` decodes and canonicalises to the bare string, exactly as it does on `alt` (Phase 1078). The slot is a full `TextSource`, so a host that narrowed it to a plain string cannot decode this input at all."
788
+ },
789
+ {
790
+ "id": "lenient-image-empty-srcset",
791
+ "kind": "lenient-accept",
792
+ "decoder": "node",
793
+ "inputFile": "lenient/lenient-image-empty-srcset.json",
794
+ "expectedFile": "lenient/lenient-image-empty-srcset.expected.json",
795
+ "description": "Image — an explicitly EMPTY `srcSet` decodes and canonicalises away to the omitted form (Phase 1080). Absent and `[]` denote the same document, so an image that declares no alternate renditions costs no key on the wire"
796
+ },
797
+ {
798
+ "id": "lenient-image-explicit-defaults",
799
+ "kind": "lenient-accept",
800
+ "decoder": "node",
801
+ "inputFile": "lenient/lenient-image-explicit-defaults.json",
802
+ "expectedFile": "lenient/lenient-image-explicit-defaults.expected.json",
803
+ "description": "Image — explicit-default `fit`/`aspectRatio`/`loading` decode and canonicalise away (omitted-when-default on both boundaries from day one, Phase 1077), and the bare-string TextSource shorthand applies to `alt`. The canonical minimal Image is exactly the pre-phase three-field form."
804
+ },
805
+ {
806
+ "id": "lenient-image-explicit-expandable-false",
807
+ "kind": "lenient-accept",
808
+ "decoder": "node",
809
+ "inputFile": "lenient/lenient-image-explicit-expandable-false.json",
810
+ "expectedFile": "lenient/lenient-image-explicit-expandable-false.expected.json",
811
+ "description": "Image — an explicit `expandable: false` decodes and canonicalises away to the omitted form (Phase 1079). `false` is the identity: not declaring an expansion and declaring that there is none are the same document"
812
+ },
630
813
  {
631
814
  "id": "lenient-master-detail-preselected-compact",
632
815
  "kind": "lenient-accept",
@@ -683,6 +866,14 @@
683
866
  "expectedFile": "lenient/lenient-scalar-transform-composition-compact.expected.json",
684
867
  "description": "Phase 719 — the compact authoring twin of `scalar-transform-composition`: 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)."
685
868
  },
869
+ {
870
+ "id": "lenient-shape-a11y-label-bare-scalar",
871
+ "kind": "lenient-accept",
872
+ "decoder": "node",
873
+ "inputFile": "lenient/lenient-shape-a11y-label-bare-scalar.json",
874
+ "expectedFile": "lenient/lenient-shape-a11y-label-bare-scalar.expected.json",
875
+ "description": "fuaran#957 ruling — the Accessibility trait's `label` (Binding<string>) and `hidden` (Binding<bool>) are ordinary Binding slots: a bare string / bool coerces to `Static` per the general §3.6 scalar rule (decode-only, canonical envelope on re-encode). Pinned at this position because two hosts once implemented the slot as bare-string-ONLY — the opposite error."
876
+ },
686
877
  {
687
878
  "id": "lenient-shape-binding-scalar-fraction",
688
879
  "kind": "lenient-accept",
@@ -867,6 +1058,54 @@
867
1058
  "expectedFile": "lenient/lenient-window-cumsum-legacy.expected.json",
868
1059
  "description": "2026-07-19 rename — the legacy cumSum window-fn tag coerces to the canonical cumulSum"
869
1060
  },
1061
+ {
1062
+ "id": "a11y-alert-assertive",
1063
+ "kind": "node-round-trip",
1064
+ "decoder": "node",
1065
+ "inputFile": "nodes/a11y-alert-assertive.json",
1066
+ "expectedFile": "nodes/a11y-alert-assertive.json",
1067
+ "description": "Accessibility (Phase 955 — the announcement pair: role alert + liveRegion assertive)"
1068
+ },
1069
+ {
1070
+ "id": "a11y-button-named",
1071
+ "kind": "node-round-trip",
1072
+ "decoder": "node",
1073
+ "inputFile": "nodes/a11y-button-named.json",
1074
+ "expectedFile": "nodes/a11y-button-named.json",
1075
+ "description": "Accessibility (Phase 955 — Button: an explicit named role on a kind that already has a native one)"
1076
+ },
1077
+ {
1078
+ "id": "a11y-image-decorative",
1079
+ "kind": "node-round-trip",
1080
+ "decoder": "node",
1081
+ "inputFile": "nodes/a11y-image-decorative.json",
1082
+ "expectedFile": "nodes/a11y-image-decorative.json",
1083
+ "description": "Accessibility (Phase 955 — Image: the decorative shape, empty alt + hidden Static true)"
1084
+ },
1085
+ {
1086
+ "id": "a11y-link-labelled",
1087
+ "kind": "node-round-trip",
1088
+ "decoder": "node",
1089
+ "inputFile": "nodes/a11y-link-labelled.json",
1090
+ "expectedFile": "nodes/a11y-link-labelled.json",
1091
+ "description": "Accessibility (Phase 955 — Link: the accessible name overriding the visible 'Read more' text)"
1092
+ },
1093
+ {
1094
+ "id": "a11y-wrapper-all-slots",
1095
+ "kind": "node-round-trip",
1096
+ "decoder": "node",
1097
+ "inputFile": "nodes/a11y-wrapper-all-slots.json",
1098
+ "expectedFile": "nodes/a11y-wrapper-all-slots.json",
1099
+ "description": "Accessibility (Phase 955 — all six trait slots at once on a wrapper; Static label, named role, polite)"
1100
+ },
1101
+ {
1102
+ "id": "a11y-wrapper-state-bound",
1103
+ "kind": "node-round-trip",
1104
+ "decoder": "node",
1105
+ "inputFile": "nodes/a11y-wrapper-state-bound.json",
1106
+ "expectedFile": "nodes/a11y-wrapper-state-bound.json",
1107
+ "description": "Accessibility (Phase 955 — State-bound label + hidden, mixed-case custom role doc-pageFooter, off)"
1108
+ },
870
1109
  {
871
1110
  "id": "badge-1",
872
1111
  "kind": "node-round-trip",
@@ -891,6 +1130,22 @@
891
1130
  "expectedFile": "nodes/boundary-1.json",
892
1131
  "description": "ErrorBoundary (Markdown child + Callout fallback)"
893
1132
  },
1133
+ {
1134
+ "id": "box-break-before-1",
1135
+ "kind": "node-round-trip",
1136
+ "decoder": "node",
1137
+ "inputFile": "nodes/box-break-before-1.json",
1138
+ "expectedFile": "nodes/box-break-before-1.json",
1139
+ "description": "Layout/Box (Phase 1473 breakBefore, the only new member)"
1140
+ },
1141
+ {
1142
+ "id": "box-keep-together-1",
1143
+ "kind": "node-round-trip",
1144
+ "decoder": "node",
1145
+ "inputFile": "nodes/box-keep-together-1.json",
1146
+ "expectedFile": "nodes/box-keep-together-1.json",
1147
+ "description": "Layout/Box (Phase 1473 keepTogether, the only new member)"
1148
+ },
894
1149
  {
895
1150
  "id": "btn-1",
896
1151
  "kind": "node-round-trip",
@@ -899,6 +1154,14 @@
899
1154
  "expectedFile": "nodes/btn-1.json",
900
1155
  "description": "Input/Button"
901
1156
  },
1157
+ {
1158
+ "id": "btn-copy-bound",
1159
+ "kind": "node-round-trip",
1160
+ "decoder": "node",
1161
+ "inputFile": "nodes/btn-copy-bound.json",
1162
+ "expectedFile": "nodes/btn-copy-bound.json",
1163
+ "description": "Phase 1126 — Input/Button whose Action.WriteToClipboard carries a BOUND payload (a TextSource, not a bare string), resolved at dispatch time"
1164
+ },
902
1165
  {
903
1166
  "id": "btn-copy-link",
904
1167
  "kind": "node-round-trip",
@@ -931,6 +1194,14 @@
931
1194
  "expectedFile": "nodes/btn-read-workbook.json",
932
1195
  "description": "Input/Button (Action.ReadFileBody base64)"
933
1196
  },
1197
+ {
1198
+ "id": "button-print",
1199
+ "kind": "node-round-trip",
1200
+ "decoder": "node",
1201
+ "inputFile": "nodes/button-print.json",
1202
+ "expectedFile": "nodes/button-print.json",
1203
+ "description": "Input/Button (Phase 1124 — Action.Print: the payload-free action, inside a Chain)"
1204
+ },
934
1205
  {
935
1206
  "id": "button-setstate-valuefrom",
936
1207
  "kind": "node-round-trip",
@@ -1115,6 +1386,14 @@
1115
1386
  "expectedFile": "nodes/drawing-empty.json",
1116
1387
  "description": "Display/Drawing (degenerate — empty)"
1117
1388
  },
1389
+ {
1390
+ "id": "drawing-nonfinite-sentinels",
1391
+ "kind": "node-round-trip",
1392
+ "decoder": "node",
1393
+ "inputFile": "nodes/drawing-nonfinite-sentinels.json",
1394
+ "expectedFile": "nodes/drawing-nonfinite-sentinels.json",
1395
+ "description": "Display/Drawing (Phase 1063 — §5/§7 non-finite sentinels at typed float scalars and a nested shape coordinate)"
1396
+ },
1118
1397
  {
1119
1398
  "id": "drawing-rotated-labels",
1120
1399
  "kind": "node-round-trip",
@@ -1131,6 +1410,30 @@
1131
1410
  "expectedFile": "nodes/drawing-tipped-shapes.json",
1132
1411
  "description": "Display/Drawing (Phase 883 — tipped shapes, incl. hostile text, Bound tip and explicit empty)"
1133
1412
  },
1413
+ {
1414
+ "id": "embed-1",
1415
+ "kind": "node-round-trip",
1416
+ "decoder": "node",
1417
+ "inputFile": "nodes/embed-1.json",
1418
+ "expectedFile": "nodes/embed-1.json",
1419
+ "description": "Display/Embed (Phase 1111 — the minimum: no ratio, no permissions, total denial)"
1420
+ },
1421
+ {
1422
+ "id": "embed-aspect-1",
1423
+ "kind": "node-round-trip",
1424
+ "decoder": "node",
1425
+ "inputFile": "nodes/embed-aspect-1.json",
1426
+ "expectedFile": "nodes/embed-aspect-1.json",
1427
+ "description": "Display/Embed (Phase 1111 — a declared aspect ratio, sharing Image's enum)"
1428
+ },
1429
+ {
1430
+ "id": "embed-permissions-1",
1431
+ "kind": "node-round-trip",
1432
+ "decoder": "node",
1433
+ "inputFile": "nodes/embed-permissions-1.json",
1434
+ "expectedFile": "nodes/embed-permissions-1.json",
1435
+ "description": "Display/Embed (Phase 1111 — one permission, the one that rides `allow` not `sandbox`)"
1436
+ },
1134
1437
  {
1135
1438
  "id": "empty-state-card",
1136
1439
  "kind": "node-round-trip",
@@ -1179,6 +1482,14 @@
1179
1482
  "expectedFile": "nodes/filters-declarative.json",
1180
1483
  "description": "Input/Filters (declarative — omitted onChange + typed range bounds)"
1181
1484
  },
1485
+ {
1486
+ "id": "filters-rating-colour",
1487
+ "kind": "node-round-trip",
1488
+ "decoder": "node",
1489
+ "inputFile": "nodes/filters-rating-colour.json",
1490
+ "expectedFile": "nodes/filters-rating-colour.json",
1491
+ "description": "Input/Filters (Phase 1130 — Rating + Color chips, declarative and auto-bound)"
1492
+ },
1182
1493
  {
1183
1494
  "id": "filters-segmented",
1184
1495
  "kind": "node-round-trip",
@@ -1187,6 +1498,14 @@
1187
1498
  "expectedFile": "nodes/filters-segmented.json",
1188
1499
  "description": "Input/Filters (SegmentedFilter horizontal)"
1189
1500
  },
1501
+ {
1502
+ "id": "filters-tokens",
1503
+ "kind": "node-round-trip",
1504
+ "decoder": "node",
1505
+ "inputFile": "nodes/filters-tokens.json",
1506
+ "expectedFile": "nodes/filters-tokens.json",
1507
+ "description": "Input/Filters (Phase 1121 — Tokens chip, constrained and auto-bound)"
1508
+ },
1190
1509
  {
1191
1510
  "id": "form-1",
1192
1511
  "kind": "node-round-trip",
@@ -1195,6 +1514,38 @@
1195
1514
  "expectedFile": "nodes/form-1.json",
1196
1515
  "description": "Input/Form (all fields)"
1197
1516
  },
1517
+ {
1518
+ "id": "form-color",
1519
+ "kind": "node-round-trip",
1520
+ "decoder": "node",
1521
+ "inputFile": "nodes/form-color.json",
1522
+ "expectedFile": "nodes/form-color.json",
1523
+ "description": "Input/Form (Phase 1130 — Color: upper-case hex, preserved not normalised)"
1524
+ },
1525
+ {
1526
+ "id": "form-combobox-freetext",
1527
+ "kind": "node-round-trip",
1528
+ "decoder": "node",
1529
+ "inputFile": "nodes/form-combobox-freetext.json",
1530
+ "expectedFile": "nodes/form-combobox-freetext.json",
1531
+ "description": "Input/Form (Phase 1113 — Combobox: allowFreeText, value outside the option set)"
1532
+ },
1533
+ {
1534
+ "id": "form-combobox-query",
1535
+ "kind": "node-round-trip",
1536
+ "decoder": "node",
1537
+ "inputFile": "nodes/form-combobox-query.json",
1538
+ "expectedFile": "nodes/form-combobox-query.json",
1539
+ "description": "Input/Form (Phase 1113 — Combobox: Query-bound option source, declarative, auto-bound value)"
1540
+ },
1541
+ {
1542
+ "id": "form-combobox-static",
1543
+ "kind": "node-round-trip",
1544
+ "decoder": "node",
1545
+ "inputFile": "nodes/form-combobox-static.json",
1546
+ "expectedFile": "nodes/form-combobox-static.json",
1547
+ "description": "Input/Form (Phase 1113 — Combobox: static options, allowFreeText omitted)"
1548
+ },
1198
1549
  {
1199
1550
  "id": "form-date",
1200
1551
  "kind": "node-round-trip",
@@ -1227,6 +1578,14 @@
1227
1578
  "expectedFile": "nodes/form-declarative-minimal.json",
1228
1579
  "description": "Input/Form (Phase 596 — symmetric auto-bind, omitted-value fields)"
1229
1580
  },
1581
+ {
1582
+ "id": "form-field-rules",
1583
+ "kind": "node-round-trip",
1584
+ "decoder": "node",
1585
+ "inputFile": "nodes/form-field-rules.json",
1586
+ "expectedFile": "nodes/form-field-rules.json",
1587
+ "description": "Input/Form (Phase 864 — declared field rules: format / pattern / length pair / cross-field compare)"
1588
+ },
1230
1589
  {
1231
1590
  "id": "form-local-1",
1232
1591
  "kind": "node-round-trip",
@@ -1251,6 +1610,22 @@
1251
1610
  "expectedFile": "nodes/form-ranged.json",
1252
1611
  "description": "Input/Form (RangedNumber — all/min-only/no bounds)"
1253
1612
  },
1613
+ {
1614
+ "id": "form-rating",
1615
+ "kind": "node-round-trip",
1616
+ "decoder": "node",
1617
+ "inputFile": "nodes/form-rating.json",
1618
+ "expectedFile": "nodes/form-rating.json",
1619
+ "description": "Input/Form (Phase 1130 — Rating: whole stars, allowHalf omitted, static value)"
1620
+ },
1621
+ {
1622
+ "id": "form-rating-halves",
1623
+ "kind": "node-round-trip",
1624
+ "decoder": "node",
1625
+ "inputFile": "nodes/form-rating-halves.json",
1626
+ "expectedFile": "nodes/form-rating-halves.json",
1627
+ "description": "Input/Form (Phase 1130 — Rating: allowHalf entry + a bound average on a ten-scale, value auto-bound)"
1628
+ },
1254
1629
  {
1255
1630
  "id": "form-segmented",
1256
1631
  "kind": "node-round-trip",
@@ -1267,6 +1642,30 @@
1267
1642
  "expectedFile": "nodes/form-toggle.json",
1268
1643
  "description": "Input/Form (Phase 766 — the Toggle switch affordance beside a Checkbox)"
1269
1644
  },
1645
+ {
1646
+ "id": "form-tokens-freetext",
1647
+ "kind": "node-round-trip",
1648
+ "decoder": "node",
1649
+ "inputFile": "nodes/form-tokens-freetext.json",
1650
+ "expectedFile": "nodes/form-tokens-freetext.json",
1651
+ "description": "Input/Form (Phase 1121 — Tokens: the shortest spelling, allowFreeText omitted (true), value auto-bound)"
1652
+ },
1653
+ {
1654
+ "id": "form-tokens-query",
1655
+ "kind": "node-round-trip",
1656
+ "decoder": "node",
1657
+ "inputFile": "nodes/form-tokens-query.json",
1658
+ "expectedFile": "nodes/form-tokens-query.json",
1659
+ "description": "Input/Form (Phase 1121 — Tokens: Query-bound suggestion source, declarative, value auto-bound)"
1660
+ },
1661
+ {
1662
+ "id": "form-tokens-suggested",
1663
+ "kind": "node-round-trip",
1664
+ "decoder": "node",
1665
+ "inputFile": "nodes/form-tokens-suggested.json",
1666
+ "expectedFile": "nodes/form-tokens-suggested.json",
1667
+ "description": "Input/Form (Phase 1121 — Tokens: allowFreeText false over a static suggestion source, tokens in reader order)"
1668
+ },
1270
1669
  {
1271
1670
  "id": "format-bindings",
1272
1671
  "kind": "node-round-trip",
@@ -1307,6 +1706,134 @@
1307
1706
  "expectedFile": "nodes/frag-ref-args.json",
1308
1707
  "description": "FragmentRef (parameterised — value + slot args)"
1309
1708
  },
1709
+ {
1710
+ "id": "frag-stdlib-confirm-action-pair",
1711
+ "kind": "node-round-trip",
1712
+ "decoder": "node",
1713
+ "inputFile": "nodes/frag-stdlib-confirm-action-pair.json",
1714
+ "expectedFile": "nodes/frag-stdlib-confirm-action-pair.json",
1715
+ "description": "FragmentDecl (stdlib 'confirm-action-pair' — A primary confirm and a secondary cancel, both writing one declared state key.)"
1716
+ },
1717
+ {
1718
+ "id": "frag-stdlib-confirm-action-pair-ref",
1719
+ "kind": "node-round-trip",
1720
+ "decoder": "node",
1721
+ "inputFile": "nodes/frag-stdlib-confirm-action-pair-ref.json",
1722
+ "expectedFile": "nodes/frag-stdlib-confirm-action-pair-ref.json",
1723
+ "description": "FragmentRef (stdlib 'confirm-action-pair' — a representative application)"
1724
+ },
1725
+ {
1726
+ "id": "frag-stdlib-empty-state-panel",
1727
+ "kind": "node-round-trip",
1728
+ "decoder": "node",
1729
+ "inputFile": "nodes/frag-stdlib-empty-state-panel.json",
1730
+ "expectedFile": "nodes/frag-stdlib-empty-state-panel.json",
1731
+ "description": "FragmentDecl (stdlib 'empty-state-panel' — A titled nothing-to-show card whose call to action is a caller-supplied Button slot.)"
1732
+ },
1733
+ {
1734
+ "id": "frag-stdlib-empty-state-panel-ref",
1735
+ "kind": "node-round-trip",
1736
+ "decoder": "node",
1737
+ "inputFile": "nodes/frag-stdlib-empty-state-panel-ref.json",
1738
+ "expectedFile": "nodes/frag-stdlib-empty-state-panel-ref.json",
1739
+ "description": "FragmentRef (stdlib 'empty-state-panel' — a representative application)"
1740
+ },
1741
+ {
1742
+ "id": "frag-stdlib-filter-bar",
1743
+ "kind": "node-round-trip",
1744
+ "decoder": "node",
1745
+ "inputFile": "nodes/frag-stdlib-filter-bar.json",
1746
+ "expectedFile": "nodes/frag-stdlib-filter-bar.json",
1747
+ "description": "FragmentDecl (stdlib 'filter-bar' — A free-text chip and a status chip, both bound to their own host filter keys.)"
1748
+ },
1749
+ {
1750
+ "id": "frag-stdlib-filter-bar-ref",
1751
+ "kind": "node-round-trip",
1752
+ "decoder": "node",
1753
+ "inputFile": "nodes/frag-stdlib-filter-bar-ref.json",
1754
+ "expectedFile": "nodes/frag-stdlib-filter-bar-ref.json",
1755
+ "description": "FragmentRef (stdlib 'filter-bar' — a representative application)"
1756
+ },
1757
+ {
1758
+ "id": "frag-stdlib-kpi-strip",
1759
+ "kind": "node-round-trip",
1760
+ "decoder": "node",
1761
+ "inputFile": "nodes/frag-stdlib-kpi-strip.json",
1762
+ "expectedFile": "nodes/frag-stdlib-kpi-strip.json",
1763
+ "description": "FragmentDecl (stdlib 'kpi-strip' — A headed horizontal strip of one to six figures — the dashboard's top band.)"
1764
+ },
1765
+ {
1766
+ "id": "frag-stdlib-kpi-strip-ref",
1767
+ "kind": "node-round-trip",
1768
+ "decoder": "node",
1769
+ "inputFile": "nodes/frag-stdlib-kpi-strip-ref.json",
1770
+ "expectedFile": "nodes/frag-stdlib-kpi-strip-ref.json",
1771
+ "description": "FragmentRef (stdlib 'kpi-strip' — a representative application)"
1772
+ },
1773
+ {
1774
+ "id": "frag-stdlib-labelled-metric-row",
1775
+ "kind": "node-round-trip",
1776
+ "decoder": "node",
1777
+ "inputFile": "nodes/frag-stdlib-labelled-metric-row.json",
1778
+ "expectedFile": "nodes/frag-stdlib-labelled-metric-row.json",
1779
+ "description": "FragmentDecl (stdlib 'labelled-metric-row' — A label and its figure on one line — the smallest read-only composite in the set.)"
1780
+ },
1781
+ {
1782
+ "id": "frag-stdlib-labelled-metric-row-ref",
1783
+ "kind": "node-round-trip",
1784
+ "decoder": "node",
1785
+ "inputFile": "nodes/frag-stdlib-labelled-metric-row-ref.json",
1786
+ "expectedFile": "nodes/frag-stdlib-labelled-metric-row-ref.json",
1787
+ "description": "FragmentRef (stdlib 'labelled-metric-row' — a representative application)"
1788
+ },
1789
+ {
1790
+ "id": "frag-stdlib-loading-placeholder",
1791
+ "kind": "node-round-trip",
1792
+ "decoder": "node",
1793
+ "inputFile": "nodes/frag-stdlib-loading-placeholder.json",
1794
+ "expectedFile": "nodes/frag-stdlib-loading-placeholder.json",
1795
+ "description": "FragmentDecl (stdlib 'loading-placeholder' — One to eight skeleton rows — the shape a list takes while its query is in flight.)"
1796
+ },
1797
+ {
1798
+ "id": "frag-stdlib-loading-placeholder-ref",
1799
+ "kind": "node-round-trip",
1800
+ "decoder": "node",
1801
+ "inputFile": "nodes/frag-stdlib-loading-placeholder-ref.json",
1802
+ "expectedFile": "nodes/frag-stdlib-loading-placeholder-ref.json",
1803
+ "description": "FragmentRef (stdlib 'loading-placeholder' — a representative application)"
1804
+ },
1805
+ {
1806
+ "id": "frag-stdlib-metric-card",
1807
+ "kind": "node-round-trip",
1808
+ "decoder": "node",
1809
+ "inputFile": "nodes/frag-stdlib-metric-card.json",
1810
+ "expectedFile": "nodes/frag-stdlib-metric-card.json",
1811
+ "description": "FragmentDecl (stdlib 'metric-card' — A card carrying one headline figure and the caption that qualifies it.)"
1812
+ },
1813
+ {
1814
+ "id": "frag-stdlib-metric-card-ref",
1815
+ "kind": "node-round-trip",
1816
+ "decoder": "node",
1817
+ "inputFile": "nodes/frag-stdlib-metric-card-ref.json",
1818
+ "expectedFile": "nodes/frag-stdlib-metric-card-ref.json",
1819
+ "description": "FragmentRef (stdlib 'metric-card' — a representative application)"
1820
+ },
1821
+ {
1822
+ "id": "frag-stdlib-section-header",
1823
+ "kind": "node-round-trip",
1824
+ "decoder": "node",
1825
+ "inputFile": "nodes/frag-stdlib-section-header.json",
1826
+ "expectedFile": "nodes/frag-stdlib-section-header.json",
1827
+ "description": "FragmentDecl (stdlib 'section-header' — An eyebrow above a title, at a caller-chosen heading level.)"
1828
+ },
1829
+ {
1830
+ "id": "frag-stdlib-section-header-ref",
1831
+ "kind": "node-round-trip",
1832
+ "decoder": "node",
1833
+ "inputFile": "nodes/frag-stdlib-section-header-ref.json",
1834
+ "expectedFile": "nodes/frag-stdlib-section-header-ref.json",
1835
+ "description": "FragmentRef (stdlib 'section-header' — a representative application)"
1836
+ },
1310
1837
  {
1311
1838
  "id": "glayout-1",
1312
1839
  "kind": "node-round-trip",
@@ -1371,6 +1898,14 @@
1371
1898
  "expectedFile": "nodes/grid-editable-state.json",
1372
1899
  "description": "Visualisation/Grid (Phase 663/665 — editable State-sourced grid, typed rows on the wire)"
1373
1900
  },
1901
+ {
1902
+ "id": "grid-exportable-1",
1903
+ "kind": "node-round-trip",
1904
+ "decoder": "node",
1905
+ "inputFile": "nodes/grid-exportable-1.json",
1906
+ "expectedFile": "nodes/grid-exportable-1.json",
1907
+ "description": "Visualisation/Grid (Phase 1125 exportable, the only new member)"
1908
+ },
1374
1909
  {
1375
1910
  "id": "grid-field-named",
1376
1911
  "kind": "node-round-trip",
@@ -1379,6 +1914,14 @@
1379
1914
  "expectedFile": "nodes/grid-field-named.json",
1380
1915
  "description": "Visualisation/Grid (Phase 425 — field-named columns + RowKeyField, closure-free)"
1381
1916
  },
1917
+ {
1918
+ "id": "grid-keep-rows-together-1",
1919
+ "kind": "node-round-trip",
1920
+ "decoder": "node",
1921
+ "inputFile": "nodes/grid-keep-rows-together-1.json",
1922
+ "expectedFile": "nodes/grid-keep-rows-together-1.json",
1923
+ "description": "Visualisation/Grid (Phase 1473 keepRowsTogether, the only new member)"
1924
+ },
1382
1925
  {
1383
1926
  "id": "grid-paged",
1384
1927
  "kind": "node-round-trip",
@@ -1403,6 +1946,14 @@
1403
1946
  "expectedFile": "nodes/grid-reorderable.json",
1404
1947
  "description": "Visualisation/Grid (Phase 934 — declarative row reorder: omit-when-false flag; edits and reorders share one destination)"
1405
1948
  },
1949
+ {
1950
+ "id": "grid-repeat-header-1",
1951
+ "kind": "node-round-trip",
1952
+ "decoder": "node",
1953
+ "inputFile": "nodes/grid-repeat-header-1.json",
1954
+ "expectedFile": "nodes/grid-repeat-header-1.json",
1955
+ "description": "Visualisation/Grid (Phase 1473 repeatHeader, the only new member)"
1956
+ },
1406
1957
  {
1407
1958
  "id": "grid-sort-state-key",
1408
1959
  "kind": "node-round-trip",
@@ -1460,31 +2011,95 @@
1460
2011
  "description": "Display/Image (Avatar variant)"
1461
2012
  },
1462
2013
  {
1463
- "id": "link-1",
2014
+ "id": "image-caption-1",
1464
2015
  "kind": "node-round-trip",
1465
2016
  "decoder": "node",
1466
- "inputFile": "nodes/link-1.json",
1467
- "expectedFile": "nodes/link-1.json",
1468
- "description": "Display/Link"
2017
+ "inputFile": "nodes/image-caption-1.json",
2018
+ "expectedFile": "nodes/image-caption-1.json",
2019
+ "description": "Display/Image (Phase 1078 — literal caption)"
1469
2020
  },
1470
2021
  {
1471
- "id": "link-protected-1",
2022
+ "id": "image-caption-i18n-1",
1472
2023
  "kind": "node-round-trip",
1473
2024
  "decoder": "node",
1474
- "inputFile": "nodes/link-protected-1.json",
1475
- "expectedFile": "nodes/link-protected-1.json",
1476
- "description": "Display/Link (protected emailPhase 812 protection field)"
2025
+ "inputFile": "nodes/image-caption-i18n-1.json",
2026
+ "expectedFile": "nodes/image-caption-i18n-1.json",
2027
+ "description": "Display/Image (Phase 1078i18n caption with args)"
1477
2028
  },
1478
2029
  {
1479
- "id": "list-1",
2030
+ "id": "image-expandable-1",
1480
2031
  "kind": "node-round-trip",
1481
2032
  "decoder": "node",
1482
- "inputFile": "nodes/list-1.json",
1483
- "expectedFile": "nodes/list-1.json",
1484
- "description": "Display/List (ordered)"
2033
+ "inputFile": "nodes/image-expandable-1.json",
2034
+ "expectedFile": "nodes/image-expandable-1.json",
2035
+ "description": "Display/Image (Phase 1079 — expandable, the declaration alone)"
1485
2036
  },
1486
2037
  {
1487
- "id": "lvr-1",
2038
+ "id": "image-expandable-figure-1",
2039
+ "kind": "node-round-trip",
2040
+ "decoder": "node",
2041
+ "inputFile": "nodes/image-expandable-figure-1.json",
2042
+ "expectedFile": "nodes/image-expandable-figure-1.json",
2043
+ "description": "Display/Image (Phase 1079 — expandable + caption + srcSet, the gallery thumbnail)"
2044
+ },
2045
+ {
2046
+ "id": "image-presentation-1",
2047
+ "kind": "node-round-trip",
2048
+ "decoder": "node",
2049
+ "inputFile": "nodes/image-presentation-1.json",
2050
+ "expectedFile": "nodes/image-presentation-1.json",
2051
+ "description": "Display/Image (Phase 1077 — fit / aspectRatio / loading all off-default)"
2052
+ },
2053
+ {
2054
+ "id": "image-srcset-1",
2055
+ "kind": "node-round-trip",
2056
+ "decoder": "node",
2057
+ "inputFile": "nodes/image-srcset-1.json",
2058
+ "expectedFile": "nodes/image-srcset-1.json",
2059
+ "description": "Display/Image (Phase 1080 — three-candidate srcSet, authored descending)"
2060
+ },
2061
+ {
2062
+ "id": "limit-node-depth-at-max",
2063
+ "kind": "node-round-trip",
2064
+ "decoder": "node",
2065
+ "inputFile": "nodes/limit-node-depth-at-max.json",
2066
+ "expectedFile": "nodes/limit-node-depth-at-max.json",
2067
+ "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"
2068
+ },
2069
+ {
2070
+ "id": "limit-tree-item-depth-at-max",
2071
+ "kind": "node-round-trip",
2072
+ "decoder": "node",
2073
+ "inputFile": "nodes/limit-tree-item-depth-at-max.json",
2074
+ "expectedFile": "nodes/limit-tree-item-depth-at-max.json",
2075
+ "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`"
2076
+ },
2077
+ {
2078
+ "id": "link-1",
2079
+ "kind": "node-round-trip",
2080
+ "decoder": "node",
2081
+ "inputFile": "nodes/link-1.json",
2082
+ "expectedFile": "nodes/link-1.json",
2083
+ "description": "Display/Link"
2084
+ },
2085
+ {
2086
+ "id": "link-protected-1",
2087
+ "kind": "node-round-trip",
2088
+ "decoder": "node",
2089
+ "inputFile": "nodes/link-protected-1.json",
2090
+ "expectedFile": "nodes/link-protected-1.json",
2091
+ "description": "Display/Link (protected email — Phase 812 protection field)"
2092
+ },
2093
+ {
2094
+ "id": "list-1",
2095
+ "kind": "node-round-trip",
2096
+ "decoder": "node",
2097
+ "inputFile": "nodes/list-1.json",
2098
+ "expectedFile": "nodes/list-1.json",
2099
+ "description": "Display/List (ordered)"
2100
+ },
2101
+ {
2102
+ "id": "lvr-1",
1488
2103
  "kind": "node-round-trip",
1489
2104
  "decoder": "node",
1490
2105
  "inputFile": "nodes/lvr-1.json",
@@ -1507,6 +2122,22 @@
1507
2122
  "expectedFile": "nodes/markdown-1.json",
1508
2123
  "description": "Display/Markdown"
1509
2124
  },
2125
+ {
2126
+ "id": "masonry-1",
2127
+ "kind": "node-round-trip",
2128
+ "decoder": "node",
2129
+ "inputFile": "nodes/masonry-1.json",
2130
+ "expectedFile": "nodes/masonry-1.json",
2131
+ "description": "Layout/Masonry (Phase 1082 — column-fill, three columns)"
2132
+ },
2133
+ {
2134
+ "id": "masonry-gap",
2135
+ "kind": "node-round-trip",
2136
+ "decoder": "node",
2137
+ "inputFile": "nodes/masonry-gap.json",
2138
+ "expectedFile": "nodes/masonry-gap.json",
2139
+ "description": "Layout/Masonry (Phase 1082 — explicit gap)"
2140
+ },
1510
2141
  {
1511
2142
  "id": "master-detail-multi-field",
1512
2143
  "kind": "node-round-trip",
@@ -1539,6 +2170,62 @@
1539
2170
  "expectedFile": "nodes/math-1.json",
1540
2171
  "description": "Display/Math (block LaTeX)"
1541
2172
  },
2173
+ {
2174
+ "id": "media-audio-1",
2175
+ "kind": "node-round-trip",
2176
+ "decoder": "node",
2177
+ "inputFile": "nodes/media-audio-1.json",
2178
+ "expectedFile": "nodes/media-audio-1.json",
2179
+ "description": "Display/Media (Phase 1076 — the Audio variant, whose payload is the discriminator alone)"
2180
+ },
2181
+ {
2182
+ "id": "media-audio-transcript-1",
2183
+ "kind": "node-round-trip",
2184
+ "decoder": "node",
2185
+ "inputFile": "nodes/media-audio-transcript-1.json",
2186
+ "expectedFile": "nodes/media-audio-transcript-1.json",
2187
+ "description": "Display/Media (Phase 1110 — the audio transcript floor)"
2188
+ },
2189
+ {
2190
+ "id": "media-video-1",
2191
+ "kind": "node-round-trip",
2192
+ "decoder": "node",
2193
+ "inputFile": "nodes/media-video-1.json",
2194
+ "expectedFile": "nodes/media-video-1.json",
2195
+ "description": "Display/Media (Phase 1076 — video at its minimum, both bool defaults omitted)"
2196
+ },
2197
+ {
2198
+ "id": "media-video-autoplay-1",
2199
+ "kind": "node-round-trip",
2200
+ "decoder": "node",
2201
+ "inputFile": "nodes/media-video-autoplay-1.json",
2202
+ "expectedFile": "nodes/media-video-autoplay-1.json",
2203
+ "description": "Display/Media (Phase 1076 — autoplay declared, both shared bools off-default)"
2204
+ },
2205
+ {
2206
+ "id": "media-video-captions-1",
2207
+ "kind": "node-round-trip",
2208
+ "decoder": "node",
2209
+ "inputFile": "nodes/media-video-captions-1.json",
2210
+ "expectedFile": "nodes/media-video-captions-1.json",
2211
+ "description": "Display/Media (Phase 1110 — one captions track, elected default)"
2212
+ },
2213
+ {
2214
+ "id": "media-video-poster-1",
2215
+ "kind": "node-round-trip",
2216
+ "decoder": "node",
2217
+ "inputFile": "nodes/media-video-poster-1.json",
2218
+ "expectedFile": "nodes/media-video-poster-1.json",
2219
+ "description": "Display/Media (Phase 1076 — video with a poster frame in the case payload)"
2220
+ },
2221
+ {
2222
+ "id": "media-video-tracks-2",
2223
+ "kind": "node-round-trip",
2224
+ "decoder": "node",
2225
+ "inputFile": "nodes/media-video-tracks-2.json",
2226
+ "expectedFile": "nodes/media-video-tracks-2.json",
2227
+ "description": "Display/Media (Phase 1110 — three tracks, authored order, two default elections)"
2228
+ },
1542
2229
  {
1543
2230
  "id": "metric-1",
1544
2231
  "kind": "node-round-trip",
@@ -1587,6 +2274,14 @@
1587
2274
  "expectedFile": "nodes/metric-float-exp-pos.json",
1588
2275
  "description": "Display/Metric (float divergence-zone — 1e21 scientific)"
1589
2276
  },
2277
+ {
2278
+ "id": "metric-inverted-polarity",
2279
+ "kind": "node-round-trip",
2280
+ "decoder": "node",
2281
+ "inputFile": "nodes/metric-inverted-polarity.json",
2282
+ "expectedFile": "nodes/metric-inverted-polarity.json",
2283
+ "description": "Display/Metric (Phase 867 — inverted trendPolarity: a falling wait time is an improvement)"
2284
+ },
1590
2285
  {
1591
2286
  "id": "metric-invoke",
1592
2287
  "kind": "node-round-trip",
@@ -1595,6 +2290,14 @@
1595
2290
  "expectedFile": "nodes/metric-invoke.json",
1596
2291
  "description": "Display/Metric (Phase 283 — Binding.Invoke capability source)"
1597
2292
  },
2293
+ {
2294
+ "id": "metric-nonfinite-sentinel",
2295
+ "kind": "node-round-trip",
2296
+ "decoder": "node",
2297
+ "inputFile": "nodes/metric-nonfinite-sentinel.json",
2298
+ "expectedFile": "nodes/metric-nonfinite-sentinel.json",
2299
+ "description": "Display/Metric (Phase 1063 — §5/§7 non-finite sentinels behind a Binding.Static envelope)"
2300
+ },
1598
2301
  {
1599
2302
  "id": "modal-1",
1600
2303
  "kind": "node-round-trip",
@@ -1627,6 +2330,14 @@
1627
2330
  "expectedFile": "nodes/multiselect-1.json",
1628
2331
  "description": "Input/Select (multi-select — list value)"
1629
2332
  },
2333
+ {
2334
+ "id": "multiselect-chip-list-param",
2335
+ "kind": "node-round-trip",
2336
+ "decoder": "node",
2337
+ "inputFile": "nodes/multiselect-chip-list-param.json",
2338
+ "expectedFile": "nodes/multiselect-chip-list-param.json",
2339
+ "description": "Layout/Box (Phase 610 — multi-select chip + DataGrid scoped by a list-valued in/param)"
2340
+ },
1630
2341
  {
1631
2342
  "id": "now-environment-binding",
1632
2343
  "kind": "node-round-trip",
@@ -1635,6 +2346,22 @@
1635
2346
  "expectedFile": "nodes/now-environment-binding.json",
1636
2347
  "description": "Binding/Now (Phase 765 — the host-furnished instant: a text slot + a Transform param feeding dateDiffDays)"
1637
2348
  },
2349
+ {
2350
+ "id": "popover-anchored-1",
2351
+ "kind": "node-round-trip",
2352
+ "decoder": "node",
2353
+ "inputFile": "nodes/popover-anchored-1.json",
2354
+ "expectedFile": "nodes/popover-anchored-1.json",
2355
+ "description": "Layout/Modal (Phase 1119 — Popover modality, anchored, state-bound open)"
2356
+ },
2357
+ {
2358
+ "id": "popover-open-1",
2359
+ "kind": "node-round-trip",
2360
+ "decoder": "node",
2361
+ "inputFile": "nodes/popover-open-1.json",
2362
+ "expectedFile": "nodes/popover-open-1.json",
2363
+ "description": "Layout/Modal (Phase 1119 — Popover, statically open: the SSR floor)"
2364
+ },
1638
2365
  {
1639
2366
  "id": "progress-1",
1640
2367
  "kind": "node-round-trip",
@@ -1675,6 +2402,14 @@
1675
2402
  "expectedFile": "nodes/select-1.json",
1676
2403
  "description": "Input/Select"
1677
2404
  },
2405
+ {
2406
+ "id": "shared-source-seeded-pair",
2407
+ "kind": "node-round-trip",
2408
+ "decoder": "node",
2409
+ "inputFile": "nodes/shared-source-seeded-pair.json",
2410
+ "expectedFile": "nodes/shared-source-seeded-pair.json",
2411
+ "description": "Visualisation/Grid + Display/Badge (Phase 1075 — the seeded shared source: one declared table, two readers)"
2412
+ },
1678
2413
  {
1679
2414
  "id": "skel-1",
1680
2415
  "kind": "node-round-trip",
@@ -1691,6 +2426,14 @@
1691
2426
  "expectedFile": "nodes/spark-1.json",
1692
2427
  "description": "Display/Sparkline"
1693
2428
  },
2429
+ {
2430
+ "id": "spark-nonfinite-sentinel",
2431
+ "kind": "node-round-trip",
2432
+ "decoder": "node",
2433
+ "inputFile": "nodes/spark-nonfinite-sentinel.json",
2434
+ "expectedFile": "nodes/spark-nonfinite-sentinel.json",
2435
+ "description": "Display/Sparkline (Phase 1063 — §5/§7 non-finite sentinels as float SEQUENCE elements)"
2436
+ },
1694
2437
  {
1695
2438
  "id": "split-1",
1696
2439
  "kind": "node-round-trip",
@@ -1715,6 +2458,22 @@
1715
2458
  "expectedFile": "nodes/step-1.json",
1716
2459
  "description": "Layout/Stepper"
1717
2460
  },
2461
+ {
2462
+ "id": "style-direction-isolated-1",
2463
+ "kind": "node-round-trip",
2464
+ "decoder": "node",
2465
+ "inputFile": "nodes/style-direction-isolated-1.json",
2466
+ "expectedFile": "nodes/style-direction-isolated-1.json",
2467
+ "description": "Layout/Box (Phase 1472 an Ltr value isolated inside Rtl prose)"
2468
+ },
2469
+ {
2470
+ "id": "style-direction-ltr-1",
2471
+ "kind": "node-round-trip",
2472
+ "decoder": "node",
2473
+ "inputFile": "nodes/style-direction-ltr-1.json",
2474
+ "expectedFile": "nodes/style-direction-ltr-1.json",
2475
+ "description": "Display/Markdown (Phase 1472 Direction=Ltr, the only style member)"
2476
+ },
1718
2477
  {
1719
2478
  "id": "style-role-voice-1",
1720
2479
  "kind": "node-round-trip",
@@ -1739,6 +2498,14 @@
1739
2498
  "expectedFile": "nodes/switch-1.json",
1740
2499
  "description": "Switch (view state → details/summary cases + info default)"
1741
2500
  },
2501
+ {
2502
+ "id": "switch-carousel-1",
2503
+ "kind": "node-round-trip",
2504
+ "decoder": "node",
2505
+ "inputFile": "nodes/switch-carousel-1.json",
2506
+ "expectedFile": "nodes/switch-carousel-1.json",
2507
+ "description": "Meta/Switch (Phase 1122 — an auto-advancing carousel: `autoAdvanceMs` over an index key)"
2508
+ },
1742
2509
  {
1743
2510
  "id": "switch-on-selection",
1744
2511
  "kind": "node-round-trip",
@@ -1787,6 +2554,62 @@
1787
2554
  "expectedFile": "nodes/toast-1.json",
1788
2555
  "description": "Display/Toast (Success tone, open)"
1789
2556
  },
2557
+ {
2558
+ "id": "tooltip-button-1",
2559
+ "kind": "node-round-trip",
2560
+ "decoder": "node",
2561
+ "inputFile": "nodes/tooltip-button-1.json",
2562
+ "expectedFile": "nodes/tooltip-button-1.json",
2563
+ "description": "Tooltip (Phase 1112 — the trait on a forwarding interactive kind; the description rides the button)"
2564
+ },
2565
+ {
2566
+ "id": "tooltip-icon-button-1",
2567
+ "kind": "node-round-trip",
2568
+ "decoder": "node",
2569
+ "inputFile": "nodes/tooltip-icon-button-1.json",
2570
+ "expectedFile": "nodes/tooltip-icon-button-1.json",
2571
+ "description": "Tooltip (Phase 1112 — the icon-only button: accessibility.label NAMES, tooltip DESCRIBES, describedBy merges)"
2572
+ },
2573
+ {
2574
+ "id": "tooltip-metric-1",
2575
+ "kind": "node-round-trip",
2576
+ "decoder": "node",
2577
+ "inputFile": "nodes/tooltip-metric-1.json",
2578
+ "expectedFile": "nodes/tooltip-metric-1.json",
2579
+ "description": "Tooltip (Phase 1112 — the trait on a display kind, I18n hint; description and focus stop on the wrapper)"
2580
+ },
2581
+ {
2582
+ "id": "transfer-board",
2583
+ "kind": "node-round-trip",
2584
+ "decoder": "node",
2585
+ "inputFile": "nodes/transfer-board.json",
2586
+ "expectedFile": "nodes/transfer-board.json",
2587
+ "description": "Layout/Box (Phase 1123 — cross-container transfer: two two-way columns and a one-way archive on one shared key)"
2588
+ },
2589
+ {
2590
+ "id": "tree-1",
2591
+ "kind": "node-round-trip",
2592
+ "decoder": "node",
2593
+ "inputFile": "nodes/tree-1.json",
2594
+ "expectedFile": "nodes/tree-1.json",
2595
+ "description": "Display/Tree (Phase 1120 — two levels, no State key: static and fully expanded)"
2596
+ },
2597
+ {
2598
+ "id": "tree-expanded-1",
2599
+ "kind": "node-round-trip",
2600
+ "decoder": "node",
2601
+ "inputFile": "nodes/tree-expanded-1.json",
2602
+ "expectedFile": "nodes/tree-expanded-1.json",
2603
+ "description": "Display/Tree (Phase 1120 — three levels, the expansion key named; the key IS the affordance)"
2604
+ },
2605
+ {
2606
+ "id": "tree-selection-1",
2607
+ "kind": "node-round-trip",
2608
+ "decoder": "node",
2609
+ "inputFile": "nodes/tree-selection-1.json",
2610
+ "expectedFile": "nodes/tree-selection-1.json",
2611
+ "description": "Display/Tree (Phase 1120 — the selection key AND the handler, both declared)"
2612
+ },
1790
2613
  {
1791
2614
  "id": "upload-1",
1792
2615
  "kind": "node-round-trip",
@@ -1795,6 +2618,46 @@
1795
2618
  "expectedFile": "nodes/upload-1.json",
1796
2619
  "description": "Input/FileUpload"
1797
2620
  },
2621
+ {
2622
+ "id": "upload-capture-camera-1",
2623
+ "kind": "node-round-trip",
2624
+ "decoder": "node",
2625
+ "inputFile": "nodes/upload-capture-camera-1.json",
2626
+ "expectedFile": "nodes/upload-capture-camera-1.json",
2627
+ "description": "Input/FileUpload (Phase 1116 — camera capture, image/* filtered)"
2628
+ },
2629
+ {
2630
+ "id": "upload-capture-microphone-1",
2631
+ "kind": "node-round-trip",
2632
+ "decoder": "node",
2633
+ "inputFile": "nodes/upload-capture-microphone-1.json",
2634
+ "expectedFile": "nodes/upload-capture-microphone-1.json",
2635
+ "description": "Input/FileUpload (Phase 1116 — microphone capture, audio/* filtered)"
2636
+ },
2637
+ {
2638
+ "id": "upload-destination-1",
2639
+ "kind": "node-round-trip",
2640
+ "decoder": "node",
2641
+ "inputFile": "nodes/upload-destination-1.json",
2642
+ "expectedFile": "nodes/upload-destination-1.json",
2643
+ "description": "Input/FileUpload (Phase 1117 — streamed to a registered destination)"
2644
+ },
2645
+ {
2646
+ "id": "upload-drop-1",
2647
+ "kind": "node-round-trip",
2648
+ "decoder": "node",
2649
+ "inputFile": "nodes/upload-drop-1.json",
2650
+ "expectedFile": "nodes/upload-drop-1.json",
2651
+ "description": "Input/FileUpload (Phase 1115 — drop target; acceptPaste OMITTED)"
2652
+ },
2653
+ {
2654
+ "id": "upload-paste-1",
2655
+ "kind": "node-round-trip",
2656
+ "decoder": "node",
2657
+ "inputFile": "nodes/upload-paste-1.json",
2658
+ "expectedFile": "nodes/upload-paste-1.json",
2659
+ "description": "Input/FileUpload (Phase 1115 — paste ingestion, image/* filtered; dropTarget OMITTED)"
2660
+ },
1798
2661
  {
1799
2662
  "id": "op-batch",
1800
2663
  "kind": "op-round-trip",
@@ -1972,127 +2835,432 @@
1972
2835
  "description": "UpdateStyle (Phase 147 Role=Eyebrow + Voice=Structural)"
1973
2836
  },
1974
2837
  {
1975
- "id": "reject-daterange-unordered",
2838
+ "id": "op-updatestyle (phase 1472 direction=ltr, the only style member)",
2839
+ "kind": "op-round-trip",
2840
+ "decoder": "op",
2841
+ "inputFile": "ops/op-updatestyle (phase 1472 direction=ltr, the only style member).json",
2842
+ "expectedFile": "ops/op-updatestyle (phase 1472 direction=ltr, the only style member).json",
2843
+ "description": "UpdateStyle (Phase 1472 Direction=Ltr, the only style member)"
2844
+ },
2845
+ {
2846
+ "id": "reject-a11y-hidden-nonbool",
1976
2847
  "kind": "reject",
1977
2848
  "decoder": "node",
1978
- "inputFile": "reject/reject-daterange-unordered.json",
2849
+ "inputFile": "reject/reject-a11y-hidden-nonbool.json",
1979
2850
  "expectedErrorCode": "WRONG_TYPE",
1980
- "expectedPath": "$.kind.fields[0].kind.value",
1981
- "description": "DateRange literal pair with start after endthe ordered-pair rule (Phase 725)"
2851
+ "expectedPath": "$.accessibility.hidden",
2852
+ "description": "accessibility.hidden bare scalar of the wrong type§3.6 leniency is about SHAPE, not type (Phase 955)"
1982
2853
  },
1983
2854
  {
1984
- "id": "reject-emptynodeid",
2855
+ "id": "reject-a11y-labelledby-nonstring",
1985
2856
  "kind": "reject",
1986
2857
  "decoder": "node",
1987
- "inputFile": "reject/reject-emptynodeid.json",
1988
- "expectedErrorCode": "EMPTY_NODE_ID",
1989
- "expectedPath": "$.id",
1990
- "description": "Empty top-level id"
2858
+ "inputFile": "reject/reject-a11y-labelledby-nonstring.json",
2859
+ "expectedErrorCode": "WRONG_TYPE",
2860
+ "expectedPath": "$.accessibility.labelledBy",
2861
+ "description": "accessibility.labelledBy not a NodeId string (Phase 955)"
1991
2862
  },
1992
2863
  {
1993
- "id": "reject-invalid-empty",
2864
+ "id": "reject-a11y-liveregion-nonstring",
1994
2865
  "kind": "reject",
1995
2866
  "decoder": "node",
1996
- "inputFile": "reject/reject-invalid-empty.json",
1997
- "expectedErrorCode": "INVALID_JSON",
1998
- "expectedPath": "$",
1999
- "description": "empty string"
2867
+ "inputFile": "reject/reject-a11y-liveregion-nonstring.json",
2868
+ "expectedErrorCode": "WRONG_TYPE",
2869
+ "expectedPath": "$.accessibility.liveRegion",
2870
+ "description": "accessibility.liveRegion not a JSON string (Phase 955)"
2000
2871
  },
2001
2872
  {
2002
- "id": "reject-invalid-garbage",
2873
+ "id": "reject-a11y-liveregion-unknown",
2003
2874
  "kind": "reject",
2004
2875
  "decoder": "node",
2005
- "inputFile": "reject/reject-invalid-garbage.json",
2006
- "expectedErrorCode": "INVALID_JSON",
2007
- "expectedPath": "$",
2008
- "description": "garbage input"
2876
+ "inputFile": "reject/reject-a11y-liveregion-unknown.json",
2877
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
2878
+ "expectedPath": "$.accessibility.liveRegion",
2879
+ "description": "accessibility.liveRegion outside the closed set — the message names polite | assertive | off (Phase 955)"
2009
2880
  },
2010
2881
  {
2011
- "id": "reject-invalid-truncated",
2882
+ "id": "reject-a11y-not-object",
2012
2883
  "kind": "reject",
2013
2884
  "decoder": "node",
2014
- "inputFile": "reject/reject-invalid-truncated.json",
2015
- "expectedErrorCode": "INVALID_JSON",
2016
- "expectedPath": "$",
2017
- "description": "truncated object"
2885
+ "inputFile": "reject/reject-a11y-not-object.json",
2886
+ "expectedErrorCode": "WRONG_TYPE",
2887
+ "expectedPath": "$.accessibility",
2888
+ "description": "accessibility is not an object — the trait is a record, not a bare label (Phase 955)"
2018
2889
  },
2019
2890
  {
2020
- "id": "reject-missing-binding-type",
2891
+ "id": "reject-a11y-role-nonstring",
2021
2892
  "kind": "reject",
2022
2893
  "decoder": "node",
2023
- "inputFile": "reject/reject-missing-binding-type.json",
2024
- "expectedErrorCode": "MISSING_FIELD",
2025
- "expectedPath": "$.kind.value.$type",
2026
- "description": "Binding missing $type"
2894
+ "inputFile": "reject/reject-a11y-role-nonstring.json",
2895
+ "expectedErrorCode": "WRONG_TYPE",
2896
+ "expectedPath": "$.accessibility.role",
2897
+ "description": "accessibility.role not a JSON string — the slot is open to any role NAME, not to any value (Phase 955)"
2027
2898
  },
2028
2899
  {
2029
- "id": "reject-missing-custom-moduleid",
2900
+ "id": "reject-action-print-with-payload",
2030
2901
  "kind": "reject",
2031
2902
  "decoder": "node",
2032
- "inputFile": "reject/reject-missing-custom-moduleid.json",
2033
- "expectedErrorCode": "MISSING_FIELD",
2034
- "expectedPath": "$.kind.moduleId",
2035
- "description": "Custom missing moduleId"
2903
+ "inputFile": "reject/reject-action-print-with-payload.json",
2904
+ "expectedErrorCode": "WRONG_TYPE",
2905
+ "expectedPath": "$.kind.onClick.pageRange",
2906
+ "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)"
2036
2907
  },
2037
2908
  {
2038
- "id": "reject-missing-markdown-text",
2909
+ "id": "reject-binding-float-bool",
2039
2910
  "kind": "reject",
2040
2911
  "decoder": "node",
2041
- "inputFile": "reject/reject-missing-markdown-text.json",
2042
- "expectedErrorCode": "MISSING_FIELD",
2043
- "expectedPath": "$.kind.text",
2044
- "description": "Markdown spec missing text"
2912
+ "inputFile": "reject/reject-binding-float-bool.json",
2913
+ "expectedErrorCode": "WRONG_TYPE",
2914
+ "expectedPath": "$.kind.trend",
2915
+ "description": "Binding<float> Static payload is a JSON boolean — a second float slot on the same spec, so a host guarding `value` and not `trend` is caught (Phase 1064)"
2045
2916
  },
2046
2917
  {
2047
- "id": "reject-missing-metric-value",
2918
+ "id": "reject-binding-float-sentinel-case",
2048
2919
  "kind": "reject",
2049
2920
  "decoder": "node",
2050
- "inputFile": "reject/reject-missing-metric-value.json",
2051
- "expectedErrorCode": "MISSING_FIELD",
2921
+ "inputFile": "reject/reject-binding-float-sentinel-case.json",
2922
+ "expectedErrorCode": "WRONG_TYPE",
2923
+ "expectedPath": "$.kind.fraction",
2924
+ "description": "Binding<float> bare scalar is a MIS-CASED sentinel (\"nan\") — §7's three tokens are exact, and a case-folding host would accept a document no encoder emits (Phase 1064)"
2925
+ },
2926
+ {
2927
+ "id": "reject-binding-float-string",
2928
+ "kind": "reject",
2929
+ "decoder": "node",
2930
+ "inputFile": "reject/reject-binding-float-string.json",
2931
+ "expectedErrorCode": "WRONG_TYPE",
2052
2932
  "expectedPath": "$.kind.value",
2053
- "description": "Metric spec missing value"
2933
+ "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)"
2054
2934
  },
2055
2935
  {
2056
- "id": "reject-missing-mount-scopeid",
2936
+ "id": "reject-binding-int-bool",
2057
2937
  "kind": "reject",
2058
2938
  "decoder": "node",
2059
- "inputFile": "reject/reject-missing-mount-scopeid.json",
2060
- "expectedErrorCode": "MISSING_FIELD",
2061
- "expectedPath": "$.kind.scopeId",
2062
- "description": "Mount missing scopeId (§4o)"
2939
+ "inputFile": "reject/reject-binding-int-bool.json",
2940
+ "expectedErrorCode": "WRONG_TYPE",
2941
+ "expectedPath": "$.kind.activeIndex",
2942
+ "description": "Binding<int> Static payload is a JSON boolean — §7 gives an integer slot no non-numeric form (Phase 1064)"
2063
2943
  },
2064
2944
  {
2065
- "id": "reject-missing-node-id",
2945
+ "id": "reject-binding-int-sentinel-string",
2066
2946
  "kind": "reject",
2067
2947
  "decoder": "node",
2068
- "inputFile": "reject/reject-missing-node-id.json",
2069
- "expectedErrorCode": "MISSING_FIELD",
2070
- "expectedPath": "$.id",
2071
- "description": "Node missing id"
2948
+ "inputFile": "reject/reject-binding-int-sentinel-string.json",
2949
+ "expectedErrorCode": "WRONG_TYPE",
2950
+ "expectedPath": "$.kind.activeStep",
2951
+ "description": "Binding<int> Static payload is a §7 non-finite sentinel — the sentinels are FLOAT-slot only; an integer slot has no non-finite form (Phase 1064)"
2072
2952
  },
2073
2953
  {
2074
- "id": "reject-missing-node-kind",
2954
+ "id": "reject-box-masonry-nonpositive-cols",
2075
2955
  "kind": "reject",
2076
2956
  "decoder": "node",
2077
- "inputFile": "reject/reject-missing-node-kind.json",
2078
- "expectedErrorCode": "MISSING_FIELD",
2079
- "expectedPath": "$.kind",
2080
- "description": "Node missing kind"
2957
+ "inputFile": "reject/reject-box-masonry-nonpositive-cols.json",
2958
+ "expectedErrorCode": "WRONG_TYPE",
2959
+ "expectedPath": "$.kind.layout.cols",
2960
+ "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"
2081
2961
  },
2082
2962
  {
2083
- "id": "reject-missing-switch-cases",
2963
+ "id": "reject-color-value-not-hex",
2084
2964
  "kind": "reject",
2085
2965
  "decoder": "node",
2086
- "inputFile": "reject/reject-missing-switch-cases.json",
2087
- "expectedErrorCode": "MISSING_FIELD",
2088
- "expectedPath": "$.kind.cases",
2089
- "description": "Switch missing cases"
2966
+ "inputFile": "reject/reject-color-value-not-hex.json",
2967
+ "expectedErrorCode": "WRONG_TYPE",
2968
+ "expectedPath": "$.kind.fields[0].kind.value",
2969
+ "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"
2090
2970
  },
2091
2971
  {
2092
- "id": "reject-missing-switch-default",
2972
+ "id": "reject-combobox-allowfreetext-nonbool",
2093
2973
  "kind": "reject",
2094
2974
  "decoder": "node",
2095
- "inputFile": "reject/reject-missing-switch-default.json",
2975
+ "inputFile": "reject/reject-combobox-allowfreetext-nonbool.json",
2976
+ "expectedErrorCode": "WRONG_TYPE",
2977
+ "expectedPath": "$.kind.fields[0].kind.allowFreeText",
2978
+ "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"
2979
+ },
2980
+ {
2981
+ "id": "reject-daterange-unordered",
2982
+ "kind": "reject",
2983
+ "decoder": "node",
2984
+ "inputFile": "reject/reject-daterange-unordered.json",
2985
+ "expectedErrorCode": "WRONG_TYPE",
2986
+ "expectedPath": "$.kind.fields[0].kind.value",
2987
+ "description": "DateRange literal pair with start after end — the ordered-pair rule (Phase 725)"
2988
+ },
2989
+ {
2990
+ "id": "reject-embed-missing-title",
2991
+ "kind": "reject",
2992
+ "decoder": "node",
2993
+ "inputFile": "reject/reject-embed-missing-title.json",
2994
+ "expectedErrorCode": "MISSING_FIELD",
2995
+ "expectedPath": "$.kind.title",
2996
+ "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"
2997
+ },
2998
+ {
2999
+ "id": "reject-embed-permission-nonstring",
3000
+ "kind": "reject",
3001
+ "decoder": "node",
3002
+ "inputFile": "reject/reject-embed-permission-nonstring.json",
3003
+ "expectedErrorCode": "WRONG_TYPE",
3004
+ "expectedPath": "$.kind.permissions[0]",
3005
+ "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"
3006
+ },
3007
+ {
3008
+ "id": "reject-embed-unknown-permission",
3009
+ "kind": "reject",
3010
+ "decoder": "node",
3011
+ "inputFile": "reject/reject-embed-unknown-permission.json",
3012
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3013
+ "expectedPath": "$.kind.permissions[0]",
3014
+ "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"
3015
+ },
3016
+ {
3017
+ "id": "reject-emptynodeid",
3018
+ "kind": "reject",
3019
+ "decoder": "node",
3020
+ "inputFile": "reject/reject-emptynodeid.json",
3021
+ "expectedErrorCode": "EMPTY_NODE_ID",
3022
+ "expectedPath": "$.id",
3023
+ "description": "Empty top-level id"
3024
+ },
3025
+ {
3026
+ "id": "reject-fieldrule-empty",
3027
+ "kind": "reject",
3028
+ "decoder": "node",
3029
+ "inputFile": "reject/reject-fieldrule-empty.json",
3030
+ "expectedErrorCode": "WRONG_TYPE",
3031
+ "expectedPath": "$.kind.fields[0].rule",
3032
+ "description": "FieldRule declaring no constraint — a message alone is not a rule (Phase 864)"
3033
+ },
3034
+ {
3035
+ "id": "reject-fieldrule-length-unordered",
3036
+ "kind": "reject",
3037
+ "decoder": "node",
3038
+ "inputFile": "reject/reject-fieldrule-length-unordered.json",
3039
+ "expectedErrorCode": "WRONG_TYPE",
3040
+ "expectedPath": "$.kind.fields[0].rule",
3041
+ "description": "FieldRule minLength above maxLength — the ordered-pair rule on a length pair (Phase 864)"
3042
+ },
3043
+ {
3044
+ "id": "reject-formfield-near-miss-validation",
3045
+ "kind": "reject",
3046
+ "decoder": "node",
3047
+ "inputFile": "reject/reject-formfield-near-miss-validation.json",
3048
+ "expectedErrorCode": "WRONG_TYPE",
3049
+ "expectedPath": "$.kind.fields[0].validation",
3050
+ "description": "'validation' near-miss on a FormField — the canonical key is 'rule' (Phase 864)"
3051
+ },
3052
+ {
3053
+ "id": "reject-image-expandable-nonbool",
3054
+ "kind": "reject",
3055
+ "decoder": "node",
3056
+ "inputFile": "reject/reject-image-expandable-nonbool.json",
3057
+ "expectedErrorCode": "WRONG_TYPE",
3058
+ "expectedPath": "$.kind.expandable",
3059
+ "description": "Image expandable `\"true\"` — the stringified boolean, refused rather than coerced. A truthiness rule would have to rule on `\"false\"` and `\"\"` too, and two hosts ruling differently would disagree about whether the document declares an affordance at all"
3060
+ },
3061
+ {
3062
+ "id": "reject-image-srcset-nonpositive-width",
3063
+ "kind": "reject",
3064
+ "decoder": "node",
3065
+ "inputFile": "reject/reject-image-srcset-nonpositive-width.json",
3066
+ "expectedErrorCode": "WRONG_TYPE",
3067
+ "expectedPath": "$.kind.srcSet[1].width",
3068
+ "description": "SrcSetEntry width 0 — refused: a `w` descriptor names the intrinsic pixel width a browser selects on, so a non-positive value describes a candidate that can never be selected. The first entry is well-formed on purpose, so the path has to identify the second"
3069
+ },
3070
+ {
3071
+ "id": "reject-image-srcset-null",
3072
+ "kind": "reject",
3073
+ "decoder": "node",
3074
+ "inputFile": "reject/reject-image-srcset-null.json",
3075
+ "expectedErrorCode": "WRONG_TYPE",
3076
+ "expectedPath": "$.kind.srcSet",
3077
+ "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"
3078
+ },
3079
+ {
3080
+ "id": "reject-invalid-empty",
3081
+ "kind": "reject",
3082
+ "decoder": "node",
3083
+ "inputFile": "reject/reject-invalid-empty.json",
3084
+ "expectedErrorCode": "INVALID_JSON",
3085
+ "expectedPath": "$",
3086
+ "description": "empty string"
3087
+ },
3088
+ {
3089
+ "id": "reject-invalid-garbage",
3090
+ "kind": "reject",
3091
+ "decoder": "node",
3092
+ "inputFile": "reject/reject-invalid-garbage.json",
3093
+ "expectedErrorCode": "INVALID_JSON",
3094
+ "expectedPath": "$",
3095
+ "description": "garbage input"
3096
+ },
3097
+ {
3098
+ "id": "reject-invalid-truncated",
3099
+ "kind": "reject",
3100
+ "decoder": "node",
3101
+ "inputFile": "reject/reject-invalid-truncated.json",
3102
+ "expectedErrorCode": "INVALID_JSON",
3103
+ "expectedPath": "$",
3104
+ "description": "truncated object"
3105
+ },
3106
+ {
3107
+ "id": "reject-limit-json-depth",
3108
+ "kind": "reject",
3109
+ "decoder": "node",
3110
+ "inputFile": "reject/reject-limit-json-depth.json",
3111
+ "expectedErrorCode": "LIMIT_EXCEEDED",
3112
+ "expectedPath": "$",
3113
+ "description": "§21 max JSON depth — 257 levels of bare array nesting, ONE past the limit. Well-formed and merely too deep, so LIMIT_EXCEEDED rather than INVALID_JSON (rule 2). Pins the boundary exactly; its at-the-limit twin below pins the other side"
3114
+ },
3115
+ {
3116
+ "id": "reject-limit-json-depth-at-max",
3117
+ "kind": "reject",
3118
+ "decoder": "node",
3119
+ "inputFile": "reject/reject-limit-json-depth-at-max.json",
3120
+ "expectedErrorCode": "WRONG_TYPE",
3121
+ "expectedPath": "$",
3122
+ "description": "§21 max JSON depth — 256 levels, EXACTLY at the limit. Not a valid node, so it must fail; the point is that it fails on SHAPE and not as a limit breach. Rule 1 in the one form the reject machinery can express for a syntactic bound: a host whose guard sits one level too tight answers LIMIT_EXCEEDED here and fails"
3123
+ },
3124
+ {
3125
+ "id": "reject-limit-node-depth",
3126
+ "kind": "reject",
3127
+ "decoder": "node",
3128
+ "inputFile": "reject/reject-limit-node-depth.json",
3129
+ "expectedErrorCode": "LIMIT_EXCEEDED",
3130
+ "expectedPath": "$",
3131
+ "description": "§21 max node depth — one level past the limit (25). Refused with LIMIT_EXCEEDED, never INVALID_JSON (rule 2) and never a throw (rule 3)"
3132
+ },
3133
+ {
3134
+ "id": "reject-limit-op-depth",
3135
+ "kind": "reject",
3136
+ "decoder": "op",
3137
+ "inputFile": "reject/reject-limit-op-depth.json",
3138
+ "expectedErrorCode": "LIMIT_EXCEEDED",
3139
+ "expectedPath": "$",
3140
+ "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"
3141
+ },
3142
+ {
3143
+ "id": "reject-limit-tree-item-depth",
3144
+ "kind": "reject",
3145
+ "decoder": "node",
3146
+ "inputFile": "reject/reject-limit-tree-item-depth.json",
3147
+ "expectedErrorCode": "LIMIT_EXCEEDED",
3148
+ "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]",
3149
+ "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"
3150
+ },
3151
+ {
3152
+ "id": "reject-media-autoplay-nonbool",
3153
+ "kind": "reject",
3154
+ "decoder": "node",
3155
+ "inputFile": "reject/reject-media-autoplay-nonbool.json",
3156
+ "expectedErrorCode": "WRONG_TYPE",
3157
+ "expectedPath": "$.kind.kind.autoplay",
3158
+ "description": "Media autoplay `\"true\"` — the stringified boolean, refused rather than coerced. A truthiness rule would have to rule on `\"false\"` and `\"\"` too, and two hosts ruling differently would disagree about whether a page starts playing by itself"
3159
+ },
3160
+ {
3161
+ "id": "reject-media-missing-label",
3162
+ "kind": "reject",
3163
+ "decoder": "node",
3164
+ "inputFile": "reject/reject-media-missing-label.json",
3165
+ "expectedErrorCode": "MISSING_FIELD",
3166
+ "expectedPath": "$.kind.label",
3167
+ "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"
3168
+ },
3169
+ {
3170
+ "id": "reject-media-track-default-nonbool",
3171
+ "kind": "reject",
3172
+ "decoder": "node",
3173
+ "inputFile": "reject/reject-media-track-default-nonbool.json",
3174
+ "expectedErrorCode": "WRONG_TYPE",
3175
+ "expectedPath": "$.kind.tracks[0].default",
3176
+ "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"
3177
+ },
3178
+ {
3179
+ "id": "reject-media-track-missing-srclang",
3180
+ "kind": "reject",
3181
+ "decoder": "node",
3182
+ "inputFile": "reject/reject-media-track-missing-srclang.json",
3183
+ "expectedErrorCode": "MISSING_FIELD",
3184
+ "expectedPath": "$.kind.tracks[0].srcLang",
3185
+ "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"
3186
+ },
3187
+ {
3188
+ "id": "reject-missing-binding-type",
3189
+ "kind": "reject",
3190
+ "decoder": "node",
3191
+ "inputFile": "reject/reject-missing-binding-type.json",
3192
+ "expectedErrorCode": "MISSING_FIELD",
3193
+ "expectedPath": "$.kind.value.$type",
3194
+ "description": "Binding missing $type"
3195
+ },
3196
+ {
3197
+ "id": "reject-missing-custom-moduleid",
3198
+ "kind": "reject",
3199
+ "decoder": "node",
3200
+ "inputFile": "reject/reject-missing-custom-moduleid.json",
3201
+ "expectedErrorCode": "MISSING_FIELD",
3202
+ "expectedPath": "$.kind.moduleId",
3203
+ "description": "Custom missing moduleId"
3204
+ },
3205
+ {
3206
+ "id": "reject-missing-markdown-text",
3207
+ "kind": "reject",
3208
+ "decoder": "node",
3209
+ "inputFile": "reject/reject-missing-markdown-text.json",
3210
+ "expectedErrorCode": "MISSING_FIELD",
3211
+ "expectedPath": "$.kind.text",
3212
+ "description": "Markdown spec missing text"
3213
+ },
3214
+ {
3215
+ "id": "reject-missing-metric-value",
3216
+ "kind": "reject",
3217
+ "decoder": "node",
3218
+ "inputFile": "reject/reject-missing-metric-value.json",
3219
+ "expectedErrorCode": "MISSING_FIELD",
3220
+ "expectedPath": "$.kind.value",
3221
+ "description": "Metric spec missing value"
3222
+ },
3223
+ {
3224
+ "id": "reject-missing-mount-scopeid",
3225
+ "kind": "reject",
3226
+ "decoder": "node",
3227
+ "inputFile": "reject/reject-missing-mount-scopeid.json",
3228
+ "expectedErrorCode": "MISSING_FIELD",
3229
+ "expectedPath": "$.kind.scopeId",
3230
+ "description": "Mount missing scopeId (§4o)"
3231
+ },
3232
+ {
3233
+ "id": "reject-missing-node-id",
3234
+ "kind": "reject",
3235
+ "decoder": "node",
3236
+ "inputFile": "reject/reject-missing-node-id.json",
3237
+ "expectedErrorCode": "MISSING_FIELD",
3238
+ "expectedPath": "$.id",
3239
+ "description": "Node missing id"
3240
+ },
3241
+ {
3242
+ "id": "reject-missing-node-kind",
3243
+ "kind": "reject",
3244
+ "decoder": "node",
3245
+ "inputFile": "reject/reject-missing-node-kind.json",
3246
+ "expectedErrorCode": "MISSING_FIELD",
3247
+ "expectedPath": "$.kind",
3248
+ "description": "Node missing kind"
3249
+ },
3250
+ {
3251
+ "id": "reject-missing-switch-cases",
3252
+ "kind": "reject",
3253
+ "decoder": "node",
3254
+ "inputFile": "reject/reject-missing-switch-cases.json",
3255
+ "expectedErrorCode": "MISSING_FIELD",
3256
+ "expectedPath": "$.kind.cases",
3257
+ "description": "Switch missing cases"
3258
+ },
3259
+ {
3260
+ "id": "reject-missing-switch-default",
3261
+ "kind": "reject",
3262
+ "decoder": "node",
3263
+ "inputFile": "reject/reject-missing-switch-default.json",
2096
3264
  "expectedErrorCode": "MISSING_FIELD",
2097
3265
  "expectedPath": "$.kind.default",
2098
3266
  "description": "Switch missing default Node"
@@ -2106,6 +3274,60 @@
2106
3274
  "expectedPath": "$.kind.stateKey",
2107
3275
  "description": "Switch missing stateKey"
2108
3276
  },
3277
+ {
3278
+ "id": "reject-modal-modality-nonstring",
3279
+ "kind": "reject",
3280
+ "decoder": "node",
3281
+ "inputFile": "reject/reject-modal-modality-nonstring.json",
3282
+ "expectedErrorCode": "WRONG_TYPE",
3283
+ "expectedPath": "$.kind.modality",
3284
+ "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"
3285
+ },
3286
+ {
3287
+ "id": "reject-modal-modality-unknown",
3288
+ "kind": "reject",
3289
+ "decoder": "node",
3290
+ "inputFile": "reject/reject-modal-modality-unknown.json",
3291
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3292
+ "expectedPath": "$.kind.modality",
3293
+ "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"
3294
+ },
3295
+ {
3296
+ "id": "reject-nearmiss-a11y-aria-hidden",
3297
+ "kind": "reject",
3298
+ "decoder": "node",
3299
+ "inputFile": "reject/reject-nearmiss-a11y-aria-hidden.json",
3300
+ "expectedErrorCode": "WRONG_TYPE",
3301
+ "expectedPath": "$.accessibility.aria-hidden",
3302
+ "description": "accessibility 'aria-hidden' — the ARIA ATTRIBUTE name where the wire wants the slot name 'hidden'; the projection slot two hosts once dropped entirely (Phase 959)"
3303
+ },
3304
+ {
3305
+ "id": "reject-nearmiss-a11y-arialabel",
3306
+ "kind": "reject",
3307
+ "decoder": "node",
3308
+ "inputFile": "reject/reject-nearmiss-a11y-arialabel.json",
3309
+ "expectedErrorCode": "WRONG_TYPE",
3310
+ "expectedPath": "$.accessibility.ariaLabel",
3311
+ "description": "accessibility 'ariaLabel' — the camelCase JSX prior for the accessible name; MEASURED in the emission corpora, and the whole declaration vanished (Phase 959)"
3312
+ },
3313
+ {
3314
+ "id": "reject-nearmiss-a11y-live",
3315
+ "kind": "reject",
3316
+ "decoder": "node",
3317
+ "inputFile": "reject/reject-nearmiss-a11y-live.json",
3318
+ "expectedErrorCode": "WRONG_TYPE",
3319
+ "expectedPath": "$.accessibility.live",
3320
+ "description": "accessibility 'live' — the sharpest of the set: six measured emissions against liveRegion's twelve, and the HTML idiom it comes from also spells a BOOLEAN, so it is not a safe alias (Phase 959)"
3321
+ },
3322
+ {
3323
+ "id": "reject-nearmiss-a11y-liveregion-case",
3324
+ "kind": "reject",
3325
+ "decoder": "node",
3326
+ "inputFile": "reject/reject-nearmiss-a11y-liveregion-case.json",
3327
+ "expectedErrorCode": "WRONG_TYPE",
3328
+ "expectedPath": "$.accessibility.liveregion",
3329
+ "description": "accessibility 'liveregion' — the canonical slot name un-cased to the ARIA attribute spelling; the wire is camelCase (Phase 959)"
3330
+ },
2109
3331
  {
2110
3332
  "id": "reject-nearmiss-column-readonly",
2111
3333
  "kind": "reject",
@@ -2214,6 +3436,15 @@
2214
3436
  "expectedPath": "$.parentId",
2215
3437
  "description": "InsertChild missing parentId"
2216
3438
  },
3439
+ {
3440
+ "id": "reject-op-insertchild-retired-position",
3441
+ "kind": "reject",
3442
+ "decoder": "op",
3443
+ "inputFile": "reject/reject-op-insertchild-retired-position.json",
3444
+ "expectedErrorCode": "WRONG_TYPE",
3445
+ "expectedPath": "$.position",
3446
+ "description": "InsertChild carries the retired 'position' — the migration window is closed (Phase 687)"
3447
+ },
2217
3448
  {
2218
3449
  "id": "reject-op-missing-type",
2219
3450
  "kind": "reject",
@@ -2223,6 +3454,15 @@
2223
3454
  "expectedPath": "$.$type",
2224
3455
  "description": "Op missing $type entirely"
2225
3456
  },
3457
+ {
3458
+ "id": "reject-op-movenode-retired-newposition",
3459
+ "kind": "reject",
3460
+ "decoder": "op",
3461
+ "inputFile": "reject/reject-op-movenode-retired-newposition.json",
3462
+ "expectedErrorCode": "WRONG_TYPE",
3463
+ "expectedPath": "$.newPosition",
3464
+ "description": "MoveNode carries the retired 'newPosition' — the migration window is closed (Phase 687)"
3465
+ },
2226
3466
  {
2227
3467
  "id": "reject-op-reorderchildren-wrongtype",
2228
3468
  "kind": "reject",
@@ -2250,6 +3490,33 @@
2250
3490
  "expectedPath": "$.path",
2251
3491
  "description": "UpdateProp missing path"
2252
3492
  },
3493
+ {
3494
+ "id": "reject-op-updatestyle-direction-nonstring",
3495
+ "kind": "reject",
3496
+ "decoder": "op",
3497
+ "inputFile": "reject/reject-op-updatestyle-direction-nonstring.json",
3498
+ "expectedErrorCode": "WRONG_TYPE",
3499
+ "expectedPath": "$.style.direction",
3500
+ "description": "UpdateStyle style.direction not a JSON string (Phase 1472)"
3501
+ },
3502
+ {
3503
+ "id": "reject-op-updatestyle-direction-unknown",
3504
+ "kind": "reject",
3505
+ "decoder": "op",
3506
+ "inputFile": "reject/reject-op-updatestyle-direction-unknown.json",
3507
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3508
+ "expectedPath": "$.style.direction",
3509
+ "description": "UpdateStyle style.direction outside the closed set — the op arm's twin of the node vector above (Phase 1472)"
3510
+ },
3511
+ {
3512
+ "id": "reject-rating-max-zero",
3513
+ "kind": "reject",
3514
+ "decoder": "node",
3515
+ "inputFile": "reject/reject-rating-max-zero.json",
3516
+ "expectedErrorCode": "WRONG_TYPE",
3517
+ "expectedPath": "$.kind.fields[0].kind.max",
3518
+ "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"
3519
+ },
2253
3520
  {
2254
3521
  "id": "reject-setstate-value-and-valuefrom",
2255
3522
  "kind": "reject",
@@ -2259,6 +3526,69 @@
2259
3526
  "expectedPath": "$.kind.onClick.valueFrom",
2260
3527
  "description": "SetState carrying BOTH 'value' and 'valueFrom' — exactly one is allowed; the didactic names both fields and how each is used (Phase 818)"
2261
3528
  },
3529
+ {
3530
+ "id": "reject-style-direction-nonstring",
3531
+ "kind": "reject",
3532
+ "decoder": "node",
3533
+ "inputFile": "reject/reject-style-direction-nonstring.json",
3534
+ "expectedErrorCode": "WRONG_TYPE",
3535
+ "expectedPath": "$.style.direction",
3536
+ "description": "style.direction not a JSON string (Phase 1472)"
3537
+ },
3538
+ {
3539
+ "id": "reject-style-direction-unknown",
3540
+ "kind": "reject",
3541
+ "decoder": "node",
3542
+ "inputFile": "reject/reject-style-direction-unknown.json",
3543
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3544
+ "expectedPath": "$.style.direction",
3545
+ "description": "style.direction outside the closed set — the message names auto | ltr | rtl, and the near miss is the upper-case spelling (Phase 1472)"
3546
+ },
3547
+ {
3548
+ "id": "reject-switch-autoadvance-fractional",
3549
+ "kind": "reject",
3550
+ "decoder": "node",
3551
+ "inputFile": "reject/reject-switch-autoadvance-fractional.json",
3552
+ "expectedErrorCode": "WRONG_TYPE",
3553
+ "expectedPath": "$.kind.autoAdvanceMs",
3554
+ "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)"
3555
+ },
3556
+ {
3557
+ "id": "reject-switch-autoadvance-negative",
3558
+ "kind": "reject",
3559
+ "decoder": "node",
3560
+ "inputFile": "reject/reject-switch-autoadvance-negative.json",
3561
+ "expectedErrorCode": "WRONG_TYPE",
3562
+ "expectedPath": "$.kind.autoAdvanceMs",
3563
+ "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)"
3564
+ },
3565
+ {
3566
+ "id": "reject-switch-autoadvance-zero",
3567
+ "kind": "reject",
3568
+ "decoder": "node",
3569
+ "inputFile": "reject/reject-switch-autoadvance-zero.json",
3570
+ "expectedErrorCode": "WRONG_TYPE",
3571
+ "expectedPath": "$.kind.autoAdvanceMs",
3572
+ "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)"
3573
+ },
3574
+ {
3575
+ "id": "reject-tokens-closed-without-suggestions",
3576
+ "kind": "reject",
3577
+ "decoder": "node",
3578
+ "inputFile": "reject/reject-tokens-closed-without-suggestions.json",
3579
+ "expectedErrorCode": "WRONG_TYPE",
3580
+ "expectedPath": "$.kind.fields[0].kind.allowFreeText",
3581
+ "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"
3582
+ },
3583
+ {
3584
+ "id": "reject-tokens-value-not-list",
3585
+ "kind": "reject",
3586
+ "decoder": "node",
3587
+ "inputFile": "reject/reject-tokens-value-not-list.json",
3588
+ "expectedErrorCode": "WRONG_TYPE",
3589
+ "expectedPath": "$.kind.fields[0].kind.value",
3590
+ "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"
3591
+ },
2262
3592
  {
2263
3593
  "id": "reject-tonedpill-unknown-tone",
2264
3594
  "kind": "reject",
@@ -2269,13 +3599,40 @@
2269
3599
  "description": "TonedPill tone-map value outside ToneVariant — the message names the seven legal tones (Phase 750)"
2270
3600
  },
2271
3601
  {
2272
- "id": "reject-transform-source-empty-wrapper",
3602
+ "id": "reject-tooltip-nonstring",
2273
3603
  "kind": "reject",
2274
3604
  "decoder": "node",
2275
- "inputFile": "reject/reject-transform-source-empty-wrapper.json",
3605
+ "inputFile": "reject/reject-tooltip-nonstring.json",
2276
3606
  "expectedErrorCode": "WRONG_TYPE",
2277
- "expectedPath": "$.kind.source.source",
2278
- "description": "fuaran#815a State wrapper carrying only a key (no defaultValue/value) is NOT unwrappable: the Transform source stays a binding object and the columnar decode refuses it"
3607
+ "expectedPath": "$.tooltip",
3608
+ "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"
3609
+ },
3610
+ {
3611
+ "id": "reject-tree-item-missing-id",
3612
+ "kind": "reject",
3613
+ "decoder": "node",
3614
+ "inputFile": "reject/reject-tree-item-missing-id.json",
3615
+ "expectedErrorCode": "MISSING_FIELD",
3616
+ "expectedPath": "$.kind.items[0].id",
3617
+ "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"
3618
+ },
3619
+ {
3620
+ "id": "reject-tree-item-missing-label",
3621
+ "kind": "reject",
3622
+ "decoder": "node",
3623
+ "inputFile": "reject/reject-tree-item-missing-label.json",
3624
+ "expectedErrorCode": "MISSING_FIELD",
3625
+ "expectedPath": "$.kind.items[0].label",
3626
+ "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"
3627
+ },
3628
+ {
3629
+ "id": "reject-tree-nested-item-missing-id",
3630
+ "kind": "reject",
3631
+ "decoder": "node",
3632
+ "inputFile": "reject/reject-tree-nested-item-missing-id.json",
3633
+ "expectedErrorCode": "MISSING_FIELD",
3634
+ "expectedPath": "$.kind.items[0].children[0].id",
3635
+ "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"
2279
3636
  },
2280
3637
  {
2281
3638
  "id": "reject-unknown-binding",
@@ -2286,6 +3643,15 @@
2286
3643
  "expectedPath": "$.kind.value.$type",
2287
3644
  "description": "Binding $type 'Bogus'"
2288
3645
  },
3646
+ {
3647
+ "id": "reject-unknown-capture-source",
3648
+ "kind": "reject",
3649
+ "decoder": "node",
3650
+ "inputFile": "reject/reject-unknown-capture-source.json",
3651
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3652
+ "expectedPath": "$.kind.capture",
3653
+ "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"
3654
+ },
2289
3655
  {
2290
3656
  "id": "reject-unknown-drawing-curve-command",
2291
3657
  "kind": "reject",
@@ -2304,6 +3670,15 @@
2304
3670
  "expectedPath": "$.kind.shapes[0].$type",
2305
3671
  "description": "Drawing Shape $type 'Blob' — the closed-shape default-deny (Phase 524)"
2306
3672
  },
3673
+ {
3674
+ "id": "reject-unknown-image-aspect",
3675
+ "kind": "reject",
3676
+ "decoder": "node",
3677
+ "inputFile": "reject/reject-unknown-image-aspect.json",
3678
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3679
+ "expectedPath": "$.kind.aspectRatio",
3680
+ "description": "ImageAspect value '16/9' — the CSS ratio spelling, refused: the slot is a closed token vocabulary (Square | FourThree | ThreeTwo | SixteenNine), and admitting a numeric pair would reintroduce the free-form escape the tokens replace"
3681
+ },
2307
3682
  {
2308
3683
  "id": "reject-unknown-link-protection",
2309
3684
  "kind": "reject",
@@ -2313,6 +3688,15 @@
2313
3688
  "expectedPath": "$.kind.protection",
2314
3689
  "description": "LinkProtection value 'rot13' — the closed protection DU default-denies (Phase 812)"
2315
3690
  },
3691
+ {
3692
+ "id": "reject-unknown-media-kind",
3693
+ "kind": "reject",
3694
+ "decoder": "node",
3695
+ "inputFile": "reject/reject-unknown-media-kind.json",
3696
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3697
+ "expectedPath": "$.kind.kind.$type",
3698
+ "description": "MediaKind case 'Stream' — refused. The variant set is closed at Video | Audio; a third surface is an admission, not a spelling a decoder may guess at. The path carries `.$type` because MediaKind is discriminated, unlike the bare-enum slots"
3699
+ },
2316
3700
  {
2317
3701
  "id": "reject-unknown-mount-direction",
2318
3702
  "kind": "reject",
@@ -2349,6 +3733,42 @@
2349
3733
  "expectedPath": "$.style.tone",
2350
3734
  "description": "ToneVariant value 'Magenta'"
2351
3735
  },
3736
+ {
3737
+ "id": "reject-unknown-trend-polarity",
3738
+ "kind": "reject",
3739
+ "decoder": "node",
3740
+ "inputFile": "reject/reject-unknown-trend-polarity.json",
3741
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3742
+ "expectedPath": "$.kind.trendPolarity",
3743
+ "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"
3744
+ },
3745
+ {
3746
+ "id": "reject-upload-acceptpaste-nonbool",
3747
+ "kind": "reject",
3748
+ "decoder": "node",
3749
+ "inputFile": "reject/reject-upload-acceptpaste-nonbool.json",
3750
+ "expectedErrorCode": "WRONG_TYPE",
3751
+ "expectedPath": "$.kind.acceptPaste",
3752
+ "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"
3753
+ },
3754
+ {
3755
+ "id": "reject-upload-destination-empty",
3756
+ "kind": "reject",
3757
+ "decoder": "node",
3758
+ "inputFile": "reject/reject-upload-destination-empty.json",
3759
+ "expectedErrorCode": "WRONG_TYPE",
3760
+ "expectedPath": "$.kind.destination",
3761
+ "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"
3762
+ },
3763
+ {
3764
+ "id": "reject-upload-droptarget-nonbool",
3765
+ "kind": "reject",
3766
+ "decoder": "node",
3767
+ "inputFile": "reject/reject-upload-droptarget-nonbool.json",
3768
+ "expectedErrorCode": "WRONG_TYPE",
3769
+ "expectedPath": "$.kind.dropTarget",
3770
+ "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"
3771
+ },
2352
3772
  {
2353
3773
  "id": "reject-wrongnodekind-sparkler",
2354
3774
  "kind": "reject",
@@ -2367,6 +3787,15 @@
2367
3787
  "expectedPath": "$.kind.$type",
2368
3788
  "description": "Top-level kind 'Widget' is not a recognised node kind"
2369
3789
  },
3790
+ {
3791
+ "id": "reject-wrongtype-box-break-before",
3792
+ "kind": "reject",
3793
+ "decoder": "node",
3794
+ "inputFile": "reject/reject-wrongtype-box-break-before.json",
3795
+ "expectedErrorCode": "WRONG_TYPE",
3796
+ "expectedPath": "$.kind.breakBefore",
3797
+ "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)"
3798
+ },
2370
3799
  {
2371
3800
  "id": "reject-wrongtype-box-children",
2372
3801
  "kind": "reject",
@@ -2376,6 +3805,24 @@
2376
3805
  "expectedPath": "$.kind.children",
2377
3806
  "description": "Box children is object not array"
2378
3807
  },
3808
+ {
3809
+ "id": "reject-wrongtype-box-keep-together",
3810
+ "kind": "reject",
3811
+ "decoder": "node",
3812
+ "inputFile": "reject/reject-wrongtype-box-keep-together.json",
3813
+ "expectedErrorCode": "WRONG_TYPE",
3814
+ "expectedPath": "$.kind.keepTogether",
3815
+ "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)"
3816
+ },
3817
+ {
3818
+ "id": "reject-wrongtype-clipboard-payload",
3819
+ "kind": "reject",
3820
+ "decoder": "node",
3821
+ "inputFile": "reject/reject-wrongtype-clipboard-payload.json",
3822
+ "expectedErrorCode": "WRONG_TYPE",
3823
+ "expectedPath": "$.kind.onClick.text",
3824
+ "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)"
3825
+ },
2379
3826
  {
2380
3827
  "id": "reject-wrongtype-discriminator",
2381
3828
  "kind": "reject",
@@ -2394,6 +3841,33 @@
2394
3841
  "expectedPath": "$.kind.defaultSort.column",
2395
3842
  "description": "bound-grid defaultSort column -1 — the same non-negative bound the staticRows spelling carries, same record, same message (Phase 861)"
2396
3843
  },
3844
+ {
3845
+ "id": "reject-wrongtype-grid-exportable",
3846
+ "kind": "reject",
3847
+ "decoder": "node",
3848
+ "inputFile": "reject/reject-wrongtype-grid-exportable.json",
3849
+ "expectedErrorCode": "WRONG_TYPE",
3850
+ "expectedPath": "$.kind.exportable",
3851
+ "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)"
3852
+ },
3853
+ {
3854
+ "id": "reject-wrongtype-grid-exportable-number",
3855
+ "kind": "reject",
3856
+ "decoder": "node",
3857
+ "inputFile": "reject/reject-wrongtype-grid-exportable-number.json",
3858
+ "expectedErrorCode": "WRONG_TYPE",
3859
+ "expectedPath": "$.kind.exportable",
3860
+ "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)"
3861
+ },
3862
+ {
3863
+ "id": "reject-wrongtype-grid-keep-rows-together",
3864
+ "kind": "reject",
3865
+ "decoder": "node",
3866
+ "inputFile": "reject/reject-wrongtype-grid-keep-rows-together.json",
3867
+ "expectedErrorCode": "WRONG_TYPE",
3868
+ "expectedPath": "$.kind.keepRowsTogether",
3869
+ "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)"
3870
+ },
2397
3871
  {
2398
3872
  "id": "reject-wrongtype-grid-page-size-zero",
2399
3873
  "kind": "reject",
@@ -2403,6 +3877,33 @@
2403
3877
  "expectedPath": "$.kind.pageSize",
2404
3878
  "description": "grid pageSize 0 — a page of no rows names no page; the schema says the same with minimum:1 (Phase 862)"
2405
3879
  },
3880
+ {
3881
+ "id": "reject-wrongtype-grid-repeat-header",
3882
+ "kind": "reject",
3883
+ "decoder": "node",
3884
+ "inputFile": "reject/reject-wrongtype-grid-repeat-header.json",
3885
+ "expectedErrorCode": "WRONG_TYPE",
3886
+ "expectedPath": "$.kind.repeatHeader",
3887
+ "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)"
3888
+ },
3889
+ {
3890
+ "id": "reject-wrongtype-grid-transfer-in-key",
3891
+ "kind": "reject",
3892
+ "decoder": "node",
3893
+ "inputFile": "reject/reject-wrongtype-grid-transfer-in-key.json",
3894
+ "expectedErrorCode": "WRONG_TYPE",
3895
+ "expectedPath": "$.kind.transferInKey",
3896
+ "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)"
3897
+ },
3898
+ {
3899
+ "id": "reject-wrongtype-grid-transfer-out-key",
3900
+ "kind": "reject",
3901
+ "decoder": "node",
3902
+ "inputFile": "reject/reject-wrongtype-grid-transfer-out-key.json",
3903
+ "expectedErrorCode": "WRONG_TYPE",
3904
+ "expectedPath": "$.kind.transferOutKey",
3905
+ "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)"
3906
+ },
2406
3907
  {
2407
3908
  "id": "reject-wrongtype-heading-level",
2408
3909
  "kind": "reject",