cmux-next: a link opened to the right appears in its split, never in the source pane first

AU
3d ago
+1234-206

A web link opened to the right (a terminal's Cmd-click with Open Links and Files: Split right, or a chat link with links.pagesFollowPlacement on, or a link dropped on a pane's right or bottom edge) first showed as another tab of the source pane, then slid into a new right split. Reported 2026-10-20 on nightly 828d367273f, where a pull request link clicked in an agent chat did exactly that.

Cause. LinkOpener made the browser tab hidden in the source pane (new-frontend-browser-tab), waited for the store to list it, then moved it with move-tab-to-split. The tab was in the source pane's strip for that whole round trip.

Fix. The tab is now created directly in its new split.

  • Daemon: new-frontend-browser-tab takes split: {direction, pane_id?, split_id?} (frontend-browser-tab-split-v1). It reuses the pane.split path of conversation-tab-split-v1 (now a shared TabSplit / split_tab_pane_with_fields_as): one commit, one pane-added delta, and the source pane never lists the tab. split needs pane, after with split is a bad request, and a keyed request fingerprints the split. Keys stored without split keep their old fingerprint byte for byte. A right split beside Home's lone pinned dock is a 0.5-wide column, as the conversation split is. spec/commands.md, spec/sdk-schema.json and the regenerated bindings carry the field.
  • App: PaneBrowserTabSplit shows the provisional split with a stand-in browser tab in the click's frame, along with its even sizing. It then sends one create with the provisional pane's client-minted ids, and the daemon's pane replaces the stand-in by pane_id. A refusal takes the split and its sizing away. Closing the new pane before the reply closes the tab when it arrives.
  • Kept on the old make-then-move path: older daemons, files, .newColumn, left or top drops, popups the engine already made, and any pane splitRoom would not split in place (a chat dock, no room, or a strip column too narrow, which opens a new column instead).

Not changed: what a chat link does by default. Since https://github.com/manaflow-ai/cmux/pull/22822 it opens as a tab in the chat's own pane. Related open PR: https://github.com/manaflow-ai/cmux/pull/22949 (the lone-chat dock hand-off animation of an agent-opened tab), a different path (AgentBesidePlacement).

Test

  • Swift: LinkOpenSplitDirectTests checks that a Cmd-click to the right makes one create with split and no move, that the split shows at once, and that the source pane lists only its own tab; it also covers the fallbacks. FrontendBrowserTabSplitRequestTests covers the wire encoding, and a scripted daemon checks that an older daemon is never sent split.
  • Rust: server/frontend_browser_split_tests.rs covers the capability being advertised, one pane-added delta, the title kept, left and up, malformed requests refused, keyed replay and conflict, and the lone pinned dock.
  • Red commit d9f23d0b85c88 (tests only), fix 92ce549f6cf63, review follow-up 08c04fcf270ca.

Review

A correctness-first review subagent first returned FIX:

  1. The direct split skipped splitRoom, so it split narrow strip columns. Fixed in 08c04fcf270ca.
  2. It also split chat docks. Fixed in 08c04fcf270ca.
  3. The settle sequence is read from the current connection, not the replying one. Low; masked because ProvisionalSplit keeps the split until the daemon's layout places the pane. Left as is.
  4. Test polling. Fixed: the test now awaits the create.

Changelog

  • Fixed: A link opened to the right now appears in its split right away, instead of showing as a tab of the pane you clicked in first.

Observed / Not run

  • Observed: the Python checks check-sdk-schema.py, check-spec-inventory.py, bindings/codegen/generate.py --check, and prepush-gate.py with no new violations.
  • Pending, added in comments: the fleet Swift suites on 08c04fcf270ca and the red commit, and the hosted Rust --filter frontend_browser and --filter conversation_tab_split.
  • Not run: tagged-app dogfood of the click.

🤖 Generated with Claude Code


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


Summary by cubic

Fixes a link opened to the right (a terminal's Cmd-click, a chat link with links.pagesFollowPlacement on, or a link dropped on a pane's edge) showing as another tab of the source pane first, then sliding into its split. The tab is now created directly in the new split, so the source pane never lists it.

  • new-frontend-browser-tab takes a split field (frontend-browser-tab-split-v1), reusing the conversation tab split's pane.split path: one commit, one pane-added delta, and the source pane never lists the tab.
  • The app shows the provisional split with a stand-in tab in the click's frame and sends one create with client-minted ids; a refusal removes the split.
  • The direct split respects the same rules as the old path: a pane that splitRoom will not split in place (a chat dock, no room, or a strip column too narrow) falls back to opening a new column.
  • Older daemons, files, .newColumn, left or top drops, and engine-made popups keep the old make-then-move path.
  • Regenerated all SDK bindings and updated the command, schema, and api-compat specs; a Rust refactor shares the split code between conversation and browser tabs without changing the encoded request shape.