Skip to content

fix(cmux-next): stop the pane revealing a page that is still deliberately blank - #24384

Closed
teamleaderleo wants to merge 2 commits into
feat-cmux-nextfrom
cmux/pane-giveup-after-handshake
Closed

teamleaderleo wants to merge 2 commits into
feat-cmux-nextfrom
cmux/pane-giveup-after-handshake

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Leo, 2026-10-22: "the initial load is kinda slow it like flickers the initial thing/paints and
renders it... and then it just goes to black screen for a while?"

#24262 fixes the flicker — the pane taking back the chat's last frame while its page was still
coming. It narrows the black screen from about 7 s to about 2 s but cannot close it, because the
rest of the gap is two budgets that disagree. This closes that.

The two budgets

The pane's loading state gives up on a page that has not reported a first paint after two checks in
sight (about 8 s) and shows the page as it is. That give-up is there for a page that cannot
report: WebKit holds the animation frames of a window it thinks is not visible, so pane.painted
may never come at all (cx-txnr).

But the agent page also holds its own first paint back for up to CHAT_DRAW_HOLD_MS — 10 s after
its handshake — while it waits for the chat's rows. So the host was revealing, at 8 s, a page that
intended to stay blank until 10 s. That is Leo's black screen: the pane's own frame goes, and what
replaces it is a page deliberately showing nothing.

The page's 10 s hold is not the thing to shorten. Its own comment records why it is 10 s: a
shorter hold showed a busy daemon's chat as an empty page with only its composer — Leo, 2026-10-21,
"I LOAD IN AND IT'S A BLACK SCREEN". Lowering it walks back into the bug it was raised to fix. So
the host's bound is the one that moves.

What changes

A page that answered the handshake is not the stuck case: its bridge is up, and it reports by its
own deadline whether the chat arrived or not. It now gets aliveChecks checks rather than
silentChecks, counted from the handshake — the first check that sees it answer starts the
count over. That detail matters: a bound measured from the pane's creation still fires early when a
slow daemon answers late in the silent window, which is exactly the case that hurts. A page that
says nothing at all keeps today's bound, unchanged.

No protocol change: the host already knows, from model.hasHandshake.

The cost is that a page which handshakes and then truly hangs holds its spinner longer than before.
That is the right way round — a spinner says loading, a black pane says broken — and it is still
bounded.

Reproduce and repair

cmux-ci run --script scripts/ci/package-test-lane.sh --arg suite \
  --arg Packages/macOS/CmuxNext --arg AgentPaneColdChatRevealTests

Three tests, each doing a different job:

  • aHandshakedPageKeepsItsLoadingStatePastTheSilentBound is the regression: it is red with this
    change reverted, because base reveals the page at the silent bound.
  • aHandshakedPageStillShowsAsItIsAtItsOwnBound is the canary. It passes on base too — its job is
    to fail if the check timer never fires in the test host at all, which would make the one above
    pass vacuously. The two drive the same timer.
  • theAliveBoundClearsThePagesOwnBudget reads CHAT_DRAW_HOLD_MS out of App.tsx instead of
    restating it, so raising either side alone fails here rather than bringing the gap back quietly.
    A renamed or unreadable constant fails too, rather than passing vacuously.

Observed / Not run

Observed: python3 scripts/cmux-next/prepush-gate.py reports no new violations.
pr-overlap read 428 open PRs and found none touching this.

Not run: the red leg has not executed yet, and I am not going to call it a receipt before it
has. Both CI routes for a CmuxNext suite are deeply queued today — a light agent-class fleet step
for this package sat unassigned for 2.5 h behind 220 jobs, and cmux-next swift test on hosted
macOS has been queued over an hour on #24357. The same focused command above, with this commit's
source change reverted and the tests kept, is the red leg; I will post it here when it runs. If it
comes back green the test does not bite and it gets reworked before this lands.

Also not run: any live launch. The gap needs a cold launch against a loaded daemon to show itself,
and both timings here are read from source rather than filmed. Say the word and I will film a cold
launch before and after on a capture slot.

No capture: this is a timing change on a native pane, and a still cannot show a gap between two
frames.

Changelog

Fixed: the agent pane no longer shows a blank page for a few seconds when a chat is slow to open.

Leo-ask: ask-665f9ad8ba

🤖 Generated with Claude Code

Agent session: big-red


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


Summary by cubic

Fixes the black-screen gap on cold chat loads by stopping the agent pane from revealing a page that is deliberately still blank. The pane gave up on an unpainted page after about 8 seconds, but a page that answered the handshake holds its first paint for up to 10 seconds while waiting for chat rows, so it revealed a page that intended to show nothing.

Bug Fixes

  • A page that answered the handshake now gets three checks, counted from the handshake, before it is revealed; a page that never answers keeps the old bound.
  • A test reads the page's CHAT_DRAW_HOLD_MS from App.tsx and requires the host bound to clear it, so neither side can drift back into a gap.
  • A handshaked page that genuinely hangs keeps its spinner longer, which is preferred over showing a broken blank pane.

Written for commit 26a4454. Summary will update on new commits.

View guided diff Turn on auto-fix

…tely blank

Leo 2026-10-22: "the initial load is kinda slow it like flickers the initial
thing/paints and renders it... and then it just goes to black screen for a
while". The second half of that is two budgets that do not agree.

The pane's loading state gives up on a page that has not reported a first paint
after two checks in sight (about 8 s) and shows the page as it is. That exists
for a page that cannot report at all: WebKit holds the animation frames of a
window it thinks is not visible, so `pane.painted` may never come (cx-txnr).
But the agent page also holds its own first paint back for up to
`CHAT_DRAW_HOLD_MS`, 10 s after its handshake, while it waits for the chat's
rows -- a hold that was itself raised to 10 s because a shorter one showed a
busy daemon's chat as an empty page ("I LOAD IN AND IT'S A BLACK SCREEN",
2026-10-21). So the host revealed, at 8 s, a page that intended to stay blank
until 10 s: the black screen.

A page that answered the handshake is not the stuck case. Its bridge is up and
it will report by its own deadline whether the chat arrived or not, so it now
gets `aliveChecks` checks instead of `silentChecks`, counted from the handshake
-- the first check that sees it answer starts the count over, because a slow
daemon can answer at any point inside the silent bound, and a bound measured
from the pane would still fire early for a late handshake. A page that says
nothing at all keeps today's bound exactly.

The two numbers have to stay ordered, so `theAliveBoundClearsThePagesOwnBudget`
reads `CHAT_DRAW_HOLD_MS` out of App.tsx rather than restating it: raising
either side alone fails that test instead of bringing the gap back quietly.

This is the half of the symptom that #24262 narrows but cannot close -- that one
keeps the pane's own last frame from being taken early, this one keeps the pane
from revealing a blank page -- and the cost is that a page which handshakes and
then truly hangs holds a spinner longer than before, which is the right way
round: a spinner says loading, a black pane says broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15cb8-bbf0-7010-8d04-f9f2c0cb55df&host=big-red
@cursor

cursor Bot commented Oct 22, 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 Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 26a4454: 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=pr24384 -f ref=cmux/pane-giveup-after-handshake -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 84d5d32 to compare; do not merge until green; see lane-rules

@blacksmith-sh

blacksmith-sh Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Found 11 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp8496lg31/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmpykab590j/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
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
test_base_passes (main.NoticesPairTests.test_base_passes)/
test_base_passes (main.NoticesPairTests.test_base_passes)
View Logs
test_every_submodule_free_checkout_on_an_owned_runner_resets_submodules (main.Reuse
dWorkspaceSubmodules.test_every_submodule_free_checkout_on_an_owned_runner_resets_submo
dules)/
test_every_submodule_free_checkout_on_an_owned_runner_resets_submodules (main.Reuse
dWorkspaceSubmodules.test_every_submodule_free_checkout_on_an_owned_runner_resets_submo
dules)
View Logs
test_every_submodule_free_checkout_on_an_owned_runner_resets_submodules (main.Reuse
dWorkspaceSubmodules.test_every_submodule_free_checkout_on_an_owned_runner_resets_submo
dules) (job='web-bundles')/
test_every_submodule_free_checkout_on_an_owned_runner_resets_submodules (main.Reuse
dWorkspaceSubmodules.test_every_submodule_free_checkout_on_an_owned_runner_resets_submo
dules) (job='web-bundles')
View Logs

...and 1 more test failures. View all on Blacksmith

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

@cursor

cursor Bot commented Oct 22, 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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Orphaned: no activity for 13 h, and no lane holds a lease on it. Adopted by the orphan sweep: merged feat-cmux-next in at 26a4454. A later pass queues it for the merge hub if it is reviewed, small and red only on base reds.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Orphaned: only the sweep's base merge since, and no lane holds a lease on it. The orphan sweep could not merge feat-cmux-next in: conflicts in Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneView+Loading.swift. Listed for a lane to resolve.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by nx-chat on big-red · took over from session 01a15cb8

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Stale (13d, conflicts); rebuild fresh from feat-cmux-next if still wanted

teamleaderleo added a commit that referenced this pull request Oct 27, 2026
* test(cmux-next): retain live chat cover through page draw budget

Rebuilds the behavioral tests from #24384 on current feat-cmux-next. A live handshake followed by chat loading must not reveal the page at the silent-page timeout; the ordinary silent bound must still reveal a stuck page.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-9874ceb9f419089c209b

* fix(cmux-next): keep the loading cover while a live page waits on chat

Rebuilds #24384 on current feat-cmux-next. The host revealed a page after eight seconds although that live page deliberately waits up to ten seconds after its handshake for chat rows. Keep the silent-page watchdog unchanged, reset the alive-page grace after its handshake, and cancel both the timer and handshake reader when the page paints. No background colors or glass defaults change.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-9874ceb9f419089c209b
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Superseded by #26161 (commit e482c83728bd9 live loading-cover fix); this stale PR is already closed.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 26a44547 Deployed Oct 22, 2026 by teamleaderleo via namespace autofix #1519
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