feat(desktop): side panel tabs scroll horizontally instead of collapsing to icons

Merged
DA
8d ago
+353-120

Summary

Dean: "I don't like how the tabs turn into just their icons. Let's remove that behavior and just go with the horizontal scrolling."

Compact mode from #1910 (and #1945's subagent tabs, which followed it) is gone. Tabs never collapse to icons; the strip scrolls instead, the way Chrome, VS Code, and Cursor do it.

  • Every tab stands whole: icon + label (+ × under the existing reveal rules) at its natural width, up to 200px. A longer label, like a subagent's job, ends in an ellipsis, and the Tooltip shows the full name (before any existing hint, e.g. the plan's repository). A label shown whole gets no tooltip, as before.
  • Horizontal scroll: no scrollbar is drawn (scrollbar-width: none, the cleaner option). A vertical wheel over the strip scrolls it sideways, taking a line-mode wheel's line as 16px and a page as the strip's width; a trackpad's sideways swipe already works natively. overscroll-behavior-x: contain keeps the scroll from passing on to anything else.
  • Edge fades: the edge(s) with hidden tabs fade out over the existing --scroll-fade token. They run on a CSS scroll timeline, the same pattern as the panel's list fades (scroll-edge-fade), so a strip with nothing to scroll is left alone and no JavaScript checks for overflow. It's a mask, so it has no colour and works in either appearance; the design contract allows black only in a mask's alpha.
  • Scroll into view: a tab newly chosen (clicked, opened from "+" or ⌥⌘1–4, a subagent opened in a tab) and a tab reached by the arrow keys, Home/End, ⇧⌘←/→, or Delete moving focus on is scrolled into view, clear of the fade (the strip's scroll-padding-inline). Only the strip's own scrollLeft moves, never scrollIntoView, so the shell never scrolls sideways (#1884) and a panel sliding in isn't dragged along. It's a smooth glide through a new --scroll-glide token, which is auto (instant) under reduced motion and in a capture run, set where the duration tokens are zeroed. The first reveal when a strip is drawn is instant. Narrowing the strip (dragging the panel's edge, resizing the window) keeps the chosen tab in view, also instantly.
  • Layout: the "+" stays pinned after the scroll area and the full-screen toggle after that; neither is scrolled or overlapped.
  • Plan toolbar: its single tab still truncates to fit the heading rather than scrolling, as compact mode had it, so long plan names aren't cut at 200px.
  • Drag-to-reorder (#1935) is unchanged; it already keeps a drag within the strip's visible room. I left out edge auto-scroll while dragging: only the fixed tabs can be dragged, and they always sit first, so it isn't the cheap win the brief allowed for.
  • Deleted: the Compact context, fitTabs and its compact state, the ResizeObserver that fitted the row, the data-compact styles, and the compact test. Two small observers take their place: one on the strip, which keeps the chosen tab in view as it narrows, and one on each tab's label, which tracks whether the label is cut short (for its tooltip).

Tests

desktop-plans.test.ts, through the real page. Each new test fails on main and passes here:

  • In a strip too narrow for its tabs, every tab keeps its label and none that shows whole gets a tooltip. This replaces the compact test; main gave unchosen tabs a tooltip there.
  • A subagent tab with a long job is cut short and its tooltip shows the full job; a tab shown whole gets none.
  • Narrowing a strip that showed every tab keeps the chosen tab in view.
  • A vertical wheel scrolls a strip with hidden tabs, one line-mode line is 16px, a sideways swipe is left to the native scroll, and a strip with nothing hidden doesn't move.
  • The plan's tab, cut short as its toolbar narrows, shows its full name in the tooltip.
  • In a strip that shows only two tabs: clicking a tab outside the visible area scrolls it into view, Home brings the tab it reaches into view, and a tab reopened with ⌥⌘1 at the strip's end is scrolled into view. This is checked by reading the pill's position against the strip after the strip's own scroll.

./scripts/check.sh exits 0.

Verification

CI could not verify this UI change: CI builds nothing for the Mac, and ./scripts/verify.sh was not run (this is a Linux cloud VM; LUKE-159 is the manual Mac pass).

Instead, I rendered the real DesktopPlans page with the built stylesheet in headless Chrome, driven in real time over the DevTools protocol: a 380px side panel with the four fixed tabs, two coding agents, and two subagents. Measured there: opening the panel leaves scrollLeft at 0; clicking the last tab glides it into view (0 → 112 at 80ms → 762, the end); a vertical wheel of 150 scrolls the strip 150 back; the shell's own scrollLeft stays 0 throughout.

At open (dark): the plan's tab, and the panel's strip fading where Transcript runs on, with "+" and the full-screen button pinned:

window, dark

The panel's row: at its start, scrolled over the agent tabs (fades at both ends), over the subagent tabs, and at the end with the chosen subagent tab revealed.

DarkLight

At open (light):

window, light

🤖 Generated with Claude Code


Open workspace in Conductor


Devin Review