@fuaran-ui/conformance 0.19.0 → 0.21.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 (65) hide show
  1. package/corpus/lenient/lenient-1521-int-slot-integral-float.expected.json +1 -0
  2. package/corpus/lenient/lenient-1521-int-slot-integral-float.json +1 -0
  3. package/corpus/lenient/lenient-1521-payload-integer-beyond-int53.expected.json +1 -0
  4. package/corpus/lenient/lenient-1521-payload-integer-beyond-int53.json +1 -0
  5. package/corpus/lenient/lenient-filterable-static-dashboard-compact.expected.json +1 -1
  6. package/corpus/lenient/lenient-navigate-target-self.expected.json +1 -0
  7. package/corpus/lenient/lenient-navigate-target-self.json +1 -0
  8. package/corpus/manifest.json +437 -0
  9. package/corpus/nodes/action-confirm-cancel.json +1 -0
  10. package/corpus/nodes/action-confirm.json +1 -0
  11. package/corpus/nodes/action-focus.json +1 -0
  12. package/corpus/nodes/action-navigate-bound.json +1 -0
  13. package/corpus/nodes/action-navigate-target.json +1 -0
  14. package/corpus/nodes/chart-annotation-bands.json +1 -0
  15. package/corpus/nodes/chart-annotation-events.json +1 -0
  16. package/corpus/nodes/chart-annotations.json +1 -0
  17. package/corpus/nodes/chart-axis-titles.json +1 -1
  18. package/corpus/nodes/chart-data-labels.json +1 -1
  19. package/corpus/nodes/chart-legend-position.json +1 -1
  20. package/corpus/nodes/chart-state-rows.json +1 -1
  21. package/corpus/nodes/chart-temporal-x.json +1 -1
  22. package/corpus/nodes/chart-value-format.json +1 -1
  23. package/corpus/nodes/custom-nonascii-keys.json +1 -0
  24. package/corpus/nodes/expr-params-state-selection.json +1 -0
  25. package/corpus/nodes/expr-scalar.json +1 -0
  26. package/corpus/nodes/filterable-static-dashboard.json +1 -1
  27. package/corpus/nodes/form-local-declared.json +1 -0
  28. package/corpus/nodes/format-since.json +1 -0
  29. package/corpus/nodes/metric-float-1e17.json +1 -0
  30. package/corpus/nodes/node-visible.json +1 -0
  31. package/corpus/nodes/now-grain.json +1 -0
  32. package/corpus/nodes/switch-on-transform-scalar.json +1 -0
  33. package/corpus/nodes/switch-predicate-only.json +1 -0
  34. package/corpus/nodes/switch-predicate.json +1 -0
  35. package/corpus/reject/reject-binding-floatseq-sentinel-case.json +1 -0
  36. package/corpus/reject/reject-chart-annotation-date-unparseable.json +1 -0
  37. package/corpus/reject/reject-chart-annotation-nonfinite.json +1 -0
  38. package/corpus/reject/reject-chart-annotation-range-unordered.json +1 -0
  39. package/corpus/reject/reject-confirm-missing-onconfirm.json +1 -0
  40. package/corpus/reject/reject-confirm-nested.json +1 -0
  41. package/corpus/reject/reject-expr-col-reference.json +1 -0
  42. package/corpus/reject/reject-expr-unbound-param.json +1 -0
  43. package/corpus/reject/reject-int-slot-fractional.json +1 -0
  44. package/corpus/reject/reject-int-slot-out-of-range.json +1 -0
  45. package/corpus/reject/reject-json-bare-nan.json +1 -0
  46. package/corpus/reject/reject-json-duplicate-key.json +1 -0
  47. package/corpus/reject/reject-json-lone-high-surrogate.json +1 -0
  48. package/corpus/reject/reject-json-lone-low-surrogate.json +1 -0
  49. package/corpus/reject/reject-json-number-empty-exponent.json +1 -0
  50. package/corpus/reject/reject-json-number-leading-plus.json +1 -0
  51. package/corpus/reject/reject-json-number-leading-zero.json +1 -0
  52. package/corpus/reject/reject-json-number-no-integer-part.json +1 -0
  53. package/corpus/reject/reject-json-number-trailing-point.json +1 -0
  54. package/corpus/reject/reject-json-raw-control-char.json +1 -0
  55. package/corpus/reject/reject-json-surrogate-pair-split.json +1 -0
  56. package/corpus/reject/reject-json-trailing-content.json +1 -0
  57. package/corpus/reject/reject-local-codec-no-inverse.json +1 -0
  58. package/corpus/reject/reject-local-oncommit-and-committo.json +1 -0
  59. package/corpus/reject/reject-navigate-target-invalid.json +1 -0
  60. package/corpus/reject/reject-now-grain-invalid.json +1 -0
  61. package/corpus/reject/reject-spark-element-nonnumeric.json +1 -0
  62. package/corpus/reject/reject-switch-case-match-and-when.json +1 -0
  63. package/corpus/reject/reject-switch-case-neither.json +1 -0
  64. package/corpus/schema.json +1580 -76
  65. package/package.json +3 -3
@@ -0,0 +1 @@
1
+ {"id":"skel-1","kind":{"$type":"Skeleton","rows":3}}
@@ -0,0 +1 @@
1
+ {"id":"skel-1","kind":{"$type":"Skeleton","rows":3.0}}
@@ -0,0 +1 @@
1
+ {"id":"custom-int53","kind":{"$type":"Custom","componentId":"trend-card","moduleId":"analytics","props":{"beyond":9007199254740992,"boundary":9007199254740991}}}
@@ -0,0 +1 @@
1
+ {"id":"custom-int53","kind":{"$type":"Custom","componentId":"trend-card","moduleId":"analytics","props":{"beyond":9007199254740993,"boundary":9007199254740991}}}
@@ -1 +1 @@
1
- {"id":"filterable-static-dashboard","kind":{"$type":"Box","children":[{"id":"content-filters","kind":{"$type":"Filters","items":[{"kind":{"$type":"Choice","options":{"$type":"Static","value":[{"label":"EMEA","value":"emea"},{"label":"Americas","value":"amer"}]}},"label":"Region","name":"region"},{"kind":{"$type":"Choice","options":{"$type":"Static","value":[{"label":"Drama","value":"drama"},{"label":"Documentary","value":"docs"}]}},"label":"Genre","name":"genre"}]}},{"id":"retention-chart","kind":{"$type":"Chart","kind":"Line","source":{"$type":"Transform","params":[{"from":{"$type":"Filter","name":"region"},"name":"region"},{"from":{"$type":"Filter","name":"genre"},"name":"genre"}],"pipeline":[{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"region"},"op":"eq","right":{"$type":"param","name":"region"}}},{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"genre"},"op":"eq","right":{"$type":"param","name":"genre"}}}],"source":{"columns":{"genre":{"validity":[true,true],"values":["drama","docs"]},"month":{"validity":[true,true],"values":["jan","jan"]},"region":{"validity":[true,true],"values":["emea","amer"]},"retention":{"validity":[true,true],"values":[0.62,0.55]}},"schema":[{"name":"genre","type":"string"},{"name":"month","type":"string"},{"name":"region","type":"string"},{"name":"retention","type":"float"}]}},"stacked":false,"title":"Retention","xField":"month","yFields":["retention"]}},{"id":"episode-grid","kind":{"$type":"DataGrid","columns":[{"field":"month","kind":{"$type":"Text"},"label":"Month"},{"field":"retention","kind":{"$type":"Text"},"label":"Retention"}],"rowKeyField":"month","source":{"$type":"Transform","params":[{"from":{"$type":"Filter","name":"region"},"name":"region"},{"from":{"$type":"Filter","name":"genre"},"name":"genre"}],"pipeline":[{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"region"},"op":"eq","right":{"$type":"param","name":"region"}}},{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"genre"},"op":"eq","right":{"$type":"param","name":"genre"}}}],"source":{"columns":{"genre":{"validity":[true,true],"values":["drama","docs"]},"month":{"validity":[true,true],"values":["jan","jan"]},"region":{"validity":[true,true],"values":["emea","amer"]},"retention":{"validity":[true,true],"values":[0.62,0.55]}},"schema":[{"name":"genre","type":"string"},{"name":"month","type":"string"},{"name":"region","type":"string"},{"name":"retention","type":"float"}]}}}}],"heading":"Content performance","layout":{"$type":"Auto"},"role":"Dashboard"}}
1
+ {"id":"filterable-static-dashboard","kind":{"$type":"Box","children":[{"id":"content-filters","kind":{"$type":"Filters","items":[{"kind":{"$type":"Choice","options":{"$type":"Static","value":[{"label":"EMEA","value":"emea"},{"label":"Americas","value":"amer"}]}},"label":"Region","name":"region"},{"kind":{"$type":"Choice","options":{"$type":"Static","value":[{"label":"Drama","value":"drama"},{"label":"Documentary","value":"docs"}]}},"label":"Genre","name":"genre"}]}},{"id":"retention-chart","kind":{"$type":"Chart","kind":"Line","source":{"$type":"Transform","params":[{"from":{"$type":"Filter","name":"region"},"name":"region"},{"from":{"$type":"Filter","name":"genre"},"name":"genre"}],"pipeline":[{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"region"},"op":"eq","right":{"$type":"param","name":"region"}}},{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"genre"},"op":"eq","right":{"$type":"param","name":"genre"}}}],"source":{"columns":{"genre":{"validity":[true,true],"values":["drama","docs"]},"month":{"validity":[true,true],"values":["jan","jan"]},"region":{"validity":[true,true],"values":["emea","amer"]},"retention":{"validity":[true,true],"values":[0.62,0.55]}},"schema":[{"name":"genre","type":"string"},{"name":"month","type":"string"},{"name":"region","type":"string"},{"name":"retention","type":"float"}]}},"title":"Retention","xField":"month","yFields":["retention"]}},{"id":"episode-grid","kind":{"$type":"DataGrid","columns":[{"field":"month","kind":{"$type":"Text"},"label":"Month"},{"field":"retention","kind":{"$type":"Text"},"label":"Retention"}],"rowKeyField":"month","source":{"$type":"Transform","params":[{"from":{"$type":"Filter","name":"region"},"name":"region"},{"from":{"$type":"Filter","name":"genre"},"name":"genre"}],"pipeline":[{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"region"},"op":"eq","right":{"$type":"param","name":"region"}}},{"$type":"filter","pred":{"$type":"binary","left":{"$type":"col","name":"genre"},"op":"eq","right":{"$type":"param","name":"genre"}}}],"source":{"columns":{"genre":{"validity":[true,true],"values":["drama","docs"]},"month":{"validity":[true,true],"values":["jan","jan"]},"region":{"validity":[true,true],"values":["emea","amer"]},"retention":{"validity":[true,true],"values":[0.62,0.55]}},"schema":[{"name":"genre","type":"string"},{"name":"month","type":"string"},{"name":"region","type":"string"},{"name":"retention","type":"float"}]}}}}],"heading":"Content performance","layout":{"$type":"Auto"},"role":"Dashboard"}}
@@ -0,0 +1 @@
1
+ {"id":"len-nav-self","kind":{"$type":"Button","label":"Home","onClick":{"$type":"Navigate","route":"/"},"variant":"Primary"}}
@@ -0,0 +1 @@
1
+ {"id":"len-nav-self","kind":{"$type":"Button","label":{"$type":"Literal","text":"Home"},"onClick":{"$type":"Navigate","route":"/","target":"Self"},"variant":"Primary"}}
@@ -554,6 +554,22 @@
554
554
  "expectedFile": "lenient/lenient-1126-clipboard-literal-envelope.expected.json",
555
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
556
  },
557
+ {
558
+ "id": "lenient-1521-int-slot-integral-float",
559
+ "kind": "lenient-accept",
560
+ "decoder": "node",
561
+ "inputFile": "lenient/lenient-1521-int-slot-integral-float.json",
562
+ "expectedFile": "lenient/lenient-1521-int-slot-integral-float.expected.json",
563
+ "description": "§7.1 — an integer-VALUED number written with a fractional part decodes at an integer slot and canonicalises to the integer spelling. Two hosts refused `3.0` outright, which refuses a document whose intent is unambiguous for its spelling; `2.5` at the same slot stays a WRONG_TYPE, so the accept set widens by exactly the values an integer slot can hold"
564
+ },
565
+ {
566
+ "id": "lenient-1521-payload-integer-beyond-int53",
567
+ "kind": "lenient-accept",
568
+ "decoder": "node",
569
+ "inputFile": "lenient/lenient-1521-payload-integer-beyond-int53.json",
570
+ "expectedFile": "lenient/lenient-1521-payload-integer-beyond-int53.expected.json",
571
+ "description": "§2 rule 5 — integer identity in a rule-12 payload stops at ±(2⁵³−1). `boundary` is at the limit and survives verbatim; `beyond` is one past it, has no representation every host holds exactly, and decodes to the nearest double. Pinned as a lenient NORMALISATION rather than left implicit, because three hosts silently rounded it, one kept an int64 and one an arbitrary-precision integer — the same document, five canonical byte sequences, five hash-chain digests"
572
+ },
557
573
  {
558
574
  "id": "lenient-460-alias-emphasis-muted",
559
575
  "kind": "lenient-accept",
@@ -818,6 +834,14 @@
818
834
  "expectedFile": "lenient/lenient-master-detail-preselected-compact.expected.json",
819
835
  "description": "Phase 719 — the compact authoring twin of `master-detail-preselected`: all-valid columns as bare arrays (Core#88) and the inferable schema omitted; the canonical envelope stays the encoder's output. The pack teaches this form by example — the exemplar/prose contradiction measurably taught the envelope (claude-opus 81% envelope share, 2026-07-28 census)."
820
836
  },
837
+ {
838
+ "id": "lenient-navigate-target-self",
839
+ "kind": "lenient-accept",
840
+ "decoder": "node",
841
+ "inputFile": "lenient/lenient-navigate-target-self.json",
842
+ "expectedFile": "lenient/lenient-navigate-target-self.expected.json",
843
+ "description": "Phase 1536 — an explicit `target: \"Self\"` normalises away; `Self` is omitted at default, so the canonical form carries no `target` member at all"
844
+ },
821
845
  {
822
846
  "id": "lenient-null-static-options",
823
847
  "kind": "lenient-accept",
@@ -1106,6 +1130,46 @@
1106
1130
  "expectedFile": "nodes/a11y-wrapper-state-bound.json",
1107
1131
  "description": "Accessibility (Phase 955 — State-bound label + hidden, mixed-case custom role doc-pageFooter, off)"
1108
1132
  },
1133
+ {
1134
+ "id": "action-confirm",
1135
+ "kind": "node-round-trip",
1136
+ "decoder": "node",
1137
+ "inputFile": "nodes/action-confirm.json",
1138
+ "expectedFile": "nodes/action-confirm.json",
1139
+ "description": "Phase 1537 — Input/Button whose Action.Confirm asks a BOUND question (naming the selected row) before a Call; no onCancel, which is how the language spells 'nothing happens'"
1140
+ },
1141
+ {
1142
+ "id": "action-confirm-cancel",
1143
+ "kind": "node-round-trip",
1144
+ "decoder": "node",
1145
+ "inputFile": "nodes/action-confirm-cancel.json",
1146
+ "expectedFile": "nodes/action-confirm-cancel.json",
1147
+ "description": "Phase 1537 — Input/Button whose Action.Confirm carries a LITERAL prompt and both branches; the only fixture in which the recursive encoder descends into a named continuation rather than a Chain list"
1148
+ },
1149
+ {
1150
+ "id": "action-focus",
1151
+ "kind": "node-round-trip",
1152
+ "decoder": "node",
1153
+ "inputFile": "nodes/action-focus.json",
1154
+ "expectedFile": "nodes/action-focus.json",
1155
+ "description": "Phase 1537 — Input/Button whose Action.Focus moves the caret to an addressed node, chained after the write that emptied it"
1156
+ },
1157
+ {
1158
+ "id": "action-navigate-bound",
1159
+ "kind": "node-round-trip",
1160
+ "decoder": "node",
1161
+ "inputFile": "nodes/action-navigate-bound.json",
1162
+ "expectedFile": "nodes/action-navigate-bound.json",
1163
+ "description": "Phase 1536 — Input/Button whose Action.Navigate carries a BOUND route (a TextSource over the grid's selection), resolved at dispatch time and gated on the resolved value"
1164
+ },
1165
+ {
1166
+ "id": "action-navigate-target",
1167
+ "kind": "node-round-trip",
1168
+ "decoder": "node",
1169
+ "inputFile": "nodes/action-navigate-target.json",
1170
+ "expectedFile": "nodes/action-navigate-target.json",
1171
+ "description": "Phase 1536 — Input/Button whose Action.Navigate names a Blank target (opened with noopener,noreferrer); `Self` is omitted, so this is the only shape that puts `target` on the wire"
1172
+ },
1109
1173
  {
1110
1174
  "id": "badge-1",
1111
1175
  "kind": "node-round-trip",
@@ -1242,6 +1306,30 @@
1242
1306
  "expectedFile": "nodes/chart-1.json",
1243
1307
  "description": "Visualisation/Chart"
1244
1308
  },
1309
+ {
1310
+ "id": "chart-annotation-bands",
1311
+ "kind": "node-round-trip",
1312
+ "decoder": "node",
1313
+ "inputFile": "nodes/chart-annotation-bands.json",
1314
+ "expectedFile": "nodes/chart-annotation-bands.json",
1315
+ "description": "Visualisation/Chart (Phase 1492 — annotations: range bands in all three addressing forms, value / category / date)"
1316
+ },
1317
+ {
1318
+ "id": "chart-annotation-events",
1319
+ "kind": "node-round-trip",
1320
+ "decoder": "node",
1321
+ "inputFile": "nodes/chart-annotation-events.json",
1322
+ "expectedFile": "nodes/chart-annotation-events.json",
1323
+ "description": "Visualisation/Chart (Phase 1491 — annotations: event markers in both x-address forms, category and date)"
1324
+ },
1325
+ {
1326
+ "id": "chart-annotations",
1327
+ "kind": "node-round-trip",
1328
+ "decoder": "node",
1329
+ "inputFile": "nodes/chart-annotations.json",
1330
+ "expectedFile": "nodes/chart-annotations.json",
1331
+ "description": "Visualisation/Chart (Phase 1490 — annotations: two data-addressed reference lines, one labelled)"
1332
+ },
1245
1333
  {
1246
1334
  "id": "chart-axis-titles",
1247
1335
  "kind": "node-round-trip",
@@ -1354,6 +1442,14 @@
1354
1442
  "expectedFile": "nodes/custom-bounded-advisory.json",
1355
1443
  "description": "Custom (bounded escape, AdvisoryWarning hash + no exposed-ids)"
1356
1444
  },
1445
+ {
1446
+ "id": "custom-nonascii-keys",
1447
+ "kind": "node-round-trip",
1448
+ "decoder": "node",
1449
+ "inputFile": "nodes/custom-nonascii-keys.json",
1450
+ "expectedFile": "nodes/custom-nonascii-keys.json",
1451
+ "description": "Custom (§2 rule 2 — astral vs BMP payload keys, the UTF-16-order discriminator)"
1452
+ },
1357
1453
  {
1358
1454
  "id": "dash-empty",
1359
1455
  "kind": "node-round-trip",
@@ -1442,6 +1538,22 @@
1442
1538
  "expectedFile": "nodes/empty-state-card.json",
1443
1539
  "description": "Layout/Box (Phase 767 — the canonical empty state with a CTA: one Card region, no nested Callout)"
1444
1540
  },
1541
+ {
1542
+ "id": "expr-params-state-selection",
1543
+ "kind": "node-round-trip",
1544
+ "decoder": "node",
1545
+ "inputFile": "nodes/expr-params-state-selection.json",
1546
+ "expectedFile": "nodes/expr-params-state-selection.json",
1547
+ "description": "Binding/Expr (Phase 1534 — params from State / Selection / Query / Filter / Now, including an InParam membership test)"
1548
+ },
1549
+ {
1550
+ "id": "expr-scalar",
1551
+ "kind": "node-round-trip",
1552
+ "decoder": "node",
1553
+ "inputFile": "nodes/expr-scalar.json",
1554
+ "expectedFile": "nodes/expr-scalar.json",
1555
+ "description": "Binding/Expr (Phase 1534 — scalar logic with no params: concat, arithmetic, AND/NOT, a null test)"
1556
+ },
1445
1557
  {
1446
1558
  "id": "fact-1",
1447
1559
  "kind": "node-round-trip",
@@ -1602,6 +1714,14 @@
1602
1714
  "expectedFile": "nodes/form-local-debounce.json",
1603
1715
  "description": "Input/Form (Local-bound text, OnDebounce 250)"
1604
1716
  },
1717
+ {
1718
+ "id": "form-local-declared",
1719
+ "kind": "node-round-trip",
1720
+ "decoder": "node",
1721
+ "inputFile": "nodes/form-local-declared.json",
1722
+ "expectedFile": "nodes/form-local-declared.json",
1723
+ "description": "Input/Form (Fuaran-UI Phase 1538 — a DECLARED Local: codec + commitTo, no closure)"
1724
+ },
1605
1725
  {
1606
1726
  "id": "form-ranged",
1607
1727
  "kind": "node-round-trip",
@@ -1674,6 +1794,14 @@
1674
1794
  "expectedFile": "nodes/format-bindings.json",
1675
1795
  "description": "Binding.Format (number/currency/percent/date/relativeTime across locales)"
1676
1796
  },
1797
+ {
1798
+ "id": "format-since",
1799
+ "kind": "node-round-trip",
1800
+ "decoder": "node",
1801
+ "inputFile": "nodes/format-since.json",
1802
+ "expectedFile": "nodes/format-since.json",
1803
+ "description": "Binding.Format (Phase 1533 — Since: the instant-reading twin of RelativeTime, declared unit and auto)"
1804
+ },
1677
1805
  {
1678
1806
  "id": "frag-decl-1",
1679
1807
  "kind": "node-round-trip",
@@ -2250,6 +2378,14 @@
2250
2378
  "expectedFile": "nodes/metric-float-17sig.json",
2251
2379
  "description": "Display/Metric (float divergence-zone — 17 significant digits)"
2252
2380
  },
2381
+ {
2382
+ "id": "metric-float-1e17",
2383
+ "kind": "node-round-trip",
2384
+ "decoder": "node",
2385
+ "inputFile": "nodes/metric-float-1e17.json",
2386
+ "expectedFile": "nodes/metric-float-1e17.json",
2387
+ "description": "Display/Metric (§2 rule 5 — 1e17, one past the fixed-point window)"
2388
+ },
2253
2389
  {
2254
2390
  "id": "metric-float-bigint",
2255
2391
  "kind": "node-round-trip",
@@ -2338,6 +2474,14 @@
2338
2474
  "expectedFile": "nodes/multiselect-chip-list-param.json",
2339
2475
  "description": "Layout/Box (Phase 610 — multi-select chip + DataGrid scoped by a list-valued in/param)"
2340
2476
  },
2477
+ {
2478
+ "id": "node-visible",
2479
+ "kind": "node-round-trip",
2480
+ "decoder": "node",
2481
+ "inputFile": "nodes/node-visible.json",
2482
+ "expectedFile": "nodes/node-visible.json",
2483
+ "description": "Node envelope (Phase 1535 — visible: removal from flow, three predicate spellings, beside an aria-hidden sibling)"
2484
+ },
2341
2485
  {
2342
2486
  "id": "now-environment-binding",
2343
2487
  "kind": "node-round-trip",
@@ -2346,6 +2490,14 @@
2346
2490
  "expectedFile": "nodes/now-environment-binding.json",
2347
2491
  "description": "Binding/Now (Phase 765 — the host-furnished instant: a text slot + a Transform param feeding dateDiffDays)"
2348
2492
  },
2493
+ {
2494
+ "id": "now-grain",
2495
+ "kind": "node-round-trip",
2496
+ "decoder": "node",
2497
+ "inputFile": "nodes/now-grain.json",
2498
+ "expectedFile": "nodes/now-grain.json",
2499
+ "description": "Binding/Now (Phase 1533 — the declared grain: minute/hour/day text slots + a Day-grain Transform param)"
2500
+ },
2349
2501
  {
2350
2502
  "id": "popover-anchored-1",
2351
2503
  "kind": "node-round-trip",
@@ -2514,6 +2666,30 @@
2514
2666
  "expectedFile": "nodes/switch-on-selection.json",
2515
2667
  "description": "Meta/Switch (Phase 768 — the selector widened: `on` takes a Selection, the branch follows the clicked row)"
2516
2668
  },
2669
+ {
2670
+ "id": "switch-on-transform-scalar",
2671
+ "kind": "node-round-trip",
2672
+ "decoder": "node",
2673
+ "inputFile": "nodes/switch-on-transform-scalar.json",
2674
+ "expectedFile": "nodes/switch-on-transform-scalar.json",
2675
+ "description": "Meta/Switch (Phase 1535 — a computed selector: a Transform yielding one cell, read through the scalar path)"
2676
+ },
2677
+ {
2678
+ "id": "switch-predicate",
2679
+ "kind": "node-round-trip",
2680
+ "decoder": "node",
2681
+ "inputFile": "nodes/switch-predicate.json",
2682
+ "expectedFile": "nodes/switch-predicate.json",
2683
+ "description": "Meta/Switch (Phase 1535 — predicate cases: a State bool and an Expr, MIXED with a match case in one ordered list)"
2684
+ },
2685
+ {
2686
+ "id": "switch-predicate-only",
2687
+ "kind": "node-round-trip",
2688
+ "decoder": "node",
2689
+ "inputFile": "nodes/switch-predicate-only.json",
2690
+ "expectedFile": "nodes/switch-predicate-only.json",
2691
+ "description": "Meta/Switch (Phase 1535 — selected entirely by predicate: no selector, nothing to write)"
2692
+ },
2517
2693
  {
2518
2694
  "id": "table-1",
2519
2695
  "kind": "node-round-trip",
@@ -2932,6 +3108,15 @@
2932
3108
  "expectedPath": "$.kind.value",
2933
3109
  "description": "Binding<float> Static payload is a non-sentinel string — §7's float accept set is JSON number plus exactly \"NaN\" / \"Infinity\" / \"-Infinity\" (Phase 1064)"
2934
3110
  },
3111
+ {
3112
+ "id": "reject-binding-floatseq-sentinel-case",
3113
+ "kind": "reject",
3114
+ "decoder": "node",
3115
+ "inputFile": "reject/reject-binding-floatseq-sentinel-case.json",
3116
+ "expectedErrorCode": "WRONG_TYPE",
3117
+ "expectedPath": "$.kind.source",
3118
+ "description": "Binding<float seq> ELEMENT is a MIS-CASED sentinel (\"nan\") — the sequence twin of reject-binding-float-sentinel-case: §7's three tokens are exact at an element too, so a host that routes elements through a looser reader than its scalar slots is caught (Phase 1099)"
3119
+ },
2935
3120
  {
2936
3121
  "id": "reject-binding-int-bool",
2937
3122
  "kind": "reject",
@@ -2959,6 +3144,33 @@
2959
3144
  "expectedPath": "$.kind.layout.cols",
2960
3145
  "description": "Masonry cols 0 — refused: a column count names how many columns the children fill, so a non-positive one describes a layout no renderer can realise. Refused rather than canonicalised to `Auto`, unlike a column-less `Grid`: that leniency works because `Auto` already means the browser's own choice, and masonry has no such case for the rewrite to land on"
2961
3146
  },
3147
+ {
3148
+ "id": "reject-chart-annotation-date-unparseable",
3149
+ "kind": "reject",
3150
+ "decoder": "node",
3151
+ "inputFile": "reject/reject-chart-annotation-date-unparseable.json",
3152
+ "expectedErrorCode": "WRONG_TYPE",
3153
+ "expectedPath": "$.kind.annotations[0].at.iso",
3154
+ "description": "chart event marker addressing a date that names no calendar day — the address is what the marker is drawn at, and an unreadable one would place it at 1970-01-01 and drag the axis back with it (Phase 1491)"
3155
+ },
3156
+ {
3157
+ "id": "reject-chart-annotation-nonfinite",
3158
+ "kind": "reject",
3159
+ "decoder": "node",
3160
+ "inputFile": "reject/reject-chart-annotation-nonfinite.json",
3161
+ "expectedErrorCode": "WRONG_TYPE",
3162
+ "expectedPath": "$.kind.annotations[0].value",
3163
+ "description": "chart annotation carrying the §7 NaN sentinel — the value slot is narrowed to the finite numbers (Phase 1490)"
3164
+ },
3165
+ {
3166
+ "id": "reject-chart-annotation-range-unordered",
3167
+ "kind": "reject",
3168
+ "decoder": "node",
3169
+ "inputFile": "reject/reject-chart-annotation-range-unordered.json",
3170
+ "expectedErrorCode": "WRONG_TYPE",
3171
+ "expectedPath": "$.kind.annotations[0].range",
3172
+ "description": "chart range band whose pair runs from its upper value to its lower one — a band names an interval, and swapping the ends silently would draw one the author did not describe (Phase 1492)"
3173
+ },
2962
3174
  {
2963
3175
  "id": "reject-color-value-not-hex",
2964
3176
  "kind": "reject",
@@ -2977,6 +3189,24 @@
2977
3189
  "expectedPath": "$.kind.fields[0].kind.allowFreeText",
2978
3190
  "description": "a Combobox `allowFreeText` of `\"yes\"` — refused rather than coerced. The slot decides whether values OUTSIDE the option set are admitted, so a lenient truthiness read is a constraint silently relaxed by a string: `\"yes\"`, `\"no\"` and `\"false\"` are all non-empty, and a host that coerced them would widen the field on two of the three. It also omits at `false`, so absence already spells the safe answer and a wrong-typed present value can only mean the emitter meant something it did not say"
2979
3191
  },
3192
+ {
3193
+ "id": "reject-confirm-missing-onconfirm",
3194
+ "kind": "reject",
3195
+ "decoder": "node",
3196
+ "inputFile": "reject/reject-confirm-missing-onconfirm.json",
3197
+ "expectedErrorCode": "MISSING_FIELD",
3198
+ "expectedPath": "$.kind.onClick",
3199
+ "description": "Confirm with no 'onConfirm' — a question with no answer. The other two members are absent-legal (`onCancel`) or shorthand-legal (`prompt` as a bare string), so this vector is what keeps an emitter from generalising 'optional' across the case (Phase 1537)"
3200
+ },
3201
+ {
3202
+ "id": "reject-confirm-nested",
3203
+ "kind": "reject",
3204
+ "decoder": "node",
3205
+ "inputFile": "reject/reject-confirm-nested.json",
3206
+ "expectedErrorCode": "WRONG_TYPE",
3207
+ "expectedPath": "$.kind.onClick.onConfirm.ops[1]",
3208
+ "description": "A Confirm inside a Confirm's continuation, reached through a Chain — confirmation is bounded at one question, and the check walks the DECODED continuation so a chain cannot hide the nesting (Phase 1537)"
3209
+ },
2980
3210
  {
2981
3211
  "id": "reject-daterange-unordered",
2982
3212
  "kind": "reject",
@@ -3022,6 +3252,24 @@
3022
3252
  "expectedPath": "$.id",
3023
3253
  "description": "Empty top-level id"
3024
3254
  },
3255
+ {
3256
+ "id": "reject-expr-col-reference",
3257
+ "kind": "reject",
3258
+ "decoder": "node",
3259
+ "inputFile": "reject/reject-expr-col-reference.json",
3260
+ "expectedErrorCode": "WRONG_TYPE",
3261
+ "expectedPath": "$.kind.text.binding.expr",
3262
+ "description": "a `col` reference inside a Binding.Expr — an Expr evaluates against its params alone and has no row for a column name to read, so the reference can never resolve. The remedy is a different BINDING, not a different spelling: Binding.Transform supplies the frame `col` reads from. Refused at decode rather than left to fail once per render on every host (Phase 1534)"
3263
+ },
3264
+ {
3265
+ "id": "reject-expr-unbound-param",
3266
+ "kind": "reject",
3267
+ "decoder": "node",
3268
+ "inputFile": "reject/reject-expr-unbound-param.json",
3269
+ "expectedErrorCode": "WRONG_TYPE",
3270
+ "expectedPath": "$.kind.text.binding.expr",
3271
+ "description": "a Binding.Expr whose expression reads `param quantity` while its own `params` binds only `unitPrice`. Unlike Binding.Transform — where an unbound filter param prunes its step, the deliberate unset-chip leniency — an Expr has no step to prune and no rows to fall back on, so the reference has no value it could ever take. Refused at decode, where the missing name can be named (Phase 1534)"
3272
+ },
3025
3273
  {
3026
3274
  "id": "reject-fieldrule-empty",
3027
3275
  "kind": "reject",
@@ -3076,6 +3324,24 @@
3076
3324
  "expectedPath": "$.kind.srcSet",
3077
3325
  "description": "Image srcSet `null` — refused. Absent means the EMPTY LIST (the missing-list-field decode class); a present null is a second spelling for an absence that already has one, and admitting it would let two hosts disagree about the canonical bytes of the same document"
3078
3326
  },
3327
+ {
3328
+ "id": "reject-int-slot-fractional",
3329
+ "kind": "reject",
3330
+ "decoder": "node",
3331
+ "inputFile": "reject/reject-int-slot-fractional.json",
3332
+ "expectedErrorCode": "WRONG_TYPE",
3333
+ "expectedPath": "$.kind.rows",
3334
+ "description": "§7.1 — a fractional value at an integer slot. Three hosts TRUNCATED it to 2, discarding the author's value at a slot the author typed; two refused it. §7.1 refuses, and accepts `2.0` (see the lenient twin). Twin: the `skel-1` node fixture"
3335
+ },
3336
+ {
3337
+ "id": "reject-int-slot-out-of-range",
3338
+ "kind": "reject",
3339
+ "decoder": "node",
3340
+ "inputFile": "reject/reject-int-slot-out-of-range.json",
3341
+ "expectedErrorCode": "WRONG_TYPE",
3342
+ "expectedPath": "$.kind.rows",
3343
+ "description": "§7.1 — a value outside the 32-bit range an integer slot can hold. The cast was implementation-defined: the same bytes became Int32.MinValue on one runtime and 1410065408 on another, which is §20's defect one layer above the syntax. Twin: the `skel-1` node fixture"
3344
+ },
3079
3345
  {
3080
3346
  "id": "reject-invalid-empty",
3081
3347
  "kind": "reject",
@@ -3103,6 +3369,114 @@
3103
3369
  "expectedPath": "$",
3104
3370
  "description": "truncated object"
3105
3371
  },
3372
+ {
3373
+ "id": "reject-json-bare-nan",
3374
+ "kind": "reject",
3375
+ "decoder": "node",
3376
+ "inputFile": "reject/reject-json-bare-nan.json",
3377
+ "expectedErrorCode": "INVALID_JSON",
3378
+ "expectedPath": "$",
3379
+ "description": "§20.2 row 4 — a bare `NaN` literal. RFC 8259 forbids it as a bare token and §7's QUOTED sentinels are the specified representation, which row 8 accepts unchanged; one host accepted the bare form and re-emitted it. Twin: the `markdown-1` node fixture (drop the trailing member)"
3380
+ },
3381
+ {
3382
+ "id": "reject-json-duplicate-key",
3383
+ "kind": "reject",
3384
+ "decoder": "node",
3385
+ "inputFile": "reject/reject-json-duplicate-key.json",
3386
+ "expectedErrorCode": "INVALID_JSON",
3387
+ "expectedPath": "$",
3388
+ "description": "§20.2 row 1 — a repeated object member. THE row that changes what a document MEANS rather than whether it is accepted: this host kept the first occurrence and every other host the last, so a vetting host and a rendering host saw different trees from identical bytes with no error anywhere. Twin: the `markdown-1` node fixture (drop the second `id`)"
3389
+ },
3390
+ {
3391
+ "id": "reject-json-lone-high-surrogate",
3392
+ "kind": "reject",
3393
+ "decoder": "node",
3394
+ "inputFile": "reject/reject-json-lone-high-surrogate.json",
3395
+ "expectedErrorCode": "INVALID_JSON",
3396
+ "expectedPath": "$",
3397
+ "description": "§20.2 row 6 — an unpaired HIGH surrogate. The second row that changes MEANING silently: two hosts lowered it to U+FFFD, two kept it in a UTF-16 string, and one kept it and then raised an uncatchable encoding error at the first canonical-bytes boundary rather than at decode. Twin: the `markdown-1` node fixture (drop the escape)"
3398
+ },
3399
+ {
3400
+ "id": "reject-json-lone-low-surrogate",
3401
+ "kind": "reject",
3402
+ "decoder": "node",
3403
+ "inputFile": "reject/reject-json-lone-low-surrogate.json",
3404
+ "expectedErrorCode": "INVALID_JSON",
3405
+ "expectedPath": "$",
3406
+ "description": "§20.2 row 6 — an unpaired LOW surrogate. Pinned separately from its high twin because a pairing check written as \"a high must be followed by a low\" passes this vector while leaving the class open. Twin: the `markdown-1` node fixture (drop the escape)"
3407
+ },
3408
+ {
3409
+ "id": "reject-json-number-empty-exponent",
3410
+ "kind": "reject",
3411
+ "decoder": "node",
3412
+ "inputFile": "reject/reject-json-number-empty-exponent.json",
3413
+ "expectedErrorCode": "INVALID_JSON",
3414
+ "expectedPath": "$",
3415
+ "description": "§20.2 row 3 — an exponent marker with no digits. Twin: the `skel-1` node fixture (drop the `e`)"
3416
+ },
3417
+ {
3418
+ "id": "reject-json-number-leading-plus",
3419
+ "kind": "reject",
3420
+ "decoder": "node",
3421
+ "inputFile": "reject/reject-json-number-leading-plus.json",
3422
+ "expectedErrorCode": "INVALID_JSON",
3423
+ "expectedPath": "$",
3424
+ "description": "§20.2 row 3 — a leading `+` on a number. RFC 8259 does not permit it; three hosts accepted it because they reached a platform number parser without checking the grammar first. Twin: the `skel-1` node fixture (drop the `+`)"
3425
+ },
3426
+ {
3427
+ "id": "reject-json-number-leading-zero",
3428
+ "kind": "reject",
3429
+ "decoder": "node",
3430
+ "inputFile": "reject/reject-json-number-leading-zero.json",
3431
+ "expectedErrorCode": "INVALID_JSON",
3432
+ "expectedPath": "$",
3433
+ "description": "§20.2 row 3 — a leading zero. RFC 8259's `int` production is `0` or a non-zero digit followed by digits. Twin: the `skel-1` node fixture (drop the leading zero)"
3434
+ },
3435
+ {
3436
+ "id": "reject-json-number-no-integer-part",
3437
+ "kind": "reject",
3438
+ "decoder": "node",
3439
+ "inputFile": "reject/reject-json-number-no-integer-part.json",
3440
+ "expectedErrorCode": "INVALID_JSON",
3441
+ "expectedPath": "$",
3442
+ "description": "§20.2 row 3 — a number with no integer part. Refused as a GRAMMAR violation before the slot's type is ever consulted, which is why the vector sits at a slot that would refuse a number anyway: the point is that `.5` is not a number at all. Twin: the `markdown-1` node fixture (drop the trailing member)"
3443
+ },
3444
+ {
3445
+ "id": "reject-json-number-trailing-point",
3446
+ "kind": "reject",
3447
+ "decoder": "node",
3448
+ "inputFile": "reject/reject-json-number-trailing-point.json",
3449
+ "expectedErrorCode": "INVALID_JSON",
3450
+ "expectedPath": "$",
3451
+ "description": "§20.2 row 3 — a decimal point with no fractional digits. Twin: the `skel-1` node fixture (drop the point)"
3452
+ },
3453
+ {
3454
+ "id": "reject-json-raw-control-char",
3455
+ "kind": "reject",
3456
+ "decoder": "node",
3457
+ "inputFile": "reject/reject-json-raw-control-char.json",
3458
+ "expectedErrorCode": "INVALID_JSON",
3459
+ "expectedPath": "$",
3460
+ "description": "§20.2 row 5 — a RAW U+0009 inside a string. RFC 8259 requires a C0 control character to be escaped and §2 rule 6 requires a conformant encoder to escape it, so accepting the raw byte admits input this host's own encoder cannot produce. The escaped spelling stays legal. Twin: the `markdown-1` node fixture (the tab escaped, or a space)"
3461
+ },
3462
+ {
3463
+ "id": "reject-json-surrogate-pair-split",
3464
+ "kind": "reject",
3465
+ "decoder": "node",
3466
+ "inputFile": "reject/reject-json-surrogate-pair-split.json",
3467
+ "expectedErrorCode": "INVALID_JSON",
3468
+ "expectedPath": "$",
3469
+ "description": "§20.2 row 6 — both halves of a pair present but separated. A host that counts surrogates rather than pairing them adjacently accepts this and reassembles a scalar the author never wrote. Twin: the `markdown-1` node fixture"
3470
+ },
3471
+ {
3472
+ "id": "reject-json-trailing-content",
3473
+ "kind": "reject",
3474
+ "decoder": "node",
3475
+ "inputFile": "reject/reject-json-trailing-content.json",
3476
+ "expectedErrorCode": "INVALID_JSON",
3477
+ "expectedPath": "$",
3478
+ "description": "§20.2 row 2 — content after the root value. §1 makes a wire artefact a single JSON document; two hosts refused this and three accepted it, which is a framing ambiguity rather than a tolerance. Twin: the `markdown-1` node fixture (drop everything after the root object)"
3479
+ },
3106
3480
  {
3107
3481
  "id": "reject-limit-json-depth",
3108
3482
  "kind": "reject",
@@ -3148,6 +3522,24 @@
3148
3522
  "expectedPath": "$.kind.items[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0]",
3149
3523
  "description": "§21.5 the tree-item axis — 25 nested TreeItem rows inside ONE node. Counted separately from the node axis, which cannot see it at all (the whole hierarchy is one node) and from the syntactic bound, which at ~50 levels is nowhere near reached. The `TreeOp.Batch` lesson on a third axis"
3150
3524
  },
3525
+ {
3526
+ "id": "reject-local-codec-no-inverse",
3527
+ "kind": "reject",
3528
+ "decoder": "node",
3529
+ "inputFile": "reject/reject-local-codec-no-inverse.json",
3530
+ "expectedErrorCode": "WRONG_TYPE",
3531
+ "expectedPath": "$.kind.fields[0].kind.value.codec",
3532
+ "description": "a Binding.Local declaring a `Currency` codec. `Binding.Format` carries a LocaleSource because it renders for reading; a Local codec carries none, because whatever it renders it must be able to parse back from the reader's keystrokes — and a currency symbol, a locale decimal mark and a grouping separator are all chosen by the reader's locale, not by the document. `Number` is the one case with a total, locale-independent inverse. Refused at decode rather than left to format one way and parse another (Fuaran-UI Phase 1538)"
3533
+ },
3534
+ {
3535
+ "id": "reject-local-oncommit-and-committo",
3536
+ "kind": "reject",
3537
+ "decoder": "node",
3538
+ "inputFile": "reject/reject-local-oncommit-and-committo.json",
3539
+ "expectedErrorCode": "WRONG_TYPE",
3540
+ "expectedPath": "$.kind.fields[0].kind.value.commitTo",
3541
+ "description": "a Binding.Local carrying BOTH an `onCommit` closure sentinel and a declared `commitTo` key. The SetState value/valueFrom law, applied to the buffer's flush: exactly one commit destination. A decoding host can honour only the declared one — the closure is `\"<closure>\"` and nothing more — so admitting the pair would make the same bytes commit to two different places depending on who read them (Fuaran-UI Phase 1538)"
3542
+ },
3151
3543
  {
3152
3544
  "id": "reject-media-autoplay-nonbool",
3153
3545
  "kind": "reject",
@@ -3292,6 +3684,15 @@
3292
3684
  "expectedPath": "$.kind.modality",
3293
3685
  "description": "a Modal `modality` of `\"Sheet\"` — outside the closed pair `Modal | Popover`. A sheet, a drawer and a menu are all PRESENTATIONS of one of the two modalities rather than a third one, so the answer to this document is not to widen the enum but to say which of the two it meant. The path is the bare slot, per §6 and the Phase 1073 ruling: a bare enum carries no discriminator on the wire, so there is no `.$type` to name"
3294
3686
  },
3687
+ {
3688
+ "id": "reject-navigate-target-invalid",
3689
+ "kind": "reject",
3690
+ "decoder": "node",
3691
+ "inputFile": "reject/reject-navigate-target-invalid.json",
3692
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3693
+ "expectedPath": "$.kind.onClick.target",
3694
+ "description": "Action.Navigate's target is the closed enum Self | Blank, not HTML's target attribute. `_blank` is refused rather than aliased: the vocabulary it comes from also contains `_parent` and `_top`, which are frame-busting gestures a hosted tree must not be able to ask for, so accepting any of it would teach an emitter the wrong vocabulary (Phase 1536)"
3695
+ },
3295
3696
  {
3296
3697
  "id": "reject-nearmiss-a11y-aria-hidden",
3297
3698
  "kind": "reject",
@@ -3364,6 +3765,15 @@
3364
3765
  "expectedPath": "$.kind.sortable",
3365
3766
  "description": "grid-level 'sortable' — the staticRows spelling reached for on the bound path, where sortability is sortStateKey + per-column narrowing (Phase 863)"
3366
3767
  },
3768
+ {
3769
+ "id": "reject-now-grain-invalid",
3770
+ "kind": "reject",
3771
+ "decoder": "node",
3772
+ "inputFile": "reject/reject-now-grain-invalid.json",
3773
+ "expectedErrorCode": "UNKNOWN_DU_CASE",
3774
+ "expectedPath": "$.kind.value.binding.grain",
3775
+ "description": "Binding.Now's grain is Second | Minute | Hour | Day — `Week` is a RelativeTimeUnit member and not a TimeGrain one. The two vocabularies are deliberately different sizes: a grain truncates a calendar instant, and a week, a month and a year have no truncation every host agrees on. A host that accepted this and rendered at day grain would be answering a question the document did not ask (Phase 1533)"
3776
+ },
3367
3777
  {
3368
3778
  "id": "reject-null-action-aitool-args",
3369
3779
  "kind": "reject",
@@ -3526,6 +3936,15 @@
3526
3936
  "expectedPath": "$.kind.onClick.valueFrom",
3527
3937
  "description": "SetState carrying BOTH 'value' and 'valueFrom' — exactly one is allowed; the didactic names both fields and how each is used (Phase 818)"
3528
3938
  },
3939
+ {
3940
+ "id": "reject-spark-element-nonnumeric",
3941
+ "kind": "reject",
3942
+ "decoder": "node",
3943
+ "inputFile": "reject/reject-spark-element-nonnumeric.json",
3944
+ "expectedErrorCode": "WRONG_TYPE",
3945
+ "expectedPath": "$.kind.source",
3946
+ "description": "A Sparkline series element of \"lots\" — a typed float SEQUENCE slot types its ELEMENTS, not just the binding. spark-nonfinite-sentinel proves the §7 sentinels are accepted, which an entirely untyped slot also does by never reading an element, so this is the vector that separates the two (Phase 1099)"
3947
+ },
3529
3948
  {
3530
3949
  "id": "reject-style-direction-nonstring",
3531
3950
  "kind": "reject",
@@ -3571,6 +3990,24 @@
3571
3990
  "expectedPath": "$.kind.autoAdvanceMs",
3572
3991
  "description": "Switch autoAdvanceMs is 0 — refused, never canonicalised to absence. An absent key is already the only spelling of \"does not advance\", so accepting a zero would make two shapes mean one thing and hide the emitter's misreading of the slot (Phase 1122)"
3573
3992
  },
3993
+ {
3994
+ "id": "reject-switch-case-match-and-when",
3995
+ "kind": "reject",
3996
+ "decoder": "node",
3997
+ "inputFile": "reject/reject-switch-case-match-and-when.json",
3998
+ "expectedErrorCode": "WRONG_TYPE",
3999
+ "expectedPath": "$.kind.cases[0].when",
4000
+ "description": "a Switch case carrying both `match` and `when` — the two ways of selecting a case, in one case. `match` compares the switch's `on` selector against a literal; `when` evaluates a Binding<bool> and consults no selector at all. Refused rather than resolved by precedence: a precedence rule would need specifying, agreeing on five hosts and remembering, for a document nobody meant to write (Phase 1535)"
4001
+ },
4002
+ {
4003
+ "id": "reject-switch-case-neither",
4004
+ "kind": "reject",
4005
+ "decoder": "node",
4006
+ "inputFile": "reject/reject-switch-case-neither.json",
4007
+ "expectedErrorCode": "MISSING_FIELD",
4008
+ "expectedPath": "$.kind.cases[0].match",
4009
+ "description": "a Switch case carrying neither `match` nor `when` — no condition, so no rendering could be right. Refused at decode rather than skipped silently at render: skipping it renders the `default` and reports nothing, which is the exact class of silence this phase's finding was filed about. The message names both fields, so the author is told which two spellings exist (Phase 1535)"
4010
+ },
3574
4011
  {
3575
4012
  "id": "reject-tokens-closed-without-suggestions",
3576
4013
  "kind": "reject",