Skip to content

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

Open
austinywang wants to merge 5 commits into
feat-cmux-nextfrom
fix/link-open-split-direct
Open

austinywang wants to merge 5 commits into
feat-cmux-nextfrom
fix/link-open-split-direct

Conversation

@austinywang

@austinywang austinywang commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

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 #22822 it opens as a tab in the chat's own pane. Related open PR: #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 d9f23d0 (tests only), fix 92ce549, review follow-up 08c04fc.

Review

A correctness-first review subagent first returned FIX:

  1. The direct split skipped splitRoom, so it split narrow strip columns. Fixed in 08c04fc.
  2. It also split chat docks. Fixed in 08c04fc.
  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 08c04fc 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.

Written for commit e2e936a. Summary will update on new commits.

View guided diff Turn on auto-fix

austinywang and others added 3 commits October 20, 2026 22:40
…er in the source pane (red)

User report 2026-10-20: a pull request link clicked in an agent chat opened as
another tab of the chat's pane, then slid into a right split. Fails: the
browser tab is made in the source pane and moved with move-tab-to-split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e daemon commit

new-frontend-browser-tab takes split {direction, pane_id?, split_id?}
(frontend-browser-tab-split-v1), sharing conversation-tab-split-v1's pane.split
path: the tab is created in the new pane, one pane-added delta, the source pane
never lists it. The app shows the split with a stand-in tab in the click's
frame and sends one create; an older daemon, files and new columns keep the
make-then-move path. A web link dropped on a right or bottom edge splits
directly too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ock rules (review)

Review: the direct split skipped AppServices.splitRoom, so a narrow strip
column split below its minimum and a chat dock split. A pane that does not
split in place keeps the move, which opens a new column or refuses. The test
awaits the create instead of polling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang austinywang added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@austinywang
austinywang temporarily deployed to cmux-tui-merge-tree October 21, 2026 06:00 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review e2e936a: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr23032 -f ref=fix/link-open-split-direct -f platform=macos -f backend_mode=local

Not verified: macOS jobs did not run: compile and app tests were skipped on e2e936a3ef; ci-status is green without them.

CI passes on e2e936a3ef (run 39566778334 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

red: cmux-next checks (god files, concurrency, crash safety, l10n); NEW: red check: Daemon capabilities export is fresh (+2 more) is not red on the base 5fe3814; do not merge until green; see lane-rules

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Bead cx-wrtuo created for this PR by the cmux coordinator.

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp7zj8f2_v/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmpukt2c5c3/summary-missing.json (live):/summary-missing.json (live): View Logs
test_live_typed_catalog_matches_every_registry (main.ContractRegistryTests.test_liv
e_typed_catalog_matches_every_registry)/
test_live_typed_catalog_matches_every_registry (main.ContractRegistryTests.test_liv
e_typed_catalog_matches_every_registry)
View Logs
test_live_typed_catalog_matches_every_registry (main.ContractRegistryTests.test_liv
e_typed_catalog_matches_every_registry)/
test_live_typed_catalog_matches_every_registry (main.ContractRegistryTests.test_liv
e_typed_catalog_matches_every_registry)
View Logs
the committed export failed: check-daemon-capabilities-fresh: plans/cmux-next/
daemon-capabilities.json is stale:
View Logs
the committed export failed: check-daemon-capabilities-fresh: plans/cmux-next/
daemon-capabilities.json is stale:
View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator

cmux-next guards / defaults-snapshot is red on 24 of 24 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@austinywang

Copy link
Copy Markdown
Contributor Author

Review (correctness-first subagent) and resolution, head 08c04fc

The first pass, on 92ce549, returned FIX.

  1. Medium: the direct split skipped AppServices.splitRoom, so a narrow strip column split below its minimum. Fixed in 08c04fc: LinkOpener.webSplit takes the direct path only when splitRoom says .split; otherwise it falls back to the move, which opens a new column or refuses.
  2. Medium-low: a web link dropped on a chat dock's right or bottom edge split the dock. Fixed by the same check, since splitRoom refuses chat docks first.
  3. Low: PaneBrowserTabSplit reads the settle sequence from the current connection, not the one that replied. Left as is: ProvisionalSplit keeps the split until the daemon's layout places the pane by its pane_id, so a reconnect cannot drop it early. Follow-up if wanted: thread the replying connection's sequence through BrowserTabService.create.
  4. Low: the test polled with Task.sleep. Fixed: it now awaits the create through a stream, under .timeLimit(.minutes(1)).

Swift, fleet (cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg suite --arg Packages/macOS/CmuxNext --arg <filters>, Xcode 26.6):

  • Green on 08c04fc, step 9107a4f0bbfeeff2e6e8c950 on aws-m4pro-6. Passed: LinkOpenSplitDirectTests, FrontendBrowserTabSplitRequestTests, LinkOpenerTests, BrowserEnabledToggleTests, FrontendBrowserTabLateReplyTests, NewTabSplitRefusalTests.
  • Red on d9f23d0 (tests only), step a6abe8bfca6721c1e7465184: the build fails because the tests use the new API.
  • An earlier green attempt, ee77b3834a9187c94cf74769, lost its worker's lease (an infrastructure failure, not a test result).

Rust: ./scripts/verify-cmux-tui-hosted.sh --filter frontend_browser is running on 92ce549. The later commit touches only Swift.

…d_browser_tab; api-compat surface lists split

CI: E0592 duplicate keyed_frontend_browser_tab in frontend_browser_keys.rs (the
new private helper reused open_url's name), and api-compat-check found
spec/api-compat.surface.json stale for new-frontend-browser-tab's split.
Also drops the unnecessary crate::mux:: qualifications rustc warned about.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang
austinywang temporarily deployed to cmux-tui-merge-tree October 21, 2026 07:53 — with GitHub Actions Inactive
…en-split-direct

# Conflicts:
#	cmux-tui/spec/commands.md
@austinywang
austinywang temporarily deployed to cmux-tui-merge-tree October 21, 2026 07:59 — with GitHub Actions Inactive
@austinywang

Copy link
Copy Markdown
Contributor Author

Head e2e936a, base feat-cmux-next at 5fe3814 (merged in).

Changes since the review pass, checked by the primary agent:

  • 4e7f93c: fixes the CI compile error on 92ce549/08c04fcf270ca. It was E0592: the new private helper reused the name keyed_frontend_browser_tab, which resource_router/open_url.rs already uses, so the helper is now keyed_frontend_browser_tab_with. The commit also adds split to spec/api-compat.surface.json, using the same four entries as new-conversation-tab, and drops the unneeded crate::mux:: qualifications.
  • e2e936a: merges origin/feat-cmux-next. One conflict, in cmux-tui/spec/commands.md: both sides added a sentence to the capabilities paragraph, and both are kept. After the merge, bindings/codegen/generate.py --check, check-sdk-schema.py and check-spec-inventory.py pass.

CI on this head:

  • api-compat, cmux-next fast tier (cargo) and ci-status pass.
  • Base red, not this PR: protocol contract and inventory, both at step "Test public resource boundary checker". test_check_resource_api_boundary.py fails the same way on a clean checkout of the base tip 5fe3814 (boundary.sdk-descriptor: catalog_sha256 must match canonical resource-operations-v2.json). This PR leaves bindings/*/.cmux-resource-api.json and resource-operations-v2.json unchanged.
  • The Swift CI jobs are still pending. The focused fleet Swift suites passed on 08c04fc (step 9107a4f0bbfeeff2e6e8c950), and the later commits change no Swift.

Verdict: no red of its own; reviewed. Labeling ready-to-land.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Review (Codex gpt-6-astra xhigh): BLOCK at e2e936a. @austinywang

  • LinkOpener.swift:125-130 — Direct splits ignore background: the daemon always makes the new pane active, while the old move path used focus: !background. Background links configured to open in a right split now steal focus.

@lawrencecchen lawrencecchen removed the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Oct 31, 2026
@lawrencecchen

Copy link
Copy Markdown
Contributor

Removed ready-to-land: the newest review on this PR is a BLOCK (Review (Codex gpt-6-astra xhigh): BLOCK at e2e936a, 2026-10-26), and the PR is DIRTY (conflicts with feat-cmux-next). A BLOCK beats an older SHIP, so the label made this PR count as ready in the merge-wait numbers for 10 days without being mergeable. @austinywang: to land it, address the BLOCK findings, merge feat-cmux-next, and get a new independent review receipt at the new head.

teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…it in the click's frame

LinkOpener's split right and split below no longer make the tab in the
source pane and move it once the store lists it (two daemon round
trips, and the opening played on the source pane first). A web link's
split shows now with a stand-in tab (PaneBrowserTabSplit) and one
new-frontend-browser-tab {pane, split} makes the tab in the daemon's
new pane (frontend-browser-tab-split-v1). A folder's terminal opens as
Cmd-D's optimistic split. An older daemon keeps make-and-move.

PaneBrowserTabSplit and the tests come from austinywang's #23032.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…it in the click's frame

LinkOpener's split right and split below no longer make the tab in the
source pane and move it once the store lists it (two daemon round
trips, and the opening played on the source pane first). A web link's
split shows now with a stand-in tab (PaneBrowserTabSplit) and one
new-frontend-browser-tab {pane, split} makes the tab in the daemon's
new pane (frontend-browser-tab-split-v1). A folder's terminal opens as
Cmd-D's optimistic split. An older daemon keeps make-and-move.

PaneBrowserTabSplit and the tests come from austinywang's #23032.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…ock rules

As in austinywang's #23032 (08c04fc): a chat dock, a pane with no
room or a strip column too narrow keeps the move, which refuses or
opens a new column as every split does.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
@teamleaderleo

Copy link
Copy Markdown
Collaborator

Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over.


For awareness: this PR no longer applies on today's feat-cmux-next, so its work is carried, with your authorship, on two new PRs.

Nothing was pushed to this branch. It stays yours to close.

teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…it in the click's frame

LinkOpener's split right and split below no longer make the tab in the
source pane and move it once the store lists it (two daemon round
trips, and the opening played on the source pane first). A web link's
split shows now with a stand-in tab (PaneBrowserTabSplit) and one
new-frontend-browser-tab {pane, split} makes the tab in the daemon's
new pane (frontend-browser-tab-split-v1). A folder's terminal opens as
Cmd-D's optimistic split. An older daemon keeps make-and-move.

PaneBrowserTabSplit and the tests come from austinywang's #23032.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…ock rules

As in austinywang's #23032 (08c04fc): a chat dock, a pane with no
room or a strip column too narrow keeps the move, which refuses or
opens a new column as every split does.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…it in the click's frame

LinkOpener's split right and split below no longer make the tab in the
source pane and move it once the store lists it (two daemon round
trips, and the opening played on the source pane first). A web link's
split shows now with a stand-in tab (PaneBrowserTabSplit) and one
new-frontend-browser-tab {pane, split} makes the tab in the daemon's
new pane (frontend-browser-tab-split-v1). A folder's terminal opens as
Cmd-D's optimistic split. An older daemon keeps make-and-move.

PaneBrowserTabSplit and the tests come from austinywang's #23032.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…ock rules

As in austinywang's #23032 (08c04fc): a chat dock, a pane with no
room or a strip column too narrow keeps the move, which refuses or
opens a new column as every split does.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6
teamleaderleo added a commit that referenced this pull request Nov 3, 2026
…'s frame, never in the source pane first (#32900)

* fix(cmux-next): a link or folder opened to the right shows in its split in the click's frame

LinkOpener's split right and split below no longer make the tab in the
source pane and move it once the store lists it (two daemon round
trips, and the opening played on the source pane first). A web link's
split shows now with a stand-in tab (PaneBrowserTabSplit) and one
new-frontend-browser-tab {pane, split} makes the tab in the daemon's
new pane (frontend-browser-tab-split-v1). A folder's terminal opens as
Cmd-D's optimistic split. An older daemon keeps make-and-move.

PaneBrowserTabSplit and the tests come from austinywang's #23032.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6

* bench(next): instant-new-surfaces names link_right

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6

* fix(cmux-next): a link's split open goes before the in-pane stand-in (review)

A local foreground link took openStandIn first: the tab showed in the
source pane and its split request never finished, so the source's open
turns queued forever. The split branch now runs first; the test checks
that the next open beside the pane still runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6

* fix(cmux-next): a link's direct split keeps the split-room and chat-dock rules

As in austinywang's #23032 (08c04fc): a chat dock, a pane with no
room or a strip column too narrow keeps the move, which refuses or
opens a new column as every split does.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6

* fix(cmux-next): a link's direct split ends its turn once the daemon's pane is listed; incognito keeps the move (review)

router-review (gpt-6.1-sol) BLOCK at 6027476: the reply can come before the
pane's events, so the next queued link aimed at the provisional pane; and an
incognito pane could mount about:blank before the reply registered its URL.
BrowserTabSplitCommand now waits (30 s bound) for the store to list the made
tab, as LinkOpener's move does; incognito panes keep make-and-move. The bench's
quick arm skips commands with no key (link_right).

Regression tests: LinkOpenSplitDirectTests.theTurnWaitsForTheDaemonsPaneNotJustTheReply,
anIncognitoPaneKeepsTheMove.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-ed9226b71907d8aaa0b6

---------

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>

This branch was previously deployed

2 inactive deployments
ci-routing-agent — e2e936a3 Deployed Oct 21, 2026 by github-actions[bot]
cmux-tui-merge-tree — e2e936a3 Deployed Oct 21, 2026 by austinywang
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants