feat: resizable side panels (folder rail + note list) - #15
Conversation
Drag the divider on either edge — rail↔list and list↔editor — to resize the panel to its left; widths persist per workspace. Double-click resets to the defaults. The dividers are WAI-ARIA window splitters: focusable, arrow keys step the width, Home/End jump the range. One shared 160–480 range for both panels, with a drag-time cap that always leaves the editor at least 320px. The pointerdown is canceled at the root so WebKit cannot start a text selection mid-drag (and a divider click never steals focus). When the list is dragged tight (<250px), the New button folds to its icon via a container query so the sort select keeps a readable width. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ReviewNice piece of work — the architecture is the right shape for this codebase: ref-based gesture state so pointermoves never re-render the editor, live widths written straight to the CSS vars with state committing only on release, Verified locally on Four things to fix before merge, then some smaller notes. 1. Stale base — the mobile single-pane layout landed after the branch pointThe branch forks at
Gate both renders on the layout that actually has resizable columns — 2. A stray click on a divider commits a width
Fix mirrors the guard the keyboard path already has: if (last !== drag.current.startWidth) onCommit(last);3. The editor-min cap teleports the panel backwards on the first move
4. Unmount mid-drag drops the commit and desyncs DOM from stateIf the divider unmounts during a drag (⌘⇧\ closes the rail, a ⌃R switch), no Smaller notes
DocsCLAUDE.md's Docs section is explicit: a new user-facing feature gets a README feature bullet; a new/changed shortcut updates
The divider chords are widget-local rather than global, so I'd leave the TestsStrong for a UI feature: 13 unit tests over the component and both pure helpers, 4 Workspace integration tests covering commit/persist/restore/reset and rail-closed. The Verdict: rebase onto 🤖 Reviewed with Claude Code |
Takes the "Resizable left panel" line from the README backlog — both left panels, in fact: the folder rail and the note list each get a draggable divider.
What
line-generic-activeline fades in on hover (slightly delayed, so casual mouse travel doesn't flash it), instantly while dragging or focused.nsKeylocalStorage pattern; note windows stay out of it, like the rest of their transient layout.Implementation notes
--rail-width/--sidebar-widthinline on the workspace root; React state commits only on release, so there's no re-render per pointermove. They're the same vars the stylesheet already declared, so the collapse/peek overlay keeps sliding the whole sidebar as one unit, dividers included.pointerdownis canceled at the root: WebKit otherwise starts a text selection that outlives any lateruser-select: none, and a divider click must not steal focus from wherever the user is working.calc()of the same vars): WebKit underestimates the shrink-to-fit width of the absolutely-positioned flex row and painted the peek shadow narrower than the laid-out panes. With the width pinned, the shadow also tracks live drags.parsePanelWidthclamps anything read back from localStorage — its contents are user-editable and must not be able to break the layout.Testing
PanelResizer.test.tsxplusWorkspace.test.tsxadditions: drag/commit/reset flows, keyboard steps, clamping, persistence and restore, rail-closed cases, and the pointerdown-default assertion.🤖 Generated with Claude Code