Skip to content

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

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-9910-children-slot-declare-and-pin-r2
Sep 24, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-9910-children-slot-declare-and-pin-r2

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.

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 #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. #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/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.

Re-cut of branch claude/issue-9910-children-slot-declare-and-pin as one
commit on the current main: same tree, model-free trailers.

Co-Authored-By: Claude <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 978507b9a (merge-base with origin/main): 41 file(s) changed outside .changeset/, read against 1290 pending declaration(s) that publish a body (1859 pending in total). · run

… read the census counts (objectui#9910)

`scripts/__tests__/body-dialect-census.test.ts` counts the literal
`renderChildren(schema.children)` reads in `navigation/sidebar.tsx` and pins
ten across the eleven `sidebar-*` registrations (`sidebar-trigger` reads
none). The objectui#9910 file-level comment spelled that call once in prose,
so the count read eleven on CI (Test shard 5/8 of the superseded PR). The
prose now says the same thing without spelling the call; no registration
moved and the pin's count stands.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
@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-Di8H8DDM.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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dd496c932f3b13eedc57fe9b72aea02db17ab634

Rendered by an isolated review subagent spawned by the domain:ui#4 seat; its served tier was checked against its transcript stamps (105 of 105 at the review tier). Adopted by this seat; the two blocking items go back to the same dev as patch round 1.

Head re-read from GET /pulls/10270 at review time: dd496c932f3b13eedc57fe9b72aea02db17ab634, unchanged from the brief. Two commits on origin/main at 88a4ef616 (c80eac97f the re-cut tree, dd496c932 the sidebar comment fix); git diff --stat 88a4ef616..head = 42 files, +1097/-799, matching the PR. Ruling comment 5814512168 read verbatim; all 10 card comments read, including the step-1 measurement report 5808588768 that the ruling's option text descends from.

① Derived judgments

  • Declared set = renderers that put schema.children on the page — RIGHT. Counted at head: html-elements.tsx TAGS has 37 entries with VOID_TAGS = {img, hr, br}, and the factory emits the slot via ...(isVoid ? [] : [{ name: 'children', type: 'slot' }]) (L196) beside isVoid ? undefined : renderChildren(schema?.children) (L179) — 34 declared, 3 not, same predicate on both lines. semantic.tsx tags = 7. Layout primitives 7 (aspect-ratio, box, card, container, flex, grid, stack), label/description fallbacks 5 (alert, badge, button, toggle, span), plus div, form, scroll-area, 10 of the 11 sidebar-* (a shared CHILDREN_SLOT const; sidebar-trigger gets none), and layout page-header = 34+7+7+5+3+10+1 = 67, of which sidebar and sidebar-menu-button are the two context-only rows — the PR's "65 bare + 2" and the ruling's ~67 reconcile. Reverse spot-check: a repo-wide grep for schema.children / schema?.children over packages/*/src (non-test) finds, among registration files the PR did NOT touch, only containers.tsx (page:card L960, page:section L1167, page:footer L2332, page:sidebar L2359 — all four declare the slot via PAGE_CONTAINER_INPUTS L86-90 or L1011), page.tsx (five page kinds share name: 'children', type: 'array', itemType: 'component' at L706-708) and tooltip.tsx (L12 schema.content || renderChildren(schema.children), slot at L55, no isContainer). page:tabs (L898, isContainer: true) and page:accordion (L1109, isContainer: true) read no schema.children and declare no slot, so their TRUE not-a-container survives as the PR pins. page:header (L2269) neither reads nor declares. Every other untouched renderChildren( call site (carousel, resizable, table, list, accordion, collapsible, tabs, the overlays, plugin-timeline) passes a non-children key (e.g. item.content). Declares-but-never-renders: the census pins sidebar, sidebar-menu-button and tooltip by name in CONTEXT_PROBES AND pins that each still fails the bare probe ("dead weight" red), so the three context/portal declarations are held, not waved through.
  • No widening of what authors may write; designer unchanged — RIGHT. children was already in BASE_PROPS (validate.ts L81, now spelled CHILD_LIST_KEY), so it was legal on every node and never drew unknown-prop; the slot declaration only decides the containment warning. codegen.ts L83-85 drops 'slot' arms and emits an input only with a non-slot arm, so the generated .d.ts cannot change (no sdui.manifest.json / sdui-intrinsics.d.ts is tracked in this repo). Grep of packages/app-shell/src, packages/plugin-designer/src, packages/core/src, apps/console/src (non-test) finds no consumer of type: 'slot' inputs, so nothing the designer shows moves in-repo.
  • validate.ts reads only the declared input at both sites — RIGHT. acceptsChildren(comp) = comp.inputs.some(i ⇒ i.name === CHILD_LIST_KEY) (L57-59); the children branch (L221) and checkRetiredBodyDialect(..., acceptsChildren(comp)) (L152) both read it. git grep isContainer over validate.ts and body-dialect.ts at head hits comments only. manifestFromConfigs projects inputs: (c.inputs ?? []) (index.ts L199), and validateTree already did comp.inputs.map before this PR (L107), so an input-less registration cannot make the new predicate throw.
  • False not-a-container risk, weighed honestly — RIGHT as ruled, with one out-of-repo cost named. Page kinds: an array-typed children counts (name-based predicate; parser pin array-typed row). Namespaced keys: the census now iterates every known key including ui: twins and page:*/layout: spellings; unknown-component rows are asserted empty so nothing passes by absence. In-repo plugins: zero registrations outside packages/components + packages/layout read schema.children (sweep above), so no in-repo plugin gains a warning. Third-party widgets registered through WidgetRegistry (RuntimeWidgetManifest.isContainer flows to ComponentMeta.isContainer at WidgetRegistry.ts L190): a widget that nests children and relied on isContainer: true to stay silent now draws not-a-container (severity warning, no render change) until it declares a children input. This is exactly the fallback the ruling forbids ("There is no fallback to isContainer"), the re-described RuntimeWidgetManifest.isContainer face tells such an author what to declare, and the objectstack save gate moves in the same release under Q3-A (objectstack#19969). Ruled cost, not a defect; recorded here so it is not rediscovered.
  • Published isContainer faces — TRUE of the code; the ledger discrepancy resolves to phrasing. apps/console/src/__tests__/public-contract.test.ts L489-491: DECLARED_LAYOUT_CONTAINERS = LAYOUT_VOCABULARY.filter(type ⇒ getMeta(type)?.isContainer === true); L641-650 then require every member to be in PUBLIC_BLOCKS or recorded in UNCURATED_LAYOUT_CONTAINERS (L568-584: aspect-ratio plus the seven sectioning tags, each with a reason and an issue). So the flag is what puts a block INTO the ledger's population; the code never excludes a flagged block from it. The PR's faces ("listed in the public layout ledger" in base.ts L1079-1081, base.zod.ts L730, sdui-parser/src/types.ts L234-236, index.ts L208-214, content/docs/guide/react-pages.md, component-registry.md) are therefore true of the code. The ruling's "excluded from the react-page scope and the public layout ledger" is inverted on the ledger half — that wording was inherited verbatim from step-1 report 5808588768's Q2 option A text, whose own reader table two rows earlier had correctly described the ledger as a flag READER "in its layout-containment meaning". The faces contradict the ruling's phrasing only; its intent (the flag means layout containment, consumed by the react-page scope builder and the public-contract layout ledger, never "can have children") is matched. react-page.tsx L75-77 still does if (!tag || cfg.isContainer) continue; over getPublicConfigs() — untouched, as Q2 orders. Non-blocking: the PR's Deviations section does not record this departure from the ruling's phrasing; and the faces' "skipped by the react-page JSX scope builder" holds for public configs and "listed in the ledger" for LayoutSchema arms — the consumers are named correctly, the qualifiers are elided. The skills/objectui/guides/plugin-development.md face still reads "Accepts child components" at head (L75) — correctly left to governed PR docs(skills): isContainer means layout containment, not "accepts children" (objectui#9910 Q2, governed) #10268 as the ruling orders.
  • Two new exports — RIGHT. git diff 88a4ef616..head over packages/sdui-parser/src/ adds exactly export const CHILD_LIST_KEY = 'children', export function acceptsChildren(comp: Pick[ManifestComponent, 'inputs']) and the one re-export line in index.ts; nothing else is newly exported. package.json exports has a single . entry (./dist/index.js / .d.ts), so both are reachable only via the package entry. Both carry JSDoc naming the ruling, the census and the no-fallback rule. acceptsChildren takes Pick[..., 'inputs'], the minimal shape.
  • Changeset — WRONG in two sentences (blocking; see verdict). Verified TRUE: "four times" drift (objectui#3900/[finding] flex registers without isContainer while grid/card/container declare it — the tier's most-used layout primitive produces every not-a-container warning #6740/finding(components): container-declaration census — 8 more registrations render schema.children without declaring isContainer, and this is the THIRD time the same gap has been found one registration at a time #6764/[Decision] What instrument stops the container-declaration class regenerating — and do the remaining 45 get fixed before it lands? #6779), "a dozen schema.body readers" (body-dialect-census pins BODY_ONLY = 12), the 65+2 count, the per-family list, "ten sidebar-* parts", "Nine public blocks gain the slot" (PUBLIC_BLOCKS at head carries exactly alert, badge, box, button, card, container, flex, grid, stack of the touched registrations; div, span, form, toggle, scroll-area, aspect-ratio, the html/sectioning tags, sidebar-*, page-header are not public), "a slot input emits no JSX attribute", "isContainer is kept everywhere it was declared" (no isContainer line removed anywhere in the diff), "react-page scope builder keeps reading it unchanged", "baseline at zero" (excluded: {}, undeclared: {}), "page:tabs, page:accordion, responsive-grid now draw the TRUE not-a-container" (pinned in the ratchet direction-3 block and the layout census). NOT TRUE as written: (1) "re-described on every published face (ComponentMeta, WidgetManifest, ComponentMetaSchema, the manifest types)" — this repo has no WidgetManifest type; the face re-described is RuntimeWidgetManifest.isContainer (packages/types/src/widget.ts L76, L116-122), and that file's own header (L33-55) records that the bare name WidgetManifest was retired from the spec under ADR-0049 and is "owned by NOBODY", the Runtime prefix kept by choice. A CHANGELOG reader following WidgetManifest finds no such export. (2) "an input named children ... (the shape { name: 'children', type: 'slot' } that tooltip, the four page:* containers and the five page kinds already carried)" — the five page kinds carry { name: 'children', type: 'array', itemType: 'component' } (page.tsx L706-708); the PR body's own Deviations says so. The main clause (an input NAMED children) is the true predicate; the parenthetical attributes the slot shape to five registrations that never carried it. Both are one-line edits to .changeset/9910-children-slot-declare-and-pin.md; no code needs to move. The repo's own check-changeset-claims.mjs header classifies exactly this shape ("BORN FALSE") as a filed defect class, and the gate is report-only by design, so CI green does not clear it.
  • Pins/census — real and able to fail — RIGHT. Ratchet (container-declaration-ratchet.test.tsx): undeclaredRenderers (renders and no slot) must be within the admitted maps, which are empty, so any new undeclared renderer is red; bareLiars (slot and no bare render) must each have a CONTEXT_PROBES entry that renders the MARK in context, and each probe subject is pinned as still failing bare; direction 3 asserts containment === !declaresChildren over every non-unknown row "whatever isContainer says"; the no-fallback pin guards its own vacuity (flaggedWithoutSlot must be non-empty) and names page:tabs / page:accordion (flag on, slot off, renders false, containment true) and button (flag off, public, renders, declares, containment false); sidebar-trigger inside the provider is the negative control for the context fixture; controls one to four (control set renders AND declares, no unknown-component, every undeclared row draws the diagnostic, population greater than 200 with more than 60 declaring). Ledger: both maps must contain only live violators (so the file can only shrink) and the note must carry the new predicate phrases. Layout census (packages/layout/src/__tests__/containment-declared-slot-9910.test.tsx): declared iff rendered per layout: key, containment follows the declaration, layout:responsive-grid / layout:page:card / layout:app-schema-renderer are flagged-no-slot and must keep the TRUE warning. Parser 2x2 (containment-declared-slot-9910.test.ts): flag-only draws, slot-only silent, both silent, neither draws, array-typed counts, body follows the same predicate, manifest still carries isContainer. The dev's claim (flag-only rows draw, slot-only rows draw none) is asserted in three files. body-dialect-census: the matcher /renderChildren\(schema\.children\)/g lives in scripts/__tests__/body-dialect-census.test.ts (L196), which this diff does not touch; the count at head is 10; commit dd496c932 changed only four prose lines of the sidebar.tsx file comment (diff shown, no registration moved), so the count returned to 10 without loosening anything. CI "Test (shard 5/8)" — the shard red on fix(sdui-parser,components,layout,types): containment is the declared children slot, not isContainer (objectui#9910) #10267 — is success on this head.

② Semver level

.changeset/9910-children-slot-declare-and-pin.md declares minor on @object-ui/sdui-parser, @object-ui/components, @object-ui/layout, @object-ui/types. New public exports on sdui-parser require at least minor — met; no major anywhere — met. The four frontmatter packages are exactly the four source packages the diff touches (packages/sdui-parser, packages/components, packages/layout, packages/types; content/docs/** and scripts/** are unpackaged). All four sit in the single fixed group of .changeset/config.json (with core, react, every plugin-*, app-shell, console, ...), so they version together regardless; the additive children inputs on 67 registrations and the description-only types change are consistent with minor. CI Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read all success — RIGHT.

③ Boundary flags

  • Body opens with Part of #9910 and says explicitly it does not close the card; regex scan of title, body and both commit messages for close/fix/resolve + #9910 returns 0 hits. Clause-②: yes present. Commits reference (objectui#9910) in the subject only.
  • Commit trailers on both commits, verbatim shape: Co-Authored-By: Claude followed by the noreply at anthropic.com address, and Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C. No model identifier (no Fable, Opus, Sonnet, Haiku, no claude- model id, no version) appears in either message. The prior trailer that named a model rode fix(sdui-parser,components,layout,types): containment is the declared children slot, not isContainer (objectui#9910) #10267, which is closed, merged=false — superseded as the body says. Observation only: "Claude" is the assistant product name; if the seat's model-free rule reads the bare product name as a model reference, that is the seat's call, the text is reported here exactly.
  • Check-runs on dd496c932f3b13eedc57fe9b72aea02db17ab634: 43 total — 40 success, 3 skipped (Test (coverage), Test (coverage shard ${{ matrix.shard }}/4), dependabot — coverage and dependabot jobs skipped by workflow condition, not this PR), 0 failure, 0 in_progress, 0 queued. The PR body's "declared to CI" items are all green: Lint, Type Check, Test plus shards 1/8 to 8/8, Test (dist pins), Build & E2E, Build Docs, Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan, Skill Example Check. Nothing is honestly in_progress because nothing is in progress.
  • Non-blocking observations: (a) container-declaration-census.test.tsx header L193 still says "objectui#6804's ruling keeps undeclared" in a sentence the following "Re-pointed by objectui#9910" paragraph supersedes — stale prose, not a pin; (b) page-container-authorable-keys.test.tsx L180-182 says "the public layout ledger lists them" of page:section / page:footer / page:sidebar, which are curated PUBLIC_BLOCKS members of the ledger's population rather than UNCURATED_LAYOUT_CONTAINERS rows — loose, not false; (c) the PR body repeats the WidgetManifest misnomer (not published, so not blocking on its own); (d) codegen.ts L176 block-list "Container" column now literally means layout container — acknowledged by the PR, unchanged here.

Implemented-by: claude/issue-9910-children-slot-declare-and-pin-r2
Reviewed-by: session_01BP8CMtACxTdLjqR6rhd33C

VERDICT: FAIL

Blocking defects (both confined to .changeset/9910-children-slot-declare-and-pin.md, which publishes verbatim into four CHANGELOGs at the next release and cannot be corrected afterwards; every code, test, docs and types change reviewed above is RIGHT and needs no edit):

  1. The sentence "isContainer is kept everywhere it was declared and re-described on every published face (ComponentMeta, WidgetManifest, ComponentMetaSchema, the manifest types)" names WidgetManifest, a type this repo does not export; the face re-described is RuntimeWidgetManifest.isContainer (packages/types/src/widget.ts L76 / L122), and that file's header records the bare name as retired and deliberately unused. Fix: spell it RuntimeWidgetManifest.
  2. The parenthetical "(the shape { name: 'children', type: 'slot' } that tooltip, the four page:* containers and the five page kinds already carried)" is false for the five page kinds, which carry { name: 'children', type: 'array', itemType: 'component' } (packages/components/src/renderers/layout/page.tsx L706-708), as the PR's own Deviations section states. Fix: say the ten already carried an input NAMED children — slot-typed on tooltip and the four page:* containers, array-typed on the five page kinds — which is exactly what the name-based predicate reads.

Generated by Claude Code

…nputs exactly (objectui#9910 patch round 1)

Two sentences the review tier (record 5817084234) found false in
`.changeset/9910-children-slot-declare-and-pin.md`, which publishes verbatim
into four CHANGELOGs: this repo exports no `WidgetManifest` — the re-described
face is `RuntimeWidgetManifest.isContainer` — and the five page kinds carry an
`array`-typed `children` input, not the `slot` shape; the predicate reads the
NAME, so the ten already counted. One parenthetical corrected on the same
re-read: `span` renders `children` ahead of `value`, not as a label fallback.
No code, test, docs or types change.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3038.3 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-Ckn_6LXJ.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) 549.39KB 131.62KB
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.41KB 64.01KB
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) 258.85KB 67.53KB
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.56KB 3.71KB
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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5be909f25121b2c901df1fb7bc450f91763d88ba

Rendered by an isolated review subagent spawned by the domain:ui#4 seat; its served tier was checked against its transcript stamps (66 of 66 at the review tier). Adopted by this seat. Supersedes the FAIL record 5817084234 (head dd496c9) for the current head.

Head re-read from GET /pulls/10270 at review time: 5be909f25121b2c901df1fb7bc450f91763d88ba, unchanged from the brief. Four commits: c80eac97f (re-cut tree), dd496c932 (sidebar comment fix, the head the prior record 5817084234 judged), 99fabe9d1 (changeset prose, patch round 1), 5be909f25 (merge of origin/main at 978507b9a). 42 changed paths per the API and per git diff --name-only 978507b9a 5be909f. Ruling 5814512168, prior record 5817084234, the dev's addendum 5817372844 and the re-read flag 5816530125 read in full.

① Derived judgments

  • Changeset, the two FAILED sentences — now TRUE. (1) packages/types/src/widget.ts L76 exports RuntimeWidgetManifest; L116-122 carry the re-description; git grep for an exported WidgetManifest over packages/ at head returns nothing. The changeset now names RuntimeWidgetManifest. (2) The ten pre-existing children inputs, counted at their source: tooltip.tsx L55 (slot); containers.tsx L86-91 PAGE_CONTAINER_INPUTS (slot) spread by section L1172/1178, footer L2338/2344, sidebar L2364/2370, each namespace: 'page' + skipFallback, so page:section / page:footer / page:sidebar; page:card L1011 (slot); page.tsx L706-708 pageMeta { name: 'children', type: 'array', itemType: 'component' } registered five times L753-757 as page / app / utility / home / record. A sweep of packages/, apps/, examples/ (non-test) at base 978507b9a finds exactly these four sites and no other children input, so "ten … slot-typed on tooltip and the four page:* containers, array-typed on the five page kinds" is exact. "The predicate reads the NAME" is validate.ts L57-59 (input.name === CHILD_LIST_KEY), pinned by the parser test "an input NAMED children counts whatever its coarse type".
  • The third correction — TRUE. span.tsx L143 {renderChildren(schema.children) || schema.value}: children ARE read ahead of value. The four left in the fallback group are correct: button.tsx L73, badge.tsx L32, toggle.tsx L38 read schema.label || renderChildren(schema.children); alert.tsx L22 reads schema.description || renderChildren(...), so "label and description fallbacks" holds.
  • Every other sentence re-read against head — TRUE. "Reads exactly one declaration": validate.ts L220 !acceptsChildren(comp), L150 hands acceptsChildren(comp) to checkRetiredBodyDialect; isContainer in validate.ts / body-dialect.ts appears in comments only. New exports: index.ts L12. "Drifted four times": objectui#3900 (page-header), [finding] flex registers without isContainer while grid/card/container declare it — the tier's most-used layout primitive produces every not-a-container warning #6740 (flex), finding(components): container-declaration census — 8 more registrations render schema.children without declaring isContainer, and this is the THIRD time the same gap has been found one registration at a time #6764 (title itself says "THIRD time"), [Decision] What instrument stops the container-declaration class regenerating — and do the remaining 45 get fixed before it lands? #6779 (the decision over the remaining 45). "A dozen schema.body readers": scripts/__tests__/body-dialect-census.test.ts L152-157 pins BODY_ONLY.length = 12. "[Decision] Should button declare isContainer? — the one public hit among the 53, carved out of #6779 by ruling #6804 ruling the flag OFF … deletes a public tag from every react page's JSX scope": react-page.tsx L75-77 still if (!tag || cfg.isContainer) continue; over getPublicConfigs(), file not in the diff. "The maintainer ruled declare-and-pin (2026-09-24)": comment 5814512168 created 2026-09-24T12:54Z. "65 measured bare plus sidebar and sidebar-menu-button": html-elements.tsx TAGS = 37 entries (counted), VOID_TAGS = {img, hr, br}, slot emitted at L196 iff not void → 34; semantic.tsx L13 = 7; layout primitives 7; button / badge / alert / toggle / span 5; div / form / scroll-area 3; sidebar.tsx CHILDREN_SLOT (L53) used on 10 of 11 registrations, sidebar-trigger L275 declares only className; layout/src/index.ts L145 page-header → 34+7+7+5+3+10+1 = 67. "Nine public blocks gain the slot in sdui.manifest.json": packages/core/src/registry/public-blocks.ts PUBLIC_BLOCKS intersected with the touched registrations is exactly {flex, grid, stack, card, container, box, badge, alert, button}; none of the 34 html tags, 7 sectioning tags, div, span, form, toggle, scroll-area, aspect-ratio, sidebar-*, page-header is listed; sdui-parser/scripts/gen-manifest.ts L20-23 serializes getPublicConfigs() through manifestFromConfigs into that file (a generated artifact, not tracked here — the sentence is about the generated file). "A slot input emits no JSX attribute": codegen.ts L83-85 valueArms drops 'slot', an input is emitted only with a non-slot arm. "isContainer is kept everywhere it was declared": every removed line carrying isContainer in the diff is a test/fixture/JSON-note/comment line, the two tier reads, or the zod describe string; no registration lost the flag and none gained it. "Re-described on every published face": base.ts L1077-1088, widget.ts L116-122, base.zod.ts L730, sdui-parser/src/types.ts L151-162 and index.ts L72-79 (the manifest types), plus content/docs/guide/component-registry.md L142 and react-pages.md L141-146; a sweep for "can have children" / "can contain child" over the repo outside CHANGELOG.md, .changeset, skills/ (governed, ruled to its own PR) and tests finds no remaining face. Census both directions: ratchet describe blocks L330 (renders ⇒ declares), L355 (declares ⇒ renders, CONTEXT_PROBES L224 for sidebar / sidebar-menu-button / tooltip), L407 (direction 3, "whatever isContainer says", page:tabs / page:accordion L432, button L444); layout census L82-130. Baseline JSON: excluded: {}, undeclared: {}. "page:tabs, page:accordion, responsive-grid now draw the TRUE not-a-container": layout/src/index.ts L217-225 registers responsive-grid with isContainer: true and inputs columns / gap only; ResponsiveGrid.tsx L70/L139 renders the React children prop, never schema.children; pinned at layout census L121-130 and ratchet L432-469.
  • Frontmatter and title unchanged between dd496c932 and head (lines 1-8 identical; git diff --stat dd496c932 99fabe9d1 = the one changeset file, 7+/6-, all body prose).
  • Dev's 38-changeset table — spot-checked on 13 bodies read in full at head, all agree with the dev's verdicts. 6740-flex-is-container: still true — flex.tsx L190 keeps isContainer: true, its diff is whitespace plus one input line, react-page still does not inject it, and the family pin layout-containers-declare-containment.test.tsx still asserts the flag on flex / grid / card / container (L60, L142) with the childless control moved to tabs (L121-123, asserting it declares no children input). 6764-container-declaration-census: still true — semantic.tsx L69 and aspect-ratio.tsx L47 keep the flag; "children is a base property … the flag widens no spec surface" holds (BASE_PROPS L84). 5905-componentinput-inputtype-tombstone, 5905-componentinput-retire-constraint-keys, 7493-componentinput-retire-label-defaultvalue-advanced: the sentence "forwards exactly six keys per input — name, type, required, enum, binding, description" is FALSE on origin/main (6099dd870) independently of this PR: manifestFromConfigs in packages/sdui-parser/src/index.ts forwards a seventh key, of: i.of === undefined ? undefined : canonicalizeInputType(i.of), under a comment citing objectui#8067; content/docs/guide/plugin-development.md L369 already documents "the seven keys the manifest forwards — name, type, of, required, enum, binding, description". The function body is byte-identical between origin/main and this head (diff → IDENTICAL), and this PR's index.ts delta is one export line plus a JSDoc on RegistryConfigLike.isContainer, so the PR makes the claim neither more nor less false. Pre-existing; the dev's ⚠ annotation is accurate and correctly handed to the changeset-claim lane rather than folded into this diff. The other paragraphs of those three bodies that name base.ts / base.zod.ts / widget.ts (retirement kit, "all three now record the ruling") are untouched by this PR's docblock-only edits. 5632-sidebar-trigger-dom-passthrough: still true — sidebar-trigger L275 inputs: [{ name: 'className', type: 'string' }], no CHILDREN_SLOT, pinned declaresChildren false at ratchet L400/L514. default-children-retired-5051, 8499-node-slot-registered-arms (37 tags, 7 sectioning), component-meta-converge-and-deprecate-alias-5893, 6067-component-meta-derive-from-canonical (Registry.ts not in the diff), components-react-page-published-dts-text-5666 (react-page.tsx untouched), zod-base-schema-mirror-parity-4605 (base.zod.ts moved only the ComponentMetaSchema.isContainer describe), catalog-layout-props-sweep-4891-4890 (stack.tsx diff = whitespace plus the slot line, no spacing), 7735-zod-mirrors-stop-authoring-defaults (container.tsx L26 ?? 'xl', flex.tsx L18 || 'start'): all still true.
  • Merge of origin/main introduced nothing beyond main's own delta — CONFIRMED. merge-base(99fabe9d1, 978507b9a) = 88a4ef616 (the re-cut base). git merge-tree --write-tree 99fabe9d1 978507b9a yields tree 5a9b1f98c, and git diff 5a9b1f98c 5be909f is EMPTY: the merge commit is exactly the clean automatic merge, no hand edits. git diff --stat 88a4ef616 978507b9a -- [42 paths] is empty (main touched none of them), git diff --stat 99fabe9d1 5be909f -- [42 paths] is empty, and the PR's delta against its base with hunk headers stripped is byte-identical before (88a4ef616..99fabe9d1) and after (978507b9a..5be909f) the merge. Between dd496c932 and head the PR's own paths differ only in the changeset prose.
  • Non-blocking observation: containers.tsx L78-79 (a file this PR does not touch) still says "isContainer: true (already set on all three) is what authorizes children to the parser's not-a-container check" — false at head. It is a JSDoc on the non-exported PAGE_CONTAINER_INPUTS const, not emitted into any .d.ts, so it is not a published face and the changeset's "every published face" sentence stands; it is stale in-source prose a reader will meet. Dedupe words: PAGE_CONTAINER_INPUTS isContainer authorizes.
  • Non-blocking observation: checkRetiredBodyDialect is exported from the package entry (index.ts L28) and its fourth parameter narrows from boolean | undefined to boolean while its meaning moves from the flag to the slot predicate. The PR body's Deviations records this; the changeset does not name the signature move. Not a false sentence — an omission an external caller of that export would have welcomed.
  • Non-blocking observation: card objectui#6740 returns Not Found from the public issues API; its existence is corroborated by the pending changeset 6740-flex-is-container.md and by merged commits #6808 / #6901 that cite it. Not a defect.

② Semver level

minor on @object-ui/sdui-parser, @object-ui/components, @object-ui/layout, @object-ui/types — frontmatter identical at dd496c932 and head; all four are non-private published packages at 17.6.0 and each has published source in the diff. Consistent with the change: two new exports (acceptsChildren, CHILD_LIST_KEY), added registration inputs, a diagnostic re-pointed from the flag to the input, docblock / describe re-descriptions; the one type-level narrowing (checkRetiredBodyDialect's fourth parameter) sits under the repo's no-major policy (scripts/check-changeset-no-major.mjs) and the PR's Clause-②: yes. No major anywhere. Check-runs Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check and Changeset Claim Re-read are all success on the head.

③ Boundary flags

  • Body opens Part of #9910 — … It does not close the card on merge. A regex scan of title plus body for close / fix / resolve forms followed by an issue reference returns 0 matches; the title's fix( is a conventional-commit type followed by a scope parenthesis, and objectui#9910 is not a GitHub-resolvable closing reference. None of the four commit messages carries a closing keyword aimed at [Decision] after objectui#6771 converges ten readers onto children, the containment diagnostic puts a FALSE not-a-container on the one key they do read — fix the flag, or fix the tier that reads it #9910.
  • Commit messages (GET /pulls/10270/commits, 4 commits): the three authored commits carry Co-Authored-By: Claude (noreply at anthropic.com) and a Claude-Session: URL trailer; the merge commit carries the default merge message. A scan for fable / opus / sonnet / haiku / claude- plus a digit / [1m] / gpt / gemini returns 0 hits on every commit. The model-named trailer the body mentions (748fc2484) belongs to the superseded fix(sdui-parser,components,layout,types): containment is the declared children slot, not isContainer (objectui#9910) #10267 and is not in this PR's history.
  • Check-runs on 5be909f25121b2c901df1fb7bc450f91763d88ba: 43 total — 40 success, 3 skipped (Test (coverage), Test (coverage shard ${{ matrix.shard }}/4), dependabot, each a conditional job), 0 failure, 0 in_progress, 0 neutral. Success includes Lint, Type Check, Test shards 1-8, Test (dist pins), Build & E2E, Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan. Combined commit status: success (one status, Vercel "Canceled by Ignored Build Step").
  • PR state open, mergeable: true, mergeable_state: clean, base main at 978507b9a; origin/main has since advanced to 6099dd870 on paths outside this PR.
  • This review performed no GitHub writes, used no MCP GitHub tools, and made no repository edits; the local checkout was only fetched and read.

Implemented-by: claude/issue-9910-children-slot-declare-and-pin-r2
Reviewed-by: session_01BP8CMtACxTdLjqR6rhd33C

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 24, 2026 16:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 5ea623e Sep 24, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-9910-children-slot-declare-and-pin-r2 branch September 24, 2026 16:13
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