Repository navigation
fix(cmux-next): stop the pane revealing a page that is still deliberately blank - #24384
teamleaderleo wants to merge 2 commits into
Conversation
…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
Bugbot needs on-demand usage enabledBugbot 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. |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
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 |
|
Found 11 test failures on Blacksmith runners: Failures
...and 1 more test failures. View all on Blacksmith
|
Bugbot needs on-demand usage enabledBugbot 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. |
|
Orphaned: no activity for 13 h, and no lane holds a lease on it. Adopted by the orphan sweep: merged |
|
Orphaned: only the sweep's base merge since, and no lane holds a lease on it. The orphan sweep could not merge |
|
Owned by nx-chat on big-red · took over from session 01a15cb8 |
|
Stale (13d, conflicts); rebuild fresh from feat-cmux-next if still wanted |
* 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
|
Superseded by #26161 (commit e482c83728bd9 live loading-cover fix); this stale PR is already closed. |
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
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.paintedmay 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 afterits 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
aliveCheckschecks rather thansilentChecks, counted from the handshake — the first check that sees it answer starts thecount 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
Three tests, each doing a different job:
aHandshakedPageKeepsItsLoadingStatePastTheSilentBoundis the regression: it is red with thischange reverted, because base reveals the page at the silent bound.
aHandshakedPageStillShowsAsItIsAtItsOwnBoundis the canary. It passes on base too — its job isto 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.
theAliveBoundClearsThePagesOwnBudgetreadsCHAT_DRAW_HOLD_MSout of App.tsx instead ofrestating 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.pyreports no new violations.pr-overlapread 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
lightagent-class fleet stepfor this package sat unassigned for 2.5 h behind 220 jobs, and
cmux-next swift teston hostedmacOS 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
Need help on this PR? Tag
@codesmith-botwith 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
CHAT_DRAW_HOLD_MSfromApp.tsxand requires the host bound to clear it, so neither side can drift back into a gap.Written for commit 26a4454. Summary will update on new commits.