cmux-next: a clicked link's tab takes the focus; closing it gives the focus back

Merged
TE
15d ago
+504-59

Leo, 2026-10-21: a link clicked in a chat opened its github.com tab in a split but left the focus in the chat. His Cmd-W then closed the chat instead of the tab.

Behavior, as in Chrome:

  • A plain click focuses the opened tab. A Cmd-click opens it in the background.
  • Cmd-W closes the focused tab.
  • Closing a tab opened by a click gives the focus back to where the link was clicked. In the link's own pane, the close selects the tab the link was clicked in again, in every window that shows the pane.
  • Everything a person clicks counts as theirs: a chat link, a file link, the hover card's Open, the chat header's Changes, Preview and Files (besideTakesFocus). Only an agent's own open stays in the background.
  • A click while the agent is mid-turn is the person's: an agent's open still in flight lands unselected and does not take the focus.

The focused-pane cue itself (ring, dim) is window-focus-opacity's own PR. This PR does not touch ScreenContentView+Visibility.swift.

The race the click tests found

The window refuses an agent page's own makeFirstResponder (WebKit's, for the page's own focus()) for a page outside the focused pane (AgentPaneView.refusesSelfFocus). It read the focus before the focus coordinator's queued topology. So when a link's tab took the focus through a still-queued pane change, the chat page's own focus ask looked like the focused pane's. The queued change then landed the focus on the link's tab, and the page's report, reduced right after it, took it back: the tab showed selected with the keyboard still in the chat. The window now judges that ask against currentState() (pending topologies reduced), only for a page's own ask outside an input event and the focus model's moves.

Tests (red first)

  • ClickedLinkFocusTests (same-tree daemon): a link in its own pane, in the pane on the right, the header's Preview, a file link, a click while an agent's open is in flight, an agent's open from a lone chat, and aPagesOwnFocusAskLosesToAQueuedLinkTabFocus (the race above, without timing).
  • BrowserTabOpenersCloseFocusTests: the closed link tab's successor is its opener, in every window showing the pane.
  • LinkRouterCmdClickTests.aCmdClickInAPageViewOpensInTheBackground.

Observed / Not run

  • Red: cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg=suite --arg Packages/macOS/CmuxNext --arg ClickedLinkFocusTests,... --ref <sha>: aLinksTabInItsPaneTakesTheFocusAndItsCloseSelectsTheSourceAgain failed at line 46 on 3159e23d and on 281579ea (before the close fix). aPagesOwnFocusAskLosesToAQueuedLinkTabFocus failed 3 of 3 on aeb526e4 (before the guard fix); before it, the click tests failed in 4 of 8 runs.
  • Green: on a19911d5, ClickedLinkFocusTests passed 8 of 8 runs, with AgentPaneSelfFocusTests, NewTabHoldFocusTests and BrowserTabOpenersCloseFocusTests. After the base merge (a1eb8057) and the test changes for it, 8 of 8 runs pass on ffe26d85 plus the one-line RemoteChildSessionsTests fix the base needs to compile its tests (open as #23362/#23363/#23369), with ChatDockLinkSplitMotionTests, AgentPaneSelfFocusTests and BrowserTabOpenersCloseFocusTests.
  • Observed: the fleet app compile and link (cmux-ci compile-check --app) passes on a19911d5 and on a1eb8057. prepush-gate.py: no new violations.
  • Not run: CI's own Release compile and Debug scheme compile were still queued (fleet backlog). CI's cmux-next swift test on 3fecec9c failed only on base reds (settings schema exports, AgentPaneImageActionsTests.swift:72 and its crash, the same on #22795).

No capture: the change is where keyboard focus goes after a click, not how anything looks; the click tests above drive it against a real daemon and window.

Changelog

  • Fixed: A link clicked in an agent chat focuses its tab, Cmd-click opens it in the background, and closing the tab returns focus to the chat.

Agent session: huge-blue


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


Summary by cubic

Fixes link focus behavior in cmux-next so a clicked link takes focus in its new tab, Cmd-click opens it in the background, and closing it returns focus to where the link was clicked, matching Chrome.

  • A plain click focuses the opened tab; Cmd-click opens it in the background; Shift-Command still picks the other browser.
  • Closing a tab opened by a click returns focus to the source pane; in the source's own pane the close selects the source tab again rather than the adjacent one. Each pane controller records its own apply's tabs, so even a pane's first close — in any window showing it — finds the clicked tab's opener.
  • The page a link was clicked in can no longer steal the keyboard back once the tab lands focused: the window judges the page's own focus ask against the queued pane changes, so a link tab whose focus was still queued no longer loses to the page.
  • A link hover card's Open now opens any http(s) link in cmux via browser.openIn, where browser.open only worked for a local preview page.
  • The chat header's Changes, Preview, and Files open where an agent's beside run would, but as a person's click (besideTakesFocus) the new tab takes focus and its close returns it to the chat; an agent's beside open still keeps focus in the chat.
  • A person's click mid-turn wins over an agent's open in flight: the agent's tab still lands unselected, and the app's own refocus of a docked chat is now programmatic so a second open in flight doesn't read it as the person acting.
  • Adds ClickedLinkFocusTests covering same-pane, split-pane, header, file-link, mid-turn, and page-focus-ask scenarios, plus LinkRouterCmdClickTests for background opening.