cmux NEXT: a pane's tab strip shows with one tab too; its empty space drags the window again
Leo (2026-11-03):
"oh shit you guys completely removed the top there is nothinig at the top until you make a tab... which... is interesting... but kinda weird... i also can't drag from this top area at all? idk if this is actually a bug tbh"
"i think you guys are currently hiding it completely when there's only the one tab... but i think that's just too alien of a look :("
Regressed by #33132 (cmux NEXT: a chat alone in its pane hides its lone tab, so the page takes the tab row). That removal was deliberate: Leo asked for it that morning, and design/tabs.md recorded it as a settled design, quoting his "sure you can use it to click and drag the window but like... that's not that big of a deal, is it?". His words above replace that design.
What changes. A pane's tab strip shows at every tab count, one included:
PaneContentView.layoutno longer hides the strip for a lone chat tab. The strip keeps the same height and frame with zero, one or several tabs, so nothing moves when a tab arrives.- The strip's empty space is the window's titlebar again, through the existing
TabStripView.titlebarPress/TitlebarDragPolicypath: drag moves the window, and a double-click runs the user's titlebar action. Tabs, chips and + keep their own presses, and tab drag-reorder is unchanged. No new mechanism. - Removes
PaneTabRowSharing.tabRowTakesLoneTabandTabStripModel.hasLoneTab, which had no other users. - The transcript's solo-tab inset (#33142,
soloTab: on && !row.tabsShown) only applied while the lone tab was hidden, so it no longer flips when a pane goes between one and two tabs. That flip moved the chat by a row height.
Nothing else is restyled. The chat's header tools still share the row beside the tabs.
Check. TabRowToolsStayClickableTests.theStripShowsAtOneHeightWhateverItsTabCount (replaces aLoneChatTabGivesTheChatTheRow, which pinned the hide). With a chat sharing the row and 0, 1 and 2 tabs, it checks:
- the strip is shown, with the same frame at every count;
- the chat is told the pane draws its tabs;
- a point in the strip's empty space resolves to
.empty(the titlebar hit that moves the window), and every tab and + rect resolves to.strip.
Follow-ups. With the hide gone, the hidden-tab paths in AgentPaneTabRow (tabsShown == false: soloTab, the Chromium holeFill, the zero-width tabs hole) and the page's [data-solo-tab] CSS are dead code. They can go in a removal PR.
Spec: leo-workspace design/tabs.md ("A chat alone in its pane draws no tab" is replaced; updated in a notes PR).
Risk: small, one component (pane chrome layout). It removes a hide condition and adds no new state or timers.
Agent session: agent session
🤖 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Shows a pane's tab strip at every tab count, one included, so a chat alone in its pane no longer leaves a bare, undraggable strip of page at the top. The strip's empty space is the window's titlebar again: it drags the window and double-click runs the user's titlebar action, while tabs, chips, +, and tab drag-reorder keep their own presses. The strip keeps its height and frame at every count, so nothing moves when a tab arrives.
Removes PaneTabRowSharing.tabRowTakesLoneTab and TabStripModel.hasLoneTab, which had no other users. The transcript's solo-tab inset only applied while a lone tab hid the strip, so it no longer flips when a pane goes between one and two tabs. The hidden-tab paths in AgentPaneTabRow and the page's solo-tab CSS are now dead code, left for a removal PR.