fix(playground): daemon-gate wedges on hidden-tab first mount#404
fix(playground): daemon-gate wedges on hidden-tab first mount#404ljagiello wants to merge 1 commit into
Conversation
`tick()` bailed when `document.visibilityState !== "visible"`, leaving
the daemon-gate's loading branch mounted forever on any tab that
mounted while hidden: `state.loading` only flips inside `check()`'s
`finally`, so no probe → no flip → gate stuck on "CONNECTING…"
indefinitely.
Affected tabs:
- Background-opened tabs (5s+ visible wedge until next scheduled tick
lands on a visible state)
- DevTools-Protocol-driven tabs (Claude in Chrome, headless test
harnesses) where `visibilityState` never transitions to "visible" —
permanent wedge with the daemon perfectly healthy
The bail's stated rationale ("Chrome throttles network in hidden
tabs") doesn't survive close inspection: Chrome throttles timers, not
fetch; the steady cadence is 30s; the daemon is `localhost`, not a
metered network. One extra `/api/daemon/health` request per 30s on a
hidden tab is negligible.
The first attempt at this PR (commit f6e41fb) compensated for the
bail by adding an unconditional first-probe path and a
`visibilitychange` listener. That worked but introduced a race in
`scheduleNext` (two `check().finally(scheduleNext)` chains could
overlap and leak a `setTimeout` handle) and added ~40 lines of
machinery to recover behavior the bail was suppressing. Dropping the
bail collapses the whole patch to a comment swap + one deleted
branch, with no race surface.
Verified in MCP-driven Chrome (`visibilityState === "hidden"`): page
renders the workspace inspector within ~600ms after first mount and
the Run-button flow completes end-to-end.
f6e41fb to
5f7d8ea
Compare
|
Hey — did a QA pass on this and noticed the original wedge already looks fixed on Setup:
Both fix the wedge, but main goes silent once connected — for MCP / Claude-in-Chrome the tab is permanently hidden, so we'd read frozen daemon state forever. This PR keeps probing. Cost is one localhost fetch per 30s in healthy state (5s while unhealthy), Net: genuinely useful for the CDP/MCP path — unblocks QA of signal-trigger features from Claude-in-Chrome without faking visibility. |
🪷 Koan
The tab opens.
No one is looking.
The probe never runs.
Summary
tick()bailed whendocument.visibilityState !== "visible", leaving the daemon-gate's loading branch mounted forever on any tab that mounted while hidden:state.loadingonly flips insidecheck()'sfinally, so no probe → no flip → gate stuck on "CONNECTING…" indefinitely.Affected tabs:
Fix
Drop the bail. That's it. One `if` block + a comment removed.
The bail's stated rationale ("Chrome throttles network in hidden tabs hard enough that a probe is more likely to time out") doesn't survive close inspection: Chrome throttles timers, not fetch; the steady cadence is 30s; the daemon is `localhost`, not a metered network. One extra `/api/daemon/health` request per 30s on a hidden tab is negligible.
Why not the visibilitychange-listener approach
An earlier version of this PR (
f6e41fb1, force-pushed) preserved the bail and compensated with an unconditional first-probe path plus avisibilitychangelistener (~40 lines). That worked but introduced a real race inscheduleNext— twocheck().finally(scheduleNext)chains could overlap and leaksetTimeouthandles. Reviewer (#404 review doc) called out that dropping the bail collapses the patch and eliminates the race surface entirely. This version is that collapse.Test plan
Verified in MCP-driven Chrome (`visibilityState === "hidden"`):
Without the fix, the same MCP tab wedges on `CONNECTING…` indefinitely (reproducible across hard reloads).
Independent of and orthogonal to PR #402 (Run-button `?nowait=true`); together they make MCP-driven QA of any signal-trigger feature actually feasible.