cmux NEXT: a tab opened to the side starts its open on the right, never in the left pane
Leo-ask: ask-f6988c508c
Leo-ask: ask-35d63f0002
A link, file or agent tab opened to the side played its tab-open animation in the pane it came from, on the left, and only then came in on the right. Leo: "the animation for opening up a new tab starts off in the left pane, and it doesn't start off at the right", and from a lone main pane, "it plays the opening animation on the main window and then opens the tab on the right split". Now that tab first shows where it lands.
Cause. Since 8a84c978 (2026-10-19, "open links and paths in the pane right of their source, or a new split"), a tab opened to the side is made unselected in the source pane and then moved by one daemon move: into a new split or column (LinkOpener.move, AgentBesidePlacement), or kept there while the lone chat docks left (MainPaneLayout.dockChat). The source pane's strip listed the tab for that whole round trip, so with ui.animations.newTab on (the default) its grow played in the left strip (TabStripView+Sync adds and grows any new cell) before the pane on the right appeared.
Fix. OutboundTab keeps such a tab out of the source strip:
- from the open's start: the daemon's echo can list the tab before its answer names it, so until then every tab the pane did not already have is held back (at most 2 s);
- until the tab has left the pane (a split or column), or, for a lone chat's link, until the chat has left it. That pane is the one sliding in on the right (#22949's hand-off), so the tab's grow plays there;
- a failed move lists the tab where it is at once, as before; a pending selection waits until the strip lists its tab;
- the open's own stand-in (a file or page tab shows one until the daemon's tab replaces it) stays out too; the tab the pane shows or waits to select is never held back, so a person's Cmd-T in that window keeps its selection; an agent tab is held under its stand-in's id and the store's id from the moment
AgentTabStore.rekeymakes it; an answer slower than 2 s still hides its own tab, and every hold ends within 60 s even if its move hangs.
Wired into LinkOpener (split right, new column, lone chat dock, dropped links), MainPaneLayout.dockChat/openAgentTab, and AgentBesidePlacement (an agent's tab beside its chat). Routing is unchanged (#23886 handles that).
On the "speed-up". History has no ease-in curve on a new tab or pane to restore. The tab grow has been Chrome's ease-out tween since 601d6ff7 / 8606d5ba (2026-10-14). The only open motion that speeds up is a pane spring starting from rest, which a split or the lone chat's hand-off pane plays on the right (split spring, 56b4722d; shortened in #23092). It was hidden behind the left strip's grow, which this removes. Related history: 924f4af9 (2026-10-18) stopped arriving panes sliding in at their final size, which #22795 brought back for side panes; #22822 made page links tabs of their own pane (being undone in #23886).
Test
SideOpenStripTests (CmuxNextAppTests) runs the real LinkOpener, PaneController strip and daemon store:
- a Cmd-click split right: the left strip never lists the new tab, not after the daemon's echo, not after its answer, not after the move lands;
- a lone chat's link: the tab stays out while the chat is in the pane and shows once the chat has left;
- a failed move: the tab shows in its pane at once;
- a file or page opened to the side: its stand-in never shows in the left strip, before or after the open names the daemon's tab.
Fleet runs (cmux-ci run ... package-test-lane.sh suite Packages/macOS/CmuxNext SideOpenStripTests):
- red commit 119f0c70afe6 (test only): failed as expected; the left strip listed
[1, 2]after the echo and after the answer (step f591c56d57067be4d079e7f5); - 41e1ad1d927f: the first three tests passed (step c63a86f575a1ff4dff9df203); 796a313f83bf (the head): SideOpenStripTests with its fourth test, plus LinkOpenerTests, MainPaneLayoutTests, ChatDockLinkSplitMotionTests, BesideColumnRevealTests and LinkDragDropTests, queued on a saturated fleet (step ea9973311226b39897e1aed3); results go in a comment.
App compile-check (cmux-ci compile-check --app): passed at ea3670291214 (job 79a206581d9b9d617cb5dbb5) and 41e1ad1d927f (job 0e58578f3a34998af436ca9c); 796a313f83bf, the head (job 3e914c7968f1efe5d251165d).
Recording
Fleet DEV builds of the red commit (before: the base app) and the fix, on a capture mini, default settings. The link was opened through the terminal's Cmd-click path (TerminalSession.activateLink) and a lone agent chat's link path. debug.pane_chrome was sampled every 2 to 3 ms and frames recorded per display frame. Shown 4x slower, cropped to the tab strips; a red label marks frames where the left strip lists the new tab.
| Run | Left strip lists the new tab | Right pane first appears |
|---|---|---|
| before, terminal split right (2 runs) | from +98 to +183 ms and +75 to +157 ms | +172 ms, +146 ms |
| before, lone chat | from +55 to +103 ms | +122 ms |
| after, terminal split right (2 runs) | never | +111 ms, +132 ms (already holding its tab) |
| after, lone chat | never | +109 ms (the pane slides in from the right edge) |


No capture: cmux-ci capture keeps one frame a second, too slow for a flash under 100 ms; the recording above samples every display frame instead.
Review
An independent review subagent returned SHIP at ea3670291214 with should-fixes (a person's tab selected in the 2 s before the answer, an answer slower than 2 s, a dock that never shows, stale doc links); 41e1ad1d927f addressed them. A second review returned BLOCK at 41e1ad1d927f: sparing every stand-in brought the bug back for files and pages opened to the side, and the alias table gets pruned; 796a313f83bf fixes both, with a test. Not changed: the process viewer (AgentProcessTerminal) still begins its placement after its spawn returns, so its tab can show for a moment on the left when the daemon's echo beats the reply. Austin's open PR 23032 creates a Cmd-click's browser tab directly in its split on the daemon side; it doesn't cover the lone chat, agent or file paths, and the two don't conflict.
CI reds at the head are all in files and checks this PR doesn't touch: the Objective-C selector probe (CEFFileDragRelay), release-notary's highlights test (restored-terminals-come-back.md), Guard status (routes to that one), the web bundle build (React Compiler errors in webviews), the fast tier (CmuxNextSettingsTests' ResetAllRetiredKeysTests.swift does not compile) and the conventions check (AttentionHighlightLook.swift).
Agent session: cosmic-gray
🤖 Generated with Claude Code