…ewport (objectui#11464)
Local edit (3) keeps `data-collapsible` set while the sidebar is expanded,
where upstream blanks it. The icon rules were qualified by the collapsed
state to match; the offcanvas rules were not, so an expanded offcanvas
panel was pushed one sidebar-width off its edge, its gap reserved no width,
and its rail took the collapsed styling.
The six offcanvas lines (the gap, the left and right offsets, the rail's
three) are now qualified by the collapsed state through a declared patch
family, sidebarOffcanvasStateQualifierPatches, anchored on registry bytes,
so a sync re-applies them. The shadcn-components.json note for edit (3)
now says what qualifies which rules.
Pinned in Chromium by e2e/sidebar-offcanvas-geometry.spec.ts: the real
primitive, the sidebar node's renderer and SidebarNav server-rendered from
source, real Tailwind, measured at 1280px.
Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
Fixes #11464
Clause-②: no
What this changes
The Shadcn
Sidebarin itsoffcanvasform (the default) drew its expanded panel one sidebar-width past the edge it is anchored to, out of the viewport. It also reserved no width beside the page, and its rail took the collapsed styling. Expanded and collapsed drew the same thing, so a user could not tell the two states apart.Root cause. Upstream blanks
data-collapsiblewhile the sidebar is expanded, so itsgroup-data-[collapsible=offcanvas]:rules can only match a collapsed sidebar. objectui's copy keeps the attribute in every state (local edit (3) inpackages/components/shadcn-components.json). Theiconrules were qualified withgroup-data-[state=collapsed]:to match, and the offcanvas rules were not.Fix: one declared patch family,
sidebarOffcanvasStateQualifierPatches, inscripts/shadcn-local-patches.mjs. It has one anchored edit per upstream line, and each one qualifies its line by the collapsed state:sidebar-offcanvas-gap-collapsed-onlygroup-data-[collapsible=offcanvas]:w-0sidebar-offcanvas-left-offset-collapsed-onlyleft-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]sidebar-offcanvas-right-offset-collapsed-onlyright-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]sidebar-offcanvas-rail-collapsed-onlytranslate-x-0,after:left-full,hover:bg-sidebarsidebar-offcanvas-rail-left-edge-collapsed-only[[data-side=left][data-collapsible=offcanvas]_&]:-right-2sidebar-offcanvas-rail-right-edge-collapsed-only[[data-side=right][data-collapsible=offcanvas]_&]:-left-2group-data-*rules take thegroup-data-[state=collapsed]:prefix theiconrules already carry. The two arbitrary-selector rail rules take[data-state=collapsed]inside the selector, the spelling the rail's collapsed cursor rules beside them already use.findwas written fromscripts/__tests__/fixtures/shadcn-registry/sidebar.registry.txt. All 10 sidebar anchors apply to the registry bytes (the offline round-trip test).markeris the whole patched string, soverifyLocalPatchesrequires the shipped file to carry the payload byte for byte. The shippedsrc/ui/sidebar.tsxchange was written byapplyLocalPatchesitself, not by hand.shadcn-components.json's edit (3) note no longer claims "same result". It now says which rules are qualified by hand (icon), and which by the declared family (offcanvas).@object-ui/componentspatch.Geometry: BASE
6007dd4e4vs this branchInstrument: Chromium (the pre-installed
/opt/pw-browsers/chromium, driven by the repo's Playwright 1.62.1) at 1280x800. The markup comes from the real components, server-rendered from source by Vite: the primitive, the SDUIsidebarnode's renderer, andSidebarNav. The CSS is real Tailwind 4.3.3, compiled for exactly the classes that markup carries. x ranges are in px.sidebarnode,collapsibleomitted, bare hostSidebarNav collapsible="offcanvas", expandedSidebarNav collapsible="offcanvas", collapsedThe rail, offcanvas, left, expanded: at BASE it had
translate0px,right-8px and::afterleft16px, the collapsed styling. After the fix it hastranslate-50%,right-16px and::afterleft8px, identical to the expandediconrail. After the fix, every expanded offcanvas reading equals the expanded icon reading on both sides.The pin:
e2e/sidebar-offcanvas-geometry.spec.tsOption (a), a real engine, for three reasons:
e2e/record-header-title-width.spec.ts, which usespage.setContentplus real Tailwind.Build & E2Ejob runspnpm test:e2e --project=chromiumovere2e/, and asidebar.tsxchange triggers it.The spec has six tests:
sidebarnode withcollapsibleomitted draws on-screen in a bare host.SidebarNav's offcanvas form is on-screen when expanded and off-screen when collapsed.Declared fixtures, also stated in the spec header:
@object-ui/coreis a stub that recordsregister(), so the node's component comes from its real registration.@object-ui/reactand@object-ui/i18nare stubs. Neither is on the geometry path: the child list is empty here, and i18n is reached only through the mobile sheet's close label.@object-ui/components, asSidebarNavimports it, re-exports the same primitive files the barrel re-exports.The workspace packages are stubbed because this lane builds no workspace
dist/. That the realSchemaRenderermaps an omittedcollapsibletooffcanvasis already pinned through the real renderer bysidebar-node-provider-10859.test.tsx. The spec imports no workspace package by name, which is the premisescripts/__tests__/e2e-type-check.test.tspins.Reverse verification (prediction written first, every leg from a committed state)
0ce9adf7c).sidebar.tsxandshadcn-local-patches.mjswere swapped to their BASE blobs (7eb315438and9c6218b6b), and the new tests kept.shadcn-local-patches.test.ts: 2 failed / 51 passed, as predicted: the new family-ids test and "no patch outside those three families".git diff HEADis 0 bytes.ablation-replace.mjs. Each reverted one patched line in the shipped file to its registry anchor; the anchor hit 1 to 0, the blob moved, and the file was restored to blob15cca6101== HEAD with an emptygit diff HEAD. Each leg landed exactly as predicted:sidebar.tsx carries its declared patcheswent red (1 failed / 52 passed).LOCAL_PATCHES.sidebarand the shipped bytes kept. e2e: 6 passed (geometry reads the shipped bytes). The local-patches suite: 2 failed (family ids, three families). Restored to blobbc7e528aa== HEAD.Measured against the dispatch's mechanism assumptions
-right-2/-left-2) after the line triage named. They are the same defect on the same element, so they are in the family. Without them, an expanded offcanvas rail tookright: -8px(measured above).find/replace/marker/occurrences: 1). The repo enforces the payload by marker containment (verifyLocalPatches, in the "shipped primitives still carry every declared patch" block). The markers are the whole replacement strings, and a new test assertsmarker === replace, and that each replacement minus the qualifier is its anchor.shadcn-components.jsondoes not ship:@object-ui/components'filesisdist,README.md,CHANGELOG.mdandLICENSE. So the changeset describes the behaviour, not the note.SidebarNav collapsible="offcanvas"was reached at BASE (expanded -256 to 0). The same patch fixes it, and it is pinned (test 6).SidebarNav.tsxis not edited.innerWidth375: the desktop.group.peeris not rendered, and the trigger opens the[data-mobile="true"]sheet.sidebar.tsxdiff sits in the desktop return and inSidebarRail, none in theisMobilebranch.Gates (final head
0e7b02c2d)webServer, withexecutablePath: '/opt/pw-browsers/chromium'):6 passed.os-verify-lock. Three slices, 84 test files in total, all passed:scripts/__tests__files that read the changed files ore2e/:Test Files 17 passed (17),Tests 427 passed (427);Test Files 44 passed (44),Tests 1178 passed (1178);Test Files 23 passed (23),Tests 406 passed (406).ui/sidebar,SidebarProvider,SidebarNavorcollapsiblein components, layout, app-shell and schema-catalog, plus the readers of the changed files, plus both phase-2d pins.pnpm --filter @object-ui/components type-checkexit 0 (after building its dependency closure),pnpm type-check:scriptsexit 0,pnpm type-check:e2eexit 0.pnpm check:control-bytes: OK.pnpm check:new-line-citations:0 new citation(s).node scripts/check-changeset-presence.mjs:1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s).pnpm check:changeset-claims: 3 pending changesets name a touched file (10859-sidebar-node-provider-collapsible,7397-remove-duplicate-chart-primitives,9569-components-stylesheet-source-none). Each was read, and each is still true. None claims edit (3)'s "same result" or an offcanvas position, so no note was appended.check:phantom-deps,check:self-import,check:unreferenced-sources,check:esm-specifiers,check:entry-guard,check:pre-install-import-graph;check-test-path-roots,check-type-check-coverage,check-lint-coverage,check-changeset-fixed,check-changeset-no-major;check-governed-queue-guard --teston the 6 paths: NOT GOVERNED.isPathIgnored/calculateConfigForFile): all 4 changed lintable files are in it.eslint --format jsonover those 4 files: 4 files, 0 errors, 2 warnings. Both warnings arereact-refresh/only-export-componentson the export block ofsidebar.tsx, and the BASE blob carries the same 2.parserOptions.projectorprojectService, so the diff cannot move a verdict on an untouched file.pnpm shadcn:check:ui.shadcn.comis not in this container's egress allowlist (44 of 44 fetches answered 403). The offline anchor suite covers the new anchors.pnpm lintand fullpnpm test: owned by CI.playwright.config.ts: owned by CI'sBuild & E2Ejob.Acceptance notes
.github/workflows/ci.ymlcarries a dated snapshot comment ("Both readings are a SNAPSHOT") whose static complement says noe2e/spec exceptrecord-header-title-width.spec.tsnamesnode:fs. This spec does too: it readspnpm-workspace.yamland Tailwind's own CSS. It opens no markdown, which is the fact that comment's reading is about. Not edited here: outside this card's file surface. carrier: none.shadcn-components.json's sidebar note still does not mention the two earlier declared sidebar families (cookie read, optional context). This note is about the undeclared edits, so it is left as it is. carrier: none.Generated by Claude Code