Uh oh!
There was an error while loading. Please reload this page.
fix(examples): correct the invented keys the live SchemaExample fixtures teach - #6248
Conversation
…res teach The five fixtures behind the toast, command and radio-group component demos carried keys and values the shipped @object-ui/types surface does not declare, directly beside prose that #6143 round 2 had just corrected. A reader copies the demo more readily than an interface block. - toast: the variant nested in the onClick toast payload 'destructive' -> 'error' (ToastSchema, feedback.d.ts:123 / zod/feedback.zod.js:59). The TOP-LEVEL "variant": "destructive" on both files is a genuine ButtonSchema member (form.d.ts:30) and is deliberately untouched. - command: drop the invented `shortcut` key from the three CommandItem entries (form.d.ts:1329 declares value/label/icon only; the renderer reads neither). - radio-group: `direction` -> `orientation`, the declared slot (form.d.ts:377). Adds test/component-fixture-declared-keys.test.ts, which measures each of the three distinct rejection classes with a paired counter-probe — the docs gates are fence-blind to JSON fixture keys, so their green is not evidence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
yinlianghui
commented
Aug 25, 2026
Two angle-bracket fragments in the PR body above were eaten by GitHub's body sanitizer on write. Restating them here, where bare text survives, rather than leaving the body reading as if I wrote them that way:
Neither changes a measurement; both are recorded so the next reader does not read the gap as sloppiness or as a different claim. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6157
The five live
SchemaExamplefixtures behind three component docs pages taught keys and values the shipped@object-ui/typessurface does not declare — directly beside prose that #6143 round 2 had just corrected. A reader copies the demo more readily than an interface block.Verification union run at
22f71cb1b(the final commit on this branch).variantoccurrence on the two toast fixtures, classified BEFORE any was changedA blanket find-and-replace over
destructivein these files breaks two working buttons. Enumerated by indentation (2-space = top-level, 4-space = nested insideonClick), each anchor proven unique in its file:components-feedback-toast/destructive.json$.variantButtonSchema.variant—form.d.ts:30,zod/form.zod.js:153'destructive'IS a membercomponents-feedback-toast/destructive.json$.onClick.variantToastSchema.variant—feedback.d.ts:123,zod/feedback.zod.js:59'error'components-feedback-toast/error-toast.json$.variantButtonSchema.variant'destructive'IS a membercomponents-feedback-toast/error-toast.json$.onClick.variantToastSchema.variant'error'The
sedused was anchored to^ "variant": "destructive",$(4-space only). Proven on disk after the edit: injected^ "variant": "error",$→ 1 per file; removed^ "variant": "destructive",$→ 0 per file; untouched^ "variant": "destructive",$→ still 1 per file. The firstdescribeblock of the new test exists solely so a future blanket replace turns this file red.'destructive'→'error'is intended to change what the demo renders, not a cosmetic edit. The toast renderer (packages/components/src/renderers/feedback/toast.tsx:17-21) mapsvariantonto sonner's functions:'error'→toast.error(red/destructive styling); an unrecognised value such as'destructive'falls through to plaintoast()(neutral). A reviewer seeing a colour change in a toast demo is seeing the intended correction.One honest qualification, measured rather than assumed: on this tree the key sits inside a
button.onClickaction payload, and I could find no dispatcher inpackages/**that readsonClick.action— see "What I measured that the card did not" below. So today the visible effect is on the JSON source the demo publishes; the styling difference above is what lands the moment that payload is wired.packages/components/src/renderers/form/radio-group.tsxonmainreads neitherdirectionnororientation(grepped; no match for either). So the vertical and horizontal demos still render identically after this PR. #6158 is the renderer half. This was not a reason to skip, delay, or wait — the fixture is now correct against the declaration, and the renderer half lands separately.⛔ The cross-card assertion neither PR can make
Triage asked that "the horizontal demo's markup actually diverges once both land" be asserted. Neither PR can assert it.#6158 changes the renderer but not the fixtures; this PR changes the fixtures but not the renderer. The divergence becomes observable only once both #6157 and #6158 have landed, and neither PR asserts it — no fixture-shaped stub was written to fake a green. Naming the gap is deliberate; the new test's header comment records it in the same words so the next reader does not mistake the silence for coverage.
Nothing in CI parses these files as schemas.
check-doc-component-typesreadstypeliterals out of docs code blocks (green here, and it would be green whatever key I wrote);check-doc-snippet-typescompilests/tsxfences and never opens a.jsonfixture. objectui#5250 already records that hole. The gates going green means I broke nothing — it is not evidence the corrections are correct. The per-key measurement below is the verification, andexamples/schema-catalog/test/component-fixture-declared-keys.test.tsis what makes it repeatable in CI.Per-key measurement against the INSTALLED shipped surface — every probe carries a control
packages/typesbuilt fresh from this tree (pnpm --filter @object-ui/types build, exit 0) before any judgement. The five keys do not share one rejection class, which the card's single "shipped says" column flattens:.d.tsverdictvariant: "destructive"(×2)invalid_value,expected one of "default"|"success"|"warning"|"error"|"info"shortcutonCommandItem(×3 entries, 1 key)z.object)'shortcut' does not exist in type 'CommandItem'directiononradio-group(×2)BaseSchemais.passthrough()(zod/base.zod.js:171)BaseSchemacarries[key: string]: anyControls, all of which parsed/compiled green as predicted — without them these would be measurements of a schema that refuses everything rather than key-by-key results:
ToastSchema.safeParse({… variant: 'error' …})→ GREEN (control for row 1)RadioGroupSchema.safeParse({… orientation: 'vertical' …})→ GREEN (control for row 3)CommandGroupSchema.safeParse(item without shortcut)→ GREEN, and byte-identical to the parse output of the item withshortcut— which is exactly how the strip was detectedconst b7: ButtonSchema = { type: 'button', variant: 'destructive' }→ compiles GREEN — the control that pins RIDER ①const b2: RadioGroupSchema = { … orientation: 'vertical' … }/const b4: CommandItem = { value, label, icon }→ compile GREENOne prediction of mine was wrong and is worth recording: I predicted
directionon aRadioGroupSchemaobject literal would raise TS2353. It does not —BaseSchema's index signature accepts it. So row 3 is not a "type rejects it" case at all; the authority is thatRadioGroupSchemadeclaresorientation(form.d.ts:377,zod/form.zod.js:263), thatdirectionappears nowhere in its shape, and that nothing reads it. The new test asserts that structurally, and includes an explicit probe showing.successstaystruefor the invented key — because an assertion on.successhere would assert nothing.Liveness — each fixture is rendered by a
SchemaExampleon a docs pagecomponents-feedback-toast/destructivecontent/docs/components/feedback/toast.mdx## Variants)components-feedback-toast/error-toastcontent/docs/components/feedback/toast.mdx### Error Message)components-form-command/command-palette-with-shortcutscontent/docs/components/form/command.mdxcomponents-form-radio-group/vertical-layoutcontent/docs/components/form/radio-group.mdxcomponents-form-radio-group/horizontal-layoutcontent/docs/components/form/radio-group.mdxSchemaExample→getExample(id)→InteractiveDemo, so the JSON is both rendered and published as copyable source. None of the five is dead fixture data.Census re-derived on today's
main— one deltaThe card says "5 fixture keys across 4 pages". Re-derived at
e3354ba08: 5 fixture keys across 5 files on 3 docs pages (toast, command, radio-group). The "4" does not reproduce — #6143 round 2 corrected prose on four pages, and the fixture half lands on three of them. Key count and file list are otherwise exactly as measured in the card; nothing moved under tonight's docs PRs.I also swept the whole fixture tree for the same three spellings rather than trusting the list.
"direction"appears in 14 files — 12 are legitimate (flex.direction,resizable.direction, a sort descriptor'sdirection), and only the two radio-group fixtures carry it on aradio-groupnode;components-form-radio-group/form-field.jsonmatched the grep but carriesdirectionon its outerflex, correctly, and was left alone."shortcut"appears in one other file (menubar) — filed separately, see below, not touched here.What I measured that the card did not
ButtonSchema.safeParse()on either toast fixture is RED, and not because of any variant:ButtonSchema.onClickis declared as() => void | Promise<void>(form.d.ts:56,zod/form.zod.js), while every toast demo hangs an{ action: 'toast', … }object off it, and no dispatcher anywhere inpackages/**readsonClick.action. The whole button→action-payload idiom these demos teach is undeclared and, on this tree, inert. That is out of scope here and filed separately; it is also why RIDER ②'s styling statement is qualified above.ToastSchemaremains the right authority for the nested key — the payload's other keys (title,description,duration) are exactly ToastSchema's.Changeset — the gate's own verdict, not my inspection
No changeset added.
check-changeset-no-majoralso green (vacuously — no changeset to declare a bump).The new test, and the ablation that proves it bites
examples/schema-catalog/test/component-fixture-declared-keys.test.ts(+201 lines) — 16 tests, all green. Every assertion block carries a paired counter-probe so the file cannot pass by asserting nothing.Ablation (fixtures reverted to their pre-#6157 content via
git checkout e3354ba08 -- <5 paths>, run undertrap … EXIT INT TERM, restored automatically;git diff HEAD --statempty afterwards). Direction predicted before running: 7 red / 9 green, naming which seven. Observed, exactly:Mutation proven on disk before the run by grepping the injected text and separately the removed text (nested
"variant": "destructive"→ 1 per file;"variant": "error"→ 0;"shortcut"→ 3;"direction"→ 1 per radio file;"orientation"→ 0), with the landing site printed. No rebuild step is involved in this ablation and none was skipped: the test imports../src/index.js, which imports the JSON fixtures directly, so the mutation reaches the run without adisthop.@object-ui/types(which the test parses against) was built once, before any measurement, and is not what the ablation mutates.Which assertions would still pass on a revert
Stated plainly, because most of the file would:
ButtonSchemadeclaresdestructive; both toast fixtures keep a top-level destructive button (that key is not part of the revert);ToastSchemarefuses'destructive';CommandItemSchemasilently strips an undeclared key;RadioGroupSchemadeclaresorientationand notdirection;.passthrough()acceptsdirection. Those are facts about the shipped types, not about this diff, and they are supposed to hold either way — that is what makes them controls.check-doc-component-types,check-doc-snippet-types,check-control-bytes,check-changeset-presence,type-check,lint. This is Trap ① restated as a property of the tree: before this PR nothing in CI could tell the difference, which is precisely why the new test file is part of the change rather than the fixture edits alone.Verification run (all at
22f71cb1b)npx vitest run examples/schema-catalog/test/component-fixture-declared-keys.test.tsfrom repo rootTests 16 passed (16)npx vitest run examples/schema-catalog/test --maxWorkers=2from repo rootTest Files 13 passed (13),Tests 1784 passed (1784)pnpm --filter @object-ui/example-schema-catalog type-check(tsc --noEmit && tsc -p tsconfig.test.json)pnpm --workspace-concurrency=2 --filter '@object-ui/example-schema-catalog^...' buildcheck-changeset-presencenode scripts/…✅ No source of a released package changed in this range, so no changeset is owed.check-changeset-no-majornode scripts/…✅ No changeset declares a 'major' bump.check-control-bytesnode scripts/…✅ check-control-bytes: OK (scanned 5152 tracked text file(s); skipped 85 binary).check-doc-component-typesnode scripts/…✅ Every documented component type is registered.check-type-check-coveragenode scripts/…✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt…All vitest runs were issued from the repo root:
pnpm --filter <pkg> exec vitestandcd packages/x && pnpm exec vitesthit objectui#3378, which silently runsapps/console's 22 files and reports them green while running none of the target's. The 16/16 and 13-file counts above match this package's own file and test counts exactly, which is the check that the guard was not hit.Lint was narrowed, and here is the proof the narrowing excluded nothing. Instead of the repo-wide
pnpm lint, I ran eslint on the changed files only. (1) The universe comes from eslint's own configuration, not my guess: of the six files in this diff, the five.jsonfixtures are outside it — eslint reportsFile ignored because no matching configuration was suppliedfor them — leaving exactly one file in scope. (2) The count is read from--format json: 1 file linted,errorCount: 0,warningCount: 0. (3) Invariance for untouched files:eslint.config.jsconfigures noprojectService, noproject, and noparserOptions, so type-aware linting is not enabled and nothing in this diff can move the verdict on a file it does not contain. CI runs the full farm regardless.Boundary with #6158 — checked, not assumed
packages/components/src/renderers/form/radio-group.tsxand its registry meta entry are #6158's and are not touched here (git diff --name-onlyis six files, all underexamples/schema-catalog/). Checked in the other direction too: the last commit to touchexamples/schema-catalog/src/schemas/components-form-radio-group/is9986f9114, long predating #6158, and #6158's own diff is two files underpackages/components/src/renderers/form/. The boundary was respected in both directions.Generated by Claude Code