Skip to content

cmux NEXT: opening an existing chat adopts the prewarmed page once its client is connected (carries #27759) - #31033

Merged
teamleaderleo merged 21 commits into
feat-cmux-nextfrom
lane/ux-warm-agent-page
Nov 1, 2026
Merged

teamleaderleo merged 21 commits into
feat-cmux-nextfrom
lane/ux-warm-agent-page

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

HOLD: with its gates fixed (098aec3) the spare is adopted on every open, and the adopted page regresses: sidebar opens show an empty transcript after the cover leaves; reopens hold the cover ~15 s and never paint. Not to land until the adopted page draws its chat and a same-host bench shows it faster.

Leo, 2026-11-01: "the loading for everything is just slow end to end / loading into a chat and some of its recent messages, loading the various ui elements of the metadata, etc. it's not cached well enough nor prerendered/prefetched well enough". Earlier, 2026-10-29: "as in i want ours to load fast too :(".

Finding 1 of the chat-open plan (manaflow-ai/cmuxterm-hq#5914): every tab not yet shown opens a cold page.

This PR carries #27759 (its 10 commits, unchanged) and fixes the blocking finding of its last review. #27759's lane has been idle since 04:32Z. #27759 stays open for its owner to close.

What changes

From #27759. Opening an existing chat of this Mac uses the prewarmed agent page (NewTabSparePool) instead of building a new one. That covers a sidebar click, Open in Tab, chat.openTab and a subagent's pane. The page is already loaded, mounted and connected, so it switches to the chat in place (chat.select), drawing the host's kept tail when there is one, and then attaches.

  • The pane keeps its cover, with the page laid out outside the window, until the page reports the chat drawn.
  • A chat from another machine, a Chief home chat, a seeded tab and a tab with a kept or relaunch wake state each still get their own page.

See #27759's body for the full design and its three earlier review rounds.

New here (the BLOCK at cffa960, P1). Before, a spare counted as ready once it had a handshake and a drawn New Tab frame. Both come before its acpmux client connects. An adoption that arrived then queued until connect() finished, and that includes the spare's whole first chat list. A cold page attaches its chat before the list (askEarly / attachedBeforeList). So under load, adopting could be slower than the cold path it replaces.

  • The page now reports its client state (pane.clientReady {ready}): true once connect() finishes and its actions answer, false when the connection is lost. Each new handshake resets it (a reload or a reconnect).
  • NewTabSparePool.servesChat requires it. A spare that is still connecting stays in the pool, and the tab opens cold as before.
  • In Low Power Mode or at serious thermal state, no chat takes the spare. Each take would mean building another page. The tab opens cold, and the spare stays for New Tab.
  • When an existing chat takes the spare, its replacement is built once input has been quiet for 750 ms (Refill.whenQuiet), not on the next main-actor turn. A new page's load on the main thread would otherwise land inside that chat's open and the next sidebar clicks. New Tab and new workspace still refill at once (Refill.now).

Spare cost

  • Count. Still one spare per app (NewTabSpareSlot, a single slot). This PR adds no page.
  • Memory. Unchanged: one parked page, which is one Chromium renderer.
  • Idle CPU. Unchanged. perf-lead row F3 records ~2% idle CPU for Home's three renderers together. The spare is now taken more often, so pages are built more often. That build is the existing pool.makeSpare, and it happens only after quiet input on the chat path.
  • Snapshot. The parked page's per-resize snapshot (gpu-audit Add bump-version script and improve reload script #7) is already a fast-mode JPEG on Chromium (PageWebView.snapshotParams). This PR changes neither when it is taken nor how.
  • Pressure. A memory pressure warning or critical level still drops the spare. Low Power Mode and serious thermal state keep chats off it (above).

Performance

Measured with scripts/measure/chat-open.sh (chat-open-bench.py, the app's own debug.chat_open_trace from the click to the first frame of chat content), on a fleet display Mac. Base and head run in the same harness.

metric before after delta harness or run link
(pending)

Timers and observers

  • No new timers or observers.
  • The deferred refill uses the pool's existing warmTimer and idle input monitor, which stop once the spare is made (warmNow removes the monitor).
  • The hold on an adopted page ends on its paint report, or on the loading state's existing give-up check.

Tests

  • SpareSessionAdoptionTests:
    • thePageReportsItsClientReady: the request decodes; the state resets on a handshake; a ready report does not count as the person touching the page.
    • whichSpareServesAChat: the readiness, Low Power Mode and thermal decision.
    • The connected-spare helper now reports ready.
  • cmux NEXT: opening an existing chat adopts the prewarmed, connected page #27759's Swift and web tests are carried unchanged.

Risk

Agent session: agent session

🤖 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

Opening an existing chat now adopts the app's prewarmed, connected New Tab page instead of building a cold one. The page switches to the chat in place, keeping the reader's position and any subagent turn. Carries #27759's adoption path (sidebar click, Open in Tab, chat.openTab, subagent pane).

  • The page reports its client state (pane.clientReady); a spare still connecting stays in the pool and the tab opens cold.
  • The composer's audio report and the header's, project notice's and New Tab project list's mount reads are boot traffic now, so the spare is no longer refused as touched and stays adoptable.
  • No chat takes the spare in Low Power Mode or at serious thermal state, since each take means building another page.
  • A spare taken by a chat is replaced only after input is quiet (750 ms); New Tab and new workspace still refill immediately.
  • A chat the daemon lacks is shown as missing, never replaced by another chat.
  • Chats from another machine, Chief home chats, seeded tabs, and tabs with kept or relaunch wake states keep their own page.
  • The chat-open trace records whether the spare was taken (spare_adopted) or why not (spare_empty, spare_unready, spare_refused).

Written for commit 098aec3. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 12 commits October 30, 2026 00:31
A sidebar click, Open in Tab, a subagent's pane or any tab that shows an existing
chat of this Mac now adopts the NewTabSparePool spare when it is connected and
drew, and switches it to the chat in place (chat.select: the host's kept tail,
then the attach), instead of building a page that loads, connects and attaches
cold. The pool warms the next spare as after a Cmd-T.

The spare never shows its New Tab screen in the chat's pane: the page leaves it
in the adopt event's own render, and until it reports the chat drawn the pane
keeps its cover (loading state or saved frame) with the page laid out outside
the window, since a Chromium page draws in its own window over any cover.

Excluded, with their own page as before: another machine's chats, Chief home
chats (and an older subagent tab that home keeps), tabs with a seed, a New Tab
page, or a kept or relaunch wake state, and a spare not yet connected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… anchor, says a missing chat is missing

Review (codex gpt-6-astra) findings on the adoption path:
- an adoption that arrives before the page client connects runs once its actions are in;
- the chat opens where the reader left it (chat.scrollAnchor.get, as a cold open);
- a chat the daemon does not list is the tab's missing chat, and a failed open with nothing drawn says it failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and turn after a reconnect

Second review (codex gpt-6-astra): an adoption whose anchor read finished after another chat was picked opened over it; one that a reconnect had already opened lost its subagent turn and scroll anchor.

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

Third review (codex gpt-6-astra): the anchor-read fence took a connect that opened the adopted chat itself for a newer pick, and dropped the adoption's subagent turn and anchor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the adoption import list in App.tsx (keep SessionAdoption and base's withHandshakeNewTab).

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

Adoption awaited a full list reread before select() for a chat the spare's list lacked, so under a
busy acpmux an older chat waited seconds for every list page. It now opens on its attach, as a cold
open does (attachedBeforeList), with the list read again behind it; the list decides the chat is
missing only when the attach fails.

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

The spare served a tab opening an existing chat as soon as it had a handshake and a
drawn New Tab frame, which come before its acpmux client connects. An adoption then
queued behind the spare's whole first chat list (a busy acpmux paces it for seconds),
where a cold page's open attaches the chat before its list (askEarly). Review of #27759
at cffa960 (BLOCK, P1).

- The page reports its client connected or lost (`pane.clientReady`); a new handshake
  starts it over. NewTabSparePool.servesChat requires it.
- No chat takes the spare in Low Power Mode or at serious thermal state: each would make
  another page. The tab opens cold, and the spare stays for New Tab.
- A spare an existing chat took is replaced once input is quiet (750 ms), not on the
  next main-actor turn, so the new page's load does not land in that chat's open or the
  next sidebar clicks. New Tab and new workspace still refill at once.

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

cursor Bot commented Nov 1, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 098aec3: 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=pr31033 -f ref=lane/ux-warm-agent-page -f platform=macos -f backend_mode=local

Gallery before/after for 098aec3: 16 states rendered, 0 images differ, 2899 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41354707322 -R manaflow-ai/cmux -n gallery-advisory-31033-098aec3a4019b5e9c104572a5b496355b3feac77 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/standard Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide Apple System Colors unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% own App.tsx
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-edge/short-edge Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-edge/short-edge Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-edge/wide-edge Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-edge/wide-edge Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-edge/no-edge Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-edge/no-edge Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-floats/scrolled-to-end Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-floats/composer-grown Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-40 Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-40 Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-56 Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-56 Apple System Colors unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-72 Apple System Colors Light unchanged 0% own App.tsx
agent-pane.composer-rest-gap/gap-72 Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/shipped Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/shipped Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/two-thirds Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/two-thirds Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/half-tucked Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/half-tucked Apple System Colors unchanged 0% own App.tsx
agent-pane.continuation-spacing/before Apple System Colors Light unchanged 0% own App.tsx
agent-pane.continuation-spacing/before Apple System Colors unchanged 0% own App.tsx
agent-pane.copy-controls/output Apple System Colors Light unchanged 0% own App.tsx
agent-pane.copy-controls/output Apple System Colors unchanged 0% own App.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Passes: CI passes on 098aec3a40.

CI passes on 098aec3a40 (run 41354707389 attempt 1).

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: Web bundle key inputs is not red on the base 93037ef; do not merge until green; see lane-rules

Default changes

This pull request changes 1 default at 098aec3a4019 (what everyone gets without configuring anything):

  • tunable-source.CmuxNextApp/Control/DebugChatOpenTrace.swift:json: "299ee38ef9feaa638b8ac37661619814dfa3ba4031363d516e84840cfa1bc7c5" -> "efa437604b054a487f4dd740989919b4712aa37f6f84e0cb039f7047bfce81f7"

Signed off on this commit by review (independent Codex gpt-6.1-sol, high, via the review router; full diff from merge base 93037ef1de to this head).

Merge receipt: merged as 4453d0b036bb with these defaults changed, signed off by review (independent Codex gpt-6.1-sol, high, via the review router; full diff from merge base 93037ef1de to this head).

Regenerate the snapshot with python3 scripts/ci/defaults_guard.py snapshot.

Shipped in:

@blacksmith-sh

blacksmith-sh Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpfsoxuauc/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmpia4znb4u/summary-missing.json (live):/summary-missing.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
imported web bundle inputs are not routed:/imported web bundle inputs are not routed: View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, review-31033-warm-a): SHIP at ebd97da

No blocking findings. Follow-ups: capture the adoption transition (cold and cached opens); fill in the Performance table (in progress, chat-open bench before/after on a fleet display Mac).

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (next-review patch-id; carried): SHIP at 0196dec

The independent SHIP at ebd97da carries to this head: the PR's own change (git diff $(git merge-base feat-cmux-next HEAD) HEAD) has the same git patch-id --verbatim (e449bf85430ecff4) at both heads, and no merge between them was resolved by hand. Nothing new to review. A newer BLOCK, or any change to the PR's own diff, ends the carry.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at 0196dec

Base-merge delta from ebd97da (Codex SHIP there). The incoming base touches none of the PR's files (checked: empty intersection of changed paths). The only deletions are the two Cloud repository test files reverted by #31023, which nothing here uses. No blocking findings.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, next-review): SHIP at 2f283ca

SHIP at 2f283ca

Visual verdict: unchanged by this diff

  1. [P2][follow-up] direct.ts:2074-2082 treats every rejected adoption as “missing” when the background refreshSessions() promise exists, even if that refresh failed or pagination was incomplete (it returns false). This can show a transient daemon/list failure as a permanently missing chat. Use the refresh result before calling adoptedMissing.

Follow-ups:

  • [P2][follow-up] Preserve failed/disconnected state when the session-list reread does not complete; only report notListedAtOpen after a successful complete list.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
cmux-bot[bot]
cmux-bot Bot previously approved these changes Nov 1, 2026

@cmux-bot cmux-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmux-bot approves 2f283ca: an independent review passed this exact head (next-review receipt by teamleaderleo). A push needs a new review; a later BLOCK at this head dismisses this approval.

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 21:42 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 21:42 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 21:42 Inactive
@cmux-bot
cmux-bot Bot dismissed their stale review November 1, 2026 21:46

A newer verdict blocks 25938ec (no independent SHIP at the head).

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (next-review patch-id; carried): SHIP at 25938ec

The independent SHIP at 2f283ca carries to this head: the PR's own change (git diff $(git merge-base feat-cmux-next HEAD) HEAD) has the same git patch-id --verbatim (869cb9aa34f6de5e) at both heads, and no merge between them was resolved by hand. Nothing new to review. A newer BLOCK, or any change to the PR's own diff, ends the carry.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 1, 2026
…uched

After tabState went passive, the same-host bench showed every open
refusing the spare as touched by listProjects alone.

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

Copy link
Copy Markdown
Collaborator Author

Review (independent Codex gpt-6.1-sol, high, via the review router; full diff from merge base 93037ef to this head): SHIP at 098aec3.

[P2] [follow-up] webviews/src/agent-session/acpmux/direct.ts:2081 - This checks whether the relisting Promise exists instead of whether it succeeded, so simultaneous transient list and attach failures falsely mark the adopted chat as missing. Gate adoptedMissing on the awaited result being true.

@teamleaderleo
teamleaderleo merged commit 4453d0b into feat-cmux-next Nov 1, 2026
192 of 205 checks passed
@teamleaderleo
teamleaderleo deleted the lane/ux-warm-agent-page branch November 1, 2026 22:46
lawrencecchen added a commit that referenced this pull request Nov 2, 2026
…heir packages (#31456)

The arch ratchet refused feat-cmux-next's tip with [app_lines] CmuxNextApp +282
against the last green arch tip cf0a547. This moves logic that holds no app
state out of CmuxNextApp, behavior-neutral, and leaves the wiring in the App:

- #31033 (spare page adopts an existing chat) to CmuxNextAgentPane: the adoption
  decision (AgentPaneSpareAdoption), the spare's readiness (AgentPaneSpareReadiness,
  AgentPaneModel.servesExistingChat), the refusal reason (AgentPaneModel.spareRefusal),
  the trace outcome (AgentPaneSpareOutcome), the pool's slot (AgentPaneSpareSlot) and
  refill policy (AgentPaneSpareRefill).
- #30002 (Cloud chat repick) to CmuxNextAgentPane, next to AgentPaneCloudStart and
  AgentPaneCloudRepick: the started-chat record (AgentPaneCloudStarted) and the
  picked-machine match (AgentPaneCloudRepick.machineID(in:)).
- #31363: CommandHistory exposes its TerminalCommandSuggestionStore (CmuxNextHistory)
  instead of four forwarding methods.
- #31316: the feed handoff driver and its wire shape (FeedHandoffDriver,
  FeedHandoffWire) to CmuxNextAgentPane/Approvals, next to FeedHandoffPolicy,
  which it already depends on.

The pure tests move with their code (AgentPaneSpareAdoptionTests,
AgentPaneCloudStartTests). No new package edge.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Nov 2, 2026
…ted until its page draws the chat (#31486)

#31033 turned adoption on, and the adopted page regresses: sidebar opens show an
empty transcript and reopens never paint (HQ#5914). Leo, 2026-11-01: "i had to
retry to get it to load???". The spare no longer serves an existing chat, which
is the cold open from before #31033. The adopt path's fix follows.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 2, 2026
…ttach fails

adoptSession read the whole list beside the attach, on the same socket; a busy
acpmux paces that list for seconds, and adopted reopens drew later than a cold
page (HQ#5914 bench). The list now decides only a failed attach, and only a list
that was read says the chat is missing (#31033's review follow-up).

spare-session-adopt.test.ts: 1 fail on the previous commit, 7 pass here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-14c3a892e82255726cbb
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
… drawing while it is held off screen (#32448)

* test(agent pane): a hidden adopted page still reports its paint (fails: it waits for a frame)

The page's pane.painted report moves to paintReport.ts unchanged (one frame,
then a task). paint-report.test.ts fails on it: a hidden document runs no
animation frames, so the report never comes (HQ#5914).

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

* cmux NEXT: an adopted spare page reports its paint while hidden, and existing chats take the spare again

The parked spare can be hidden (Chromium marks the page hidden), and a hidden
document runs no animation frames. The page's pane.painted waited for one, and
the host keeps an adopted page away and covered until that report: reopens held
the cover for the ~15 s give-up (HQ#5914; diag bench at 7d8c57e:
visibilityState hidden, last frame before the report was asked). A hidden page
now reports on a task, at once or when it goes hidden while it waits.

The chat-open trace counts an adopted page as drawn once its pane painted: its
first frame (page_painted) came before the trace, as New Tab, so every adopted
open read page_empty.

Turns adoption back on (#31486 switched it off).

paint-report.test.ts: red on the previous commit (2 fail), green here.

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

* test(agent pane): an adopted page opens an unlisted chat with no list read beside its attach (fails)

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

* cmux NEXT: the adopted page reads the chat list again only when its attach fails

adoptSession read the whole list beside the attach, on the same socket; a busy
acpmux paces that list for seconds, and adopted reopens drew later than a cold
page (HQ#5914 bench). The list now decides only a failed attach, and only a list
that was read says the chat is missing (#31033's review follow-up).

spare-session-adopt.test.ts: 1 fail on the previous commit, 7 pass here.

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

* test(agent pane): an adopted page held outside the window keeps drawing under the pane's paint hold (fails)

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

* cmux NEXT: an adopted page held outside the window keeps drawing under the pane's paint hold

The pane's paint hold makes its incoming view transparent, and a transparent
ancestor hides a Chromium page (#28002). An adopted New Tab spare waits
100 000 pt outside the window until it drew the chat (adoptSession), so the
hold hid a page nobody could see: Chromium throttled it, and it drew the chat
only once the hold gave up at 500 ms. A page laid out wholly outside its
window's content now stays visible to Chromium whatever its ancestors' alpha;
in place, a hold still hides it.

Drops #31619's hidden-page paint report (on a task, without a frame): with the
held page drawing, the report waits for a real frame again, and a report with
no frame drawn showed the page's previous frame when the host revealed it.

AgentPaneChromiumVisibilityTests.anAdoptedPageHeldAwayKeepsDrawingUnderAPaintHold:
red on the previous commit, green here.

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

* test: cover selection after a failed spare adoption

Agent-Session: https://cmux.com/deeplink/agent?id=aid-81082f85640c1fa29ca9

* fix: retain a newer selection after spare adoption fails

Agent-Session: https://cmux.com/deeplink/agent?id=aid-81082f85640c1fa29ca9

* Agent pane: an adopted spare turns its cover on before its loading state begins

The chat-open bench counted a flash on 7-10 of 10 adopted opens: its trace
recorded the chat's saved frame ("image"), then the empty cover, then the
chat. No such frame was drawn. beginLoadingState binds the trace and
samples the surface at once, while the New Tab page's cover was still
hidden; adoptSession turned the cover on only after that, in the same main
turn, so the first sample read a state no frame showed. The cover now goes
on first, so the pane is in its loading state from the first sample.

Diagnosed on the fleet with a probe build that recorded every cover change
after the adoption; it found none, which left the trace's own first sample.

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

---------

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

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 098aec3a Deployed Nov 1, 2026 by teamleaderleo via namespace autofix #11661
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant