cmux next files: chat file links open at their line, bare file names open, chat tools stay beside a chat with a new tab
Summary
first piece of file viewer parity with the codex app (austin, 2026-10-21). three things a person hits in a chat: file links, file names, and where the chat's tools open.
file links open at their line. src/app.ts:42 in a reply opened the file at the top: the chip parser threw the line away (stripLine in chips/paths.ts). now the line goes chip -> open request -> LinkOpener -> file page -> editor:
path:42,:42:7,#L42,#L42C7,#L42-L50,:42-50, in prose, code spans, markdown links and file urls- the editor centers the line and highlights the line or range briefly (no fade with reduced motion); an open file reuses its tab; past the end clamps; markdown, images, folders ignore the line
cmux file open <path> --line 42[:7], same on mcp and the palette- line and column are bounded positive integers, checked on the page and again in swift; the deny list, root checks and gesture rule run first, unchanged
bare file names open the file. an agent writes next-sync.py for scripts/next-sync.py. a name with no folder is now a chip only once the host finds a file of that name in the chat's folders (one FolderNameIndex walk per folder, off the main thread, shared app-wide, at most 4 folders for 30 s, skipping hidden folders, node_modules and build output, never the home folder or above). best match = fewest folders below its root. the found file goes through the deny list and root checks like a written path; the page never learns a path. names are asked in their own link.inspect batches so a walk never holds up other chips. dotted code that isn't a file (console.log, user.name) stays code; versions and host:port (4.5:1, example.com:8080) stay text.
a chip that can't open says why (not found, may hold secrets, outside the chat's folders, couldn't open), as the app's refusal notice, 21 languages, only for a real click.
chat tools beside a chat with a new tab page. with [chat] [New Tab] in one pane the header's Files (and Changes, Preview, browser) opened in a new column and the strip scrolled away from the chat. a chat plus only new tab pages now counts as alone (ChatColumnPlacement.loneChat): the chat docks left, its new tab pages follow it into the chat column (chat stays selected; focus only if the person didn't click elsewhere meanwhile; a page they picked stays), the tool opens beside. the chat column accepts a new tab page dropped into it; a terminal or browser picked on a new tab page in the column opens in the strip.
also fixed on the way:
- editor sticky scroll header used the transparent editor background, so scrolled code read through it; it gets
--cmux-editor-sticky-background(near solid + the shared surface blur; a solid theme background or a themed sticky color wins) - explicit links keep their line for names without an extension or with a dot folder (
src/Makefile#L42,.github/workflows/ci.yml:5);C:\repo\app.ts:42,app.ts:1.5,#section:42are not lines; a link label that is a path no longer makes a chip inside a chip FolderNameIndex.buildgets an optionalskippingFolders(empty by default, no change for the Files pane)
not changed: where a page's links open. after #22822 a chat's links open as a tab in the chat's pane unless Debug > "Links from pages open like Cmd-click" is on; this pr leaves that alone.
| Before (file links in a chat reply) | After (file links in a chat reply) |
|---|---|
| Before (header Files from [chat] [New Tab]) | After (header Files from [chat] [New Tab]) |
|---|---|
Size: one feature (file line jumps, bare file names, chat tools beside a new tab page) whose Swift pieces, webview chips and tests depend on each other and were reviewed together over five rounds; it predates the PR size check, and splitting it now would re-run every review and dogfood round for no behaviour change.
Testing
each fix landed test first (red on the test commit, green on the fix): c0942f443eb, 113648e2e99, c33d95df5ed, 337acd2cced, 99ae77243b4, 01355b1a2b7, 52df604a80f, aaa2e01a49a.
-
cd webviews && bun test test/reply-path-line.test.tsx test/editor-page-line.test.ts test/editor-page.test.ts test/editor-sticky-background.test.ts: 116 pass, 0 fail on894d7876d15. red checks: the new link/name/reset/outside tests fail against the previous sources (checked by swapping the files back). -
scripts/web-lint.sh,bun run typecheck,scripts/cmux-next/prepush-gate.py(no new violations),verify-local.py --only swift-syntax. -
local tagged debug build (xcode 27.1) of each round, build only under its own tag;
BUILD SUCCEEDEDon954e6a9e8b0(the head before the last base merge). the dogfood app ran7efbdea94b3. -
reviews: five rounds of a claude review subagent (correctness first, REVIEW.md) plus two codex reviews; every finding fixed in the commits above. findings and resolutions in a comment.
-
base merge
fe539951537+68af67abe7c: conflicts inAgentReplySites.swift(kept both closures),LinkChips.tsx(base's newLinkHoverCardimports + this pr's), agent paneLocalizable.xcstrings(key union, checked 3-way). one review subagent on the merge and the fixes: no p0/p1. -
ci fixes from the last run (
a00f2e44254):$5/$10/$20and$HOME/notes.mdmade path chips (agent-pane-mathred), now plain text, with a test;FilePageRestoredLineTestswas missingimport CmuxNextBridge(swift test, daemon tests, generated files red); both highlights get a try-it action (release-notary red). -
after the merge:
prepush-gate.pyno new violations,bun run typecheckpass, web tests 105 pass 0 fail, tagged debug build (xcode 26.6)exit 0ona00f2e44254. -
base merge
798797446977f(3,096 base commits): conflicts in the reply links and request (base's image-aware resolve andimage.load {auto}kept with this pr'slineand bare-name lookup),ChatDockRules(base'stakesOnlyChatsplus this pr's New Tab pages),ChatColumnPlacement(DaemonCapabilityrename),AgentBesidePlacement(base'sOutboundTabmovedflag with this pr's New Tab page follow),NewTabPage+Openimports,linkStorepolicy,Markdown.tsxprose (base's time tokens +linkLabel). base's code-chiplineLabelis dropped: this pr'sPathChipalready shows the:linesuffix. -
review findings from the previous merge, fixed: a line reveal now scrolls to its column (
revealPositionInCenter) and puts the page's focus in the editor, as base's Find reveal did (a64bf0f8d909f, test first inc20c3fba3d465); the agent's tool beside a lone chat is selected before the New Tab pages follow, so a click during the open no longer leaves it unselected (17358ee77ee5b). -
red/green:
bun test webviews/src/pages/editor/lineReveal.test.ts(run outsidewebviews/so the dom preload is skipped): 0 pass 2 fail onc20c3fba3d465(revealLineInCentercalled, nofocus), 2 pass 0 fail ona64bf0f8d909f. -
merge follow-ups found by review and by the fleet run: a base test's
onOpenFileclosure arity (b8d6e959), a remote chat's code chip keeps its:line(b8d6e959), base's newFilePositioncallers inTerminalHostDelegateandDiffPageServiceported toFileLineLocation(5dfef3a8), the PR's tests read tabs throughdebugTabsafter base removedopenKeys(47b98c35), a scratch editor page root in the two page tests (8f403136), and the restored-tab test's route is the resolved path (7bcf633a). -
fleet Swift (
cmux-ci run … package-test-lane.sh suite Packages/macOS/CmuxNext …, aws-m4pro): on 47b98c35, 13 of 15 suites passed (FileLineLocation, FileOpenLine, ChatColumnPlacement, NewTabPagePickInPlace, AgentPaneReplyNames, AgentReplyNames, EditorRevealConfig, FindSection, FileExplorer, AgentPaneMediaNoAutoAction, ClickedLinkFocus, ChatDockLinkSplitMotion, HeaderDiffPlacement); FilePageRestoredLineTests and LinkOpenerTests then passed on 7bcf633a. -
two more base merges: f3c8aec7 keeps base's cx-k21v (a chat on another machine never resolves or opens this Mac's paths; this PR's bare-name lookup runs only for this Mac's chats) and fixes base's new
AgentPaneRemoteChatPathTestsclosure arity (fa0af8db); c7bbd6fd keeps base's slim inline links (#25212: no lock icon, outside-project tooltip) with this PR's line/suffix chips.test_file_open_line_localization.pyruns in ci-guards and is registered (f439c4e3). Fleet Swift on fa0af8db: LinkOpener, FilePageRestoredLine, AgentPaneReplyNames, AgentPaneMediaNoAutoAction, AgentPaneRemoteChatPath, AgentReplyNames all passed.oxfmt 0.73.0 --check(printWidth 120) passes on the edited web files.
CI reds that are not this pr
-
cmux Actions / check:fresh-chief-eval.ymluses an unpinnedactions/checkout@v4(a base workflow this PR does not touch). -
nightly-guards / Guard status,release-notary(update-with-running-agents.md),cmux-next checks: red on other fresh feat-cmux-next PRs too. -
runner refusals (
glaeda-cmux-runner-hook: refused: ... GiB free): infra, re-run by glaeda. -
defaults-snapshot: base red (red on 21 of 22 fresh feat-cmux-next heads, per the base-red watch comment). this pr changes no default. -
web / react-apps-check: browser tests for queue rows, the trust ask, soft edges, review threads, composer and stream jitter. this pr does not touch those files; many of the same tests are red on other prs (#23048).
Observed / Not run
-
observed (this round): the red/green bun run of
lineReveal.test.ts; the fleet Swift suites above; app sources compile on the fleet at 5dfef3a8 and later. -
not run (this round): the full Swift suite and web suites (CI runs them); the remote-chip
:linefix has no test of its own (it needs the webviews DOM preload, which needs an install on this Mac); no new runtime capture, see below. -
observed: the web tests above red then green; the builds; in the tagged app: links open at their line (cli
--line 30->Ln 30, Col 1,--line abc/0refused), the sticky header before/after,[chat] [New Tab]+ Files keeps the chat and its page left and files right (recording), chat file links (recording). -
not run locally (repo policy, fleet/ci only): swift tests
FileLineLocationTests,FileOpenLineTests,FilePageRestoredLineTests,LinkOpenerTests,ChatColumnPlacementTests,NewTabPagePickInPlaceTests,AgentPaneReplyNamesTests,AgentReplyNamesTests;check-action-surfaces.sh. ci runs them here. -
local build note:
AppOnboardingServices+ClassicMigration.swift:200is an ambiguousTaskinit on xcode 27.1 only; patched locally to build, not in this pr. ci uses xcode 26.
Changelog
Fixed: File links in agent replies open the file at the linked line, with a brief highlight, and reuse the tab if the file is already open
Added: A file name in an agent reply opens that file when it is in a subfolder of the chat's folder
Fixed: A file link in a chat that cannot open says why
Fixed: Files, Changes, Preview and browser tools opened from a chat header stay beside the chat when its tab strip also has a New Tab page, and the New Tab page stays with the chat
Fixed: The code editor's sticky scroll header no longer shows code through it
Proof
No capture: a fleet capture of 17358ee7 (job 246a1e28) failed before compiling, because the fleet's web-bundle builder has bun 1.4.2 and the base needs 1.4.4 since https://github.com/manaflow-ai/cmux/pull/25035; every fleet build of the current base fails the same way until the builder is updated. The recordings below are from the earlier head.
recordings above (lucas): chat file links before/after; header Files from [chat] [New Tab] before/after.
Checklist
- Behavior changes have added or updated tests
- localization: new strings in
FileOpenLine.xcstrings, the control catalog and the agent pane catalog, all 21 languages; localization audit has no open rows
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
File links in agent replies open the file at the referenced line, bare file names now resolve to real files, and chat-header tools stay beside the chat when its tab strip also contains a New Tab page.
File links and bare names
path:42,path:42:7,path#L42,path#L42-L50, andpath:42-50carry the line through the chip, open request, and file page in prose, code spans, markdown links, and file URLs.- The editor centers the line and column, takes focus, and briefly highlights the line or range; the highlight stays instead of fading under reduced motion.
- An already-open file reuses its tab and jumps; a line past the end clamps to the last line, including in large-file plain text mode; a recovered draft reveals the line again after it replaces the text.
- Find results, the group config editor, terminal file:line links, and the diff page share the same
FileLineLocationpath, replacing the oldFilePositionand therevealcommand. cmux file open, MCP, and the palette accept--line 42,42:7, or42-50, validated in Swift with bounds 1 to 10,000,000.- Bare file names (
next-sync.py) become chips only for files a host finds in the chat's folders via a shared, bounded, off-main-thread name index; found files still pass the deny list and roots, and searches never walk home or above. - Markdown link labels that are also paths no longer create nested chips;
C:\repo\app.ts:42, prices, and shell variables ($5/$10/$20,$HOME/notes.md) in prose no longer start a chip, and restored tabs keep their jump. - A chip that can't open says why after a real click; the sticky scroll header gets a near-solid background with the shared surface blur, or an opaque theme background when the theme sets one.
Chat placement with a New Tab page
- A chat counts as alone when every other tab is a New Tab page, so chat-header Files, Changes, Preview, and browser tools open beside the chat instead of scrolling it out of view.
- New Tab pages follow the chat into its chat column, which now accepts a dropped or moved new-tab page while still refusing tools, groups, and splits; terminal and browser picks from those pages open in the strip.
- The tool tab is selected even when the person acts elsewhere during the open, and a chat on another machine never resolves or opens this Mac's paths.