cmux next: chat ui parity pass (thoughts, header, thread details, side column, files)

Closed
LU
16d ago
+24398-897

update (2026-10-21): #22057 (files beside the chat) is merged into this branch, so this pr now carries the chat viewer too, with the latest feat-cmux-next. fixes from dogfooding the combined build:

  • render cards loaded nothing (blank, then "could not load the page"): newer webkit reports a new iframe's request as an empty url, and the render frame was allowed only into a nil or about:blank frame. AgentPaneNavigation now treats an empty url as blank. found with a diagnostic build: the frame's navigation reached the policy with target= empty and never reached the scheme handler.
  • the composer, pr dock and asks size from the pane like the transcript, so they stay centered under the chat with files open.
  • a new chat's first files open made the whole chat jump: files and the file viewer are lazy chunks with a null fallback (no column for a few frames while thread details had already closed), the old changes layout (chat at 44%) applied to the bare reviewing class, and closing thread details left a popover flag that revived it. the column now draws a shell while its chunk loads, the review layout waits for a panel, and closing clears both flags.
  • thread details has an x like files; files, a file and thread details are one 320 px column.

update (2026-10-20): merged feat-cmux-next again (27 conflicts, each ported onto the base's new structure, string tables regenerated, settings stamp from the helper) and lawrence's merge of #22058. the CmuxNextApp size cap passes: the new code lives in CmuxNextAgentPane, and the app target is 131 lines smaller than the base. new fix: a render guide queued with a prompt that gets withdrawn or replaced now waits for the next prompt instead of being lost (red test first, render-guide.test.ts). security pass on the whole diff: no findings, the #21760 fixes all still hold. greptile's four findings fixed red-first (render preview errors in all 21 locales; the header's terminal beside a panel opens a tab there even with new terminals opening workspaces; files search menu acts on the result; renders with bare body text grow). ci: our reds fixed (seven react compiler bailouts, a recap footer test, an extra settings stamp line); the rest is red on feat-cmux-next too (cmux-next checks, protocol contract, inventory, same-tree cmux-tui, defaults freshness, some react-apps-check suites).

update (2026-10-19): lawrence's #21760 review is addressed on this branch (#21760 merged into it before the review): nested fences stay code, only the live reply auto-runs (history waits for a click) with a hang watchdog, confirm strips show the whole payload with an origin label and hold one request at a time, openLink reports refusals, a render frame loads its document once and never navigates after (7b926b0d836, 6d1c37dd667, 5e754d77ebd, red tests first). the trust note for #13742 covers the rest.

split out to keep this one reviewable: the chat viewer (files and changes beside the chat) is #22057, thread details site icons #22058 (stacked on this branch; lawrence merged it in on 2026-10-20), and the blank-workspace layout fix #22059 (against feat-cmux-next).


first pass at getting the agent chat closer to claude / codex / t3 / cursor / conductor, plus in-chat rendering (#21760 is folded in here). almost everything is behind a switch (debug settings, or ~/.config/cmux/agent-pane/layout.json for the web-only header bits), the parity bits are off by default. the render switches (live render, render guide, render kit, render preview, render expand, table scroll) and site icons are on by default. the ones without a switch are bug fixes.

what's in here

thoughts (debug settings → pages → thought style: brain)

  • thoughts, and the notes an agent writes between steps, become one quiet line: a brain icon and the thought's first line. click it and it reads "Thoughts" over the full text. works in finished turns (inside worked for) and while a turn runs.

cleaner transcript (browser preview only for now: ?mock&sleek)

  • softer prompt bubble, slimmer code block header, quieter edited-files card, softer inline code. not wired to debug settings yet, waiting on which ones we keep.

header

  • layout.json headerStyle: quiet (every tab kind as muted icons, no box, a divider before sources), expand (hovered icon slides open to its name), labels, tray, add / add-terminal (one + menu). split down lives in the … menu in these.
  • layout.json threadTitle: true: project / thread name at the top left (the project is a label). click the name to rename the tab in place (pane.rename). a chat without a project reads "no project".
  • with threadTitle on, summary becomes thread details: this mac's name, then open in (finder, vs code, cursor, zed, xcode, whichever are installed, real app icons, remembers the last pick, folder.openIn; the app side uses the tab's own folder, the page never names a path).

right side / panes (debug settings → panes)

  • agentChat.panelTabs: with a panel beside the chat, terminal and browser add a tab to it instead of another split, and the header keeps its tools in …
  • agentChat.sideColumn: ⌥⌘B (toggle right sidebar, a stub until now) shows/hides a column beside the chat. it opens as window tiles (terminal, browser, files, changes, new chat). this goes against the "no right sidebar" rule in pointers.md, so it's here to look at, not to ship.
  • agentChat.dockBesideTabs: a chat that shares its pane with other tabs docks left when files / changes / preview opens, like a lone chat does. without it the view takes a new column that fills the screen and scrolls the chat and its tabs out of view until you close it.

files page

  • search field (the session host's git.files.search under the tab's folder), refresh button, git-ignored files listed greyed out (on this mac only, never .git, capped at 500 per folder). the folder title is gone, the tab already names it.

in-chat rendering (debug settings → pages → agentPane.labs.liveRender, on by default)

  • an agent's html in a ```cmux-render block (or a render tool call) draws inside the chat as it streams. one sandboxed frame per block from the first token: markup and styles show as the agent writes, scripts run once the block is complete, never half-written.
  • it sits in the conversation like the rest of the reply: no card, no title bar, no run/stop, no full screen, no code view. full column width, grows to its content, uses the chat's own fonts and colors.
  • ```cmux-react blocks hold a component (function App()), mounted with pinned react 18.3.1 + babel standalone 7.26.4 once the block is complete.
  • pages can talk back through window.cmux: prefill, sendPrompt, openLink and copy all wait for a click in the pane (a quiet confirm line under the page), plus theme / onTheme and an in-memory state.
  • switched off, render cards behave exactly as before (run gate, card chrome).
  • agents draw on their own (agentPane.labs.renderGuide, on by default): a short note with the first prompt of each session (hidden prompt context, app connections only) says when a visual beats prose and how to lay it out. same idea as t3code's standing html_render line.
  • native styles (agentPane.labs.renderKit, on by default): a design layer in the frame before the first byte, so plain html looks like the app while it streams (type, tables, controls, stat tiles, segmented), cmux.chart for line / area / bar / donut charts in system colors, streamed elements fade in, data-cmux-prompt follow-up buttons, and a quiet "ask to fix" note when a page throws.
  • agents can check their page first: render_preview returns a png of the page drawn off screen in the real chat frame, with console errors and layout warnings (agentPane.labs.renderPreview). wide markdown tables scroll (tableScroll), renders open larger (renderExpand).
  • faster: renders start on fence open (not after the reveal smoother), one booted frame shell, react deps load in parallel when the fence opens (block close to drawn 700 ms to about 50 ms), quieter sizing.

chat labs (debug settings → pages → agentPane.labs.*, each off by default)

  • contextMeter: context ring next to the model picker, details on hover.
  • promptCollapse: prompts over 8 lines fold with show more; find opens them.
  • messageTimes: hover a prompt to see when it was sent.
  • codeWrap: one wrap choice for every code block, remembered.
  • planProgress: the running plan's current step and a thin bar above the composer.
  • shortcutsHelp: "keyboard shortcuts" in the chat menu, the pane's live bindings in groups.
  • precedence: a labs entry in layout.json wins, then the debug settings toggle, then the default.

transcript speed (no switch)

  • while a reply streams, rows it didn't touch keep their props, so the rest of the thread doesn't re-render. 2000 turns in chrome: 86 to 12 ms per frame (p50).

fixes (no switch)

  • the transcript's latest position was 16px short of the real bottom (rows draw below the thread's top padding but the scroll range didn't count it), so the newest text sat on the composer and a scroll to the end snapped back. it now ends at the bottom with 16px of room.
  • a prompt's floating copy showed for any focus in the row, so clicking edit put it on the prompt's corner. keyboard focus only now, never over the editor.
  • send with nothing to send has no hover and the arrow cursor.
  • chat summary no longer crashes the pane ("could not be drawn") when it opens: a hook ran after an early return.
  • the thread title's project label shows in full beside a short title (was "N…").
  • dev server only: pane render errors go to $TMPDIR/cmux-pane-errors.log (post only, same origin, 20 KB cap), since the web view keeps no console a session can read.
  • layout.json pointerCursor: true puts pointer cursors on the composer's controls (the field keeps the text cursor). arrow stays the default.

screenshots

Preview (in-chat render with native styles and charts)
Preview
Preview (it builds as the agent writes)
Preview
Preview (a real agent reply, drawn without being asked)
Preview
Preview (what render_preview hands back to the agent (real app, off screen))
Preview
Preview (light)
Preview
Preview (context ring)
Preview
Preview (long prompt folding)
Preview
Preview (prompt times)
Preview
Preview (keyboard shortcuts)
Preview

files beside the chat (was #22057, behind agentPane.labs.chatViewer, on)

  • the header's files (chat actions menu, or a pinned quick action) opens the chat's folder in a column on the right: folders open in place, .. goes up (never above the chat's folder), search across the folder, a file opens in the same column.
  • a path link in a reply shows the file there too, read through the app (link.readPath): the same checks as opening a path link today (deny list, agentPane.links.outsideRoots, a user gesture), text only, capped at 1 MiB. changes and a changed file's open work as before.
  • one panel on the right: opening files or a file closes thread details; while the viewer is open, thread details floats under its header button. below 820 px the column takes the transcript's place.
  • the files list and a file have one way out, an x at the end of their title row; escape still closes.

heads up

  • render cards run agent html without a click by default, in a sandboxed frame (scripts only, no same origin) that shares the pane's web process, so a busy loop in agent code can freeze the pane. that and the talk-back bridge are a trust change, so they go to #13742 before this merges.
  • render_preview (mcp) lets the agent see a screenshot of its page before showing it; the rust side is only compiled by ci, so agents get it once a cmux binary with it is published.
  • not yet: the fast frame startup for the bundled app frame (small swift change; the dev frame has it).

verification

  • render + labs: focused bun tests each on their own (live-render, render-stream, render-fence, pane-error-endpoint, artifact-panel, chat-labs, chat-labs-b, labs-precedence, summary-card-open, ui-rules, pane-english...), chrome + webkit via playwright against the mock, every lab clicked through, render streaming sampled mid stream. two codex correctness passes and a subagent review, findings fixed. AgentPaneLabsTests added, not run locally.
  • web: typecheck, the new tests (thoughts, live notes, header add menu / tray / panel tabs, thread title, files page) and the existing transcript, header and copy tests pass locally. browser preview screenshots for the web parts.
  • swift: built and run in a tagged dev app many times; dogfooded by me. swift tests added (HeaderPanelTabsTests, AgentPaneThoughtStyleTests) but not run locally.
  • browser-lane suites (render-preview, render-kit-css, markdown-table) ran locally in chromium + webkit on 2026-10-20: 16 pass.
  • 2026-10-21 (combined branch): browser layout tests in chromium (CMUX_BROWSER_TESTS=1 bun test test/chat-viewer-layout.test.tsx): 12 pass; the 4 new-chat jump cases failed before the fix (transcript and composer 448 px apart for a frame). summary-card-open 7, chat-viewer 34, chat-files-view 7, render tests (incremental-markdown, render-stream, render-fence, live-render, page-reveal) 120, all pass. the empty-url render fix has a swift test (AgentPaneNavigationTests, ci) and was checked in the dev app.
  • not run locally: swift tests (ci), the full gate. the app compiles and runs locally (xcode 27.1; one upstream file needs a local Task<_, Never> annotation there, not part of this pr).

changelog

Added: Agent chats can show interactive pages that build as the agent writes them
Added: Files and files a chat links to open in a column beside the chat

(the other parity bits stay behind dev switches)