Skip to content

cmux NEXT agent chat: a relaunch never draws a black picture of a see-through page - #29058

Merged
teamleaderleo merged 12 commits into
feat-cmux-nextfrom
restart-black-launch-image
Nov 1, 2026
Merged

teamleaderleo merged 12 commits into
feat-cmux-nextfrom
restart-black-launch-image

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Leo, 2026-10-26: "also as for loading in stuff, on restarting after an update/maybe in general, we dont wanna see a black background on the agent chat. idk why it's black. chromium not loading fast enough? idk, the sidebar loads in the background/theming pretty quick. feels weird."

Leo, 2026-10-27: "Okay, well, what about in terms of when you just restart and update the app? There's still that black screen and what not in the agent chat area."

Why it was black

The agent chat shows in Chromium by default (agent.chromium is on). At quit, each shown chat saves a picture of its page, and the next launch draws that picture as the pane's first frame until the live page paints (AgentPaneLaunchImages). A released page that is built again draws its picture the same way (AgentPageHibernation).

A Chromium picture is a JPEG (Page.captureScreenshot), which has no alpha. In a glass, frosted or see-through window, or over a background image, the page is clear, and those clear pixels come out black. PageImageBackground.fill only flattens over an opaque color, so the relaunch drew that black picture for as long as the page took to paint again. The sidebar has no saved picture: it draws the theme and backdrop from its first frame, which is why only the chat looked black.

The New Tab cover already refuses a Chromium picture for this reason (PageWebView.snapshotKeepsClearAreas, #24692). The launch and hibernation pictures did not.

What changes

  • Save side. AgentPaneView.pageImage() returns nil when the picture can't keep clear pixels and the page paints no opaque background of its own. It reads that background in the pane's own theme scope (performWithTheme, so a workspace's own picture or glass counts) from WebTheme.pageBackground, the one source of --cmux-surface-background. With no picture, the pane shows its loading state on the window's own backdrop (the theme and glass the sidebar shows), and the page fades in over that.
  • Draw side. The first launch after an update reads the pictures the previous version saved. drawLastPage applies the same rule before drawing one (AgentPaneView.savedPictureCovers), using the tab's record (isNewTabPage) for a tab its launch defers, which has no view yet.
  • In an opaque window, or with an opaque appearance.surfaces background, the page paints opaque, so a JPEG has no clear pixels to lose. That picture still saves and draws as before, and the relaunch keeps its instant first frame.
  • WebKit pictures keep alpha (snapshotKeepsClearAreas), so they are unchanged in every window.
  • PageImageBackground.cover / covers are the one rule. The tests cover each case.
  • The chat-open bench gets --record-launch SECONDS (the app's own launch recorder on each restore relaunch, frames archived) and --backdrop. Each restore line also names the saved pictures the relaunch could draw. That turns "no black frame" into something a fleet run can check.

No timers, observers or animation loops are added. No default changes. Nothing is removed.

Not in this PR

Risk

Low. The change is in what the quit saves, never in what a live page draws. In a translucent window with Chromium, a relaunch now shows the loading state (backdrop plus a delayed spinner) instead of a black picture, until the page paints. Opaque windows and WebKit pages are unchanged.

Validation

  • App compile (fleet compile-check --app) passed at the fix commit.
  • Fleet compile-check --app passed at 313130b.
  • Focused fleet suites at 313130b: PageImageBackgroundTests, AgentPaneColdChatRevealTests, WebThemeTests: all passed.
  • Independent review (gpt-6-astra high, review router): SHIP at 313130b. Its two P2 follow-ups are listed below.

Relaunch on a fleet Mac, frosted window, Chromium chat

scripts/measure/chat-open.sh --cases=sidebar,restore --conditions=idle --runs=2 --backdrop=frosted, same worker class (aws-m4pro, Mac16,11). Before: this PR's bench on base (03c7706). After: dbde555; the commit after it changes only the deferred-tab path and the bench's wait. The app's own chat-open trace records, frame by frame, which surface the pane showed. A flash is a return to an empty surface after content.

run saved picture at quit relaunch drew it at
before, restore 0 tab_…@1x.jpg (Chromium JPEG) 764 ms
before, restore 1 tab_…@1x.jpg 748 ms
after, restore 0 none never
after, restore 1 none never

The restore case counted 2 flashes before and 0 after.

Performance

metric before after delta harness
restore: first chat content (ms, median of 2) 2126.3 2214.3 +88 (noise: on the same runs the sidebar case moved +1410, so the after worker was slower) chat-open.sh, frosted, idle
restore: saved picture drawn yes, at 756 ms no the black frame is gone same
restore: flashes 2 0 -2 same
restore: longest empty surface (ms) 710.4 1603.3 +893: the themed loading state now shows where the black picture did same

No screenshots: the app's launch recorder (--record-launch) wrote no frames on these display-less fleet workers (an empty archive both times), and a capture tour can't relaunch the app. The frame-by-frame evidence is the trace above.

Follow-ups (review, P2)

  • The bench matches the tag folder by substring (tags/chatopen also matches tags/chatopen2).
  • A JPEG saved while the page was clear is drawn when the window was made opaque between quit and launch. The black pixels would then show once. Persisting the capture's opacity beside the picture closes it.

🤖 Generated with Claude Code

Agent session: agent session

teamleaderleo and others added 2 commits October 31, 2026 06:39
…the quit saved

--record-launch SECONDS passes the app's own launch recorder to every restore
relaunch and archives the frames (--launch-frames), so a black first frame is
something a fleet run can count. Each restore line also names the saved
pictures (AgentPaneLaunchImages) the relaunch could draw.

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

Leo, 2026-10-26: "on restarting after an update/maybe in general, we dont
wanna see a black background on the agent chat."

The agent chat shows in Chromium by default. Its saved picture (the next
launchs first frame, and a released pages frame when it is built again)
is a Chromium JPEG, which has no alpha: over a glass, frosted or
see-through window, or a background image, the page is clear and those
pixels came out black. The relaunch drew that black picture until the
live page painted. A picture that cannot keep clear pixels is now saved
only when the page sits on an opaque color; otherwise the pane shows its
loading state on the window backdrop.

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

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 1964f05: 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=pr29058 -f ref=restart-black-launch-image -f platform=macos -f backend_mode=local

Default changes

This pull request changes 5 defaults at 5a520929ae2b (what everyone gets without configuring anything):

  • tunable-source.CmuxNextAgentPane/AgentPaneTheme.swift:pageBackgrounds: (none) -> "0f1e7ee9c988c29af83613584644cf10b9d1dcaa07f602a3fa8f38ec6e2db04a"
  • tunable-source.CmuxNextAgentPane/AgentPaneTheme.swift:webTheme: "1bbfc6e5172bff10b4072b20103dba861d0c27fecc526dacf7230f59e217bbdb" -> (none)
  • tunable-source.CmuxNextAgentPane/PageImageBackground.swift:cover: (none) -> "d6bea6710ce3ded7d5b6341621c011d24211d24438c8b5df9f85b5d4788ce2f3"
  • tunable-source.CmuxNextAgentPane/PageImageBackground.swift:fill: "55fcc5cd046746cd8a40b0d55fccbbdcb0d6ae439e21d8d6a142d09b6bc06ab3" -> "a85869c60db79dfcea32926158a8dc2ef1560c030ef54b3bd70590797b80fee5"
  • tunable-source.CmuxNextAgentPane/PageImageBackground.swift:isOpaque: (none) -> "066e6d922e71da9592d77cf273d3047053dcfdc5536c0d103f4277eeb77ce8cd"

Signed off on this commit by review (subagent claude-opus-5-5 medium, post-merge).

Merge receipt: merged as aaa050d762f4 with these defaults changed, signed off by review (subagent claude-opus-5-5 medium, post-merge).

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

Shipped in:

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

… paints

Review findings at 2a00805: the colors were read outside the view's theme
scope, so a workspace's own picture or glass was missed and its Chromium
picture still saved black; and a page with its own opaque surface background
(appearance.surfaces) has a clear under-page color, so its safe picture was
dropped. The decision now reads, in the view's scope, the background the page
itself paints (WebTheme.pageBackground, the one source of
--cmux-surface-background).

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

Review follow-up at c81be33: the first launch after an update reads the
pictures the previous version saved at quit, so a Chromium JPEG of a
see-through page still drew black once. The same rule now holds when a saved
picture is drawn (AgentPaneView.pictureCoversPage, in the pane's scope): one
with no alpha only over a page that paints opaque. The chat-open bench reads
the saved pictures from the build's real Application Support folder.

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

Review follow-ups at 611fb43: a tab its launch defers draws its saved picture
before its view exists, so the picture was never checked there. With no view,
a picture with no alpha now covers only when both agent surfaces would paint
opaque in the pane's scope. The chat-open bench makes the launch recorder's
folder (the app writes no frames into a missing one) and reports the archive
path make_archive actually wrote.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review at dbde555: requiring both surfaces opaque for a tab with no view yet
dropped an opaque chat's valid picture (the New Tab surface is clear by
default), and take() had already consumed it. The tab's record says whether
it is a New Tab page (AgentTabStore.isNewTabPage) before its view exists, so
the picture is judged against that one surface. The chat-open bench waits for
the launch recorder's frames.json (bounded) instead of a guessed delay.

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

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneView+PageImage.swift
@blacksmith-sh

blacksmith-sh Bot commented Oct 31, 2026

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
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 gpt-6-astra high via router-review): SHIP at 1964f05

Delta review of the merge of feat-cmux-next (hand-resolved conflict in AgentPaneView+PageImage.swift) over a600db2..1964f05: no findings. Earlier P2 follow-ups stand (tag substring match in the bench; a JPEG saved over a clear page drawn if the window turned opaque before relaunch).

At this head: compile-check --app passed; PageImageBackgroundTests, AgentPaneColdChatRevealTests, WebThemeTests passed.

@teamleaderleo
teamleaderleo marked this pull request as ready for review October 31, 2026 12:50
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 13:15 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 13:15 Inactive
@cmux-bot cmux-bot Bot removed the stack-blocker label Oct 31, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (next-review patch-id; carried): SHIP at 2bc4838

The independent SHIP at 1964f05 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 (47494b09c60f660d) 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:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol xhigh via router-review): SHIP at 2bc4838

Review of the PR diff at the head after cmux-bot's clean base merge (diff unchanged apart from line offsets). compile-check --app passed at this head.

Follow-ups (filed as issues):

  • [P1][follow-up] Revalidate snapshot engine/alpha state across the async capture (AgentPaneView+PageImage.swift reads snapshotKeepsClearAreas before and after the await).
  • [P2][follow-up] Avoid drawing stale JPEGs captured under a transparent page once the window turns opaque.

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 19:19 Inactive
@cmux-bot
cmux-bot Bot dismissed their stale review October 31, 2026 19:24

A newer verdict blocks 6baf6eb (no independent SHIP at the head).

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (next-review patch-id; carried): SHIP at 6baf6eb

The independent SHIP at 5812769 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 (47494b09c60f660d) 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:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 21:28 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 21:28 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 21:31 Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (next-review patch-id; carried): SHIP at 1977a24

The independent SHIP at 5812769 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 (47494b09c60f660d) 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:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 23:37 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 23:37 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 23:38 Inactive
@teamleaderleo
teamleaderleo merged commit aaa050d into feat-cmux-next Nov 1, 2026
160 of 162 checks passed
@teamleaderleo
teamleaderleo deleted the restart-black-launch-image branch November 1, 2026 00:13
lawrencecchen added a commit that referenced this pull request Nov 1, 2026
…loud and CmuxNextAgentPane (app_lines) (#29883)

* base red: move #29532's and #29058's CmuxNextApp lines into CmuxNextCloud and CmuxNextAgentPane

app_lines rose +5 (28ab7d2, #29532) and +6 (aaa050d, #29058).
This moves pure parts of the same code to the owning packages, with no
behavior change:

- CmuxNextCloud: CloudMachine.startChatPlanEntry(connected:chats:) (a
  listed machine as cmux link start-chat plan reads it) and
  Collection<CloudMachine>.keptForPin(listed:) (#29532's stored list).
- CmuxNextAgentPane: AgentPaneLaunchImages.takeCovering(...), the take
  plus #29058's savedPictureCovers rule.

CmuxNextApp -16 lines. CloudStartChatMachineTests call the new API and
cover the plan entry.

* AgentPaneLaunchImagesAlphaTests: takeCovering hands a saved picture with clear pixels out once
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium, post-merge): SHIP at 5a52092

This PR merged with no review at its head, so this is an after-the-fact review. No blocking findings.

Follow-ups:

  • [follow-up] PageImageBackground.swift:17-38: the doc comment for cover(_:color:pageIsOpaque:keepsClearAreas:) now sits above covers(_:pageIsOpaque:). Move it back above cover.
  • [follow-up] AgentTabContent.swift:73-74: an opaque WebKit JPEG saved while the window was opaque is dropped when the window is translucent at launch, so that one relaunch shows the loading state. Saving the capture's opacity next to the picture (already a listed follow-up) fixes both directions.
  • [follow-up] scripts/cmux-next/chat-open-bench.py:399: TAG_DIR in folder is a substring match. Compare path components instead.
  • [follow-up] AgentPaneView+PageImage.swift:134-138: the second opacity read is there only for an engine swap during the await. A one-line comment would say so.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 5a520929 Deployed Oct 31, 2026 by cmux-bot[bot] via wait for same-key tree publisher #92083
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant