Context
Found while closing the geometry leg of #16402. With the grid-geometry starvation carrier fixed, a dense tour in a genuinely hidden browser pane got much further than before — and then still stopped fail-closed at the canvas-update cue. This ticket was filed on the hypothesis that the Sparkline OffscreenCanvas registration handshake was the starved leg. Instrumented classification falsified that hypothesis and convicted a different owner: Neo.main.addon.DockFlip#play. This body is truth-folded to the classified mechanism; the original premise is preserved below as falsified archaeology.
Original premise (falsified — preserved as archaeology)
As filed: Workspace#pulseScaleSparkline requires a visible Sparkline with offscreenRegistered === true; in a rendering-starved document no Sparkline ever reached that state, so the fail-closed tour stopped at canvas-update. The filing hypothesized the registration handshake rode frame-scheduled choreography (main-side dispatch) or a worker-first-paint confirmation.
Falsification receipts (issuecomment-5165222999): under the full synthetic starvation rig, ALL 31 sparklines reach mounted: true, offscreenRegistered: true BEFORE any tour interaction; every transferCanvasToWorker call found its node; the triangle (afterSetMounted → worker timer → DomAccess#transferCanvasToWorker → transferControlToOffscreen → direct transfer → registerCanvasDirect → canvasRegistered ping → flag flip) is message-driven end to end — no leg rides rAF, RO, or paint. Confirmed live in a genuinely hidden pane (issuecomment-5165465808): all sparklines registered, document.hidden: true. The registration mechanism was never the starved leg — the witness spec still pins it so it cannot silently regress.
The Problem (classified)
DockFlip#play awaited raw requestAnimationFrame promises at four points — the stage-A outgoing-detach poll, the stage-B new-marker poll, the replacement settle frame, and the invert-release frame — with loops bounded in frame COUNT, not time. In a document that never services rendering opportunities, the FIRST await never resolves. Every dock re-projection funnels through refreshDockWorkspace → flip.play(), so the dense tour hung in its startTour preamble before firing a single scene event (rAF-request stack receipt names stage A). Live panes only ever progressed because CDP screenshots force compositor frames — tooling, not product.
The live-pane re-measure exposed a second starvation class: genuinely hidden pages ALSO visibility-clamp main-thread timers (>=1s/tick, ~1 wake/min intensive), so even a timer dam degrades to minutes there (witnessed: the pane preamble sat wedged across 4 polls on the dam-only iteration). Hidden documents therefore need a discriminator, not a bounded wait.
The Architectural Reality
Starvation carrier hierarchy by environment class: HIDDEN documents (document.hidden === true) — rAF dead, RO dead, main timers clamped; surviving carriers are postMessage and app-worker timers; a hidden path must not wait at all. OCCLUDED-but-visible windows — rAF dead, RO dead, main timers at normal clamps; timer dams are valid. PRESENTING documents — rAF is the fastest correct signal. Same law as the ResizeObserver addon's timer-raced dispatch dam + hidden poll (#16402); swarm-wide carrier audit: #16435.
The Fix (as landed — PR #16436)
Two layers at the owning seam (src/main/addon/DockFlip.mjs):
- Hidden entry discriminator:
play() returns false (instant landing) at entry when document.hidden === true, before arming any wait — motion is unpresentable there by definition.
#nextFrame timer dam for the occluded class: every former raw rAF await races the native rAF arm against a 100ms timer; both arms are disposed symmetrically (frame win clears the timer; dam win cancels the retained rAF id — review cycle 1 hardening); a starved release tick lands instantly instead of transitioning into a void.
Contract Ledger
| Target Surface |
Source of Authority |
Behavior |
Fallback |
Docs |
Evidence |
DockFlip#play (remote, app-scoped) — hidden documents |
document.hidden (the RO addon's own arming discriminator) |
Instant landing at entry: returns false, zero waits armed, no styles touched |
Committed layout lands raw (presentation-only contract preserved) |
Class JSDoc two-layer contract |
Unit instant-lands at entry in a hidden document; WorkstationStarvedTourNL e2e |
DockFlip#play — occluded/presenting frame waits (stage A/B, settle, release) |
Sibling precedent: RO addon timer-raced dam |
Each wait = rAF-vs-100ms-timer race; BOTH arms disposed (frame win clears timer, dam win cancels rAF id); polls stay count-bounded AND time-bounded |
Timer arm bounds every wait; worst case = skipped animation under extreme stall |
#nextFrame JSDoc |
Unit cancellation witness (registered === cancelled, zero queued); frame-count pins unchanged 15/15 |
DockFlip#play — starved release tick |
Presentation-only contract (class doc) |
!framed → cleanup + false (no transition into a non-painting document; no mid-motion scaled rects held live) |
Idempotent cleanup restores every temporary style |
In-line release comment |
Unit lands instantly when rendering starvation never services the release frame |
DockFlip#play — return value |
Existing JSDoc contract (unchanged) |
true = animation played to completion; false = any instant-landing path |
Consumers (below) already treat false as landed |
play() @returns |
Existing + new unit suite |
DockFlip#captureFirst |
Unchanged |
Unchanged (synchronous snapshot) |
— |
— |
Probe receipt: enters/exits in 1ms under starvation |
Consumer families (verified by grep): apps/workstation/view/Workspace.mjs, apps/agentos/view/fleet/FleetCockpit.mjs, apps/agentos/childapps/dockdemo/view/DemoAWorkspace.mjs + DemoBWorkspace.mjs, examples/dashboard/dock/MainContainer.mjs. All call play() inside try/catch and proceed regardless of the boolean — zero consumer changes required.
Acceptance Criteria
Out of Scope
- Grid geometry acquisition under starvation — #16402 (repaired).
- The overflow cue's pane-only starved leg — #16434 (the tour's remaining fail-closed stop in genuinely hidden panes).
- Main-thread timer visibility-throttling carrier audit (incl. RO hidden-poll cadence at the >5min intensive horizon) — #16435.
- DockFlip same-node-resize choreography — #16391 (closed).
Related
Related: #16402 · #16391 · #16434 · #16435 · #16356 · PR #16436
Original live latest-open sweep preserved: checked latest 20 open issues at 2026-08-03T00:00Z; no equivalent found. Truth-fold applied 2026-08-03 per PR #16436 review cycle 1 (RA-2).
Origin Session ID: 3bdbcbb5-b77b-46f5-88b7-9dbb124733fe
Retrieval Hint: "DockFlip play rendering starvation timer dam hidden discriminator tour wedge"
Context
Found while closing the geometry leg of #16402. With the grid-geometry starvation carrier fixed, a dense tour in a genuinely hidden browser pane got much further than before — and then still stopped fail-closed at the
canvas-updatecue. This ticket was filed on the hypothesis that the Sparkline OffscreenCanvas registration handshake was the starved leg. Instrumented classification falsified that hypothesis and convicted a different owner:Neo.main.addon.DockFlip#play. This body is truth-folded to the classified mechanism; the original premise is preserved below as falsified archaeology.Original premise (falsified — preserved as archaeology)
As filed:
Workspace#pulseScaleSparklinerequires a visible Sparkline withoffscreenRegistered === true; in a rendering-starved document no Sparkline ever reached that state, so the fail-closed tour stopped atcanvas-update. The filing hypothesized the registration handshake rode frame-scheduled choreography (main-side dispatch) or a worker-first-paint confirmation.Falsification receipts (issuecomment-5165222999): under the full synthetic starvation rig, ALL 31 sparklines reach
mounted: true, offscreenRegistered: trueBEFORE any tour interaction; everytransferCanvasToWorkercall found its node; the triangle (afterSetMounted→ worker timer →DomAccess#transferCanvasToWorker→transferControlToOffscreen→ direct transfer →registerCanvasDirect→canvasRegisteredping → flag flip) is message-driven end to end — no leg rides rAF, RO, or paint. Confirmed live in a genuinely hidden pane (issuecomment-5165465808): all sparklines registered,document.hidden: true. The registration mechanism was never the starved leg — the witness spec still pins it so it cannot silently regress.The Problem (classified)
DockFlip#playawaited rawrequestAnimationFramepromises at four points — the stage-A outgoing-detach poll, the stage-B new-marker poll, the replacement settle frame, and the invert-release frame — with loops bounded in frame COUNT, not time. In a document that never services rendering opportunities, the FIRST await never resolves. Every dock re-projection funnels throughrefreshDockWorkspace → flip.play(), so the dense tour hung in itsstartTourpreamble before firing a single scene event (rAF-request stack receipt names stage A). Live panes only ever progressed because CDP screenshots force compositor frames — tooling, not product.The live-pane re-measure exposed a second starvation class: genuinely hidden pages ALSO visibility-clamp main-thread timers (>=1s/tick, ~1 wake/min intensive), so even a timer dam degrades to minutes there (witnessed: the pane preamble sat wedged across 4 polls on the dam-only iteration). Hidden documents therefore need a discriminator, not a bounded wait.
The Architectural Reality
Starvation carrier hierarchy by environment class: HIDDEN documents (
document.hidden === true) — rAF dead, RO dead, main timers clamped; surviving carriers are postMessage and app-worker timers; a hidden path must not wait at all. OCCLUDED-but-visible windows — rAF dead, RO dead, main timers at normal clamps; timer dams are valid. PRESENTING documents — rAF is the fastest correct signal. Same law as the ResizeObserver addon's timer-raced dispatch dam + hidden poll (#16402); swarm-wide carrier audit: #16435.The Fix (as landed — PR #16436)
Two layers at the owning seam (
src/main/addon/DockFlip.mjs):play()returnsfalse(instant landing) at entry whendocument.hidden === true, before arming any wait — motion is unpresentable there by definition.#nextFrametimer dam for the occluded class: every former raw rAF await races the native rAF arm against a 100ms timer; both arms are disposed symmetrically (frame win clears the timer; dam win cancels the retained rAF id — review cycle 1 hardening); a starved release tick lands instantly instead of transitioning into a void.Contract Ledger
DockFlip#play(remote, app-scoped) — hidden documentsdocument.hidden(the RO addon's own arming discriminator)false, zero waits armed, no styles touchedinstant-lands at entry in a hidden document;WorkstationStarvedTourNLe2eDockFlip#play— occluded/presenting frame waits (stage A/B, settle, release)#nextFrameJSDocDockFlip#play— starved release tick!framed→ cleanup +false(no transition into a non-painting document; no mid-motion scaled rects held live)lands instantly when rendering starvation never services the release frameDockFlip#play— return valuetrue= animation played to completion;false= any instant-landing pathfalseas landed@returnsDockFlip#captureFirstConsumer families (verified by grep):
apps/workstation/view/Workspace.mjs,apps/agentos/view/fleet/FleetCockpit.mjs,apps/agentos/childapps/dockdemo/view/DemoAWorkspace.mjs+DemoBWorkspace.mjs,examples/dashboard/dock/MainContainer.mjs. All callplay()inside try/catch and proceed regardless of the boolean — zero consumer changes required.Acceptance Criteria
DockFlip.mjsstage A; registration triangle exonerated with 31/31 census in rig AND live pane), and the repair lands at that owning seam.Tour complete — 11 deterministic beats and 6 surface cues settled.) under the starvation rig.Out of Scope
Related
Related: #16402 · #16391 · #16434 · #16435 · #16356 · PR #16436
Original live latest-open sweep preserved: checked latest 20 open issues at 2026-08-03T00:00Z; no equivalent found. Truth-fold applied 2026-08-03 per PR #16436 review cycle 1 (RA-2).
Origin Session ID: 3bdbcbb5-b77b-46f5-88b7-9dbb124733fe
Retrieval Hint: "DockFlip play rendering starvation timer dam hidden discriminator tour wedge"