Skip to content

Commit 24845c4

Browse files
fix(types,components,docs): correct the texts objectui#6771 left false about body (objectui#8284) (#10879)
Fixes #8284 Clause-②: no The closing dispatch of ruling `5861449497` (batch #229 item 5: Q1 A, Q2 A, Q3 A). objectui#6771 retired `body` as a child-list spelling, so `children` is the one child-list key and `BaseSchema` refuses `body` by name. This PR corrects the texts that still described the two-channel world, including the five texts the claim amendment `5862012296` added in round 2. It changes **prose and strings only**: no key, value, refusal behaviour, issue code or path, declared type or rendered result moves. Written by the `os-dev` agent dispatched by the `domain:ui` seat #1, session `https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk`. ## What changed, sentence by sentence Every new sentence was checked against `main` plus this diff. The measurement that makes it true is in the last column. | where | old | new | measured | |:--|:--|:--|:--| | `zod/form.zod.ts` (`ToggleSchema`, `FormSchema`) and `zod/layout.zod.ts` (`BoxSchema`, `TextSpanSchema`, `ContainerSchema`, `FlexSchema`, `StackSchema`, `GridSchema`, `ScrollAreaSchema`): the `aliasKeyRefusal('body', 'children', …)` detail | "`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered an EMPTY element — no error, no warning. objectui#8284." | "`body` is the child-list spelling objectui#6771 retired — one concept, one spelling — so it is refused here by name; write the content under `children`, the one child-list key. objectui#8284." | Re-derived count: 9 (2 in `form.zod.ts`, 7 in `layout.zod.ts`); old text 9 → 0 and new text 0 → 9 on disk. Built dist: old 0, new 18 (the three published zod modules). A probe of the built `dist/zod/index.zod.js` shows each of the nine refuses `body` at path `body` with `invalid_type` and accepts `children`. The first sentence of each message ("X reads `children`, never `body`") was left alone: each renderer's only read is of `schema.children`. | | `zod/data-display.zod.ts` (`AlertSchema`, `BadgeSchema`): the `aliasKeyRefusal('body', 'children', …)` detail (round 2) | "… until objectui#6771 retired the spelling; an authored `body` now parses green through `.passthrough()` and renders an EMPTY element — no error, no warning. objectui#8284." | "… until objectui#6771 retired the spelling — one concept, one spelling — so it is refused here by name; write the content under `children`, the one child-list key. objectui#8284." The history clause ("`body` was this node's only child-list key until …") is kept. | Old 2 → 0 and new 0 → 2 on disk. Built dist: old 0, new 4. The built zod refuses `body` on both at path `body` with `invalid_type`, accepts `children`, and the message equals the `.describe()` metadata. At the 17.6.0 release commit `59f61cfb8` both renderers drew `schema.body`, which makes the kept clause true. | | `.changeset/8284-content-channel-per-component.md` | "`BaseSchema` declares two optional content channels …" and the `alert` / `badge` / `tooltip` row ("now refused: the other channel") | Kept as written. A dated note in the objectui#10533 form, dated 2026-09-28, placed after the table: at release `BaseSchema` declares one content channel, all twelve components refuse `body`, and `alert` / `badge` / `tooltip` accept `children`. It also says the Migration paragraph does not hold for those three and points at objectui#6771's entry. | Frontmatter md5 identical base → head; the diff adds 13 lines and deletes none. The three renderers' read is `renderChildren(schema.body)` at the 17.6.0 release commit `59f61cfb8` and `renderChildren(schema.children)` on `main`. The 8284 test's `ROWS` carries `dead: 'body'` for all twelve, and it passes. | | `.changeset/6877-div-guidance-names-box.md` (round 2) | "state the `body` → `children` move that every option except `card` requires" | Kept. Dated note, 2026-09-28: every option, and `div` itself, reads `children` only; the move is required with `card` included; a retype no longer drops `body` content because `div` does not draw it and validation refuses the key by name; the notice's `body` bullet now says that (objectui#8284); `deprecated.replacement` is unchanged. | The runtime probe in the `div.tsx` notice row shows `div` and all six replacements draw `children` and none draws `body`. `deprecated.replacement` is byte-identical to the 6877 commit `4562ea517`. This entry does publish: the release branch's `packages/components/CHANGELOG.md` carries its opening sentence. | | `.changeset/6773-aspect-ratio-demo-content.md` (round 2) | "`aspect-ratio.tsx` reads `ratio`, `className`, `image` (with `alt`) and `children \|\| body`" | Kept. Dated note, 2026-09-28: since this change objectui#6771 dropped the `body` arm, in `aspect-ratio.tsx` and in the nested `card` alike; both zod mirrors refuse `body` by name and both TypeScript faces declare it `never`; the four card demos already author `children`. | `aspect-ratio.tsx` reads `schema.ratio`, `className`, `image`, `alt` and `children`, with no `schema.body`. `card.tsx` has no `schema.body`. `AspectRatioSchema` and `CardSchema` declare `body?: never`, and their zod arms are `aliasKeyRefusal`. The four card demos author `children` at both levels, and the photo demo authors `image` plus `alt`. | | `.changeset/6788-context-menu-demo-content.md` (round 2) | "`card.tsx` reads … `children \|\| body` …" and "`card.tsx` accepts both, but `body` is marked legacy on `BaseSchema` and objectui#6771 is retiring it" | Kept. Dated note, 2026-09-28: that read is now `children` alone; `CardSchema` declares `body` as `never` and its zod mirror refuses it by name; `BaseSchema.body` is `never`, not a legacy member; the demo's `children` is the one spelling that renders. | `card.tsx` reads `title`, `description`, `header`, `children`, `footer`, `clickable` and `hoverable`, with no `schema.body`. The `basic-context-menu` demo's card authors `children` and carries no `body`. | | `content-channel-per-component-8284.test.ts` header | "`BaseSchema` declares TWO optional content channels …"; "This card rewrote that docblock"; "Families C (reads both — a live `children \|\| body` fallback), D … and E … are named on the card with the measurement each still needs"; "Nothing about rendering changes …" | The first is now in the past tense, with a paragraph saying what #6771 changed and what the file still pins. The docblock rewrite is credited to objectui#6771. C is done by #6771, D is pinned by `content-channel-family-d-9256.test.ts`, and E is done or superseded (ruling `5861449497`). The not-pinned section describes the two faces this file reads. | The test code is byte-identical: md5 from the first `import` to EOF matches base. The controls the header cites still fire: `span.tsx` raw 1 / comment-stripped 0, `schema.bodyExtra` in `action-icon.tsx` 1 / 1, `box.tsx` 0 / 0. Two registrations claim `sidebar`, and the second is typed `React.FC` of `any`. | | `card.tsx` JSX comment | "`\|\|` here is ALIAS RESOLUTION — `body` is the legacy spelling of `children` … the alias order is unchanged." | The one child-list key is `children`, and objectui#6771 dropped the `body` fallback arm. No `\|\|` is the guard. `renderNodeSlot` is the guard (objectui#9162), so an empty slot, `0` included, renders no `CardContent`. | The only read is `renderNodeSlot(schema.children, …)`. `isEmptyNodeSlot(0)` is true. The comment avoids the literal chain spelling because `node-slot-numeric-falsy.test.tsx` pins the source against it; the first draft quoted it and that pin went red, so this was caught locally. | | `div.tsx` `DIV_DEPRECATION_NOTICE`, third bullet | "every replacement above except "card" reads `children` only, so a blind retype drops it silently at an unchanged element count." | "`body` is the child-list spelling objectui#6771 retired, so validation refuses it by name, and neither this component nor any replacement above draws it." | Runtime probe through the real `SchemaRenderer` (temporary, uncommitted): `div`, `box`, `card`, `flex`, `container`, `stack` and `grid` each draw an authored `children` (the control fires) and none draws `body`; 7/7. The zod probe shows `DivSchema` refuses `body` by name through `BaseSchema`. The bullet names no type in double quotes, so the offered set that `deprecation-guidance-agreement` and `div-guidance-names-box` compare is unchanged. | | `div.tsx` docblock above the notice | (appended) | A dated clause: the "four of those five" reading was the #6877-era measurement, and since #6771 `card` and `div` read `children` only too. | Same probe. The docblock says the notice and `div.mdx` move in one stroke, which is why the next two rows are here. | | `div.mdx` callout | "every replacement listed above except card reads children only, so a blind retype drops that content silently" | Mirrors the notice: objectui#6771 retired that spelling, so validation refuses it by name, and neither `div` nor any replacement draws it. | Same probe. The callout is outside the list of code tags that `div-guidance-names-box` reads, so the offered set is unchanged. | | `div.mdx` Migration caveat | "Such a node must move that content into `children` as part of the swap, or it disappears from the page …" | `div` itself reads `children` too. Until the content moves, validation refuses the key and the content is missing under `div` and every replacement alike. | Same probe. | | `div.mdx` interface block | `body?: SchemaNode[]; // Alternative content prop` | Removed. `children` is annotated as the child key this component reads (the `span.mdx` wording). | `DivRenderer` reads only `schema.children`, and its registration's `inputs` is `className` plus a `children` slot. | | `page.mdx` interface block | `body?: … // Main content — one node or a list` / `children?: … // Alternative content prop` | `children?: SchemaNode \| SchemaNode[]; // Main content — one node or a list` | The block documents `type: 'page'`. All five `components-layout-page` catalog examples have `type` `page` and author `children` (0 `body`). `PageRenderer` reads only `schema.children`, and the `pageMeta` input is `children`. `PageNodeSchema.children` is one node or a list on both faces. The four `page:*` renderers that still draw `body` for stored documents are separate `@object-ui/components` registrations under the `page` namespace (in `renderers/layout/containers.tsx`); this page does not document them and nothing here touches them. | | new `.changeset/8284-refusal-and-notice-text.md` | — | `patch` for `@object-ui/types` (the published refusal messages, including `alert` and `badge` since round 2) and `@object-ui/components` (the runtime notice). | `check-changeset-presence`: "6 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)". | ⚠️ A correction to round 1's reasoning, not to the notes: `6773` and `6788` have EMPTY frontmatter, so they declare no release and publish into no CHANGELOG. On the release branch (`changeset-release/main`, head `bc117427f`), no `CHANGELOG.md` carries either entry's opening text. The positive controls fire on the same read: `6877`'s opening sentence is in `packages/components/CHANGELOG.md`, and `8284`'s is in `packages/types/CHANGELOG.md`. Their notes therefore correct false text in the tree, not text the release would publish. Both say "Since this change" rather than "Later in this same release" for that reason. ## Held out, as dispatched - `semantic.mdx` is **not touched**: PR objectui#10852 (another seat) already rewrites that interface block to teach `children` only. - The header of `examples/schema-catalog/test/card-demo-content-6788.test.tsx` still says "`card.tsx` reads `children || body`". Per the claim amendment it stays out: it is a test comment with no reach, and that file copies literals rather than reading `card.tsx`. - The 15-key residual and the `InputShorthandSchema` / `UiCalendarSchema` `Omit` erasure belong to objectui#9256, under that card's own claim. - No renderer behaviour moves, and no refusal's key or replacement key moves. ## Serial constraints (PR objectui#10714, PR objectui#10875) Re-mapped at branch time. That PR's only hunk in `zod/layout.zod.ts` is still its `TabItemSchema` insertion. On the PR's base and on `fab627ff9` that region is byte-identical; the two bases differ in this file only at `HtmlElementSchema`'s tag list. The nearest hunks here are the `GridSchema` refusal (34 lines above that region) and the `ScrollAreaSchema` refusal (46 lines below), so nothing sits within 5 lines of it. `zod/data-display.zod.ts` (round 2) is also edited by PR objectui#10714 (hunks in `ListItemSchema` and `ListSchema`) and PR objectui#10875 (hunks from `TableColumnSchema`'s docblock onward). Re-mapped at both PRs' current heads before editing. The text around the `alert` and `badge` arms is byte-identical on this base, on both PRs' bases and on `main`, and the two edits here end 39 lines before the nearest foreign hunk. No open PR touches the three pending changesets. `main` has since moved to `de1b879a6`. In `layout.zod.ts` it changed only `TextSchema`, which starts 7 lines below the `TextSpanSchema` hunk and does not overlap it. In `data-display.zod.ts` it changed only `TreeViewSchema` and below, far from the two arms. None of the other files this PR touches moved on `main`. ⚠️ The Version Packages PR objectui#5400 was still open when each round was pushed. Before the round 2 push, `.changeset/8284-content-channel-per-component.md` and the three round 2 changesets were all still on `main` (`git cat-file -e` exit 0 for each, with the `ci.yml` control at exit 0). ## Verification Round 2 (head `e5823aef7`): - `pnpm exec turbo run build --filter='@object-ui/types...'`: `Tasks: 1 successful, 1 total`. The built zod probe of `AlertSchema` / `BadgeSchema` exits 0. - `pnpm exec vitest run packages/types/`: `Test Files 256 passed (256)`, `Tests 5470 passed (5470)`. `pnpm --filter @object-ui/types run type-check`: script name echoed, exit 0. - All 24 `scripts/__tests__` suites that mention `.changeset` (a superset of the two `scripts/markdown-test-inputs.mjs` lists for `.changeset/**`), run explicitly: `Test Files 24 passed (24)`, `Tests 913 passed (913)`. - `check-changeset-overwrite` (as CI runs it, report-only): exit 0, header "⚠️ This change touches 4 changeset(s) it did not add". It lists 6773, 6788, 6877 and the 8284 entry, each with the same declaration at base and now (none for the two empty ones, `@object-ui/components: patch`, `@object-ui/types: minor`), which is its case 2, a deliberate correction. With `OS_CHANGESET_OVERWRITE_ENFORCE=1` the same findings exit 1; the workflow does not set that switch. - Lint on `zod/data-display.zod.ts` (`eslint --no-inline-config --format json`): 1 file, 0 errors, 0 warnings. The round 1 invariance argument covers it. - `check-changeset-presence` "6 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; `check-changeset-no-major`, `check-changeset-fixed`, `check-pending-changeset-literals` and `check-control-bytes` ("OK (scanned 9112 tracked text file(s)") all exit 0; `check-new-cross-file-line-citations` "0 new citation(s)"; `check-changeset-claims` is report-only and names 22 pending changesets that mention a touched file, none of which describes the `alert` / `badge` refusal text. Round 1 (head `5a82fb771` unless stated): - `pnpm exec turbo run build --filter='@object-ui/components...'`: `Tasks: 8 successful, 8 total` (on `1dd672dc6`; the only later commit rewords one JSX comment). - `pnpm exec vitest run packages/types/`: `Test Files 256 passed (256)`, `Tests 5470 passed (5470)`. - `pnpm exec vitest run packages/components/`, run in two halves: `138 passed (138)` / `1312 passed, 17 skipped`, then `183 passed, 1 skipped (184)` / `1825 passed, 7 skipped`. On `1dd672dc6` the single run caught the `card.tsx` source pin (1 failed of 3137); `5a82fb771` fixes it. - `pnpm --filter @object-ui/types --filter @object-ui/components run type-check`: both script names echoed, exit 0. The types script chains `tsconfig.test.json`, which reads the 8284 test's `@ts-expect-error` lines. - All of `scripts/__tests__` (179 files, a superset of every suite that `scripts/markdown-test-inputs.mjs` lists as reading `.changeset/**`, `content/docs/**` or `div.mdx`): `91 passed (91)` / `3558`, then `86 passed, 2 skipped (88)` / `1759`. The skips are the network-escape and timezone suites. - Gate scripts, each exit 0 with its own verdict line: `check-control-bytes`, `check-doc-component-types`, `check-doc-fence-languages`, `check-doc-example-ids`, `check-doc-expression-carriage`, `check-new-cross-file-line-citations` ("0 new citation(s)"), `check-changeset-presence`, `check-changeset-no-major`, `check-changeset-claims` (report-only: 16 pending changesets name `form.zod.ts` / `layout.zod.ts`, and none of them describes the `body` refusal text), `check-pending-changeset-literals`, `check-installed-spec-pin-claims`, `check-shell-escape-residue`, `check-comment-mask-corpus`, and `check-governed-queue-guard --test` ("NOT GOVERNED — 9 path(s)"). - NOT MEASURED: `check:doc-snippets` (round 1; round 2 touches no docs). It exited 2, "PRECONDITION NOT MET" (the 34-package build it compiles against is absent), which is not a verdict. It compiles only ts/tsx fences, and both docs blocks edited here are `plaintext` fences, so it cannot read this change. Declared to CI. - Lint, as a proven narrowing rather than the repo-wide run (that belongs to CI): `eslint --no-inline-config --format json` on the five touched TS/TSX files gives 5 files, 0 errors and 4 warnings. All four are pre-existing, on the `forwardRef` declaration lines of `div.tsx` and `card.tsx`, which this diff does not touch. Invariance: `eslint.config.js` sets no `parserOptions` / `project` / `projectService` and no type-checked preset, and no rule under `eslint-rules/` reads the disk, so no untouched file's verdict can move. ## Acceptance notes - Round 1 reported six same-family texts outside the claim: two refusal strings, three pending changesets and one test comment. The claim amendment `5862012296` added the first five to this PR (round 2); the test comment stays out, as noted above. --- _Generated by [Claude Code](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e2b3826 commit 24845c4

13 files changed

Lines changed: 142 additions & 68 deletions

File tree

‎.changeset/6773-aspect-ratio-demo-content.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ nested `card` never read `content` either, so moving only the outer key would ha
1414
an empty box for an empty card. The page's Schema block published `content` as contract
1515
while omitting `image`/`alt`; it now documents what the renderer reads.
1616

17+
⚠️ **Dated note, 2026-09-28 — the child list is `children` alone — objectui#6771.** Since
18+
this change, objectui#6771 dropped the `body` arm of that read, in `aspect-ratio.tsx` and in
19+
the nested `card` alike: both read `children` and never `body`, both zod mirrors refuse an
20+
authored `body` by name, and both TypeScript faces declare it `never`. The four card demos
21+
already author `children` at both levels, so they are unaffected. The rest of this entry is
22+
kept as the reading of this change.
23+
1724
Nothing publishes from this change — a docs page plus `@object-ui/example-schema-catalog`
1825
fixtures, both outside the release — hence the empty frontmatter. The regression control is
1926
`examples/schema-catalog/test/aspect-ratio-demo-content-6773.test.tsx`: category scope, not

‎.changeset/6788-context-menu-demo-content.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,11 @@ dialect, and objectui#6773 authored `children` in the four sibling
1818
`aspect-ratio` card demos. The renderer was NOT widened to read `content` —
1919
that would add a second dialect for one slot to a published surface.
2020

21+
⚠️ **Dated note, 2026-09-28 — `card` reads `children` alone — objectui#6771.** Since this
22+
change, objectui#6771 retired `body` as a child-list spelling: the `children || body` read
23+
above is now `children` alone, `CardSchema` declares `body` as `never` and its zod mirror
24+
refuses it by name, and `BaseSchema.body` is `never` rather than a legacy member. So the
25+
demo's `children` is the one spelling that renders, not the preferred one of two. The rest
26+
of this entry is kept as the reading of this change.
27+
2128
No package source changed, so this declares no release.

‎.changeset/6877-div-guidance-names-box.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ exactly the conversions objectui#3965 measured and rejected.
1515
Both statements now name `box` first for the mechanical swap, keep the layout components for the
1616
cases where their layout is actually wanted, and state the `body` → `children` move that every
1717
option except `card` requires. `span`'s guidance is deliberately unchanged.
18+
19+
⚠️ **Dated note, 2026-09-28 — every option, and `div` itself, reads `children` only —
20+
objectui#6771.** Later in this same release objectui#6771 retired `body` as a child-list
21+
spelling, and `card` and `div` dropped their `body` arms with it. So the `body` → `children`
22+
move is one every option requires, `card` included. A retype no longer drops `body` content
23+
either: `div` does not draw it, and validation refuses the key by name. The notice's `body`
24+
bullet now says that (objectui#8284); `deprecated.replacement` is unchanged. The rest of this
25+
entry is kept as the reading of this change.

‎.changeset/8284-content-channel-per-component.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ that names the channel to write instead:
2727
| `children` | `box`, `span`, `container`, `flex`, `stack`, `grid`, `scroll-area`, `form`, `toggle` | `body` |
2828
| `body` (at the time of this change; objectui#6771 has since retired that spelling and these three read `children`) | `alert`, `badge`, `tooltip` (which reads `content` first, the child list as its fallback) | the other channel |
2929

30+
⚠️ **Dated note, 2026-09-28 — `BaseSchema` declares one content channel, and the
31+
`alert` / `badge` / `tooltip` row is inverted at release — objectui#6771.** Later in this same
32+
release objectui#6771 retired `body` as a child-list spelling: `BaseSchema.body` is `never` on
33+
the TypeScript face and refused by name on the zod mirror, and `children` is the one
34+
child-list key. So the paragraph that opens "`BaseSchema` declares two optional content
35+
channels" no longer describes `BaseSchema`, and for all twelve components in the table the
36+
renderer reads `children` and the refused channel is `body`. That includes `alert`, `badge`
37+
and `tooltip`, which accept `children` and refuse `body`, not the other way round. For those
38+
three the Migration paragraph below does not hold as written: in the previous release their
39+
renderers drew an authored `body`, and this release refuses it. objectui#6771's entry states
40+
that migration: author `children`. The rest of this entry is kept as the reading of this
41+
change.
42+
3043
Which channel each renderer reads was measured with the TypeScript type checker over
3144
every `ComponentRegistry.register(...)` call in `packages/components` — a read site is a
3245
property access filed under the type of the object it is read from, so a docblock mention
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@object-ui/types': patch
3+
'@object-ui/components': patch
4+
---
5+
6+
Correct author-facing text that objectui#6771's retirement of the `body` child-list spelling
7+
left false (objectui#8284). Only wording moves; no key, value or rendered result does.
8+
9+
- `@object-ui/types` — the `body` refusal on `box`, `span`, `container`, `flex`, `stack`,
10+
`grid`, `scroll-area`, `form` and `toggle` explained that `body` "is inherited from
11+
`BaseSchema`, so an authored `body` parsed green here". Since objectui#6771, `BaseSchema`
12+
refuses `body` by name itself, so that explanation no longer holds. The message now says that
13+
`body` is the child-list spelling objectui#6771 retired, refused by name, and that `children`
14+
is the one child-list key. The same refusal on `alert` and `badge` said that an authored
15+
`body` "now parses green through `.passthrough()`", which that refusal itself contradicts;
16+
like the other nine, it now says that `body` is refused here by name and that `children` is
17+
the one child-list key. The refused key, the replacement it names, the issue code and the
18+
issue path are unchanged; the same string is still the `.describe()` metadata.
19+
- `@object-ui/components` — the `div` deprecation notice said that every replacement except
20+
`card` reads `children` only, so a blind retype drops `body` content. Since objectui#6771,
21+
`card` and `div` itself read `children` only as well. The notice now says that validation
22+
refuses `body` by name and that neither `div` nor any replacement draws it. The replacements
23+
it offers are unchanged.

‎content/docs/components/basic/div.mdx‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ description: "Generic container element - use Shadcn components instead"
1313
<li>When you want their layout: use <code>card</code>, <code>flex</code>, <code>container</code>, <code>stack</code>, or <code>grid</code> — each injects classes of its own, and <code>card</code> also moves children into an extra element</li>
1414
</ul>
1515
<p className="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
16-
Move any content authored under the legacy <code>body</code> key into <code>children</code> first: every
17-
replacement listed above except <code>card</code> reads <code>children</code> only, so a blind retype drops
18-
that content silently, at an unchanged element count.
16+
Move any content authored under the retired <code>body</code> key into <code>children</code> first:
17+
objectui#6771 retired that child-list spelling, so validation refuses it by name, and neither
18+
<code>div</code> nor any replacement listed above draws it.
1919
</p>
2020
</div>
2121

@@ -47,9 +47,10 @@ the page; a `div` → `box` swap does not.
4747

4848
One caveat for a node still authored in the retired `body` spelling: every
4949
option reads `children` and never `body` (objectui#6771 retired it across the
50-
protocol, `card` included). Such a node must move that content into `children`
51-
as part of the swap, or it disappears from the page without changing the element
52-
count — silently.
50+
protocol, `card` included), and so does `div` itself. Such a node must move that
51+
content into `children` as part of the swap. Until it does, validation refuses
52+
the key by name, and the content is missing from the page under `div` and under
53+
every replacement alike.
5354

5455
### For a Plain Wrapper → Use `box`
5556

@@ -83,8 +84,7 @@ interface DivSchema {
8384
type: 'div';
8485
8586
// Content
86-
children?: SchemaNode | SchemaNode[]; // Child components
87-
body?: SchemaNode[]; // Alternative content prop
87+
children?: SchemaNode | SchemaNode[]; // Child components (the child key this component reads)
8888
8989
// Styling
9090
className?: string; // Tailwind CSS classes

‎content/docs/components/layout/page.mdx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ interface PageSchema {
2525
description?: string; // Page description
2626
2727
// Content
28-
body?: SchemaNode | SchemaNode[]; // Main content — one node or a list
29-
children?: SchemaNode | SchemaNode[]; // Alternative content prop
28+
children?: SchemaNode | SchemaNode[]; // Main content — one node or a list
3029
3130
// Styling
3231
className?: string; // Tailwind CSS classes

‎packages/components/src/renderers/basic/div.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ function warnDeprecatedOnce(type: string, message: string): void {
8080
* and four of those five — `flex`, `container`, `stack`, `grid` — read
8181
* `children` ONLY, so a node that authored `body` loses its content SILENTLY,
8282
* at an unchanged element count. `box` is the one class-transparent swap, and
83-
* the old text never named it.
83+
* the old text never named it. (That was the measurement at the time. Since
84+
* objectui#6771 retired the `body` spelling, `card` and `div` itself read
85+
* `children` only as well, so `body` content is drawn by none of them and the
86+
* notice's `body` bullet says so.)
8487
*
8588
* That is why this is worth a re-ruling rather than a nice-to-have. Deprecation
8689
* guidance is followed LITERALLY, by humans and by generating models reading
@@ -116,7 +119,7 @@ const DIV_DEPRECATION_NOTICE =
116119
'[ObjectUI] The "div" component is deprecated on every authoring surface. Please use Shadcn components instead:\n' +
117120
' - For a plain wrapper the drop-in swap is "box": same element, your `className` verbatim, no layout of its own.\n' +
118121
' - Reach for "card", "flex", "container", "stack", or "grid" only when you want their layout — each injects classes of its own, and "card" also moves children into an extra element.\n' +
119-
' - Move any `body` content into `children` first: every replacement above except "card" reads `children` only, so a blind retype drops it silently at an unchanged element count.\n' +
122+
' - Move any `body` content into `children` first: `body` is the child-list spelling objectui#6771 retired, so validation refuses it by name, and neither this component nor any replacement above draws it.\n' +
120123
' This applies to JSON-authored nodes and to kind:\'html\' pages alike: an html page refuses the\n' +
121124
' tag when it compiles, naming the same replacement.\n' +
122125
'See documentation at https://www.objectui.org/docs/components for alternatives.';

‎packages/components/src/renderers/layout/card.tsx‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,16 @@ const CardRenderer = forwardRef<HTMLDivElement, { schema: CardSchema; className?
6262
{header}
6363
</CardHeader>
6464
)}
65-
{/* `||` here is ALIAS RESOLUTION — `body` is the legacy spelling of
66-
`children` — and ⛔ it is no longer the guard. That distinction is
67-
the whole point: `children: 0` used to be converted away by the
68-
accident of `0 || undefined === undefined`, which protected nothing,
69-
because the sibling `body: 0` went through `undefined || 0 === 0`
70-
and leaked. `renderNodeSlot` covers both; the alias order is
71-
unchanged. */}
65+
{/* `children` is the one child-list key: this slot used to fall back
66+
from `children` to `body` through an `||`, and objectui#6771 dropped
67+
that `body` arm when it retired the spelling (the callback's `body`
68+
below is only a local name for the slot's content). ⛔ No `||` is
69+
the guard here. That distinction was the point of objectui#9162:
70+
`children: 0` used to be converted away by the accident of
71+
`0 || undefined === undefined`, which protected nothing, because
72+
the sibling `body: 0` went through `undefined || 0 === 0` and
73+
leaked. `renderNodeSlot` is the guard, so an empty slot, `0`
74+
included, renders no `CardContent` at all. */}
7275
{renderNodeSlot(schema.children, (body) => (
7376
<CardContent>{renderChildren(body)}</CardContent>
7477
))}

‎packages/types/src/__tests__/content-channel-per-component-8284.test.ts‎

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,45 @@
77
*/
88

99
/**
10-
* objectui#8284 — the `body` / `children` duality on `BaseSchema` is resolved
11-
* PER COMPONENT: each component schema narrows to the channel its renderer
12-
* actually reads and TOMBSTONES the other, on both published faces
10+
* objectui#8284 — each component schema narrows to the content channel its
11+
* renderer actually reads and TOMBSTONES the other, on both published faces
1312
* (maintainer ruling, summon #17 decision batch #2, 2026-09-07, verbatim
14-
* 「同意」).
13+
* 「同意」). This file pins that rule on the twelve dedicated declarations
14+
* listed in `ROWS`.
1515
*
1616
* ## The defect this pins closed
1717
*
18-
* `BaseSchema` declares TWO optional content channels and its own docblock
19-
* admits that "some components use `children` instead of `body`" WITHOUT
20-
* saying which. The zod base is `.passthrough()` and both keys are optional,
18+
* `BaseSchema` used to declare TWO optional content channels, and its docblock
19+
* admitted that "some components use `children` instead of `body`" WITHOUT
20+
* saying which. The zod base is `.passthrough()` and both keys were optional,
2121
* so a node carrying the WRONG channel type-checked, parsed green, was
2222
* PRESERVED by the parse, and then rendered an EMPTY element — no error at
2323
* authoring time, none at validation time, none at render time. Seven cards
2424
* repaired one page of that each (#5027 · #3900 · #6773 · #6806 · #8197 ·
2525
* #8234 · #6939) before the declaration itself was named.
2626
*
27+
* objectui#6771 has since retired `body` as a child-list spelling on every
28+
* node: `BaseSchema.body` is `never` on the TypeScript face and refused by name
29+
* on the zod mirror, and `children` is the one child-list key. So on every row
30+
* below the renderer reads `children` and the refused channel is `body`. What
31+
* this file still pins is objectui#8284's own refusal on each row's DEDICATED
32+
* declaration: it is installed there, it names both keys and this card, it is
33+
* the `.describe()` metadata too, and it reaches a nested node.
34+
*
2735
* ## The population here is FAMILY A + B of the measured table, not "all"
2836
*
2937
* The table posted on objectui#8284 is derived from every
3038
* `ComponentRegistry.register(...)` call under
31-
* `packages/components/src/renderers/**` (114 registrations) and the read side
32-
* is measured with the TypeScript TYPE CHECKER — each `body` / `children`
33-
* property access is filed under the TYPE of the object it is read from, so a
34-
* docblock mention cannot score.
39+
* `packages/components/src/renderers/**` (114 registrations when it was taken)
40+
* and the read side is measured with the TypeScript TYPE CHECKER — each
41+
* `body` / `children` property access is filed under the TYPE of the object it
42+
* is read from, so a docblock mention cannot score.
3543
*
3644
* ⚠️ THE CONTROL MOVED, because objectui#6771 SPENT the one that stood here.
3745
* It read: the `BoxSchema` docblock in `renderers/layout/box.tsx` SAYS
3846
* `schema.body` in prose and `grep -l` counts it, the checker does not — and
39-
* the card's own `17 / 17` figure came from that query. This card rewrote that
40-
* docblock, so `box.tsx` now answers 0 to BOTH halves and a reader replaying it
47+
* the card's own `17 / 17` figure came from that query. objectui#6771 rewrote
48+
* that docblock, so `box.tsx` now answers 0 to BOTH halves and a reader replaying it
4149
* gets no divergence at all. ⛔ A control that cannot fire is worse than none:
4250
* it reads as evidence and is not.
4351
*
@@ -51,24 +59,27 @@
5159
* `renderers/action/action-icon.tsx` is a REAL property access and survives
5260
* stripping at 1, so the 0 above is a reading and not a blanked file.
5361
*
54-
* The twelve rows below are the ones where the renderer reads EXACTLY ONE
55-
* channel, the component owns a dedicated declaration, and exactly one
56-
* registration claims its `type`. `sidebar` is measured into family B and held
57-
* BACK from it, because two registrations claim `sidebar` and the second types
58-
* its schema prop `any`. Families C (reads both — a live `children || body`
59-
* fallback), D (reads neither) and E (no dedicated declaration) are named on
60-
* the card with the measurement each still needs.
62+
* The twelve rows below are the ones that table measured as reading EXACTLY ONE
63+
* channel, owning a dedicated declaration, and claimed by exactly one
64+
* registration. `sidebar` was measured into family B and held BACK, because two
65+
* registrations claim `sidebar` and the second types its schema prop `any`; it
66+
* is not a row here. The other families are not pinned in this file:
67+
* family C (read both, through a live `children || body` fallback) lost its
68+
* `body` arm to objectui#6771 and reads the `children` channel only; family D
69+
* (reads neither) is pinned by `content-channel-family-d-9256.test.ts`
70+
* (objectui#9256); family E (no dedicated declaration) was ruled done or
71+
* superseded by objectui#6771 and objectui#9910, with its residual moved to
72+
* objectui#9256 (ruling 5861449497 on objectui#8284).
6173
*
6274
* ## What is NOT pinned here, and why
6375
*
64-
* ⛔ Not the renderers. Nothing about rendering changes: a document that
65-
* authors the channel its renderer reads is byte-identical through both faces,
66-
* and a document that authors the other one rendered nothing before and
67-
* renders nothing now — it is merely REFUSED first. The counter-probes in
76+
* ⛔ Not the renderers. This file pins the two authoring faces only: a
77+
* document that authors the channel its renderer reads parses and compiles,
78+
* and a document that authors `body` is refused by both.
79+
* The render side is pinned elsewhere, by counter-probes that deliberately
80+
* author the dead channel (`body`) and assert the empty render:
6881
* `examples/schema-catalog/test/badge-demo-label-6829.test.tsx` and
69-
* `packages/components/src/__tests__/span-children-rendering.test.tsx`, which
70-
* deliberately author the dead channel and assert the empty render, therefore
71-
* keep passing.
82+
* `packages/components/src/__tests__/span-children-rendering.test.tsx`.
7283
*
7384
* ## ⚠️ Half of this file is a COMPILE-TIME assertion and vitest CANNOT read it
7485
*

0 commit comments

Comments
 (0)