Show the docked chat and its fresh tab in the click's turn

Merged
TE
17h ago
+317-31

Leo: "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-named-fix

What

A new tab opened from a lone chat docks the chat into a new left column and leaves a fresh tab in its pane (ChatColumnPlacement.route .dockChat → TabMoves.toNewDockColumn, move-tab-to-column {dock, respawn}). Until now nothing changed on screen until the daemon answered. With this change, on a daemon that takes the column pane's id (tab-column-client-pane-v1, #33184), the change shows in the click's turn:

  • The chat moves into a stand-in dock pane under a client-minted pane id. The pane leads the screen's columns, as commit_tab_drag puts a left dock first.
  • The chat's old pane shows the fresh tab's stand-in (a terminal under the host id sent with the respawn, or the browser page). It shows only until the daemon's fresh tab arrives.
  • When the daemon's pane with that id arrives, it takes the stand-in's PaneModel over in place (confirmedPane). The intent settles once the daemon's layout places it, as a split does (cx-ry0y).
  • If the daemon refuses, everything is undone exactly.

This is one new overlay, ProvisionalDock, built beside ProvisionalSplit. It reuses ProvisionalPane, ProvisionalTab, provisionalPanes and awaitsRecords; there is no second mechanism. DockColumnCommand (CmuxNextDaemonService) holds the send, so CmuxNextApp shrinks (app_lines -5). Other cases keep the reply path: a right or band dock, a screen that already has a dock on that edge, a zoomed screen, an older daemon, or a move with no respawn.

Not touched: LinkOpener, PaneBrowserTabOpener, MainPaneLayout, and PaneController's strip-refresh order.

Tests

ProvisionalDockTests (CmuxNextDaemonTests):

  • The dock and the fresh tab's stand-in show in the intent's turn, and a refusal restores the pane exactly.
  • The daemon's pane-added takes the same model over while the dock places it, and the daemon's layout settles the intent with the fresh tab in place.

Fleet runs are listed below.

Performance

metricbeforeafterdeltaharness
click to docked chat shownwaits for the daemon's reply and eventsthe click's turn (store intent)pending benchsee comment

risk:normal

Changelog

  • Changed: Opening a new tab from a lone chat now docks the chat and shows the new tab right away.

Agent session: agent session

šŸ¤– Generated with Claude Code


Autofix with [code]smith
Open in [code]smith Ā· Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Shows a chat docked from a new tab in the click's turn instead of after the daemon's reply, and adds the protocol that makes it possible.

  • move-tab-to-column takes an optional pane_id (tab-column-client-pane-v1): the new column's pane carries the caller-minted id, refused with pane_id_exists when a live or deleted resource has it.
  • DockColumnCommand and ProvisionalDock move the chat into a stand-in left dock pane under that id and show a fresh terminal's stand-in in the old pane, until the daemon's layout places the real pane.
  • DebugInstantCreateBench and the bench script gain a dock_respawn command measuring the click to the docked chat's first visible frame.
  • Right and band docks, screens with an existing edge dock, zoomed screens, older daemons, fresh browser tabs, and moves without respawn keep the reply path; a refusal or daemon rejection undoes the overlay exactly.