fix(desktop): tabs match the toolbar buttons, and narrow tabs go icon-only

Merged
DA
10d ago
+250-100

Dean asked for the tabs to match the toolbar buttons: the same height as ⋯ and the panel toggle, and an × as big as the other icons. Three more requests came in from his Mac screenshots: tabs rendering about 40px tall, buttons squeezing in a narrow window, and "B…"/"C…" tabs in a narrow panel.

What changed

Why the tabs were 40px. base.css still had a .tab rule from the old notch panel (padding: 6px 0, flex: 1, font-size: 10.5px). Nothing used it any more, but it matched the desktop tab class, so 28px + 12px of padding made every tab 40px and the row 57px. That rule is now deleted.

One set of tokens in :root (desktop.css):

  • --control-height: 30px, --control-radius: 8px and --control-glyph: 15px are used by .toolbar-button, the icon buttons, the title-bar buttons and the tabs.
  • --tab-height and --title-bar-button are gone.
  • Tab glyphs and the × are now 15px with the same 1.9 stroke as the toolbar icons, so they match.
  • The × sits in a 20px square hover target, 4px inside the pill. The pill stays 30px, and the hover/chosen reveal is unchanged.
  • The "+" after the panel's tabs is a normal toolbar icon button again.

No squeezing. .toolbar-icon-button is flex: none with a fixed 30px width. That covers ⋯, the panel toggle, the expand button and the "+", so only the tabs give way in a narrow window.

Insets and gaps.

  • --bar-inset: 12px is the left and right inset of the plan row and the side-panel row.
  • --bar-gap: 8px is the space between items in a row. Tabs inside a strip keep their 2px gap.
  • The fixed panel toggle now sits --bar-inset from the window edge. The clearance the rows leave for it is computed from the same tokens.
  • --desktop-bar-height is now 2 × --traffic-lights-middle + 1px (51px). That puts each row's middle exactly level with the traffic lights and the title-bar buttons, where 52px left it 0.5px off.
  • The left title-bar cluster keeps its 8px offset from the traffic lights (--title-bar-inset), because that spacing is set by the lights, not by a row.

Narrow tabs (tab-strip.tsx):

  • While the tabs fit, nothing truncates.
  • Once they don't fit, the strip goes compact: unchosen tabs show only their glyph in a 30px square, and their label moves into their tooltip.
  • The chosen tab keeps its glyph, label and ×. It is the only one that truncates, and it keeps at least a few characters.
  • So a tab never shows as one letter and an ellipsis.
  • The strip measures itself whenever it renders and whenever its row resizes (ResizeObserver). It removes the compact mark, measures, and puts the mark back within the same task, so the full-width layout is never painted.
  • The unread dot now sits on the glyph's corner, so it has room in both forms. In compact mode unchosen tabs have no ×, so the dot can't land next to one.
  • Tooltip now accepts a missing label and shows no pill for it. That way a tab whose hint comes and goes keeps its DOM node and its focus.

Measured (headless Chrome, getBoundingClientRect, real DesktopPlans, side panel and toggle, built stylesheet)

Window 1280, panel 420. Before: every tab 40px tall (cy 28), "+" 28px, toggle cy 25, rows 57px.

ControlBefore h / cyAfter h / cyAfter w
Plan tab40 / 2830 / 25171
Board / Code / Transcript tabs40 / 2830 / 2576.6 / 71.5 / 119.9
Copy plan30 / 2830 / 25105.4
⋯30 / 2830 / 2530
+28 / 2830 / 2530
Expand30 / 2830 / 2530
Panel toggle30 / 2530 / 2530
× hit area1820 (glyph 15)
Row height5751

At window 900 / panel 280 (the panel's least width), before: expand squeezed to 19.6px wide and "+" to 18.2px. After: both 30 × 30.

Insets after (window 1280, before in brackets):

  • Plan tab from the column's left edge: 12 (22)
  • ⋯ from the column's right edge: 12 (20)
  • Board from the panel's hairline: 12 (10)
  • Expand to toggle: 8 (8)
  • Toggle from the window edge: 12 (8)

Screenshots (headless Chrome renders of the real components on Linux; the app is dark-only, so there is no light variant)

Wide, before / after:
before wide
after wide

Panel just too narrow for its tabs whole. Before: "B…" and "C…". After: compact.
before mid
after mid

Narrow window, panel at its least width. Before: squeezed expand and "+", and Transcript cut to a stray mark. After: every button square, chosen tab "Tr…" with its ×.
before narrow
after narrow

Tests

  • New: a panel too narrow for its tabs whole names each unchosen tab in a tooltip, the chosen tab shows none, and widening the panel again removes them. This fails without the change.
  • jsdom has no ResizeObserver, so src/testing/resize-observer.ts provides a stand-in, installed through the desktop suite's setupFiles. It reports only when a test calls relayout().
  • The size, inset and gap changes are CSS only. Their evidence is the measurements and screenshots above, not a change-detector test.

Mac verification

./scripts/check.sh exits 0. CI could not verify the Mac UI, and ./scripts/verify.sh was not run. This was built on a Linux VM, so the evidence is the headless Chrome renders and measurements above. LUKE-159 is the manual Mac pass.

🤖 Generated with Claude Code


Open workspace in Conductor


Devin Review