Skip to content

fix(cmux-next): Cmd-T keeps the pane's content until the agent page paints; new terminal tabs titled with their folder - #17527

Merged
teamleaderleo merged 22 commits into
feat-cmux-nextfrom
feat-cmux-next-flicker-sweep
Oct 6, 2026
Merged

teamleaderleo merged 22 commits into
feat-cmux-nextfrom
feat-cmux-next-flicker-sweep

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Two pieces of known chrome changed after they first appeared (#17485, rows 4 and 5):

  • Cmd-T showed an empty pane. The new tab's strip entry appeared right away, but the pane went to bare wallpaper until the agent chat page drew. On a warm app that lasted about 230 ms (frames 21.983 to 22.183 s below). op-next-layout measured about 700 ms on a cold app. An agent page is transparent until its document paints, and PaneContentView.show removed the outgoing view as soon as the new one was installed.
  • Cmd-D titled the new terminal "Terminal" for one frame, then "~". An untitled terminal fell back to the generic name until the shell's first title arrived.

Before, Cmd-T from a terminal pane (fleet build of 4328967, the right pane only, 30 fps capture):

before cmd-t

Fix

  • Paint hold. The agent page now reports pane.painted once per document, on the second animation frame after its first post-handshake render. That is the first frame that shows what the page is, with the new chat's hero.
    • When a pane switches to an agent page that hasn't reported, PaneContentView keeps the outgoing view on screen. The new page sits above it at alpha 0 and keeps rendering.
    • The hold ends when the page reports, or after 500 ms. Both views then swap in one transaction.
    • Another switch or a detach ends a hold early.
    • Content that has already painted, and every non-agent content type, still swaps at once.
  • Terminal title. An untitled terminal shows its abbreviated cwd ("~") instead of "Terminal". That is what the shell's first title usually is, so the label doesn't change a frame after the tab opens. With no known cwd it keeps "Terminal".

The hold reports after the handshake, not after mount, because of an intermediate capture (tag v3). Reporting after mount swapped in the pre-handshake frame: "Connecting" with no hero. The hero then appeared 83 ms later.

After, Cmd-T from a terminal pane (fleet build of the head, tag next-flicker-v4). The terminal stays until the agent page replaces it in one frame:

after cmd-t

Tooling

  • scripts/cmux-next/flicker-check.py takes a recording of one scripted interaction and finds two patterns. It exits 1 when it finds any.
    • Blinks: something changes and returns within 100 ms.
    • Staggered settles: three or more separate steps within 300 ms.
  • scripts/cmux-next/flicker-capture.py records scripted scenarios on a capture Mac and runs the check on each. A scenario is real pointer moves, clicks and hotkeys, recorded by Cua Driver's ScreenCaptureKit recorder.
    • screencapture over SSH has no Screen Recording grant on the capture minis.
    • The recorder delivers up to about 30 fps, so a single 60 Hz frame can fall between captures. Anything lasting two frames or more shows.

Verification

  • Swift tests (not run locally, the build host can't compile Swift):
    • PanePaintHoldTests (CmuxNextAppTests): the outgoing view stays until paint, painted content swaps at once, another switch ends the hold, and the 500 ms limit.
    • AgentPanePaintTests (CmuxNextAgentPaneTests): pane.painted parses and is a registered op, waiters run once, and painting doesn't count as a user touch.
    • UntitledTerminalTabTitleTests (CmuxNextBridgeTests).
  • Each fix's test is committed before it, for the red/green record. CI runs them.
  • bun test src/agent-session/acpmux: 982 pass. tsc --noEmit is clean, and build-agent-pane-web.sh --check says the bundle is current.
  • Fleet builds (--backend-mode local): 1ff7c80e (baseline) and a32f99ed (head before the handshake change).
  • Captures on a capture Mac, a capture Mac and a capture Mac: hover a workspace row, Home and +, open a workspace, Cmd-D, Cmd-T, and back to Home.
  • Not covered: the cold New Tab (first agent page after launch). The 500 ms limit caps the hold there, so a cold page longer than that still shows the page before it paints.

Found, not fixed here

  • Crash on Cmd-T, reported on fix(cmux-next): keep next-turn pacer alive during tab replacement #17515: an intermittent EXC_BREAKPOINT in AgentPaneNextTurnPacer deinit during AgentPaneView.installTransport. It hit v2 and v4, including with fix(cmux-next): keep next-turn pacer alive during tab replacement #17515 merged.
  • First launch of a downloaded build: after Gatekeeper's "Verifying cmux…", the window drew black for about 100 ms, then empty, then "Conversations need a newer cmux daemon", then the UI. This is the daemon handoff and is left to the launch lane.
  • Agent page chrome that mounts as data arrives, from a code search and not yet recorded. These are for op-next-agent-polish and op-next-layout:
    • The composer chips (mode, model, effort, context ring) each appear when their data does.
    • The header's trailing buttons do the same.
    • The composer is hidden while handoff state loads.
  • Sidebar rows (sent to op-next-sidebar-merge): hover swaps the unread badge for the close button and re-truncates the title, and a group header's count appears on hover.

Changelog

Fixed: Cmd-T no longer shows an empty pane before the agent chat page appears, and a new terminal tab is titled with its folder from the first frame.

cmux-b2 (unregistered)

🤖 Generated with Claude Code


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

teamleaderleo and others added 11 commits October 5, 2026 20:21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… first frame

After Cmd-D the tab read "Terminal" for one frame, then the shell's
title ("~"). An untitled terminal now shows its abbreviated cwd, which
the shell's first title usually is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a recording

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cmd-T showed an empty pane for ~230 ms (warm) before the agent page's
first frame. These cover the page's first-frame report and the pane
keeping the outgoing content until it (or a 500 ms limit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cmd-T emptied the pane (~230 ms warm, ~700 ms cold) before the agent
page drew: the page is transparent until its first frame. The page now
reports that frame (pane.painted, two animation frames after mount), and
a pane switching to an agent page that has not painted keeps the
outgoing content, with the new page above it at alpha 0, until it does
or 500 ms pass. Both swap in one transaction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ker-check times frames at 60 Hz

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
screencapture over SSH has no Screen Recording grant on the capture
minis; one Cua Driver MCP session records while the steps run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a newline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le recording

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

v3 capture: after Cmd-T the kept content gave way to the new page's
pre-handshake frame ("Connecting", no hero), and the hero blinked in
83 ms later. The page now reports pane.painted on the first frame after
the handshake, so the pane keeps the outgoing content until the page
shows what it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 6, 2026
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: f3e32088-6de3-45a6-980b-c4a9386880ac

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

teamleaderleo added a commit that referenced this pull request Oct 6, 2026
…content; tests use a real terminal tab

Review of #17527: an outgoing page that hid itself when withdrawn has
nothing to keep, and a browser whose header band the strip leaves would
jump while kept. The untitled-terminal test now decodes a pty tab, and
the hold's deadline test leaves a second of slack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo

teamleaderleo commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator Author

Pre-merge review (subagent, correctness first): ship with nits, nothing blocking. Every hold ends: a page that paints, the 500 ms deadline, another switch, a detach, a moved tab or teardown. Late waiters and deadlines are tied to their own hold and a released pane. Parked spares and LaunchReveal are unaffected.

Fixed in 53144ff:

  • The untitled-terminal test used "kind":"terminal", which decodes as .other, so it never covered a real terminal tab. It now uses pty.
  • An outgoing browser whose header band the strip was pinned to would have shown its header jump during the hold. A band-pinned browser no longer holds.
  • Outgoing internal pages (Bookmarks, History…) hide themselves when withdrawn, so holding them kept an empty pane anyway. Hidden content no longer holds; there's a new test for it.
  • The deadline test now has 1 s of slack, up from 200 ms, for loaded runners.

Left as is:

  • During a hold (500 ms at most), keys and clicks go to the new page, which is still invisible. That's the same target as before, when it was visible but blank, and type-ahead into the new chat is wanted.
  • whenPainted waiters from holds that hit the deadline stay until the page paints. They're small closures holding a weak reference to the pane.
  • An untitled terminal's strip title now repeats its cwd subtitle, and the sidebar still shows the empty title.

cmux-b2 (unregistered)

…ext-flicker-sweep

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/Resources/agent-pane/pane.js
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

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

CI passes on beea666c0b (run 37491052595 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.

teamleaderleo and others added 2 commits October 6, 2026 08:16
…ext-flicker-sweep

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/Resources/agent-pane/pane.js
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 6, 2026
next-batch/20261006: #17527 and #17537 were validated green but
web / react-apps-check failed on formatting and blocked landing.
python3 -m unittest tests.test_next_batch: 5 failing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Batch queue: green. Stack next-batch/20261006 at 954248bfe072 (feat-cmux-next 7cf558be8de1 + #17527 #17537 #17542 #17553 #17554 #17559, generated files regenerated) built on the fleet, production: job c8f563681211aa65c6e94453, tag next-batch-f16f40ea (cmux-ci artifact c8f563681211aa65c6e94453). Validated this PR at 19fea9e56aef.

Own check still red: web / react-apps-check, formatting. Run bun run check:fix in webviews/ and push.

Next: land it yourself with gh-merge-green on this receipt.

…n their own file

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo and others added 5 commits October 6, 2026 08:36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… at row size (#17558)

* test(cmux-next): sidebar items, headers and tab rows draw registry icons at row size (red)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): sidebar items, headers and tab rows draw registry icons at row size

Home, App Store and the other top items, section and group chevrons,
the group pin and listed tabs drew stock SF Symbols a size or two below
the text beside them. They now draw from the cmux icon registry: one
semantic name per built-in, first-party app and reference kind, at the
row size workspace rows use, pixel-aligned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…n task (#17579)

keys(of:) lists store tabs' provider keys since #17514, so the app-only
check reads the pane's app-only tabs instead; and the store creation runs
in a task the test never yielded to before reading its requests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rface Scale

The uiScale key was in the Settings catalog but not in the page's
generated strings, so the strings freshness and locale tests failed on
feat-cmux-next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit 4160a18 into feat-cmux-next Oct 6, 2026
58 checks passed
@teamleaderleo
teamleaderleo deleted the feat-cmux-next-flicker-sweep branch October 6, 2026 16:14
teamleaderleo added a commit that referenced this pull request Oct 7, 2026
* test(cmux-next): batch dispatch runs every dispatch-gated job (red)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(cmux-next): run every tier when dispatched on a next-batch branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): cmux-next batch queue selection, stacking and bisect (red)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(cmux-next): implicit batch queue for feat-cmux-next

Debounced PR events dispatch one batch: stack eligible PRs on
feat-cmux-next, rebuild conflicted generated files, run every cmux-next
tier and a fleet production build on the stack, then land each PR with
gh-merge-green or bisect a red stack to its culprit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): address batch workflows by id, not file name

feat-cmux-next is not the default branch, so the Actions API cannot find
a workflow file that exists only there by its name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): batch keeps one base and never blames a PR for a fleet failure

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): batch skips workflow changes and stays off until enabled

GitHub refuses the job token's push or merge of a .github/workflows
change, so those PRs land by hand. Debounce dispatches only when
CMUX_NEXT_BATCH_ENABLED is 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): create batch branches at the base before pushing the stack

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): batch pushes and merges with an App token; terse bot comments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(ci): batch queue App token, off switch and workflow PRs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): batch queue serve loop, remote regeneration and local fleet build (red)

python3 -m unittest tests.test_next_batch: 14 failures, all in the new
ServeDebounce, RemoteRegeneration, LocalController and regen workflow tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ci): serve the batch queue under a gh login; regenerate on runners

next_batch.py serve polls open PRs, debounces like the debounce job and
runs one batch at a time with the operator's gh login, so it runs without
the batch App. Its pushes and merges start CI, so nothing is re-dispatched
after landing; the fleet build uses the host's cmux-ci.

Generators run the stack's code, so they no longer run on the controller:
regen-linux (web, SDK) and regen (Swift) check out the stack with a
read-only token and return a patch that the controller applies and pushes.

python3 -m unittest tests.test_next_batch: 41 pass (14 failed on 552c900).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): Swift regeneration runs the workflow ref's generator (red)

next-batch/20261006: the regen job ran the stack's own copy of
regenerate-swift-exports.sh, which the stack does not have yet (exit 127).
python3 -m unittest tests.test_next_batch: 1 failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): run the Swift export generator from the workflow's ref

The stack checkout keeps the stack's code; only the generator script comes
from the trusted ref. python3 -m unittest tests.test_next_batch: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): retry a mini job that was refused before writing a result (red)

next-batch/20261006: the Swift regen job was refused at runner setup (a
fleet build was waiting for the host) and the batch gave up.
python3 -m unittest tests.test_next_batch: 3 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): dispatch a mini job once more when it wrote no result

python3 -m unittest tests.test_next_batch: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): a production fleet build passes no backend mode (red)

cmux-ci build --production --backend-mode local: "production builds cannot
use offline backend mode". python3 -m unittest tests.test_next_batch: 1 failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): submit the production fleet build without a backend mode

python3 -m unittest tests.test_next_batch: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): the queue formats a PR's webviews before landing it (red)

next-batch/20261006: #17527 and #17537 were validated green but
web / react-apps-check failed on formatting and blocked landing.
python3 -m unittest tests.test_next_batch: 5 failing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): format a PR's webviews on a runner before landing it

A PR changing webviews/ whose only red is web formatting stays eligible.
Before landing it, regen-linux runs bun run check:fix (formatter and lint
autofix) on its head and the queue pushes the patch to the PR's branch;
gh-merge-green waits for the rerun. python3 -m unittest tests.test_next_batch: 50 OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): serve posts a receipt for owners instead of merging (red)

Landing from the queue host is not allowed yet; owners land on the
receipt. A receipt names the heavy jobs that ran, since cmux-next.yml
skips them on next-batch/* until this PR is on feat-cmux-next.
python3 -m unittest tests.test_next_batch: 4 failing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): serve posts a receipt and owners land, unless --land

python3 -m unittest tests.test_next_batch: 54 OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): page the open-PR query and retry a gateway timeout (red)

next_batch.py select: gh api graphql failed: gh: HTTP 504 (100 PRs with
files and checks in one query). python3 -m unittest tests.test_next_batch: 2 failing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): page the open-PR query 30 at a time and retry a 5xx once

python3 -m unittest tests.test_next_batch: OK; next_batch.py select: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): a local run posts receipts unless --land (red)

python3 -m unittest tests.test_next_batch: 1 failing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): a local run posts receipts unless --land

python3 -m unittest tests.test_next_batch: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): an owner push during formatting waits for the next batch (red)

Batch local-20261006-153816: #17534's owner pushed while its formatting
ran; the report showed a raw git push error. python3 -m unittest tests.test_next_batch: 1 error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): an owner push during formatting leaves the PR for the next batch

python3 -m unittest tests.test_next_batch: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): a PR with a green receipt at its head is not validated again

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): the queue never pushes to a branch it does not own (red)

An opted-in PR by another author got the formatting push too.
python3 -m unittest tests.test_next_batch: 1 failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): push formatting only to batch authors' branches

python3 -m unittest tests.test_next_batch: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): serve reports a batch author's PR closed by someone else (red)

Detect and alert only: number, actor and time to the coordinator; one
alert for 3 or more within 10 minutes. python3 -m unittest tests.test_next_batch: 4 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ci): serve reports a batch author's PR closed by someone else

The minute poll runs on its own thread, so it keeps going while a batch
runs; a PR leaving the open set costs one lookup. Detect and alert only.
python3 -m unittest tests.test_next_batch: 63 OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ci): retry a truncated GraphQL reply once (red)

serve log 20:34 and 20:37 UTC: gh api graphql failed: unexpected end of
JSON input. python3 -m unittest tests.test_next_batch: 1 failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): retry a truncated GraphQL reply once

python3 -m unittest tests.test_next_batch: OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant