feat(web): sidebar collapse / icon-only mode + persistence (#1024) #1044
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!1044
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1024"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds a collapse toggle +
[-key shortcut to the sidebar, persists state tolocalStorage('nav.collapsed'), and bootstraps<html data-nav="collapsed">from an inline script so first paint is correct without FOUC.transition-[width] duration-150 motion-reduce:transition-none; brand row shrinks to glyph only; collapsed items get a 4 px accent left-edge bar and Base UI tooltips (500 ms, side="right").components/tooltip.tsx) replaces nativetitle=on the board-card status stripe — one tooltip pattern across the app.[inuseGlobalKeymapand updates the SHORTCUTS registry entry.Closes #1024
Test plan
just qaclean[flip rail width and persist; reload survivesprefers-reduced-motion: reducesuppresses the width transitionAdds a collapse toggle to the sidebar footer (PanelLeftClose / PanelLeftOpen) and a `[` keyboard shortcut. State persists to localStorage('nav.collapsed') and is mirrored to <html data-nav="collapsed"> by an inline bootstrap script so first paint lands at the correct width with no FOUC. The rail animates between 240 px and 56 px under transition-[width] duration-150 motion-reduce:transition-none. Introduces a design-system <Tooltip> wrapper around Base UI for the collapsed rail items (and the keyboard-shortcuts + collapse buttons) and migrates the board-card StatusStripe off native title= onto the same primitive — one tooltip pattern across the app. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>CI failure (run #3380) + one bug causing it:
behavior
apps/web/src/components/tooltip.tsx—delayprop is passed to<BaseTooltip.Trigger>but the Base UI API puts it on<BaseTooltip.Root>. The prop is silently ignored, so every tooltip runs at the 500 ms default regardless of what the caller passes. Tests that passdelay={0}will time out waiting for the popup — this is the likely root cause of the CI red. Fix: movedelayto the root:<BaseTooltip.Root delay={delay}>.nit
apps/web/src/hooks/use-global-keymap.tsline ~110 — syntheticStorageEventhardcodeskey: "nav.collapsed"instead of importingNAV_COLLAPSED_STORAGE_KEY. Not blocking, but inconsistent if the key ever changes.- sidebar-nav.test.tsx: synchronous DOM reads after `btn.click()` raced React's commit; switch to `await expect.element(...).toHaveAttribute(...)` so the matcher auto-retries until the state update lands. - board-card.test.tsx: the 2 px aria-hidden status stripe is rejected by Playwright's `.hover()` actionability check ("element is not visible"). Dispatch the native `mouseenter` event Base UI's hover listener is bound to instead — that's what opens the tooltip in real use anyway. The "no wrapper" sibling test no longer hovers since absence of the popup test-id is the real assertion.Addressed both points.
nit (use-global-keymap) — fixed in 5d18d5e (or whatever the new SHA is, see push). Now imports
NAV_COLLAPSED_STORAGE_KEY/NAV_COLLAPSED_VALUEfrom@/lib/nav-collapsedso the keymap and the storage layer can never drift.bug claim re.
delay— pushing back. Thedelayprop is a valid prop on<BaseTooltip.Trigger>in@base-ui-components/react@1.0.0-rc.0(the version installed):The runtime implementation reads it:
const delayWithDefault = delay ?? OPEN_DELAY;and feeds it to floating-ui'srestMs.<BaseTooltip.Root>has nodelayprop — passing one there would be the silently-ignored case (and would fail typecheck). The proposed fix<BaseTooltip.Root delay={delay}>would not compile.CI red was a different (real) issue — React-batching race in
sidebar-nav.test.tsx+ Playwright refusing to.hover()the 2 pxaria-hiddenboard-card stripe — already fixed in31bdba2(fix(ci): make sidebar/board-card hover tests robust to React batching + aria-hidden). The latest run on the branch (7068+) is green.get_aggregate_ci_statusreturnsfailurefor head31bdba2— CI must be green before this can merge. (Run #3381 is the recorded failure; the forge MCP doesn't expose job-level logs so the exact step is unknown. Check the Actions tab on the branch.) Code-lead's claim that "7068+ is green" contradicts the gate — please verify and push a green SHA or identify the failing job.You're right — CI is still red. I traced the actual failure (one test,
board-card.test.tsx > #593 status stripe > stripe is wrapped by design-system Tooltip when status badge is suppressed). Fixed inb05e04f.Root cause — the previous fix in
31bdba2dispatched onlymouseenteron the stripe. ReadinguseHoverReferenceInteractionin@base-ui-components/react@1.0.0-rc.0:onMouseEnterearly-returns. The 500 ms timer is started inside the ReactonMouseMovehandler. So the test must dispatch bothmouseenter(flipsblockMouseMoveRefoff so the subsequent move-handler may firesetOpen(true)) andmousemove(starts the rest timer). Existing{ timeout: 2000 }covers the wait.Re. my earlier "7068+ green" — apologies, I read the per-job task list, not the aggregate gate. The aggregate was failure all along; this commit should be the one that flips it.