From c29fad64484ea7522dc2fdb258792491234b36d3 Mon Sep 17 00:00:00 2001 From: dietrich ayala Date: Sat, 15 Aug 2026 14:09:06 +0200 Subject: [PATCH] fix(hybrid-overlay): the chrome overlay exists before any window event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overlay window was minted lazily on the first `retarget()`, which runs from a `subscribeFrontChange` callback — that is, from inside a dispatch. Its registration was queued behind the dispatch in flight, so the `AttachOverlay` command issued two lines later was decided against a state that did not yet contain the overlay and was rejected as naming no such window. The rejection is swallowed, so the overlay stayed unattached and invisible for the rest of the process: no chrome ever appeared on any page host, and roughly forty-five specs failed downstream of that one rejection. The rule the fix expresses: the overlay's EXISTENCE is app infrastructure, not a consequence of any window event. Only its ATTACHMENT is event-driven. Minting it during a dispatch is what made it participate in that dispatch's ordering, so it is minted at startup instead and the ordering problem does not arise. `retarget()`'s own call is idempotent and becomes a no-op re-entry. The `'closed'` handler nulls the ids and would put a re-mint back inside a dispatch. That is recorded as a hazard rather than guarded: a guard there would re-answer the ordering question at the site where the symptom shows up. The overlay registers with no `source` and no `params`, and the session save walks only records that have both, so registering at boot rather than on first page adds nothing to the saved stack. Two further filters would exclude it anyway — the projection skips `role: 'overlay'`, and the kernel-order read excludes satellite roles. Unit suites green, 0 failures and 0 skips: window-kernel 319, window-state 257, window-kernel-properties 217, izui-roles 88, window-kernel-executor 70, window-kernel-shadow 68, session-projection 62, window-kernel-singleton 4. Both mechanical gates clean: check-r2-window-ops 15/23, check-platform-leak 0. --- apps/desktop/main/hybrid-overlay.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/desktop/main/hybrid-overlay.ts b/apps/desktop/main/hybrid-overlay.ts index f1b6590a..ffd0afc3 100644 --- a/apps/desktop/main/hybrid-overlay.ts +++ b/apps/desktop/main/hybrid-overlay.ts @@ -321,6 +321,15 @@ function ensureOverlay(): BrowserWindow { overlayWmId = null; } }); + // HAZARD: nulling these means the NEXT `ensureOverlay()` call re-mints the + // window from scratch — and that call could come from `retarget()`, i.e. + // from inside a `subscribeFrontChange` dispatch. A mint that happens mid- + // dispatch registers too late for that same dispatch's `AttachOverlay` to + // see it, so the attach is rejected (no-such-window) invisibly — the exact + // defect `initHybridOverlay`'s startup `ensureOverlay()` call exists to + // avoid. Do not paper over a runtime overlay close with a guard or retry + // here; if this ever needs handling, the re-mint has to be moved back out + // of dispatch, the same way the startup call is. // Register the chrome overlay with the window-state machine as a SATELLITE: it // can hold OS focus (Cmd+L into the navbar) but must never become `front`. @@ -790,6 +799,21 @@ export function initHybridOverlay(): void { // (the overlay host has webview:null and cannot service it; see above). wireExecuteScript(); + // Mint the overlay window and register it with the window-state machine + // HERE, before the subscription below can fire a `retarget()`. The + // overlay's EXISTENCE is app infrastructure — like the bg/cmd/hud + // renderers, it is created once at startup and is not a consequence of any + // window event; only its ATTACHMENT to a host is event-driven. Minting it + // from inside a `retarget()` call put its `WindowRegistered` registration + // mid-dispatch (queued behind the very dispatch that triggered the mint), + // so the immediately-following `AttachOverlay` was decided against a + // window-manager state that did not yet contain it and was rejected as + // no-such-window — the overlay stayed permanently unattached. Calling + // `ensureOverlay()` here, ahead of the subscription, means registration + // always lands between dispatches; `retarget()`'s own call is idempotent + // and becomes a no-op re-entry. + ensureOverlay(); + unsubscribeActive = subscribeFrontChange(() => { try { // subscribeFrontChange fires on any change to the ordering query behind -- 2.51.2