Repository navigation
fix(sdui-parser,components,layout,types): containment is the declared children slot, not isContainer (objectui#9910, re-cut) - #10270
Conversation
… `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
|
changeset-claim-re-read
|
… 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
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Rendered by an isolated review subagent spawned by the Head re-read from ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Blocking defects (both confined to
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
…ldren-slot-declare-and-pin-r2
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Rendered by an isolated review subagent spawned by the Head re-read from ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
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 branchclaude/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 (
c80eac97fon branchclaude/issue-9910-children-slot-declare-and-pin-r2, fromorigin/mainat88a4ef616) because a commit there carried a model-named co-author trailer that the squash would have appended tomain. Proof on the re-cut: the 42 changed paths are the same set,git diffagainst the superseded head over those paths is 0 bytes, and no file outside them differs fromorigin/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 anos-dev-reportaddendum 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)
What changed
The tier (
@object-ui/sdui-parser).validateTree's containment branch reads one declaration —acceptsChildren(comp), true iffcomp.inputscarries an entry namedchildren— and nothing else;isContaineris not consulted and is not a fallback.checkRetiredBodyDialect(the retiredbodyspelling, second containment site) is handed the same predicate instead ofcomp.isContainer, so the two spellings cannot disagree (assumption A3, see below). New exports:CHILD_LIST_KEY('children') andacceptsChildren.ManifestComponent.isContainer/RegistryConfigLike.isContainerare 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 putsschema.childrenon the page now declares{ name: 'children', type: 'slot' }:basic/html-elements.tsx(declared inside the factory, skipped for the three void tagsimg/hr/br);layout/semantic.tsx;aspect-ratio,box,card,container,flex,grid,stack;alert,badge,button,toggle,span(label / description / value fallbacks),div,form,scroll-area;sidebar-*parts that render children (sidebar-triggerrenders none and declares none);page-headerin@object-ui/layout.That is 65 registrations measured bare plus
sidebarandsidebar-menu-button, which only mount insidesidebar-provider— 67, the ruling's number.tooltip, the fourpage:*containers and the five page kinds already carried the input. NoisContainervalue moved anywhere.The published descriptions (
@object-ui/types, docs).ComponentMeta.isContainer,WidgetManifest.isContainerandComponentMetaSchema'sdescribenow say layout containment (react-page scope, public layout ledger), not "can have children", and point at the slot.content/docs/guide/react-pages.mdandcontent/docs/guide/component-registry.mdsay the same.react-page.tsx'sbuildComponentScopeis untouched (assumption A4).The census.
container-declaration-ratchet.test.tsxis re-pointed from the flag to the input in both directions over every known key (namespaced spellings included, sincesidebarandpage:sidebarare different registrations): renders ⇒ declares; declares ⇒ renders, with named context probes forsidebar,sidebar-menu-button(insidesidebar-provider) andtooltip(open, read from the portal), each pinned as still needed; andnot-a-containerfires 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.jsonis at zero rows (the file stays as the ruled admission ledger, with its note rewritten), andpackages/sdui-parser/src/__tests__/containment-declared-slot-9910.test.tspins the tier's 2×2 (flag × slot) reading of a manifest.Fixture triage. Parser fixtures that nested children under an
isContainer: truemanifest entry now declare the slot too (compile,render,type-attribute-collision-7235,body-dialect-6771,verify.ts); thelayout-containers-declare-containmentchildless control moved frombadge(which now declares the slot it renders) totabs; the layoutpage-header-authorable-keysdialect guard licenseschildrenby name as the protocol's base key (PROTOCOL_LICENSED, read fromCHILD_LIST_KEY) and asserts the spec does not carry it.Measurements (the PM's assumptions, each taken before editing)
registry-inputs-spec-parity: of the registrations that render children, the only ones in the gate'scoveredset arepage:card/page:footer/page:section/page:sidebar, all of which already declaredchildrenand whose spec shapes carry it (specCoveredRendersin 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). objectstackcheck:react-declaration-parityjudges theREACT_BLOCKSandobject-*blocks only, none of which renders children; measured by dumping the public manifest from this tree before and after (the wayapps/console/dev/manifest-dump.tsxbuilds it) and running the gate read-only against each from a scratchpad shared clone of objectstack at2c1011bwith@objectstack/spec's deps installed (the shared checkout has nonode_modules): control exit 0, after exit 0, output byte-identical, "no new DECLARATION divergence vs accepted baseline". Manifest delta: exactly nine public blocks gain achildreninput —flex,grid,stack,card,container,box,badge,alert,button; 59 keys before and after; zeroisContainervalues changed. Aslotinput emits no JSX attribute (emitInterfacefilters slot arms), sosdui-intrinsics.d.tsis unchanged. ⇒ neither gate goes red; no fork to report.@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;sidebarandsidebar-menu-buttonrender only inside the provider;tooltiprenders 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 Reactchildrenprop oritems[].children, neverschema.children— they now draw the TRUE diagnostic the flag used to silence).validate.tssite. Read as covered by "reads only that input": the body-dialect check emits the samenot-a-containercode for abodylist, so leaving it on the flag would make the two spellings disagree about which components take a list. Both sites readacceptsChildren(comp); the parameter is renamedacceptsChildren. Recorded under Deviations.buildComponentScopestill readscfg.isContainer; not edited.react-page-scope.test.tsxandpublic-contract.test.tspass unchanged.packages/core/src/registry/Registry.tshas zeroisContainerhits onorigin/main(grep -c= 0); the description faces arepackages/types/src/base.ts,packages/types/src/widget.tsandpackages/types/src/zod/base.zod.ts, which is where the re-description landed. Nothing inpackages/coreneeded 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(slotissue-9910-dev) and readsVERDICT command-exit 0. Final head of this branch:f34809934(a merge oforigin/mainatfa5fbd9dc; the eight newer files onorigin/mainsince then touch none of this surface).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-actionsregistry-inputs-spec-parity,htmlTierDataSourceInputDeclaration-6678,public-contract; components record-picker / toast-sonner / action-type html-tier pins; typeszod-mirror-parity,component-meta-single-declaration,default-children-retired-contract-twins;packages/core/src/registry/; kanban / detail / grid manifest pinsf34809934: ratchet, census, layout census, layout page-header, parser 2×2 pin, console parityturbo run build --filter=@object-ui/layout^... --filter=@object-ui/sdui-parser --concurrency=2pnpm --filter @object-ui/{sdui-parser,types,layout,components} type-check(script name echoed astype-check;tsconfig.test.jsonincludessrc/**/*.test.tsx, so the new tests are in the program)eslint --no-inline-configover the 38 touched.ts/.tsxfilesno-explicit-anywarningscheck-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 --testover 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-typescheck:react-declaration-parity --baseline --strictagainst the BASE manifest (control) and the AFTER manifestcheck:sdui-registration-pins(needs a console build), the fullpnpm lintandpnpm testfarm,dispatch-gates.mjs(refuses this repo; the list above is hand-derived frompackage.jsonand.github/workflows/)Reverse verification (type level, consumer side). After the build, the layout test that imports
CHILD_LIST_KEYwas mutated by appendingconst __ablationBad: number = CHILD_LIST_KEY;(marker count 0 → 1 on disk),pnpm --filter @object-ui/layout type-checkwent red withTS2322: Type 'string' is not assignable to type 'number'at the mutated line (exit 2), and the file was restored withgit checkout HEAD -- FILE: marker count back to 0,git hash-objectequal to the HEAD blobcddf5764…,git diff HEADempty. This proves the consumer type-check reads the rebuiltsdui-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
748fc2484carries aCo-Authored-Bytrailer 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.validate.tscontainment sites re-pointed together (the body-dialect parameter renamedacceptsChildren), reading "reads only that input" as covering the retired spelling's containment sentence.{ 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 anarray-typedchildrencounts.scripts/container-declaration-baseline.jsonis 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.@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-②: yesalready covers a published-type change.checkRetiredBodyDialect's fourth parameter narrows fromboolean | undefinedtoboolean(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'spage:section/page:footer/page:sidebarstill readschema?.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'sgenerateBlockList"container" column still readsisContainer; under Q2 that column now literally means layout container. Unchanged here.check-skill-examplesfor 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.page-container body read·generateBlockList container column·isContainer layout.Generated by Claude Code