Skip to content

fix(sdui-parser,components,layout,types): containment is the declared children slot, not isContainer (objectui#9910) - #10267

Closed
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/issue-9910-children-slot-declare-and-pin
Closed

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/issue-9910-children-slot-declare-and-pin

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #9910 — the code half of the maintainer ruling (Q1-A declare-and-pin, Q2-A re-description, Q3-A same release). It does not close the card on merge: the skills/objectui/** wording for Q2 is a governed surface and rides its own Tier H draft PR on branch claude/issue-9910-skills-iscontainer-wording, and the objectstack save-gate port is objectstack#19969 (Blocked-by this card). The seat marks the card done once both objectui PRs have landed; #9910 stays open until then.

Clause-②: yes

The ruling this executes (issue comment 5814512168, operative text quoted in full)

Q1 — A: declare-and-pin

Every renderer that reads schema.children declares { name: 'children', type: 'slot' } in its registration inputs, the shape ten registrations already carry. The containment diagnostic in packages/sdui-parser/src/validate.ts reads only that input. ⛔ There is no fallback to isContainer. The existing runtime census is re-pointed from the flag to the input in both directions (declared ⇔ actually renders children). Its red-when-fixed pins flip to the new truth, and container-declaration-baseline.json's excluded / undeclared rows are deleted as they clear. Scope: the ~67 registrations under packages/components + packages/layout, including tooltip, the five page kinds, sidebar and sidebar-menu-button. ⛔ B is not taken: it adds a published derived field plus a DOM render step, and it is measured blind to context and portal registrations.

Q2 — A: isContainer means layout container

Keep the field. Re-describe it on every published face (ComponentMeta.isContainer, ComponentMetaSchema, the plugin guide) as layout containment: excluded from the react-page scope and the public layout ledger, ⛔ no longer "can have children". react-page.tsx's buildComponentScope keeps reading it. The skills/** wording is a governed surface and lands as its own Tier H PR.

Q3 — A: same release, same pin bump

objectstack's save-gate copy of the parser moves in the same release and pin bump, so saving and rendering never disagree (objectstack#12719). The port card is filed in this stroke, carrying Blocked-by: objectstack-ai/objectui#9910.

Confidence gap carried to the claim: measure the effect on registry-inputs-spec-parity and objectstack check:react-declaration-parity first.

What changed

The tier (@object-ui/sdui-parser). validateTree's containment branch reads one declaration — acceptsChildren(comp), true iff comp.inputs carries an entry named children — and nothing else; isContainer is not consulted and is not a fallback. checkRetiredBodyDialect (the retired body spelling, second containment site) is handed the same predicate instead of comp.isContainer, so the two spellings cannot disagree (assumption A3, see below). New exports: CHILD_LIST_KEY ('children') and acceptsChildren. ManifestComponent.isContainer / RegistryConfigLike.isContainer are re-described as layout containment and still projected into the manifest (the objectstack save gate reads it there until objectstack#19969 lands).

The declarations (@object-ui/components, @object-ui/layout). Every renderer that puts schema.children on the page now declares { name: 'children', type: 'slot' }:

  • the 34 flow/inline HTML tags of basic/html-elements.tsx (declared inside the factory, skipped for the three void tags img / hr / br);
  • the seven sectioning tags of layout/semantic.tsx;
  • aspect-ratio, box, card, container, flex, grid, stack;
  • alert, badge, button, toggle, span (label / description / value fallbacks), div, form, scroll-area;
  • the ten sidebar-* parts that render children (sidebar-trigger renders none and declares none);
  • page-header in @object-ui/layout.

That is 65 registrations measured bare plus sidebar and sidebar-menu-button, which only mount inside sidebar-provider — 67, the ruling's number. tooltip, the four page:* containers and the five page kinds already carried the input. No isContainer value moved anywhere.

The published descriptions (@object-ui/types, docs). ComponentMeta.isContainer, WidgetManifest.isContainer and ComponentMetaSchema's describe now say layout containment (react-page scope, public layout ledger), not "can have children", and point at the slot. content/docs/guide/react-pages.md and content/docs/guide/component-registry.md say the same. react-page.tsx's buildComponentScope is untouched (assumption A4).

The census. container-declaration-ratchet.test.tsx is re-pointed from the flag to the input in both directions over every known key (namespaced spellings included, since sidebar and page:sidebar are different registrations): renders ⇒ declares; declares ⇒ renders, with named context probes for sidebar, sidebar-menu-button (inside sidebar-provider) and tooltip (open, read from the portal), each pinned as still needed; and not-a-container fires on exactly the undeclared, whatever the flag says — page:tabs / page:accordion (flagged, no slot) keep the TRUE warning, button (slot, no flag, public) draws none. A layout-side census (packages/layout/src/__tests__/containment-declared-slot-9910.test.tsx) holds the same contract for that package's five registrations. The red-when-fixed pins on the objectui#6804 population flipped to the new truth, scripts/container-declaration-baseline.json is at zero rows (the file stays as the ruled admission ledger, with its note rewritten), and packages/sdui-parser/src/__tests__/containment-declared-slot-9910.test.ts pins the tier's 2×2 (flag × slot) reading of a manifest.

Fixture triage. Parser fixtures that nested children under an isContainer: true manifest entry now declare the slot too (compile, render, type-attribute-collision-7235, body-dialect-6771, verify.ts); the layout-containers-declare-containment childless control moved from badge (which now declares the slot it renders) to tabs; the layout page-header-authorable-keys dialect guard licenses children by name as the protocol's base key (PROTOCOL_LICENSED, read from CHILD_LIST_KEY) and asserts the spec does not carry it.

Measurements (the PM's assumptions, each taken before editing)

  • A1 — the ruling's confidence gap. objectui registry-inputs-spec-parity: of the registrations that render children, the only ones in the gate's covered set are page:card / page:footer / page:section / page:sidebar, all of which already declared children and whose spec shapes carry it (specCoveredRenders in the probe) — the forward direction is unaffected; measured by running the test after the change: green (in the 21-file / 498-test run and again on the final head). objectstack check:react-declaration-parity judges the REACT_BLOCKS and object-* blocks only, none of which renders children; measured by dumping the public manifest from this tree before and after (the way apps/console/dev/manifest-dump.tsx builds it) and running the gate read-only against each from a scratchpad shared clone of objectstack at 2c1011b with @objectstack/spec's deps installed (the shared checkout has no node_modules): control exit 0, after exit 0, output byte-identical, "no new DECLARATION divergence vs accepted baseline". Manifest delta: exactly nine public blocks gain a children input — flex, grid, stack, card, container, box, badge, alert, button; 59 keys before and after; zero isContainer values changed. A slot input emits no JSX attribute (emitInterface filters slot arms), so sdui-intrinsics.d.ts is unchanged. ⇒ neither gate goes red; no fork to report.
  • A2 — the population. A temporary full-registry probe (16 console modules + @object-ui/layout, 498 known keys, never committed) at the base: 144 keys render an authored child (bare + ui: twins), 16 declare the input; bare violators = 65 distinct registrations; sidebar and sidebar-menu-button render only inside the provider; tooltip renders only open, in a portal. After the change: violators = 0, the six context/portal keys are the only declared-but-bare-silent ones and each renders in its context, flaggedNotRendering = page:tabs, page:accordion, responsive-grid, app-schema-renderer, layout:page:card (layout containers that render a React children prop or items[].children, never schema.children — they now draw the TRUE diagnostic the flag used to silence).
  • A3 — the second validate.ts site. Read as covered by "reads only that input": the body-dialect check emits the same not-a-container code for a body list, so leaving it on the flag would make the two spellings disagree about which components take a list. Both sites read acceptsChildren(comp); the parameter is renamed acceptsChildren. Recorded under Deviations.
  • A4 — react-page. buildComponentScope still reads cfg.isContainer; not edited. react-page-scope.test.tsx and public-contract.test.ts pass unchanged.
  • Falsified detail in the brief: packages/core/src/registry/Registry.ts has zero isContainer hits on origin/main (grep -c = 0); the description faces are packages/types/src/base.ts, packages/types/src/widget.ts and packages/types/src/zod/base.zod.ts, which is where the re-description landed. Nothing in packages/core needed to change.

Verification (exit codes read from files, never from a pipe)

Every vitest / build / type-check run went through scripts/pm/os-verify-lock.sh (slot issue-9910-dev) and reads VERDICT command-exit 0. Final head of this branch: f34809934 (a merge of origin/main at fa5fbd9dc; the eight newer files on origin/main since then touch none of this surface).

check reading
pnpm exec vitest run packages/sdui-parser/ + ratchet, census, layout-containers, page-container, react-page-scope, registration-uniqueness, layout page-header + containment census, examples pageheader-with-actions exit 0 — 26 files, 335 tests passed
console registry-inputs-spec-parity, htmlTierDataSourceInputDeclaration-6678, public-contract; components record-picker / toast-sonner / action-type html-tier pins; types zod-mirror-parity, component-meta-single-declaration, default-children-retired-contract-twins; packages/core/src/registry/; kanban / detail / grid manifest pins exit 0 — 21 files, 498 tests passed
re-run on the final head f34809934: ratchet, census, layout census, layout page-header, parser 2×2 pin, console parity exit 0 — 6 files, 271 tests passed
turbo run build --filter=@object-ui/layout^... --filter=@object-ui/sdui-parser --concurrency=2 exit 0 — 8 tasks successful
pnpm --filter @object-ui/{sdui-parser,types,layout,components} type-check (script name echoed as type-check; tsconfig.test.json includes src/**/*.test.tsx, so the new tests are in the program) exit 0, 0, 0, 0
eslint --no-inline-config over the 38 touched .ts / .tsx files exit 0 — 0 errors, 141 pre-existing no-explicit-any warnings
check-changeset-presence (35 published source files, 4 released packages, 1 changeset) · check-changeset-no-major · check-control-bytes · check-new-cross-file-line-citations (0 new) · check-governed-queue-guard --test over the 42 paths (NOT GOVERNED) · check-registry-bare-name-collisions · check-test-path-roots · check-pending-changeset-literals · check-changeset-claims · check-doc-fence-languages · check-doc-component-types exit 0 each
objectstack check:react-declaration-parity --baseline --strict against the BASE manifest (control) and the AFTER manifest exit 0 and exit 0, identical output
NOT MEASURED locally, declared to CI: check:sdui-registration-pins (needs a console build), the full pnpm lint and pnpm test farm, dispatch-gates.mjs (refuses this repo; the list above is hand-derived from package.json and .github/workflows/) —

Reverse verification (type level, consumer side). After the build, the layout test that imports CHILD_LIST_KEY was mutated by appending const __ablationBad: number = CHILD_LIST_KEY; (marker count 0 → 1 on disk), pnpm --filter @object-ui/layout type-check went red with TS2322: Type 'string' is not assignable to type 'number' at the mutated line (exit 2), and the file was restored with git checkout HEAD -- FILE: marker count back to 0, git hash-object equal to the HEAD blob cddf5764…, git diff HEAD empty. This proves the consumer type-check reads the rebuilt sdui-parser .d.ts (an old one has no such export). The runtime direction is the census itself: the flag-only rows (page:tabs, page:accordion, responsive-grid) draw the diagnostic, which the previous predicate could not produce.

Deviations

  • Commit 748fc2484 carries a Co-Authored-By trailer with a model name, written from the harness attribution reminder before the role file's model-free rule was applied; force-push is forbidden in this repo, so the history is not rewritten and it is reported here. The later commits use a model-free trailer; the squash merge writes this PR's title and body.
  • A3: both validate.ts containment sites re-pointed together (the body-dialect parameter renamed acceptsChildren), reading "reads only that input" as covering the retired spelling's containment sentence.
  • The five page kinds keep { name: 'children', type: 'array', itemType: 'component' } — the predicate is name-based, the ruling counted them among the ten that already carry the declaration, and the parser pin asserts an array-typed children counts.
  • scripts/container-declaration-baseline.json is kept at zero rows rather than deleted (the ruling says rows are deleted as they clear); its note is rewritten to the new predicate and the census asserts the note's contract phrases.
  • Two new published exports on @object-ui/sdui-parser (CHILD_LIST_KEY, acceptsChildren) so the census, the layout dialect guard and the objectstack port read one spelling of the predicate; Clause-②: yes already covers a published-type change.
  • checkRetiredBodyDialect's fourth parameter narrows from boolean | undefined to boolean (it is always handed the predicate's boolean now).
  • Part of #9910, not a closing keyword: the governed half and the objectstack port remain.

Acceptance notes (observed, not filed — no class a/b/c finding)

  • containers.tsx's page:section / page:footer / page:sidebar still read schema?.children || schema?.body; the read-site comment records it as a back-compat read for stored documents, not an authorable key. Unchanged here.
  • codegen.ts's generateBlockList "container" column still reads isContainer; under Q2 that column now literally means layout container. Unchanged here.
  • check-skill-examples for the governed PR answers PRECONDITION NOT MET (exit 2, needs a 12-package scoped build) in the skills worktree — declared to that PR's CI, not a red.
  • Dedupe words, should the seat want a card on the first two: page-container body read · generateBlockList container column · isContainer layout.

Generated by Claude Code

… `children` slot, not `isContainer` (objectui#9910)

`validateTree`'s `not-a-container` reads exactly one declaration: an input
named `children` in the registration `inputs` (`acceptsChildren`,
`CHILD_LIST_KEY` exported). No `isContainer` fallback; the retired `body`
dialect follows the same predicate. Every renderer under components + layout
that puts `schema.children` on the page declares `{ name: 'children', type:
'slot' }` (65 measured bare, plus `sidebar` / `sidebar-menu-button` in their
provider context). `isContainer` is kept and re-described as LAYOUT
containment on every published face. The runtime census is re-pointed from
the flag to the input in both directions, with named context probes; the
containment ledger is at zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 38 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5632-sidebar-trigger-dom-passthrough.md

  • names renderers/navigation/sidebar.tsx → packages/components/src/renderers/navigation/sidebar.tsx — edited by this change

    The renderer forwarded its whole prop bag to SidebarTrigger, which spreads its own rest onto the ANGLE-BRACKETS(button) it renders — so every authored SDUI key on the node became an attribute. Fourteen of them, one more than the shape this target derives from, because this registration also never destructured schema: every other registration in renderers/navigation/sidebar.tsx names it (they need the node's child list — spelled schema.body when this change landed, schema.children since objectui#6771 retired that spelling), while this one renders no children and named only className, so the node SchemaRenderer injects on every render rode the spread and landed as schema="[object Object]".

.changeset/5905-componentinput-inputtype-tombstone.md

  • names packages/sdui-parser/src/index.ts → packages/sdui-parser/src/index.ts — edited by this change

    The write was measured as a no-op before it was deleted, and re-measured on this branch's base rather than inherited from the card. A structural census over every inputs: array in the repository (211 regions, all tracked TS/TSX/JS sources) scores inputType at exactly ONE authoring site — the plugin-markdown registration — against name 953, type 969, label 966, description 194, enum 119, required 86 and binding 4 in the same pass over the same regions, so the instrument was not blind. The other 192 in-repo inputType hits are a DIFFERENT face: FormField.inputType (zod/form.zod.ts), the text-input renderer's prop, and SchemaBuilder.inputType, none of which sit on a ComponentInput. The publication path is unchanged and was re-confirmed: packages/sdui-parser/src/index.ts forwards exactly six keys per input — name, type, required, enum, binding, description — so an authored inputType could not reach the published sdui.manifest.json even in principle.

  • names base.ts → packages/types/src/base.ts — edited by this change

    Stale wording corrected in the same pass, because this change falsifies it: base.ts and zod/base.zod.ts both said the fork was "recorded for a ruling; until then this stays a live, writable key", and widget.ts called it "the open fork". All three now record the ruling. A reader who greps the source instead of the card thread was meeting an open fork that no longer existed.

  • names zod/base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    Stale wording corrected in the same pass, because this change falsifies it: base.ts and zod/base.zod.ts both said the fork was "recorded for a ruling; until then this stays a live, writable key", and widget.ts called it "the open fork". All three now record the ruling. A reader who greps the source instead of the card thread was meeting an open fork that no longer existed.

  • names widget.ts → packages/types/src/widget.ts — edited by this change

    Stale wording corrected in the same pass, because this change falsifies it: base.ts and zod/base.zod.ts both said the fork was "recorded for a ruling; until then this stays a live, writable key", and widget.ts called it "the open fork". All three now record the ruling. A reader who greps the source instead of the card thread was meeting an open fork that no longer existed.

.changeset/5905-componentinput-retire-constraint-keys.md

  • names packages/sdui-parser/src/index.ts → packages/sdui-parser/src/index.ts — edited by this change

    All four were declared on ComponentInput and read by nothing, on either path. No consumer reads them off a ComponentInput value, and the manifest serializer (packages/sdui-parser/src/index.ts) forwards exactly six keys per input — name, type, required, enum, binding, description — so a value authored here could not reach the published sdui.manifest.json even in principle. Re-measured on this branch's merge-base rather than inherited from the card: a structural census over every inputs: array in the repository (219 regions, all tracked files) scores min 0, max 0, step 0 and placeholder 0, against name 926, type 926, description 161, enum 114 and required 87 in the same pass over the same regions — the instrument was not blind.

  • names packages/types/src/base.ts → packages/types/src/base.ts — edited by this change

    The retirement kit: ?: never on the interface (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on the Zod mirror (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message. Deleting the members outright was the option NOT taken: ComponentInputSchema is a non-strict z.object, which strips an undeclared key silently — one silent no-op traded for another. Pinned in packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts.

  • names packages/types/src/zod/base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    The retirement kit: ?: never on the interface (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on the Zod mirror (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message. Deleting the members outright was the option NOT taken: ComponentInputSchema is a non-strict z.object, which strips an undeclared key silently — one silent no-op traded for another. Pinned in packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts.

.changeset/5928-classname-style-props-rename.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    Why the name had to move. The const declares exactly two keys — className and style, the CSS passthrough attributes a node exposes. The TypeScript StyleProps (base.ts) is the Tailwind-SCALE vocabulary: padding, margin, gap, backgroundColor, textColor, borderWidth, borderColor, borderRadius. Measured on this branch's base with an AST read of both files: 2 keys against 8, sharing ZERO keys. In this package the …Schema suffix otherwise means "runtime mirror of the like-named declaration", so the shared name asserted a mirror relationship that does not exist — and building objectui#5684's parity registry by name pairing duly put the two together and reported drift on a pair that has no counterpart at all.

.changeset/5993-button-shared-icon-resolver.md

  • names renderers/form/button.tsx → packages/components/src/renderers/form/button.tsx — edited by this change

    renderers/form/button.tsx carried its own toPascalCase, its own iconNameMap holding the single Home -> House entry, and its own index into lucide's runtime icons record — the same algorithm as renderers/action/resolve-icon.ts, but not the same function. The action:* family, complex/data-table.tsx and both menu renderers already import the shared one. The hazard was drift, not rendering: an alias added to resolve-icon.ts to absorb a lucide retirement (the objectui#5586 / Two more retired lucide spellings reach the icons-record resolver — edit in DetailView's mobile Edit action, smile as the icon renderer's own default — and only one of the four resolver copies is pinned #5622 mechanism) reached every one of those sites and silently missed ui:button, which would have gone on resolving the retired spelling to nothing while the rest of the repo resolved it correctly.

  • names form/button.tsx → packages/components/src/renderers/form/button.tsx — edited by this change

    scripts/check-lucide-icon-record-names.mjs drops form/button.tsx from DECLARED_RECORD_READERS in the same commit — that gate rediscovers record readers from source on every run and fails on drift in both directions, so the removal is verified by the gate rather than declared. It is also what now guards the dedupe: a re-inlined copy would be discovered as an undeclared record reader and fail. The census entry for the button type stays, its resolver re-pointed at resolve-icon.ts, so ui:button's authored icon names are still judged against the live record.

.changeset/6067-component-meta-derive-from-canonical.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    packages/core/src/registry/Registry.ts declared its own ComponentMeta: thirteen keys, of which nine were restated from @object-ui/types' base.ts, four were registry-only (tier, namespace, skipFallback, labelling), and tags / description were absent — although both are declared on the canonical type and on the ComponentMetaSchema zod mirror. Two of the three authorities agreed and the registration surface did not, so those two keys were unwritable at exactly the declaration most component registrations import. That is the same two-key delta objectui#5893 had just closed inside @object-ui/types, arriving a third time on a third declaration, and objectui#5671 had already made the identical move for the sibling type ComponentInput in this very file.

.changeset/6151-stack-schema-omit-collapse.md

  • names stack.tsx → packages/components/src/renderers/layout/stack.tsx — edited by this change

    The one way this can newly error, and why it ships as minor: keys on a stack node were previously answered by the index signature as any, so gap: 'large' type-checked. gap is now number | undefined and that line is a tsc error. Every value this newly rejects is one the renderer never honoured — stack.tsx feeds gap to a Tailwind numeric scale — so the change reports a defect that was already there rather than removing a capability. All three in-repo packages that name StackSchema or FlexSchema (@object-ui/components, @object-ui/core, the schema-catalog example) type-check green unchanged.

.changeset/6740-flex-is-container.md

  • names content/docs/guide/react-pages.md → content/docs/guide/react-pages.md — edited by this change

    Second consumer, and the reason this is not purely a declaration change. renderers/layout/react-page.tsx builds the JSX scope of every kind:'react' page with if (!tag || cfg.isContainer) continue;. While flex omitted the flag it was the one layout primitive of the five still injected there, so ANGLE-BRACKETS(Flex) resolved in react page source — and rendered an EMPTY div, because the injected wrapper drops children. It now behaves like its four siblings and is not injected, which is what content/docs/guide/react-pages.md has documented all along ("Layout containers are deliberately not injected ... ANGLE-BRACKETS(flex), ANGLE-BRACKETS(grid), ANGLE-BRACKETS(card) and friends have no injected wrapper"). A react page that wrote ANGLE-BRACKETS(Flex) moves from silently swallowing its children to the page-level error panel naming the identifier, with that page's documented remedy being real HTML: ANGLE-BRACKETS(div style={{ display: 'flex', gap: 16 }}).

.changeset/6764-container-declaration-census.md

  • names renderers/layout/semantic.tsx → packages/components/src/renderers/layout/semantic.tsx — edited by this change

    renderers/layout/semantic.tsx registers aside, main, header, nav, footer, section and article, and renderers/layout/aspect-ratio.tsx registers one more; all eight called renderChildren(schema.children || schema.body) when this change landed — the body arm has since been retired by objectui#6771 and they call renderChildren(schema.children) — and none declared isContainer. Nothing on the render path reads that flag, so children always rendered — what the omission did was make validateTree warn not-a-container on a child list it then rendered, on the tier built to accept AI-authored pages. A warning that lies trains authors to discount the true ones.

  • names renderers/layout/aspect-ratio.tsx → packages/components/src/renderers/layout/aspect-ratio.tsx — edited by this change

    renderers/layout/semantic.tsx registers aside, main, header, nav, footer, section and article, and renderers/layout/aspect-ratio.tsx registers one more; all eight called renderChildren(schema.children || schema.body) when this change landed — the body arm has since been retired by objectui#6771 and they call renderChildren(schema.children) — and none declared isContainer. Nothing on the render path reads that flag, so children always rendered — what the omission did was make validateTree warn not-a-container on a child list it then rendered, on the tier built to accept AI-authored pages. A warning that lies trains authors to discount the true ones.

.changeset/7087-disabled-twin-symmetry.md

  • names base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    Those 18 redeclarations are removed, on both faces. The interfaces inherit BaseSchema.disabled the way they always inherited visible; the zod mirrors inherit base.zod.ts's z.union([z.boolean(), z.string()]) through .extend()'s merged .shape, so there is no second spelling of the union to drift from — the route ChatbotSchema took in objectui#6169.

.changeset/7088-hidden-jsdoc-single-hide-path.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    Why a comment was worth a changeset: the JSDoc is the authority a later docs correction is measured against, and this one nearly propagated. While splitting the schema-reference hidden row, a reader checked it against base.ts and almost "corrected" the table's "Inverse of visible" — the half that describes shipped behaviour — toward the declaration. That row is unchanged and stays. SchemaRenderer.hiddenVisibleSynonymy.test.tsx now pins the claim the comment makes: the two keys produce the same rendered output, and hidden: true leaves no node that could carry a visibility style.

.changeset/7097-grid-2xl-breakpoint.md

  • names grid.tsx → packages/components/src/renderers/layout/grid.tsx — edited by this change

    The drop was in two layers, and both are fixed. grid.tsx had neither a 2xl read arm nor a GRID_COLS_2XL static class map. Adding the read arm alone would have produced a class name Tailwind never compiles — Tailwind v4 finds utilities by scanning source text, so a 2xl:grid-cols-${n} assembled at runtime is not a utility that exists, and the node would have rendered unstyled while a unit test went green. The twelve literal class strings are what make the class real; measured against the package's own Tailwind build, the 2xl:grid-cols-* rules go from 0 to 12 in the compiled stylesheet, with the twelve xl:grid-cols-* rules unchanged as the control.

.changeset/7493-componentinput-retire-label-defaultvalue-advanced.md

  • names packages/sdui-parser/src/index.ts → packages/sdui-parser/src/index.ts — edited by this change

    No manifest ever published them, so no consumer could ever have read them. sdui-parser's serializer (packages/sdui-parser/src/index.ts) forwards exactly six keys per input — name, type, required, enum, binding, description — so a value authored under any of the three never reached sdui.manifest.json, the generated JSX .d.ts, or a diagnostic; its boundary type has no slot for them; the registry's data-source seam reads name only; and neither the designer nor the app-shell inspectors consult registry inputs at all. A structural census over every inputs: array in the repository (re-measured on this change's merge-base, name 951 and type 951 as the controls) counted the writes: label 908, defaultValue 245, advanced 9 — written on nearly every registration, read by nothing.

  • names packages/types/src/base.ts → packages/types/src/base.ts — edited by this change

    The retirement kit: ?: never on ComponentInput (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on ComponentInputSchema (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message (one string, both channels). Pinned in packages/types/src/__tests__/component-input-retired-keys-7493.test.ts, which also holds a tree-scoped absence census over every inputs: array under packages/** and apps/**.

  • names packages/types/src/zod/base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    The retirement kit: ?: never on ComponentInput (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on ComponentInputSchema (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message (one string, both channels). Pinned in packages/types/src/__tests__/component-input-retired-keys-7493.test.ts, which also holds a tree-scoped absence census over every inputs: array under packages/** and apps/**.

.changeset/7530-predicate-envelope-declared.md

  • names base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.

.changeset/7735-zod-mirrors-stop-authoring-defaults.md

  • names container.tsx → packages/components/src/renderers/layout/container.tsx — edited by this change

    | key | mirror wrote | the renderer applies | |---|---|---| | ContainerSchema.maxWidth | 'lg' | container.tsx: ?? 'xl' | | FlexSchema.align | 'center' | flex.tsx: \|\| 'start' |

  • names flex.tsx → packages/components/src/renderers/layout/flex.tsx — edited by this change

    | key | mirror wrote | the renderer applies | |---|---|---| | ContainerSchema.maxWidth | 'lg' | container.tsx: ?? 'xl' | | FlexSchema.align | 'center' | flex.tsx: \|\| 'start' |

.changeset/7918-zod-lazy-getter-identity.md

  • names base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    The card that found this did not claim the ten were wrong. It asked whether the spelling was buying a temporal-dead-zone dodge, and that check is what shipped. Each of the ten was rewritten in place to const inner = ANGLE-BRACKETS(body); z.lazy(() => inner), the package rebuilt, and the built barrel imported in a fresh process. Eight refuse to load. Seven name the very const being declared (children: z.array(TreeNodeSchema) sits inside TreeNodeSchema's own initialiser); SchemaNodeSchema names BaseSchemaCore, which base.zod.ts declares below it. For those eight the z.lazy is load-bearing, so ActionSchema, AppMenuItemSchema, FilterGroupSchema, MenuItemSchema, NavLinkSchema, NavigationMenuItemSchema, SchemaNodeSchema and TreeNodeSchema keep the spelling they have. The two that loaded clean are memoised: FilterBuilderConditionSchema is not recursive at all, and NavigationItemSchema already defers its self-reference through an inner z.lazy(() => NavigationItemSchema) on children.

.changeset/8344-node-recursion-point-redirect.md

  • names zod/base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    Two mechanical notes for anyone editing the wiring. AnyComponentSchema is built in zod/index.zod.ts from all 13 category modules and 14 modules import zod/base.zod.ts, so the arm cannot be an import — z.lazy defers evaluation, not the module graph. It is a written option slot that index.zod.ts fills inside AnyComponentSchema's own initializer, and it is a z.union option rather than a z.lazy holder because z.lazy memoises its getter: a holder would let whichever module graph parsed first decide the accept set for the whole process. Both constraints are measured, and the reasoning lives on defineNodeComponentUnion in zod/base.zod.ts.

  • names base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    ⛔ It is NOT closed here, and the reason is measured rather than argued. The route that closes it by binding the union inside SchemaNodeSchema's z.lazy getter was implemented and pushed, and CI refused it: Build Docs failed with ReferenceError: Cannot access 'BaseSchema' before initialization out of packages/types/dist/zod/app.zod.js, because that import makes base.zod.ts depend on the barrel and a bundler is free to evaluate the resulting cycle category-module-first. Reproduced locally in one line — importing dist/zod/app.zod.js throws with the binding in place and loads clean without it. The other three candidates were measured too: a narrowed sideEffects array is not a legal declaration for this package (one gate requires every entry form to be named, another refuses a named entry with no load-time effect, and this package's entry forms are pure), a bare top-level call is dropped by the same flag, and dropping the flag costs 16,078 gzipped bytes on the console framework chunk and moves a workspace census a guard pins.

.changeset/8499-node-slot-registered-arms.md

  • names renderers/layout/semantic.tsx → packages/components/src/renderers/layout/semantic.tsx — edited by this change

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).
  • names renderers/basic/html-elements.tsx → packages/components/src/renderers/basic/html-elements.tsx — edited by this change

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).

.changeset/8871-page-node-refuses-breadcrumbs.md

  • names packages/types/src/base.ts → packages/types/src/base.ts — edited by this change

    Accept-set change, deliberately. A page document carrying breadcrumbs used to parse GREEN and render nothing. PageNodeSchema never declared the key and no renderer ever read it, so the array survived purely through BaseSchema's .passthrough(). On the TypeScript face, tsc previously accepted it too, through BaseSchema's own [key: string]: any index signature (the [key: string]: any member of BaseSchema in packages/types/src/base.ts) — the same open door the zod mirror walked through at runtime.

.changeset/9162-node-slot-guard-class-closure.md

  • names packages/types/src/zod/base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    What leaked. {schema.footer && ANGLE-BRACKETS(CardFooter)…ANGLE-BRACKETS(/CardFooter)} does not evaluate to false when the slot is falsy — it evaluates to the slot, and React renders numbers. A node slot's published zod face carries a z.number() arm (nodeUnionOptions, packages/types/src/zod/base.zod.ts), so footer: 0 is legal authored input, and it painted a stray 0 into the DOM. NaN painted three characters. The && also short-circuits, so renderChildren's own if (!children) return null first leg was never reached — which is why the objectui#8908 bridge repair could not cover any of these sites.

.changeset/9365-bridgelistview-present-tense.md

.changeset/9375-base-i18n-vocabulary-docblock.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    base.ts said of the INLINE locale map (label / description) and the KEYED bundle reference (ariaLabel) that they "each accept the other's shape vacuously", and the label docblock said the same thing one property over ("a keyed ref typed into this slot is accepted only vacuously"). That was true when objectui#4580 Q2-B wrote it; the installed pin has since closed it, and the sentence now reads as live in published .d.ts bytes while the tree says the opposite.

.changeset/9659-node-recursion-point-inert-clause.md

.changeset/catalog-layout-props-sweep-4891-4890.md

  • names stack.tsx → packages/components/src/renderers/layout/stack.tsx — edited by this change

    StackSchema extends OmitANGLE-BRACKETS(FlexSchema, 'type'), whose spacing key is gap. spacing was declared by nothing: not the TypeScript interface, not the zod mirror, not the renderer's own inputs registration. stack.tsx read it anyway, as schema.gap ?? (schema as any).spacing ?? 2 — and the as any is the whole story, since it existed to get past the type system saying the key was not there. A lenient consumer leg does not stay in the consumer: it becomes a second de-facto contract that producers write to, and 135 nodes across 39 files of the shipped schema catalog did exactly that. Every one of them rendered correctly, so nothing ever pointed at it, while the examples went on teaching the key to every author who copied them.

.changeset/component-meta-converge-and-deprecate-alias-5893.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    @object-ui/types published ComponentMeta twice, from two different declarations: base.ts and plugin-scope.ts (the latter published as PluginComponentMeta). They were structural copies, not an alias pair. plugin-scope.ts' ComponentMeta is now export type { ComponentMeta } from './base.js' — the disposition objectui#4580 ruled for the identical shape, a structural copy would reproduce the defect the moment either side moved, and the same move objectui#5671 made for the sibling type ComponentInput in the same file.

  • names zod/base.zod.ts → packages/types/src/zod/base.zod.ts — edited by this change

    Either side had already moved. base.ts declared eleven keys; the plugin-scoped copy declared nine — the same nine, minus tags and description. So a plugin author typing against the plugin-facing declaration could not write two keys the main surface advertises, and which the runtime validator already accepted: ComponentMetaSchema in zod/base.zod.ts declares all eleven, so two of the three authorities agreed and the plugin-facing one did not. resizeConstraints' six members were identical in both, so the delta was exactly those two keys.

.changeset/componentinput-reexport-4972.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    @object-ui/core's ComponentInput (registry/Registry.ts) and @object-ui/types' plugin-scoped ComponentInput (plugin-scope.ts, published as PluginComponentInput) were structural copies of the interface in @object-ui/types' base.ts. Both are now re-exports of that one declaration, which is the disposition objectui#4580 ruled for the identical shape — a structural copy would reproduce the defect the moment either side moved — and the way core/src/types/index.ts already handles SchemaNode.

.changeset/components-react-page-published-dts-text-5666.md

  • names content/docs/guide/react-pages.md → content/docs/guide/react-pages.md — edited by this change

    • The injected-scope note no longer tells authors that layout is left to "plain HTML + Tailwind". It says plain HTML. - A new paragraph states the styling contract for kind:'react' page source: source is runtime metadata, not build input. Style with inline style objects using hsl(var(--token)) theme colors, and render overlays through ObjectForm with formType "drawer" or "modal" rather than a hand-rolled fixed inset-0 backdrop. Do not author Tailwind utility classes in page source: the console's Tailwind is compiled at build time by scanning the console's own src and there is no safelist, so an authored utility class silently produces no CSS and no error anywhere. os validate reports it as page-source-className-tailwind. (ADR-0065; ADR-0080's 2026-06-30 amendment; see content/docs/guide/react-pages.md.)

.changeset/console-formpage-visible-predicates-5594.md

.changeset/default-children-retired-5051.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    If you author plugins against the published register-meta table, drop the key. It is gone from skills/objectui/guides/plugin-development.md, which had been teaching it. A meta that still declares it stays valid: ComponentMetaSchema is a plain z.object, and measured on zod 4.4.3 that STRIPS unknown keys rather than rejecting them — so the key is silently dropped from the parse output instead of failing validation. TypeScript authors get the loud signal instead: all three ComponentMeta declarations (@object-ui/types base.ts and plugin-scope.ts, @object-ui/core Registry.ts) no longer offer it, so re-declaring it is now a compile error.

  • names span.tsx → packages/components/src/renderers/basic/span.tsx — edited by this change

    No runtime behaviour changes in either direction. No code path read the key before this change, and the eleven producers that set it (sidebar.tsx x10, span.tsx) were feeding a reader that did not exist. Dropping a span or any of the ten sidebar types into the designer produces exactly the node it produced yesterday.

.changeset/grid-dom-attribute-whitelist-4787.md

  • names grid.tsx → packages/components/src/renderers/layout/grid.tsx — edited by this change

    grid.tsx ended in a bare {...gridProps} spread that removed only data-obj-* and style, so everything else SchemaRenderer hands a registered component reached the element. Measured on a canary node, eight attributes leaked: columns="4", gap="4", mdcolumns="2", smcolumns="2", name="grid_node", props="[object Object]", colorvariant="x" (the flattened props container) and an unknown authored zzcanary="leak". A responsive columns object rendered as columns="[object Object]". Layout was unaffected, so every catalog grid example rendered with them — the reason this went unnoticed.

.changeset/layout-default-jsdoc-7361.md

  • names container.tsx → packages/components/src/renderers/layout/container.tsx — edited by this change

    The published @default documentation on two layout.ts members now matches the value the renderer actually applies. ContainerSchema.maxWidth documented 'lg' while container.tsx applies schema.maxWidth ?? 'xl', and the shared FlexLayoutProps.align documented 'center' while flex.tsx applies schema.align || 'start' and stack.tsx applies schema.align || 'stretch'. The renderers are unchanged — they are the authority for what runs — so only the docblocks moved; align now states both consumers in prose instead of carrying a single @default, because one member shared by two deliberately divergent component types cannot have one correct default.

  • names flex.tsx → packages/components/src/renderers/layout/flex.tsx — edited by this change

    The published @default documentation on two layout.ts members now matches the value the renderer actually applies. ContainerSchema.maxWidth documented 'lg' while container.tsx applies schema.maxWidth ?? 'xl', and the shared FlexLayoutProps.align documented 'center' while flex.tsx applies schema.align || 'start' and stack.tsx applies schema.align || 'stretch'. The renderers are unchanged — they are the authority for what runs — so only the docblocks moved; align now states both consumers in prose instead of carrying a single @default, because one member shared by two deliberately divergent component types cannot have one correct default.

  • names stack.tsx → packages/components/src/renderers/layout/stack.tsx — edited by this change

    The published @default documentation on two layout.ts members now matches the value the renderer actually applies. ContainerSchema.maxWidth documented 'lg' while container.tsx applies schema.maxWidth ?? 'xl', and the shared FlexLayoutProps.align documented 'center' while flex.tsx applies schema.align || 'start' and stack.tsx applies schema.align || 'stretch'. The renderers are unchanged — they are the authority for what runs — so only the docblocks moved; align now states both consumers in prose instead of carrying a single @default, because one member shared by two deliberately divergent component types cannot have one correct default.

.changeset/layout-direction-default-jsdoc-7734.md

  • names flex.tsx → packages/components/src/renderers/layout/flex.tsx — edited by this change

    The published @default documentation on FlexLayoutProps.direction no longer states a value that only one of its two consumers applies. The member is declared once (objectui#6151) but flex.tsx reads schema.direction || 'row' while stack.tsx reads schema.direction || 'col' ("Default to column for Stack"), so the single @default 'row' was correct for flex and wrong for stack — whose own defaultProps.direction is 'col'. The renderers are unchanged — they are the authority for what runs — so only the docblock moved: direction now names both consumers in prose, the same remedy objectui#7361 applied to the sibling align. justify is shared by the same two consumers and both read || 'start', so its tag is correct and stays: the criterion is a DIVERGENT shared member, not a shared one.

  • names stack.tsx → packages/components/src/renderers/layout/stack.tsx — edited by this change

    The published @default documentation on FlexLayoutProps.direction no longer states a value that only one of its two consumers applies. The member is declared once (objectui#6151) but flex.tsx reads schema.direction || 'row' while stack.tsx reads schema.direction || 'col' ("Default to column for Stack"), so the single @default 'row' was correct for flex and wrong for stack — whose own defaultProps.direction is 'col'. The renderers are unchanged — they are the authority for what runs — so only the docblock moved: direction now names both consumers in prose, the same remedy objectui#7361 applied to the sibling align. justify is shared by the same two consumers and both read || 'start', so its tag is correct and stays: the criterion is a DIVERGENT shared member, not a shared one.

.changeset/page-source-tailwind-framing-5461.md

  • names content/docs/guide/react-pages.md → content/docs/guide/react-pages.md — edited by this change

    This is a published type surface: the TSDoc ships in @object-ui/types's built .d.ts and is what an author reads on hover over kind. It said a kind:'html' page is "constrained JSX/HTML + Tailwind" — and it links content/docs/guide/react-pages.md, which objectui#5413 has already corrected to say the opposite. Shipped type documentation was contradicting the guide it points readers to.

.changeset/page-source-tailwind-prose-retraction-5469.md

  • names content/docs/guide/react-pages.md → content/docs/guide/react-pages.md — edited by this change

    The tiers themselves are unchanged and every load-bearing claim survives — parse-never-execute, the untrusted-author safety argument for html, and the deprecated 'jsx' alias. Only the styling primitive is corrected, to the wording content/docs/guide/react-pages.md §Styling already uses:

  • names src/renderers/basic/html-elements.tsx → packages/components/src/renderers/basic/html-elements.tsx — edited by this change

    • @object-ui/react-runtime: README.md is published to npm (npm includes README.md in the tarball regardless of files). Its "no sandbox" callout is the paragraph that routes untrusted-author work to the html tier, and it carried the retracted framing line-wrapped across :17-18. It also gains the §Styling section it was missing — the absence is why the framing survived here. - @object-ui/sdui-parser: the corrected header of src/types.ts projects verbatim into the published dist/types.d.ts. - @object-ui/components: the corrected header of src/renderers/basic/html-elements.tsx projects verbatim into the published dist/renderers/basic/html-elements.d.ts. The kind === 'html' dispatch-arm comment in src/renderers/layout/page.tsx does not project (it is inside a function body) and is included here only because the same package already owes an entry.

.changeset/plugin-component-input-deprecate-5674.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    @object-ui/types' entry point publishes ComponentInput as PluginComponentInput. Until objectui#4972 that alias pointed at a genuinely different declaration: plugin-scope.ts restated its own nine-key ComponentInput. objectui#5671 converged that declaration onto base.ts, so the alias became a second published name for the same type, carrying no information the first does not. Two published names for one type is a shape that costs readers a step and gives AI-authored code a coin-flip between spellings.

.changeset/plugin-form-readme-classname-quantifier-5131.md

  • names packages/components/src/renderers/form/form.tsx → packages/components/src/renderers/form/form.tsx — edited by this change

    README.md's "Not a FormField key" table said a field-level className is "read on exactly one pseudo-field, type: 'section-divider'". That quantifier holds only for the renderer's explicit read — className={fp.className} on the section-divider branch of packages/components/src/renderers/form/form.tsx. The same renderer forwards every key it did not destructure, and className is not among the names taken off the field config, not among the ones stripRendererOnlyProps removes, and so rides {...fieldProps} into renderFieldComponent, whose built-in input branch spreads it onto ANGLE-BRACKETS(Input). A field-level className therefore lands visibly on ordinary built-in controls, and a reader taking "exactly one" literally concludes the opposite of what the code does (objectui#5131).

.changeset/text-input-description-aria-describedby-5735.md

  • names renderers/form/form.tsx → packages/components/src/renderers/form/form.tsx — edited by this change

    Before this the paragraph and the input were siblings with no programmatic relationship: a screen reader moving to the field announced the label and the value and never the helper text. The label half of the same block was already wired (htmlFor against the input's id), which is what made the gap specific to description rather than a general absence of a11y wiring — and the identical key authored on a field INSIDE renderers/form/form.tsx has been announced all along, so one authoring key behaved two ways depending on which container the author reached for. It no longer does.

.changeset/zod-base-schema-mirror-parity-4605.md

  • names base.ts → packages/types/src/base.ts — edited by this change

    The new pin is DERIVED rather than a hand-written key list: it reads the mirror's own .shape and compares each key against the declaration, so the next widening of base.ts that forgets this file turns it red with no list to maintain. It reads .shape and not keyof z.inputANGLE-BRACKETS(…) because that spelling was measured vacuous — .passthrough() collapses the inferred key union to bare string, and a pin written over it resolved never while five keys were demonstrably narrow. Two guards pin the derivation against both degenerations (never and string).

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 88a4ef616 (merge-base with origin/main): 41 file(s) changed outside .changeset/, read against 1283 pending declaration(s) that publish a body (1851 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3037.6 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-CF8p2OCe.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 548.45KB 131.22KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 253.40KB 63.99KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 33.36KB 10.88KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.15KB 11.05KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 71.82KB 20.13KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.00KB 35.19KB
plugin-designer (index.js) 215.98KB 44.34KB
plugin-detail (index.js) 257.69KB 67.12KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 144.65KB 37.10KB
plugin-gantt (index.js) 167.99KB 41.37KB
plugin-grid (index.js) 215.46KB 58.88KB
plugin-kanban (index.js) 48.83KB 15.21KB
plugin-list (index.js) 113.90KB 28.11KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 106.60KB 35.16KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui seat #4 (session_01BP8CMtACxTdLjqR6rhd33C), CI read at 2026-09-24T14:55Z.

Red: Test (shard 5/8) at f34809934. The failing assertion is scripts/__tests__/body-dialect-census.test.ts, in "sidebar-trigger is ruled but reads NO child list": expected 11 to be 10 on the count of renderChildren(schema.children) in sidebar.tsx.

Whose it is: this PR's. It is not red on the base branch, and the census reads a file this diff touches.

What happens next: this PR is being superseded. One of its commits carries a co-author trailer naming a model, and the squash would append that trailer to main. objectui forbids force-pushing, so the history cannot be rewritten here. The same dev is re-cutting the identical tree as one clean commit on a new branch, and the census fix goes into that branch before its draft PR opens. This PR will be closed as superseded, with a link, once the new PR exists. ⛔ No re-run here.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Superseded by objectui#10270. domain:ui seat #4, session_01BP8CMtACxTdLjqR6rhd33C, read at 2026-09-24T15:09Z.

objectui#10270 carries the identical tree over this PR's 42 paths. The seat verified that the only byte difference from this PR's head f34809934 is the sidebar.tsx comment fix for the body-dialect-census red reported above. It is re-cut as clean commits because a commit here carried a model-named co-author trailer that the squash would have appended to main. objectui forbids force-pushing, so that history could not be rewritten here. Closing this PR without merging; the review and landing continue on objectui#10270.


Generated by Claude Code

akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
… `children` slot, not `isContainer` (objectui#9910, re-cut) (objectstack-ai#10270)

Part of objectstack-ai#9910 — the code half of the maintainer ruling (Q1-A
declare-and-pin, Q2-A re-description, Q3-A same release). It does not
close the card on merge: the `skills/objectui/**` wording for Q2 is a
governed surface and rides its own Tier H draft PR on branch
`claude/issue-9910-skills-iscontainer-wording`, and the objectstack
save-gate port is objectstack#19969 (Blocked-by this card). The seat
marks the card done once both objectui PRs have landed; objectstack-ai#9910 stays open
until then.

**Supersedes objectui#10267:** same tree, re-cut as one commit
(`c80eac97f` on branch
`claude/issue-9910-children-slot-declare-and-pin-r2`, from `origin/main`
at `88a4ef616`) because a commit there carried a model-named co-author
trailer that the squash would have appended to `main`. Proof on the
re-cut: the 42 changed paths are the same set, `git diff` against the
superseded head over those paths is 0 bytes, and no file outside them
differs from `origin/main`. The Verification section below is quoted
verbatim from objectstack-ai#10267 and its head references name that PR's tree; the
re-verification on this head is posted as an `os-dev-report` addendum on
the card. objectstack-ai#10267 remains open for the seat to close as superseded.

Clause-②: yes

## The ruling this executes (issue comment 5814512168, operative text
quoted in full)

> ## Q1 — **A: declare-and-pin**
> Every renderer that reads `schema.children` declares `{ name:
'children', type: 'slot' }` in its registration `inputs`, the shape ten
registrations already carry. The containment diagnostic in
`packages/sdui-parser/src/validate.ts` reads **only** that input. ⛔
There is no fallback to `isContainer`. The existing runtime census is
re-pointed from the flag to the input **in both directions** (declared ⇔
actually renders children). Its red-when-fixed pins flip to the new
truth, and `container-declaration-baseline.json`'s `excluded` /
`undeclared` rows are deleted as they clear. Scope: the ~67
registrations under `packages/components` + `packages/layout`, including
`tooltip`, the five page kinds, `sidebar` and `sidebar-menu-button`. ⛔ B
is not taken: it adds a published derived field plus a DOM render step,
and it is measured blind to context and portal registrations.
>
> ## Q2 — **A: `isContainer` means layout container**
> Keep the field. Re-describe it on every published face
(`ComponentMeta.isContainer`, `ComponentMetaSchema`, the plugin guide)
as **layout containment**: excluded from the react-page scope and the
public layout ledger, ⛔ no longer "can have children".
`react-page.tsx`'s `buildComponentScope` keeps reading it. The
`skills/**` wording is a governed surface and lands as its own Tier H
PR.
>
> ## Q3 — **A: same release, same pin bump**
> objectstack's save-gate copy of the parser moves in the same release
and pin bump, so saving and rendering never disagree
(objectstack#12719). The port card is filed in this stroke, carrying
`Blocked-by: objectstack-ai#9910`.
>
> **Confidence gap carried to the claim:** measure the effect on
`registry-inputs-spec-parity` and objectstack
`check:react-declaration-parity` first.

## What changed

**The tier (`@object-ui/sdui-parser`).** `validateTree`'s containment
branch reads one declaration — `acceptsChildren(comp)`, true iff
`comp.inputs` carries an entry named `children` — and nothing else;
`isContainer` is not consulted and is not a fallback.
`checkRetiredBodyDialect` (the retired `body` spelling, second
containment site) is handed the same predicate instead of
`comp.isContainer`, so the two spellings cannot disagree (assumption A3,
see below). New exports: `CHILD_LIST_KEY` (`'children'`) and
`acceptsChildren`. `ManifestComponent.isContainer` /
`RegistryConfigLike.isContainer` are re-described as layout containment
and still projected into the manifest (the objectstack save gate reads
it there until objectstack#19969 lands).

**The declarations (`@object-ui/components`, `@object-ui/layout`).**
Every renderer that puts `schema.children` on the page now declares `{
name: 'children', type: 'slot' }`:

- the 34 flow/inline HTML tags of `basic/html-elements.tsx` (declared
inside the factory, skipped for the three void tags `img` / `hr` /
`br`);
- the seven sectioning tags of `layout/semantic.tsx`;
- `aspect-ratio`, `box`, `card`, `container`, `flex`, `grid`, `stack`;
- `alert`, `badge`, `button`, `toggle`, `span` (label / description /
value fallbacks), `div`, `form`, `scroll-area`;
- the ten `sidebar-*` parts that render children (`sidebar-trigger`
renders none and declares none);
- `page-header` in `@object-ui/layout`.

That is 65 registrations measured bare plus `sidebar` and
`sidebar-menu-button`, which only mount inside `sidebar-provider` — 67,
the ruling's number. `tooltip`, the four `page:*` containers and the
five page kinds already carried the input. No `isContainer` value moved
anywhere.

**The published descriptions (`@object-ui/types`, docs).**
`ComponentMeta.isContainer`, `WidgetManifest.isContainer` and
`ComponentMetaSchema`'s `describe` now say layout containment
(react-page scope, public layout ledger), not "can have children", and
point at the slot. `content/docs/guide/react-pages.md` and
`content/docs/guide/component-registry.md` say the same.
`react-page.tsx`'s `buildComponentScope` is untouched (assumption A4).

**The census.** `container-declaration-ratchet.test.tsx` is re-pointed
from the flag to the input in both directions over every known key
(namespaced spellings included, since `sidebar` and `page:sidebar` are
different registrations): renders ⇒ declares; declares ⇒ renders, with
named context probes for `sidebar`, `sidebar-menu-button` (inside
`sidebar-provider`) and `tooltip` (open, read from the portal), each
pinned as still needed; and `not-a-container` fires on exactly the
undeclared, whatever the flag says — `page:tabs` / `page:accordion`
(flagged, no slot) keep the TRUE warning, `button` (slot, no flag,
public) draws none. A layout-side census
(`packages/layout/src/__tests__/containment-declared-slot-9910.test.tsx`)
holds the same contract for that package's five registrations. The
red-when-fixed pins on the objectui#6804 population flipped to the new
truth, `scripts/container-declaration-baseline.json` is at zero rows
(the file stays as the ruled admission ledger, with its note rewritten),
and
`packages/sdui-parser/src/__tests__/containment-declared-slot-9910.test.ts`
pins the tier's 2×2 (flag × slot) reading of a manifest.

**Fixture triage.** Parser fixtures that nested children under an
`isContainer: true` manifest entry now declare the slot too (`compile`,
`render`, `type-attribute-collision-7235`, `body-dialect-6771`,
`verify.ts`); the `layout-containers-declare-containment` childless
control moved from `badge` (which now declares the slot it renders) to
`tabs`; the layout `page-header-authorable-keys` dialect guard licenses
`children` by name as the protocol's base key (`PROTOCOL_LICENSED`, read
from `CHILD_LIST_KEY`) and asserts the spec does not carry it.

## Measurements (the PM's assumptions, each taken before editing)

- **A1 — the ruling's confidence gap.** objectui
`registry-inputs-spec-parity`: of the registrations that render
children, the only ones in the gate's `covered` set are `page:card` /
`page:footer` / `page:section` / `page:sidebar`, all of which already
declared `children` and whose spec shapes carry it (`specCoveredRenders`
in the probe) — the forward direction is unaffected; measured by running
the test after the change: green (in the 21-file / 498-test run and
again on the final head). objectstack `check:react-declaration-parity`
judges the `REACT_BLOCKS` and `object-*` blocks only, none of which
renders children; measured by dumping the public manifest from this tree
before and after (the way `apps/console/dev/manifest-dump.tsx` builds
it) and running the gate read-only against each from a scratchpad shared
clone of objectstack at `2c1011b` with `@objectstack/spec`'s deps
installed (the shared checkout has no `node_modules`): control exit 0,
after exit 0, output byte-identical, "no new DECLARATION divergence vs
accepted baseline". Manifest delta: exactly nine public blocks gain a
`children` input — `flex`, `grid`, `stack`, `card`, `container`, `box`,
`badge`, `alert`, `button`; 59 keys before and after; zero `isContainer`
values changed. A `slot` input emits no JSX attribute (`emitInterface`
filters slot arms), so `sdui-intrinsics.d.ts` is unchanged. ⇒ neither
gate goes red; no fork to report.
- **A2 — the population.** A temporary full-registry probe (16 console
modules + `@object-ui/layout`, 498 known keys, never committed) at the
base: 144 keys render an authored child (bare + `ui:` twins), 16 declare
the input; bare violators = 65 distinct registrations; `sidebar` and
`sidebar-menu-button` render only inside the provider; `tooltip` renders
only open, in a portal. After the change: violators = 0, the six
context/portal keys are the only declared-but-bare-silent ones and each
renders in its context, `flaggedNotRendering` = `page:tabs`,
`page:accordion`, `responsive-grid`, `app-schema-renderer`,
`layout:page:card` (layout containers that render a React `children`
prop or `items[].children`, never `schema.children` — they now draw the
TRUE diagnostic the flag used to silence).
- **A3 — the second `validate.ts` site.** Read as covered by "reads only
that input": the body-dialect check emits the same `not-a-container`
code for a `body` list, so leaving it on the flag would make the two
spellings disagree about which components take a list. Both sites read
`acceptsChildren(comp)`; the parameter is renamed `acceptsChildren`.
Recorded under Deviations.
- **A4 — react-page.** `buildComponentScope` still reads
`cfg.isContainer`; not edited. `react-page-scope.test.tsx` and
`public-contract.test.ts` pass unchanged.
- **Falsified detail in the brief:**
`packages/core/src/registry/Registry.ts` has zero `isContainer` hits on
`origin/main` (`grep -c` = 0); the description faces are
`packages/types/src/base.ts`, `packages/types/src/widget.ts` and
`packages/types/src/zod/base.zod.ts`, which is where the re-description
landed. Nothing in `packages/core` needed to change.

## Verification (exit codes read from files, never from a pipe)

Every vitest / build / type-check run went through
`scripts/pm/os-verify-lock.sh` (slot `issue-9910-dev`) and reads
`VERDICT command-exit 0`. Final head of this branch: `f34809934` (a
merge of `origin/main` at `fa5fbd9dc`; the eight newer files on
`origin/main` since then touch none of this surface).

| check | reading |
|---|---|
| `pnpm exec vitest run packages/sdui-parser/` + ratchet, census,
layout-containers, page-container, react-page-scope,
registration-uniqueness, layout page-header + containment census,
examples pageheader-with-actions | exit 0 — 26 files, 335 tests passed |
| console `registry-inputs-spec-parity`,
`htmlTierDataSourceInputDeclaration-6678`, `public-contract`; components
record-picker / toast-sonner / action-type html-tier pins; types
`zod-mirror-parity`, `component-meta-single-declaration`,
`default-children-retired-contract-twins`;
`packages/core/src/registry/`; kanban / detail / grid manifest pins |
exit 0 — 21 files, 498 tests passed |
| re-run on the final head `f34809934`: ratchet, census, layout census,
layout page-header, parser 2×2 pin, console parity | exit 0 — 6 files,
271 tests passed |
| `turbo run build --filter=@object-ui/layout^...
--filter=@object-ui/sdui-parser --concurrency=2` | exit 0 — 8 tasks
successful |
| `pnpm --filter @object-ui/{sdui-parser,types,layout,components}
type-check` (script name echoed as `type-check`; `tsconfig.test.json`
includes `src/**/*.test.tsx`, so the new tests are in the program) |
exit 0, 0, 0, 0 |
| `eslint --no-inline-config` over the 38 touched `.ts` / `.tsx` files |
exit 0 — 0 errors, 141 pre-existing `no-explicit-any` warnings |
| `check-changeset-presence` (35 published source files, 4 released
packages, 1 changeset) · `check-changeset-no-major` ·
`check-control-bytes` · `check-new-cross-file-line-citations` (0 new) ·
`check-governed-queue-guard --test` over the 42 paths (NOT GOVERNED) ·
`check-registry-bare-name-collisions` · `check-test-path-roots` ·
`check-pending-changeset-literals` · `check-changeset-claims` ·
`check-doc-fence-languages` · `check-doc-component-types` | exit 0 each
|
| objectstack `check:react-declaration-parity --baseline --strict`
against the BASE manifest (control) and the AFTER manifest | exit 0 and
exit 0, identical output |
| NOT MEASURED locally, declared to CI: `check:sdui-registration-pins`
(needs a console build), the full `pnpm lint` and `pnpm test` farm,
`dispatch-gates.mjs` (refuses this repo; the list above is hand-derived
from `package.json` and `.github/workflows/`) | — |

**Reverse verification (type level, consumer side).** After the build,
the layout test that imports `CHILD_LIST_KEY` was mutated by appending
`const __ablationBad: number = CHILD_LIST_KEY;` (marker count 0 → 1 on
disk), `pnpm --filter @object-ui/layout type-check` went red with
`TS2322: Type 'string' is not assignable to type 'number'` at the
mutated line (exit 2), and the file was restored with `git checkout HEAD
-- FILE`: marker count back to 0, `git hash-object` equal to the HEAD
blob `cddf5764…`, `git diff HEAD` empty. This proves the consumer
type-check reads the rebuilt `sdui-parser` `.d.ts` (an old one has no
such export). The runtime direction is the census itself: the flag-only
rows (`page:tabs`, `page:accordion`, `responsive-grid`) draw the
diagnostic, which the previous predicate could not produce.

## Deviations

- Commit `748fc2484` carries a `Co-Authored-By` trailer with a model
name, written from the harness attribution reminder before the role
file's model-free rule was applied; force-push is forbidden in this
repo, so the history is not rewritten and it is reported here. The later
commits use a model-free trailer; the squash merge writes this PR's
title and body.
- A3: both `validate.ts` containment sites re-pointed together (the
body-dialect parameter renamed `acceptsChildren`), reading "reads only
that input" as covering the retired spelling's containment sentence.
- The five page kinds keep `{ name: 'children', type: 'array', itemType:
'component' }` — the predicate is name-based, the ruling counted them
among the ten that already carry the declaration, and the parser pin
asserts an `array`-typed `children` counts.
- `scripts/container-declaration-baseline.json` is kept at zero rows
rather than deleted (the ruling says rows are deleted as they clear);
its note is rewritten to the new predicate and the census asserts the
note's contract phrases.
- Two new published exports on `@object-ui/sdui-parser`
(`CHILD_LIST_KEY`, `acceptsChildren`) so the census, the layout dialect
guard and the objectstack port read one spelling of the predicate;
`Clause-②: yes` already covers a published-type change.
- `checkRetiredBodyDialect`'s fourth parameter narrows from `boolean |
undefined` to `boolean` (it is always handed the predicate's boolean
now).
- `Part of objectstack-ai#9910`, not a closing keyword: the governed half and the
objectstack port remain.

## Acceptance notes (observed, not filed — no class a/b/c finding)

- `containers.tsx`'s `page:section` / `page:footer` / `page:sidebar`
still read `schema?.children || schema?.body`; the read-site comment
records it as a back-compat read for stored documents, not an authorable
key. Unchanged here.
- `codegen.ts`'s `generateBlockList` "container" column still reads
`isContainer`; under Q2 that column now literally means layout
container. Unchanged here.
- `check-skill-examples` for the governed PR answers PRECONDITION NOT
MET (exit 2, needs a 12-package scoped build) in the skills worktree —
declared to that PR's CI, not a red.
- Dedupe words, should the seat want a card on the first two:
`page-container body read` · `generateBlockList container column` ·
`isContainer layout`.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant