feat(desktop): the side panel runs full height, goes full screen, and snaps past its bounds

Merged
DA
11d ago
+714-107

What

Bottom of a three-PR stack: (A, this) the right panel → (B) a resizable left sidebar built on this PR's resize hook → (C) panel in/out animations.

Full height

The side panel now runs the full height of the window instead of sitting under the plan toolbar. The plan toolbar (title, Choose folder…, Copy plan, ⋯) spans only the document's column. While the panel is open, its own top row is level with the toolbar and has the same height as the drag strip. It holds the tabs on the left and [Expand panel][Hide panel] on the right. While the panel is closed, the toolbar shows the toggle at its right edge, as before. Copy plan and ⋯ always stay in the toolbar and never move into the panel.

Drag regions: the panel's top row is drag. Its tabs and buttons are no-drag and come after the row in DOM order. The resize edge is now the panel's last child, so the row's later drag rect can't take the top of the edge. With #1840 (rebased on), the sidebar toggle comes after everything, so it also stays clickable over a full-screen panel's top row when the sidebar is folded. That row also gets the folded sidebar's traffic-light clearance.

Full screen

  • The panel takes the whole work column: the document's area plus its own. The left sidebar stays open or folded as it was.
  • The document, its toolbar and the call bar are hidden (hidden, not unmounted), the same way ChatGPT hides the chat.
  • Exiting returns the panel to the width it had.
  • Full screen is not persisted. Hiding the panel also leaves full screen, so showing it again docks it.
  • Escape exits full screen. plans.back() treats full screen as the layer above the open plan, so the app's existing Escape chain leaves full screen first and only then leaves the plan. As before, the mic-mute Escape comes first. Menus and fields that handle their own Escape (the ⋯ menu, the settings search, Excalidraw's inputs) stop propagation, so they still answer it themselves.
  • The button sits immediately left of the panel toggle and has two states: Expand panel (arrows pointing out) and Exit full screen (arrows pointing in). ExpandIcon and CollapseIcon are new glyphs in @sidecar/panel. There's no shortcut or tooltip registry on main yet, so the button uses the existing title convention and has no chord.

Overshoot snapping, double-click, keys

desktop/use-resizable-edge.ts is the shared hook. You give it side (left/right edge), bounds (MIN/MAX/DEFAULT), reserve (the room the neighbouring column keeps) and the callbacks onResize, onCollapse and onExpand. It returns the separator's props plus the pending snap.

  • Inside the bounds: a drag is clamped. The greatest width is min(MAX, container − reserve), measured when the press starts, so a narrow window holds the document at 360px. That matches the CSS max-width that was already there.
  • Snapping (SNAP_OVERSHOOT = 80px): if the pointer goes 80px past the least width, the panel collapses. If it goes 80px past the greatest width it can actually take, the panel goes full screen.
  • Preview and release: while dragging, the pending snap shows as data-snap. Whatever the release would remove fades: the panel for a collapse, the document for an expand. The snap is committed on release, and a drag that comes back inside the bounds doesn't snap. Both snaps restore the width from before the drag, so the panel reopens at its old width.
  • Double-click sets the default width.
  • Keys: arrows step the width (the arrow pointing away from the panel widens it), Home goes to min, End to max, Enter to default.

Tests

desktop-plans.test.ts, through DesktopPlans over the real useSidePanel:

  • going past the min collapses and reopens at the old width
  • going past the max goes full screen, and exiting gives back the width
  • a narrow window snaps past the document's reserve
  • a drag that returns inside the bounds doesn't snap
  • clamping at the bounds
  • double-click resets to the default
  • Home, End and Enter
  • the full-screen button
  • hiding while full screen
  • the panel's top row holds [Expand panel][Hide panel] and never Copy plan or ⋯, docked or full screen

use-plans-tab.test.ts: stepping back (Escape) from full screen docks the panel and keeps the plan open, and the next step leaves the plan.

Mac verification

CI could not verify the Mac UI, and ./scripts/verify.sh was not run. I built this on a Linux cloud VM. LUKE-159 is the manual Mac verify.sh pass. To check the layout, I rendered DesktopPlans (closed, docked, full screen with the sidebar open and folded, both snap previews) with the built stylesheet in headless Chrome. The panel runs full height, its row lines up with the toolbar, and in full screen the row clears the traffic lights. The screenshots are not uploaded here. Drag regions and real pointer drags can only be checked on a Mac.

./scripts/check.sh exits 0, and the renderer tests pass after rebasing on #1840.

🤖 Generated with Claude Code


Open workspace in Conductor


Devin Review