Repository navigation
refactor(components)!: the sidebar node supplies its own provider; retire the ten sidebar-* primitives; the sidebar documents teach the node (objectui#10859 batch 8, phase 2d) - #11463
Conversation
…en none is above it, and honours the boolean collapsible (objectui#10859 batch 8, phase 2d) Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
…ebar node; tests stop naming the retired sidebar parts (objectui#10859 batch 8, phase 2d) Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
…d dated notes for the retired sidebar parts (objectui#10859 batch 8, phase 2d) Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
…debar documents (objectui#10859 batch 8, phase 2d) Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
…ading its form (objectui#10859 batch 8, phase 2d) Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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: PR objectui#11463 on card objectui#10859, batch 8 phase 2d (fork 3 of the seat's fork ruling ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Reading time: 2026-10-02T13:53Z |
…tch8-phase2d Merges objectui#11460 (objectui#11441), #11461 and #11462. The ratchet keeps both retirements (UNREGISTERED_11441 and the phase 2d row) and pins 8 / 362; the 9910 changeset keeps both dated notes. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
…known-types changeset states the merged base (objectui#10859 batch 8, phase 2d) Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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: Delta record for PR objectui#11463 on card objectui#10859 (batch 8 phase 2d) at its re-pin head. The full record ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Reading time: 2026-10-02T14:34Z |
Part of #10859
Clause-②: yes
Phase 2d of batch 8 on the family card, under "Fork 3" of the seat's fork ruling
5948252391(route A, conditional). Thesidebarregistration now mounts its ownSidebarProvideronly when no provider is above it. The fourcomponents-basic-sidebarcatalog documents andsidebar.mdxteach that node. Thesidebarregistration honours the spec's booleancollapsible. The tensidebar-*primitives retire. Implemented byos-devfordomain:uiseat 1, sessionsession_01YLg8XqWGJ785fwQ5v4pH37. Base:origin/main5988b6b53. Main was merged after objectui#11457 (phase 2c) landed asad1785c1d, and again after objectui#11458 landed; neither merge touched this PR's files. objectui#11441 (PR objectui#11460) landed first, so on the merged base this PR moves the ratchet 18 → 8.Premise, verified first: safe detection exists
shadcn's
useSidebar()throws outside a provider, and its context is private to the primitive. The primitive gainsuseOptionalSidebar(), a non-throwing read that returnsnullexactly whereuseSidebar()would throw. It is added as a declared local patch inscripts/shadcn-local-patches.mjs(two anchored edits on registry bytes: the reader and its export). The no-touch rule forsrc/ui/**is kept. The SDUI node wraps only when that read returnsnull.Both cases from the ruling were measured through the real
SchemaRenderer. A provider is counted by itsgroup/sidebar-wrapperelement.SidebarProvider,openfalse then truedata-stategoes fromcollapsedtoexpanded@object-ui/layoutAppShellwithdefaultOpenfalse and aSidebarTriggerin its navbarcollapsed, and clicking "Toggle Sidebar" makes itexpandedThe tests that pin these cases are
sidebar-node-provider-10859.test.tsxin@object-ui/componentsandapp-shell-sidebar-node-one-provider-10859.test.tsxin@object-ui/layout. Ablation 1 below forces the detection off, and every host-with-provider row goes red. There is no fork, so neither fallback route (B or C) is used.collapsible: the boolean is honouredfalsemaps to shadcn's'none': the in-flow, fixed-width form, which ignores the open state.trueor an absent key passes no prop, so shadcn keeps the default the node applied before (offcanvas). Before this change, the boolean reached the DOM verbatim asdata-collapsible="true"/"false", which no rule matches. A render pins each value (false→ in-flow;true/ absent →offcanvas; never the verbatim boolean). Any other value is forwarded unchanged, as before, andobjectui validaterefuses it asinvalid_typeatcollapsible(measured for'none'and'icon'). The registration'scollapsibleinput is now declared as a boolean. ItsdefaultPropsno longer namecollapsible: that'icon'was never read at render, becauseSchemaRendererdoes not read a registration'sdefaultProps.The teaching: four documents and
sidebar.mdxEach document is now one
{ "type": "sidebar", "collapsible": false, "children": [...] }node, with armed children:basic-sidebarandgrouped-sidebar:textheadings and ghostbuttonitems, and ingrouped-sidebaraseparatorbetween the groups;collapsible-sidebar: threebuttonitems;sidebar-with-badges:flexrows with their children inproperties(objectui#11276), each holding atextnode and abadgenode. The 6806 pins ("Inbox12", "Drafts3", two labelled badges) still hold.The documents author
collapsible: falsebecause that is the one form that draws in every host. The docs-site hosts mount their provider withdefaultOpen={false}, so a collapsible-form document starts collapsed and off-canvas there.collapsible-sidebarkeeps its id. Its curated title is now "Non-Collapsible Sidebar", with a description, because the old title named a behaviour the document does not show.Measured by a render:
sidebar-node-docs-10859.test.tsxrenders each document bare and inside the docs-host shape (SidebarProviderwithmin-h-0 w-fullanddefaultOpen={false}). In both shapes the test checks exactly one provider, the in-flow form, every authored string, and no diagnostic panel.Measured in a browser: the real renderer's markup was compiled with the repo's Tailwind 4.3.3 and laid out in Chromium at 1280×800.
basic-sidebaroccupies x 16 to 272, 256 px wide and hit-testable, both bare and in the docs host. At 375 px the same holds.Measured by
objectui validate: the built CLI'svalidatecommand returns exit 0, "Schema is valid!", for all four documents. As a control, the basebasic-sidebar.jsonreturns exit 1,invalid_unionattype. The strict face (StrictAnyComponentSchema) accepts all four too.sidebar.mdxnow describes the node and the twocollapsiblevalues, and its Schema block is a typedSidebarSchemaliteral. That literal validates on both faces.check:doc-snippetsjudges it: withcollapsibleset to'none', the gate reported 1 failed block, a TS2322 on that line, and at head it reports 0 failed. The page no longer lists theitems/groupskeys, which no face declares.Retirement: the ten
sidebar-*primitivesThe premise gate was re-measured at
origin/mainad1785c1d/4a1adb76eand objectstackorigin/mainecb6ca02, at a type position, for both spellings:sidebar-keys): 0 in either repo.sidebar-provider,sidebar-header,sidebar-content,sidebar-group,sidebar-menu,sidebar-menu-item,sidebar-menu-button,sidebar-footer,sidebar-insetandsidebar-triggerare unregistered, with theirui:twins. One tombstone docblock inrenderers/navigation/sidebar.tsxdescribes the retired calls without quoting them, so the node-slot source reader does not mistake them for live calls. The shadcn components behind the ten stay exported for JSX.Tests that named the retired keys moved with them:
components; 168 targets and 84 rows overall), and its count prose follows. The "ui:sidebar-triggeris CLEAN" case goes, because there is no registration left to sweep; the ratchet pins the absence.sidebar-trigger-dom-leak-5632.test.tsxis deleted with its subject. The sweep's historical note gains a dated line.sidebar-menu-buttonleaves andbuttongoes from 116 to 127 (the new items). In the layout leak census,flexgoes from 248 to 252,textfrom 699 / 155 to 687 / 140 andboxfrom 86 to 82. Each move has a comment explaining it.sidebarnow renders its child under the bare probe, and that is asserted. The ruled-family row is nowbutton,badge,alertandsidebar.sidebar-groupinline-locale read-site cases go, with a dated note. The body-dialect census pin now reads the one remaining registration.shadcn-local-patches.test.tsfilters the cookie family by issue and adds the optional-context family.Ratchet
REFUSED_AT_TYPE(bare)sidebar-*keysNAMESPACED_REFUSED_AT_TYPEui:sidebar-*twinsSTILL_REFUSED_10859now holds the 8 the ruling kept (objectui#11440), and its set-equality row holds. objectui#11441's row and this phase's row both stay. A new phase-2d row checks three things: the ten keys are out of the population and still refused attype, their twins are gone, andsidebaris registered and not refused.ui:sidebaris still registered, and it stays among the namespaced keys objectui#10872 counts as refused, so the row asserts only that it is present. The namespaced non-vacuity floor goes from 400 to 350 (391 keys now), with a comment. ⛔ Neither pin rises. objectui#11441 landed first, so this PR mergedmain(ed54d285c) and re-pinned at2095abad1.Changesets
@object-ui/components,minor, BREAKING (authoring): the ten keys, with a migration per spelling. Each target was measured to validate on both faces:sidebarwithclassName;buttonwithvariant: "ghost";text;separator; aflexrow holdingtextandbadge; and aflexrow holding thesidebarnode and abox.@object-ui/components,minor: the provider detection, thecollapsiblemapping and the newuseOptionalSidebarexport.@object-ui/cli,minor, BREAKING (authoring): the regenerated known-types list.layout,app-shellandexample-schema-catalogchanged test files or private content only. Five pending entries gain dated notes: 11347,5632-sidebar-trigger-dom-passthrough, 6771, 9910 andform-control-renderers-dom-props-5632. Each note is a pure append.Local runs (final head
a27ff4385)Re-pin round at
2095abad1(dev report5954519792on objectui#10859). After mergingmaind93e53f5d:regenerate-known-schema-types --check: OK;cli/componentstype-checks: Done.The runs below were taken at
a27ff4385.Final pin union at
a27ff4385(throughos-verify-lock,vitest run --maxWorkers=2): the 7 pins plus the catalog gallery, both catalog leak censuses, the 6806 file, the DOM-leak sweep, the inline-locale and cookie-restore tests, the catalog-index and known-types derivation tests, the types node-slot reader, the CLI known-types test, and the three designer tests objectui#11458 brought in (designer-zod-arms-10859among them). Result:Test Files 21 passed (21),Tests 1098 passed (1098), VERDICT command-exit 0.At
21b03a578(before the last merge; this PR's files differ from the final head only in thesidebar-node-provider-10859test file):packages/componentsin full, in three calls:154 passed,98 passed | 1 skipped,100 passedfiles.examples/schema-catalog/: 39 files. The one red was the layout census, which was then updated.packages/cli/andpackages/layout/plus the layout census and five readers: 65 files.Exit 0 at
a27ff4385:check-changeset-presence(9 source files of 4 released packages, 3 changesets);docs:check-links,markdown-test-inputs --audit,regenerate-known-schema-types --check,regenerate-catalog-index.py --check;changeset:check,check:changeset-claims,check:component-surface-parity,check:control-bytes;check:doc-example-ids,check:doc-example-readers,check:doc-examples,check:doc-fences,check:doc-types,check:doc-snippets;check:element-data-source-declaration,check:handler-key-reads,check:new-line-citations(0 new),check:pending-changeset-literals,check:prompt-keys,check:readme-exports;check:registry-bare-names,check:self-import,check:side-effects-array,check:spec-symbols,check:test-path-roots,check:unreferenced-sources;check:vi-mock-inherit,check:vi-mock-specifiers,check-vi-mock-override-shape,check-lint-coverage;pnpm check(the built CLI's self-check: 287 validated, 0 did not validate);check:sdui-registration-pins(16 of 16).cli..., and@object-ui/console....objectui validate(built CLI): exit 0 for the four documents, and exit 1 for the basebasic-sidebar.json.Type-check at
a27ff4385:components,cli,layout,example-schema-catalogandapp-shellare Done, including each package's test tsconfig. Counted with--listFilesOnly, the new and edited test files are inside those configs.ESLint, narrowed, at
a27ff4385:eslint --format jsonover the 16 changed ts/tsx/mjs files, the way each package'seslint .runs it, gives 0 errors and 5 warnings. The 13 files that exist at base give 0 errors and 13 warnings on their base blobs; the other 3 are new. The population is the rooteslint.config.js, and all 16 files were linted with none ignored. That config sets noprojectService/parserOptions.project(0 matches), so no untouched file's verdict can move. Fullturbo run lintis CI's.Not run:
shadcn:check, which needs the registry over the network. The offline anchor tests inshadcn-local-patches.test.tscover the new patches.Reverse verification
The predictions were written before any leg ran.
BASE leg. Ten source files went back to
4a1adb76e: the renderer, the primitive, the patch list, the known-types list, the four documents,catalog-meta.jsonandindex.ts. The 7 pins ran against them.collapsible: falserow: on base it rendered nothing (a bare throw), and nothing has nodata-collapsibleeither. The rows now assert that the node drew before reading its form (commit000ce424c).Tests 32 failed | 104 passed (136), exactly as predicted. The AppShell pin was green.git diff HEADis 0 lines, and status is 0 lines.Ablation 1: provider detection forced off (the node always wraps). The anchor went from 1 hit to 0, and the blob from
3141c810ecbatob66caf1c8051.Tests 7 failed | 18 passed (25), exactly those 7. No bare-host row moved.Ablation 2:
collapsiblemapping removed (the authored value forwarded verbatim). The blob went from3141c810ecbato72f62c65ab53.Tests 12 failed | 13 passed (25), exactly those 12.Both ablations ran through
scripts/ablation-replace.mjs, with a trap restore of their own. Restored: blob3141c810ecbaequals HEAD andgit diff HEADis empty after each leg.Acceptance notes
offcanvasrules (the gap'sw-0and the container's negativeleft/right) are not qualified by state. The repository's undeclared local edit (3) keepsdata-collapsibleset while expanded, where upstream blanks it. So an expanded off-canvas sidebar sits off-screen at md and wider. That falsifies the edit's "same result" note inshadcn-components.json.{ type: 'sidebar' }(collapsible absent ortrue, expanded) sits at x −256 to −1, out of the viewport. The same markup with the attribute blanked as upstream does sits at x 0 to 255. Acollapsible="icon"sidebar, whose rules are qualified, sits at x 0 to 255.icon. The taught documents authorfalse. This PR keeps the ruling's "trueor absent → the current default".sidebar.mdxdescribes the collapsible form only as positioned against the viewport and does not claim that it opens visibly.SidebarSchemadeclarestitle,nav,content,footer,position,defaultCollapsed,collapsed,width,collapsedWidthand avariantenum that differs from shadcn's. All of these validate on both faces, but the renderer reads onlycollapsible,childrenandclassName, and spreads the rest onto shadcn.@object-ui/types' README still teachessidebar.nav. This belongs to the same "declared means enforced" family ascollapsible; the docs here teach only what is read.sidebar.mdxnow teachescollapsible: falsethroughout. The ruling's sentence "a baresidebarnode draws visibly in any host" holds for that form, measured in jsdom and Chromium. For the collapsible form it is limited by the first note.Generated by Claude Code