Skip to content

fix(hud): stop being born click-through, wait for the renderer to ask - #279

Merged
EtienneLescot merged 3 commits into
release/v1.9.0from
fix/hud-born-click-through-on-release-1.9.0
Aug 5, 2026
Merged

fix(hud): stop being born click-through, wait for the renderer to ask#279
EtienneLescot merged 3 commits into
release/v1.9.0from
fix/hud-born-click-through-on-release-1.9.0

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Cherry-pick of #269 (merged to main as 6eb5bbb, 7e14d17, 7049644) onto the open RC branch, per AGENTS.md § Release branches.

Refs #266 — the HUD painted on screen and every click dead from the first launch, reported on 1.8.0.

The fix: createHudOverlayWindow() asked for click-through at construction and from the renderer's mount effect. Only the second is safe. On Windows the forward option is a global WH_MOUSE_LL hook, and that hook is the only route back out — Chromium sends no pointermove to a window it has made input-transparent, so the renderer can never ask to leave the state, and Electron latches the install behind forwarding_mouse_messages_. One refused or revoked hook and the UI is inert with no way back. The construction-time call goes; the renderer's ask stays.

Full mechanism, measurements and platform analysis in #269.

Verification on this branch: the diff is byte-identical to what landed on main (diff of both ranges is empty). Windows e2e re-run on the cherry-pick — result in a comment below.

The HUD asked to be input-transparent twice: once here, at construction,
and once from the renderer's mount effect. The first one is the whole bug
in #266 — the app running, the bar painted, and every click, drag and
button dead, forever, from the very first launch.

On Windows `setIgnoreMouseEvents(true, { forward: true })` is a global
WH_MOUSE_LL hook, and that hook is the only route back: Chromium delivers
no pointermove to a window it has made input-transparent, so the renderer
cannot ask to leave the state it is stuck in. Electron latches the install
behind `forwarding_mouse_messages_` and only retries after a
setIgnoreMouseEvents(false) — the call the dead hook prevents. So one hook
that is refused, or that Windows revokes for overrunning the 300 ms
LowLevelHooksTimeout, bricks the UI with no way out.

Construction time is the worst possible moment to ask for it: that hook
callback runs on the main thread, which is still booting the app. The
renderer asks a frame or two later, over IPC, on a thread that is provably
pumping messages — and if that ask never comes, the bar stays clickable
instead of turning into a ghost.

Costs an invisible rectangle that can swallow one desktop click in the two
frames between show and mount. Refs #266.
Two halves, because both are load-bearing after #266: nothing may call
setIgnoreMouseEvents while the HUD window is being constructed, and the
renderer must still ask for it once it has mounted.

The window is recreated through the app's own path (second-instance →
showMainWindow → createHudOverlayWindow) with the native call taped, and
the tape is read back synchronously — no await between arming it and
snapshotting, so no renderer IPC can slip into what is meant to be
construction only. The source selector is opened first purely to keep the
window list non-empty while the HUD is destroyed: emptying it fires
window-all-closed, which quits the app under the test.

Ablated: restoring the deleted line turns duringConstruction into
[[true, {forward: true}]] and the first assertion fails, so the test does
cover the regression it claims to.
"A frame or two" was an assumption. Timing the real app from ready-to-show
to the renderer's first hud-overlay-ignore-mouse-events puts it at 83 and
90 ms over two clean runs — roughly forty times what the comment claimed,
and the number a reviewer should be weighing against #266.

(A third run read -403 ms: the tape catches setIgnoreMouseEvents on the
prototype, so an IPC still in flight from the destroyed HUD's renderer
lands on the new window and dates the ask before the window exists. The
probe was throwaway; the two clean runs are the ones quoted.)
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 441b1c78-5385-4fae-bfec-0d9ffa139a71

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot

Copy link
Copy Markdown
Collaborator Author

Local Windows e2e re-run on this branch is blocked, not skipped, and not passing.

vite build fails in my worktree before it can produce a bundle:

X [ERROR] Expected string in JSON but found "<<"
    ../../../package.json:4:0:
      4 │ <<<<<<< HEAD

That path resolves outside the worktree, into the main checkout, whose package.json currently carries conflict markers from an in-flight rebase (main...origin/main [ahead 44, behind 101]). esbuild walks up past the worktree when bundling the vite config and trips on it. Nothing to do with this branch, and not mine to resolve — left untouched.

I did get a green run out of the guard test right after that failure, and it is worthless: the build had exited 1, so it exercised the previous bundle. Not counting it.

What stands instead:

  • The cherry-picked diff is byte-identical to what landed on maindiff <(git diff release/v1.9.0..HEAD) <(git diff 5efe5e62..70496441) is empty.
  • That content was verified on Windows before the merge (see fix(hud): stop being born click-through, wait for the renderer to ask #269: guard test green, ablated by restoring the deleted line).
  • CI here builds in a clean checkout: Build, Test, Type Check, Typecheck (tests), Lint and the macOS/Windows bundles are green on this branch.

@EtienneLescot

Copy link
Copy Markdown
Collaborator Author

Local Windows e2e now run for real — the blocker in my previous comment was an unrelated conflicted package.json in the parent checkout, since resolved. Fresh bundle (vite build exit 0, dist-electron/main.js rebuilt), full suite against the real Electron app:

✓ source selector opens, lists thumbnails, and selects a screen/window source
✓ the HUD asks for click-through instead of being born with it
✗ launch window opens an existing video into the editor          # #130
✗ launch window opens an existing project into the editor        # #130

2 passed, 2 failed (2.5m)

Identical to the pre-merge run on #269. The two failures are #130launch-open-video-button and launch-open-project-button exist nowhere in src/, so those tests cannot pass on any commit.

Disregard the "worthless green" note above: that run is superseded by this one.

@EtienneLescot
EtienneLescot merged commit 6142886 into release/v1.9.0 Aug 5, 2026
15 checks passed
@EtienneLescot
EtienneLescot deleted the fix/hud-born-click-through-on-release-1.9.0 branch August 5, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant