feat(desktop): collapsible left sidebar, toggled beside the traffic lights and with ⌘B
Merged
DA
11d ago+440+440+012345678901234567890123456789-77-77-01234567890123456789
Summary
- Toggle: a sidebar icon button sits just right of the macOS traffic lights, at a fixed position in the drag strip (the button itself is
no-drag). It stays in the same spot whether the sidebar is open or folded. Itsaria-labelis "Hide sidebar" / "Show sidebar", its tooltip adds(⌘B), and it setsaria-keyshortcuts="Meta+B". - Folding: the plans' shell gets a
0sidebar column, so the work column takes the full width right away. The sidebar slides off the left edge over it, using transform and opacity on--duration-quick/--motion-exit. Those tokens drop to 1ms under reduced motion and to 0 in capture. A folded sidebar isinert, so Tab can't land on rows that aren't visible. While folded, the plan toolbar starts its title clear of the traffic lights and the toggle, and Luke's captions centre on the full window instead of the old work column. - ⌘B toggles the sidebar while the Plans tab has the keyboard. It does nothing in Settings, under a sheet, or behind the sign-in gate. The Keyboard shortcuts page lists it as a fixed row, and Settings search can find it.
- Persistence: the folded state is kept in the renderer's
localStorage. That lives under Luke's state root, so it survives a relaunch. No other window or process reads it, so it does not go through the settings file as a schema setting. A fixture run starts with the sidebar open and stores nothing, so evidence frames never pick up a developer's own folded state. This matches the side panel from #1829. - Settings: Settings always shows its page list and never shows the toggle. Leaving Settings brings back the plans view folded or open, as it was.
One departure from the brief: the brief asked for a width/opacity transition. design/check-design-contract.mjs (run by check.sh) and docs/DESIGN.md refuse layout-property transitions because text reflows on every frame. So the column changes size instantly and the sidebar moves with transform and opacity only.
The settings-panel props builder moved to #testing/settings-panel-props. It now has two callers: the existing settings-panel test and the new shell test.
Evidence
- Platform-independent checks:
./scripts/check.shexit 0 (Linux). - New
desktop/desktop-shell.test.ts(jsdom) goes throughDesktopShellanduseSidebarCollapse. It covers four things:- The toggle hides and restores the sidebar.
- The folded state survives a remount.
- ⌘B folds and unfolds the sidebar on Plans.
- Settings keeps its page list, shows no toggle, ignores ⌘B, and hands the plans back still folded.
- A fixture run starts open and doesn't touch the stored state. Control-B is left to text fields.
- macOS Electron verification (
./scripts/verify.sh):not run. CI could not verify this change: it builds nothing for the Mac, and this PR was written in a Linux sandbox. LUKE-159 is the manual Mac pass.
Visual evidence
- Fixture screenshots:
not attached. I renderedDesktopShellover the builtstyles.cssin headless Chrome on Linux and checked four states: expanded, folded, Settings while folded, and the Keyboard shortcuts row. Chrome draws no traffic lights, and the toggle's 80px left offset assumes they end at about 72px, so its position next to the real lights is unverified on a Mac.
Physical-device evidence
- Screenshot or screen recording:
not attached - Device/display configuration:
not recorded
Invariants
-
./scripts/check.shgreen../scripts/verify.shnot run (no Mac available). - JSON Schema goldens unchanged.
- The behavior change ships with a test that fails without it. No change-detector test added.
- Gateway envelope goldens unchanged.
- No new
astype assertion. - No new
Effect.runPromise/runSync/runFork. - No new raw async primitives.
- No discarded Effect.
- Test count grows by three. The shortcuts search expectation now includes the new row.
- No hand-rolled file replaced.
-
CLAUDE.md/AGENTS.mduntouched. -
PRIVACY.mdunchanged. The stored value is one boolean and leaves the machine nowhere. - No dependency changes.
🤖 Generated with Claude Code