perf(next): closing the last tab of a split pane removes the pane at once, not after the program read
Leo, 2026-11-03: "it's so slow to open and close idk if there's some sort of threading issue but it's nigh instant for claude and codex so we nede to be at least as fast". Leo, 2026-10-25: "closing the right pane seems be like... waiting for the right tab's closing animation to play before closing".
leo-named-fix
What was slow
Cmd-W (or a tab's close button) on the last terminal of a split's pane (the right pane, the usual case) kept the pane on screen until the daemon's process read answered (TabCloseGuard: does a program still run in it? up to 1 s per terminal). #31875 already hid the closing tab in the key's turn, but only when its pane kept another tab; a pane the close emptied waited for the read and only then left (prepareClose -> showPaneClose).
Fix
TabCloseGuard.close: a pane the close empties now hides with its tabs in the key's turn too, when its layout can show it leaving (LayoutModel.canShowPaneClose: a split's pane or a column's only pane; never the screen's last pane, which would show empty) and it holds no browser page (asking a page starts its close; the browser path is #32876). The terminal stays alive: the read still decides.- No program: the hide lifts and the close's own intent shows the pane close again in the same turn (
HiddenTabClose.liftruns the newliftedhooks:LayoutModel.withdrawPaneClosedrops the hold unshown), so nothing is drawn in between. - A program runs: the pane comes back with its tab, its selection and its focus before the question; Cancel keeps it.
- No program: the hide lifts and the close's own intent shows the pane close again in the same turn (
TabStripModel.cmdWTarget(_:closing:skipped:): the pure Cmd-W target pick moved from the App into CmuxNextTabs (no behavior change), which pays for the App lines this adds.
Design spec: plans/cmux-next/close-animation/DESIGN.md (the pane leaves in the key's frame); this extends it to a close the daemon's read still decides.
Risk
risk:normal. Close-path state: the hide/restore of an emptied pane while the read decides. A rolled-back close shows the pane again through the same cancel path a refused daemon close uses. Not covered: the screen's last pane (nothing to show in its place) and the warnings-off path where only the undo-toast decision reads (CloseUndoToasts.decideFirst, follow-up). A pane in a column with rows is #32907.
Performance
| metric | before | after | delta | harness or run link |
|---|---|---|---|---|
| Cmd-W on a split's last idle terminal -> pane gone from the shown layout, p50 (15 rounds) | 8.52 ms | 1.96 ms | -6.56 ms (-77%) | CmdWEmptiedSplitPaneLeavesInItsTurnTests.measureCmdWToEmptiedPaneGone; before at 83ba468 (fleet step 2c8b77668d07787212926acb), after at cea57c4 |
| same, p95 | 25.31 ms | 2.23 ms | -23.08 ms (-91%) | same |
| same, max | 39.13 ms | 3.14 ms | -35.99 ms | same |
The before is an idle same-tree daemon on an idle Mac; under load the read waits up to its 1 s bound, while the after does not depend on the read at all. The two runs landed on different fleet Macs (the controller picked them).
Tests
CmdWEmptiedSplitPaneLeavesInItsTurnTests (same-tree daemon, held read): the emptied pane leaves in the key's turn and its close lands; with sleep 600 running, the pane, tab and focus come back before the question and Cancel keeps it.
cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref SHA --arg=suite --arg=Packages/macOS/CmuxNext --arg=CmdWEmptiedSplitPaneLeavesInItsTurnTests,CmdWIdleTerminalClosesInItsTurnTests,CmdWTargetTests --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app
- Red at the test-only commit 83ba468: both behavior tests fail (the pane stays shown and focused during the read).
- Green at cea57c4: all 3 tests,
CmdWIdleTerminalClosesInItsTurnTests(11, the #31875 suite),CmdWTargetTests. PaneCloseControllerTests,PaneCloseApplyTests: every Swift Testing test passed at 191b5ab and cea57c4. Their step reports failure because the XCTest leg (no XCTest tests selected) aborts with signal 6 after about 650 s on some workers. The same abort shows up at the test-only base commit 83ba468, and not at all on the worker that ran the cea57c4 suites above.
Observed / Not run
Ran: compile-check --package CmuxNext at 191b5ab (passed, 213 s); the suites above. Not run: a STRESS burst-bench row, a DEV build by hand.
Changelog
Fixed: Closing the last tab of a split pane (the right pane) removes the pane at once instead of after a short wait.
No capture: timing only.
Agent session: agent session
š¤ Generated with Claude Code