Repository navigation
fix(cli,vscode-extension,components,runner): move the body child-list dialect off the platform’s producers, and teach the two VS Code readers children - #9846
Conversation
…ling Both extension-host readers guarded their child-list recursion on `schema.body` alone, while `children` is the spelling declared by the TypeScript face and zod mirror in `@object-ui/types`, by `validateSchema` in `@object-ui/core` (which reads `schema.children || schema.body`), and by the manifest tier's `BASE_PROPS` in `@object-ui/sdui-parser`. An author writing the declared spelling therefore got a blank preview and children that were never validated -- both failures silent, with no diagnostic and no refusal. Read `children` first and KEEP the `body` arm, so every `body`-spelled document behaves exactly as before. Removing that arm is a separate, already-ruled step and is not done here. The validator's diagnostic path now names the key the document actually used, so a `children`-spelled node no longer reports problems at a `.body[...]` address that does not occur in the file. Pinned by executing the shipped sources rather than asserting on their text: the preview's renderer lives inside a template literal that nothing type-checks, and the validator is transpiled and run against a stub of the `vscode` members it touches. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
Six code paths emitted `body`-spelled child lists into metadata a user then owns or that ships as a component's declared default: packages/cli/src/commands/init.ts 53 nodes packages/vscode-extension/src/extension.ts 14 nodes packages/components/.../complex/carousel.tsx 3 nodes packages/components/.../complex/resizable.tsx 2 nodes packages/runner/src/App.tsx 2 nodes packages/components/.../complex/scroll-area.tsx 1 node Counts are the node counts `pnpm census:body-dialect` reports, not line counts; `init.ts` matches `body` on 55 LINES because two of them are the HTML body tags in the generated `index.html`, which are not schema nodes and are untouched here. The three `defaultProps` cases were the sharpest: the registration shipped the dialect inside its own declared default, so the component emitted by default a spelling the retirement is removing. Every node type involved -- `div`, `card`, `page` -- already read `children`, so this is a rename and not a re-shape. New pins, neither of which existed before: the scaffolded output of every `objectui init` template, and every `Create New Schema` template. Both discover their template population from the code rather than listing it, so a template added later is covered without editing the test. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
…w template The comments added with the `children` arm sat INSIDE the preview's HTML template literal, where a backtick is not decoration -- it ends the string. `tsc` reported TS1005/TS1443 at the first one. Nothing else caught it, and the reason is worth keeping: the pins for this behaviour read the provider as TEXT rather than importing it, so they were green against a file TypeScript rejected. `type-check` is what found it. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
Scored `minor` across the four released packages this branch touches. The reader half is a user-visible defect fix, not a refactor note, so the changeset names the symptom -- a silently blank preview and unvalidated children for a spelling the platform blesses on four faces. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This branch migrated six producers off the `body` dialect, which made two claims in the census pin false. Repairing a claim this diff falsified is what is owed; the claims themselves are kept. "the platform SHIPS the dialect it is being asked to refuse" keeps its wording and its ordering rationale, and gains a live subject: six producers moved, and `generatePage` in commands/generate.ts still writes a `pages/NAME.json` spelled `body`. Both halves are asserted, so either a migrated producer drifting back or that last producer moving without this block being re-pointed will red. Deliberately NOT inverted into "the producers now carry `children`" -- that asserts this branch's own diff back at itself and throws away the reason the block exists. The generic-reader block's assertions were already passing, so they are not weakened: the two vscode lines are STRENGTHENED to the same `schema.children || schema.body` spelling the core line pins, which is what those readers now say. Its comment recorded the three readers as alike; they were not, and that difference is the whole of this branch. Found by CI, not locally: this test lives in scripts/__tests__ and reads other packages' source text, so no affected-package suite reaches it. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
The strengthened reader assertion did not bite. Ablating the `children`
arm out of providers/SchemaValidator.ts left the pin GREEN, because that
file documents the arm by quoting `schema.children || schema.body` in a
comment directly above the line implementing it -- `toContain` was
matching the prose.
A `readCode` helper strips whole-line comments, and the three source-text
blocks use it. Whole-line only, so a `//` inside a string survives: both
a URL and the preview provider's inlined webview script appear in the
files these blocks read.
This is the principle the scanner is already pinned on one block above
("prose is not a call site"), applied to the assertions themselves.
Caught by ablation, which is the only reason it was visible at all: the
assertion was green both before and after the mutation.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Re:
|
| type | key | read at |
|---|---|---|
CarouselSchema |
opts |
complex/carousel.tsx — opts={schema.opts} |
CarouselSchema |
orientation |
complex/carousel.tsx |
CarouselSchema |
itemClassName |
complex/carousel.tsx — per-slide class |
⭐ All three still true, verified at source on this head: :23 opts={schema.opts}, :24 orientation={schema.orientation || 'horizontal'}, :30 className={schema.itemClassName}.
Read as a paragraph, as the gate asks, not line by line. That body's claim is about undeclared-but-consumed keys — which props the renderer reads that the schema never declares. This change touches none of that: its entire edit to carousel.tsx is three defaultProps lines, each moving a nested div's child list from body: to children: inside the slide content:
- [{ type: 'div', className: 'p-4 sm:p-6 md:p-8 border rounded bg-slate-50', body: [{ type: 'text', content: 'Slide 1' }] }],
+ [{ type: 'div', className: 'p-4 sm:p-6 md:p-8 border rounded bg-slate-50', children: [{ type: 'text', content: 'Slide 1' }] }],⇒ ⛔ no declaration moves, ⛔ no read site moves, ⛔ no key is added or removed on CarouselSchema.
Measured rather than asserted, with a control in the same command: of the changed lines in carousel.tsx on this branch, 0 mention opts, orientation or itemClassName; the same pattern over the file at this head resolves 6 times. ⇒ an admissible zero, ⛔ not a bare one.
⛔ No changeset body needs correcting, and none was edited on account of this gate.
.changeset/9821-…md on objectui#9851, under a seat-amended fence. ⇒ this answer is 「read and judged」, ⛔ not 「the gate never finds anything」.
domain:ui#2 execution seat · session_018HrVaotisyhgmot9o2MLRq · gate answer · readings taken 2026-09-18T10:18Z
Generated by Claude Code
Fixes #7181
Clause-②: no
Moves the
bodychild-list dialect off the platform's PRODUCERS, and — per the seat'sruling of 2026-09-18T09:03Z on the card — converges the two VS Code extension readers
onto
childrenfirst so the producers can move at all.Why the reader change is here and not in a later step
The card's delivery order asked for a stop condition first: does every face the
producers' output crosses accept
childrentoday? Five of six did. The two extensionreaders did not — they guarded on
schema.bodyalone, so moving the producers withoutthem would have shipped a blank preview. That was measured before any producer moved,
reported, and ruled on; the amended fence added exactly those two files.
The reader fix is a live defect fix in its own right, true today with no part of the
retirement being true. An author spelling a child list
children— blessed by theBaseSchemadeclaration, the zod mirror, core'svalidateSchemaand the authoringtier's
BASE_PROPS— got a silently blank preview and children that were nevervalidated. That is what the changeset names.
What changed
Readers (
childrenfirst,bodyKEPT — purely additive, nothing narrowed):packages/vscode-extension/src/providers/PreviewProvider.ts— the card branch and thedefault container branch
packages/vscode-extension/src/providers/SchemaValidator.ts— the recursion guard, andthe diagnostic path now names the key the document actually used instead of addressing
problems to
.body[...]in a file containing no such keyProducers (node counts as
pnpm census:body-dialectreports them):packages/cli/src/commands/init.tspackages/vscode-extension/src/extension.tspackages/components/src/renderers/complex/carousel.tsxpackages/components/src/renderers/complex/resizable.tsxpackages/runner/src/App.tsxpackages/components/src/renderers/complex/scroll-area.tsxEvery node type involved (
div,card,page) already readchildren, so this is arename, not a re-shape.
Evidence
The executed probe that decided the stop, run against the preview's own shipped
inline renderer extracted verbatim: the same
cardnode printeddescendant elements rendered: 2underbodyand1underchildren. Thebodyleg is the lit control inthe same run, so the
1is a real drop. That probe is now a committed test rather than aone-off.
Five ablations, each proving a pin can fail (via
ablation-replace, which asserts themutation landed by blob hash and proves the restore against
HEAD):expected +0 to be 1expected [] to include 'Missing required property "type"'init.tssimple template back tobodyextension.tsempty template back tobodyEvery restore leg reported
blob == HEADwith an emptygit diff HEAD, and the tree wasclean after all five.
Census before and after, same instrument, same root:
That reconciles exactly: 230 − 75 migrated + 5 deliberately kept. The 5 are
body-spelledfixtures in the new pins, which assert BOTH spellings so a regression in the kept
bodyarm is caught too. All six producer files are absent from the after-census; the control is
that the census still reports 195 rows elsewhere in the same run.
Suites and gates (all on the final tree):
vitest run packages/cli/ packages/vscode-extension/ packages/runner/vitest run packages/components/turbo run type-check(4 touched packages)check-changeset-presencecheck-changeset-no-majorcheck-control-bytescheck-new-cross-file-line-citationscheck-governed-queue-guard --testeslint --no-inline-configover the 11 changed filesThe lint run is narrowed to the changed files. Stated so it can be checked: the repo's
eslint config declares no
project/projectService, so type-aware linting is off andthis diff cannot move the verdict on any file it does not touch; the file count is from
--format json.type-checkearned its place here. The first version of the reader comments put rawbackticks inside the preview's HTML template literal, which ends the string. Every test
stayed green — the pins read that provider as TEXT rather than importing it — and only
tsccaught it. Fixed in its own commit, and the ablation was re-run afterwards toconfirm it still bites.
Seat note — evidence is UNDERSTATED above, and one CI repair round happened
domain:ui#2execution seat (session_018HrVaotisyhgmot9o2MLRq), 2026-09-18T10:18Z. The dev writes a PR body once and ⛔ never patches it; the body is the seat's to keep current.The Evidence table above cites 27+281 test files across four packages. That is now conservative, ⛔ not wrong. After a CI repair round the branch is verified over 719 test files / 15139 tests across SEVEN trees —
scripts/,examples/andpackages/types/added — plustsc -p tsconfig.scripts.jsonwith the edited file proven inside that program by--listFiles.Why the repair round happened, and ⭐ what it found. CI went red on
Test (shard 3/4):scripts/__tests__/body-dialect-census.test.tspins that the producers still carrybody— this card's own finding, written down as a test — so migrating them reds it. ⛔ That is the pin doing its job, and it is 100% this PR's to repair. The repair keeps the block's claim verbatim and re-points its subject at objectui#9847; ⛔ it was deliberately not inverted into 「the producers now carrychildren」, which would assert this branch's own diff back at itself and discard the ordering rationale.⭐⭐ And the mandatory ablation caught a PHANTOM PASS inside that repair. The strengthened reader assertion did ⛔ not bite: ablating the⚠️ Invisible to the assertion's own result — green before and after the mutation — and only an ablation shows it. Without the mandatory ablation this would have shipped as a pin that could never fail.
childrenarm out ofproviders/SchemaValidator.tsleft the pin GREEN, because that file documents the arm by quotingschema.children || schema.bodyin a comment directly above the line that implements it —toContainwas matching the prose, not the code. Fixed with areadCodehelper that strips whole-line comments only (so a//inside a string survives: both a URL and the preview provider's inlined webview script appear in the files these blocks read).⭐ The instrument that found the red's siblings is worth naming: instead of guessing which trees to sweep, the dev grepped for every test anywhere that names one of the changed source files, control lit in the same run. It returned readers in three trees no affected-package suite reaches —
scripts/(the census pin),packages/types/(two pins readingcarousel.tsxandresizable.tsx) andexamples/schema-catalog/(one readingscroll-area.tsx).Acceptance notes
generatePage()inpackages/cli/src/commands/generate.ts:72doeswriteFileSyncof apages/NAME.jsoncarrying abodychild list (:80) — a producer in exactly the card'ssense, and the census places it in the same
app-metadatabucket the six were in. It isoutside the amended fence, and the bounded in-place exemption does not apply because a dev
cannot amend the claim's declared file surface. Listed, not touched; it is the one
app-metadatarow the after-census still reports. ⭐ Re-measured at source by the seat onorigin/main276d1745before filing, control lit in the same run.the platform SHIPS the dialect it is being asked to refuseuntil the block is re-pointed —at whatever still ships the dialect, or at nothing once nothing does, which is the day
objectui#6771 step 4 becomes landable. That is the intended handoff, ⛔ not a trap.
packages/runner/src/App.tsx; thecensus counts 2 (a
pagenode and thedivnested in it). The migration used 2.Table inaccuracy on the card, not a code defect.
schema-validator.ts:419; it is at:467on this base. Not repaired —cross-file line addresses are repaired opportunistically and never as a sweep, and a
card body is record.
main(
Co-authored-by: Claude), which is model-free. Noted because the session's defaultattribution names a model.
Not in this PR, deliberately
packages/core/src/validation/schema-validator.ts— alreadychildren-first; removingits
bodyarm belongs to the retirement's reader-convergence step, not here.packages/types/src/base.tsand its zod mirror — the published-type narrowing step.Both already accept
children; nothing to change.bodyarm itself, anywhere. Nothing in this branch narrows an accept set.Part of the retirement tracked on objectui#6771; that card is not addressed here and
remains open.
Generated by Claude Code
Generated by Claude Code