|
| 1 | +--- |
| 2 | +'@object-ui/types': minor |
| 3 | +'@object-ui/components': minor |
| 4 | +'@object-ui/core': minor |
| 5 | +'@object-ui/sdui-parser': minor |
| 6 | +'@object-ui/cli': minor |
| 7 | +'@object-ui/app-shell': minor |
| 8 | +'object-ui': minor |
| 9 | +--- |
| 10 | + |
| 11 | +**BREAKING (authoring surface): `body` is no longer a child-list key. Author `children`.** |
| 12 | + |
| 13 | +`BaseSchema` declared two spellings of one concept — `body` and `children` — and left the |
| 14 | +choice per component. **Thirteen** registrations read `body` and nothing else, so it was |
| 15 | +their ONLY door — the twelve the ruling enumerated plus `tooltip`, which it did not, and |
| 16 | +which is the count the committed instrument carries (`BODY_ONLY` plus |
| 17 | +`BODY_ONLY_UNRULED` in `scripts/body-dialect-census.mjs`). `div`, `card`, `page`, |
| 18 | +`button`, `aspect-ratio`, the seven sectioning tags and the safe-HTML tag factory read |
| 19 | +`children || body` and took either. The authoring tier only ever knew `children`, so |
| 20 | +an author writing the one spelling that resolved got `unknown-prop` — the same warning a |
| 21 | +typo draws, on the tier built to accept AI-authored pages, where the diagnostic **is** the |
| 22 | +contract (objectui#6771). |
| 23 | + |
| 24 | +Ruled 2026-09-01: one concept, one spelling, and the spelling is `children`. |
| 25 | + |
| 26 | +## What changed |
| 27 | + |
| 28 | +- **Renderers.** `alert`, `badge`, `tooltip` and the `sidebar-*` family read `children`. |
| 29 | + Every `children || body` fallback drops its `body` arm **except the four `page:*` |
| 30 | + reads named below** — `div`, `card`, `button`, `aspect-ratio`, the sectioning tags, |
| 31 | + the safe-HTML tag factory behind ~36 tags, `page`'s flat content list, and |
| 32 | + `@object-ui/core`'s recursive `validateSchema`. |
| 33 | +- **Item-level `body` is a DIFFERENT key and is untouched.** `list` draws each entry |
| 34 | + as `item.content || renderChildren(item.body)` and `tabs` as |
| 35 | + `item.content || item.body`, both filed under the ITEM type rather than the node, |
| 36 | + and `tabs` still ships `body` inside its own `defaultProps`. ⛔ Neither is this |
| 37 | + spelling and neither is refused here: the node-level retirement does not reach a |
| 38 | + member of a declared `items` array. Retiring the item-level dialect is |
| 39 | + objectui#9590's card, and the two named above are recorded on it. |
| 40 | +- **What still reads `body` at NODE level, and why.** Four renderer reads, all `page:*`: `page:card` |
| 41 | + (renderer and Studio canvas) and the three thin `page:section` / `page:footer` / |
| 42 | + `page:sidebar` containers. ⛔ Authoring the key is refused on them as it is |
| 43 | + everywhere else; only the READ survives, for documents already STORED under it. |
| 44 | + `@objectstack/spec` states the ground on `PageContainerProps` itself: «The renderers |
| 45 | + keep reading `body` as a back-compat fallback for stored documents; that fallback is |
| 46 | + objectui's to retire on its own schedule, and it is not a second authorable |
| 47 | + spelling.» ⚠️ What separates the two halves is a CONVERSION, not a ground: the spec's |
| 48 | + registry carries `page-card-body-to-children` (`toMajor: 17`, surface |
| 49 | + `page.component.page:card.body`, shipped `retiredFromLoadPath: true` in spec 17.0.0) |
| 50 | + and carries none for the other three — so a stored row under their key has no |
| 51 | + migration path at all. Nothing in this repository authors any of them, and the |
| 52 | + committed census over those keys is how to see that rather than take it on trust: |
| 53 | + `node scripts/body-dialect-census.mjs --keys page:section,page:footer,page:sidebar,page:card,card`. |
| 54 | + ⚠️ This sentence used to restate that table's answer instead of pointing at it, and |
| 55 | + the very commit that edited the sentence moved the figure — a count copied into prose |
| 56 | + is what AGENTS.md #9 forbids, so the instrument replaces the number. Read the table |
| 57 | + for three things: `card` + `body` is a control that FIRES on the head that ships (so |
| 58 | + a zero elsewhere is a reading, not a blind spot), every node still carrying `body` |
| 59 | + under these keys falls in the `test` bucket, and `page:section` is a LIT zero — 6 |
| 60 | + nodes resolved, 0 `body`, 4 `children` — rather than a key nobody asked about. |
| 61 | + Retiring the THREE THIN reads is objectui#9916, |
| 62 | + which asks for the stored-corpus reading they turn on; `page:card`'s read is not |
| 63 | + that card's either — it is objectui's own, grounded by objectstack#5775 / |
| 64 | + ADR-0087 D2 rather than owned by it, and it ends when the stored rows have been |
| 65 | + replayed through the conversion that already shipped for it. |
| 66 | +- **The published type.** `BaseSchema.body` is `never` on the TypeScript face and an |
| 67 | + alias refusal naming `children` on the Zod mirror, as are the four per-component |
| 68 | + redeclarations (`CardSchema`, `AspectRatioSchema`, `PageNodeSchema`, `TooltipSchema`). |
| 69 | + ⚠️ Refused **by name**, not deleted: `BaseSchema` carries an index signature and the |
| 70 | + mirror ends `.passthrough()`, so a deleted member would be accepted silently and |
| 71 | + rendered by nothing — the exact silence this retirement ends. |
| 72 | +- **The VS Code extension (`object-ui`) changes behaviour, not just teaching.** Its |
| 73 | + preview no longer draws a `body`-spelled document — the node renders empty, the way |
| 74 | + the runtime renders it — and its validator now emits a warning naming `children` at |
| 75 | + the key's own position. That warning is skipped for node types that declare their |
| 76 | + own `body` input (`record:alert`), so a declared translation-map `body` is not |
| 77 | + reported as the retired spelling. |
| 78 | +- **Two new `@object-ui/sdui-parser` exports.** `RETIRED_CHILD_LIST_KEY` (the retired |
| 79 | + spelling, so a consumer names it once) and `checkRetiredBodyDialect` (the diagnostic |
| 80 | + the tier substitutes for `unknown-prop` on that key). |
| 81 | +- **The authoring tier.** `sdui-parser` answers an authored `body` with the replacement |
| 82 | + named, instead of the bare "has no prop" every typo gets — and a `body` child list |
| 83 | + under a non-container draws the same containment code the `children` spelling draws. |
| 84 | +- **Corpus and teaching, in the same change.** 114 authored nodes across the docs site, |
| 85 | + the schema catalog, five package READMEs and the VS Code extension's snippets, JSON |
| 86 | + schema, syntax map, hovers and completions. The platform does not refuse a spelling it |
| 87 | + still ships. |
| 88 | + |
| 89 | +## Migrating |
| 90 | + |
| 91 | +Rename the key. `{ "type": "card", "body": [...] }` becomes |
| 92 | +`{ "type": "card", "children": [...] }`; both faces now name `children` in the refusal, |
| 93 | +and `pnpm census:body-dialect` reports where the dialect still lives in a tree. |
| 94 | + |
| 95 | +⚠️ **Four renderer reads are deliberately untouched, all in the `page:*` namespace** — |
| 96 | +`page:card` and the three thin `page:section` / `page:footer` / `page:sidebar` |
| 97 | +containers. They are read-only back-compat paths for STORED documents, not a second |
| 98 | +authoring face: `@objectstack/spec`'s `PageContainerProps` says so itself — `children` |
| 99 | +is canonical, `body` is deliberately not declared, and «the renderers keep reading |
| 100 | +`body` as a back-compat fallback for stored documents; that fallback is objectui's to |
| 101 | +retire on its own schedule». |
| 102 | + |
| 103 | +⛔ **Authoring `body` on them is still refused**, on both published faces and at the |
| 104 | +authoring tier, exactly as everywhere else. What survives is the READ. |
| 105 | + |
| 106 | +⚠️ The two halves are not in the same state. `page:card` has a migration path — the |
| 107 | +spec's conversions registry carries `page-card-body-to-children` (`toMajor: 17`, |
| 108 | +surface `page.component.page:card.body`), which shipped with `retiredFromLoadPath: true` |
| 109 | +in spec 17.0.0. The other three have **no conversion at all**, so a stored row under |
| 110 | +their key has nowhere to be migrated to. ⇒ the two halves are tracked separately, and |
| 111 | +the pointer is not one card: retiring the three thin reads is objectui#9916, whose |
| 112 | +question is the stored corpus; retiring `page:card`'s read was **grounded by** |
| 113 | +objectstack#5775 / ADR-0087 D2, whose conversion has already shipped and whose |
| 114 | +remaining condition is replayed rows. ⚠️ Grounded by, not owned by — the distinction |
| 115 | +is the point. objectstack#5775 is CLOSED and its body is a props-declaration audit |
| 116 | +that carries no such step, so a reader sent there for the action finds none. The |
| 117 | +action is objectui's, on the spec's own terms quoted above: «that fallback is |
| 118 | +objectui's to retire on its own schedule.» |
| 119 | + |
| 120 | +**Non-rendering readers keep their arm on the same rule**, and none of them renders |
| 121 | +anything: while a renderer still reaches stored `body` content, a reader that must see |
| 122 | +the SAME content keeps its arm, or the renderer draws what the reader cannot find. |
| 123 | +Those are the two tab-subtree walkers in `renderers/layout/containers.tsx`, |
| 124 | +`app-shell`'s `pageSchemaIntrospect` (`CONTAINER_KEYS`) and `PageBlockInspector` |
| 125 | +(`STRUCTURAL_PROP_KEYS`, the inspector half of a stored `properties.body`), and the |
| 126 | +CLI's `OBJECTUI_STRUCTURAL_KEYS` — a file-IDENTIFICATION marker, where keeping `body` is |
| 127 | +what lets an old file still be recognised as an ObjectUI node and therefore refused, |
| 128 | +instead of silently not judged. |
0 commit comments