--- title: 'The Two Failure Modes of a Click-Through Overlay' excerpt: "GeekBye's window floats on top of everything and lets your clicks pass through it — except where it has buttons. That is a two-sided contract, and v1.8.5 and v1.8.14 are what it looks like when each side breaks: one release where the overlay swallowed a system dialog, one where it stole your keystrokes. The winning fix for the second was deleting code." date: '2026-07-18' author: 'Steven' authorAvatar: '/images/blog/authors/steven.jpg' coverImage: '/images/blog/covers/the-two-failure-modes-of-a-click-through-overlay.svg' tags: ['Engineering', 'Electron', 'Desktop', 'GeekBye Releases'] keywords: - 'electron setignoremouseevents click through overlay' - 'transparent always on top window pass clicks through' - 'electron overlay steals focus from other apps' - 'macos permission dialog behind always on top window' - 'forward true mouse events electron passthrough' - 'overlay window focus management ipc' lastModified: '2026-07-18' tldr: "GeekBye's main window is a frameless, transparent, always-on-top overlay. It uses Electron's `setIgnoreMouseEvents` to let mouse clicks fall through its transparent regions to whatever app is underneath, while still capturing clicks on its own controls — a two-sided contract: receive input exactly where it paints UI, be invisible to input everywhere else. Two releases are the two ways that contract breaks. v1.8.5 fixed the transparency side: the overlay sat at the highest always-on-top level, which stacked it *above* macOS permission dialogs and let its onboarding backdrop swallow their clicks — so the fix was teaching the window to *cede* z-order and input at the right moment, and to always unwind that cession even if an IPC call failed. v1.8.14 fixed the capture side: to be interactable the overlay kept focusing itself, which yanked keystrokes away from the app you were actually using — and the shippable fix, after a whole reverted branch of custom-drag and non-focusable experiments, was simply deleting every `focus()` call, because the overlay drives itself over IPC and never needed OS focus at all." keyTakeaways: - 'An always-on-top click-through overlay lives under a two-sided input contract: it must receive input exactly where it paints UI and be perfectly transparent to input everywhere else — and every bug is a violation of one side or the other' - 'GeekBye implements the contract with a per-`mousemove` toggle — `setIgnoreMouseEvents(overUI ? false : true, { forward: true })` keyed on a `data-ui-region` hover test — and `forward: true` is load-bearing: it keeps mousemove events flowing to an ignoring window so it can detect when the cursor re-enters a control' - "The highest always-on-top level is a liability, not a win: at `screen-saver` level the overlay stacked above macOS system permission dialogs and hid them — v1.8.5's fix lowers the window during a permission request, using a deliberate `floating → status → screen-saver` z-level dance because macOS won't re-raise to a level it's already at" - 'Make every cession of input reversible: v1.8.5 wraps the permission-mode exit in `try/finally` with a safety timeout, so a failed IPC can never strand the overlay in a state where a dark backdrop hides your screen forever' - 'The best fix is often subtraction: v1.8.14 explored custom drag handling and a non-focusable window for a week, reverted all of it, and shipped by deleting every `focus()` and `app.focus({ steal: true })` call — the overlay never needed OS focus because it talks to its own renderer over IPC' --- GeekBye's main window is not a normal window. It's frameless, transparent, and always on top — a sheet of glass floating over whatever you're doing, with a few controls painted on it. The interesting engineering isn't that it floats; it's that your mouse is supposed to _pass straight through the glass_ everywhere except the controls. Click a button on the overlay and the overlay gets the click. Click the empty space next to it and the click lands on your meeting app underneath, as if the overlay weren't there at all. That is a contract with two sides: **receive input exactly where there's UI, and be completely invisible to input everywhere else.** Both sides have to hold at every instant, and every bug in this part of the app is a violation of one side or the other. Two releases, two months apart, are the cleanest possible illustration — because each one broke a different side. ## The contract, mechanically The implementation is a hook, `useMousePassthrough`, and it's small. The window is click-through _by default_. A global `mousemove` listener asks one question on every move: is the cursor over a control? Controls are tagged with a `data-ui-region` attribute, so the test is literally `target.closest('[data-ui-region]')`. Over a control, it calls `setIgnoreMouseEvents(false)` and the window captures clicks. Over transparent space, it calls `setIgnoreMouseEvents(true, { forward: true })` and clicks fall through. That `forward: true` is the load-bearing detail. Once a window is ignoring mouse events, it normally stops receiving them entirely — including the `mousemove` events the hook needs to notice the cursor coming _back_ over a control. `forward: true` tells Electron to keep forwarding moves to the ignoring window anyway, so it can re-arm itself. Without it, the overlay would go click-through once and never come back. The hook also only calls across the IPC boundary on state _transitions_ — cursor crossing the boundary between UI and glass — not on every pixel of movement. Remember that optimization; it has a hole, and v1.8.5 fell in it. ## v1.8.5: when the transparency side breaks The transparency side of the contract is "be invisible to input everywhere there's no UI." v1.8.5 is what it looks like when the overlay is _too present_ — when it captures or blocks input it should have let past. The trigger was macOS permission dialogs, and there were two distinct failures stacked on top of each other. **The overlay was physically above the dialog.** An always-on-top window has a _level_, and GeekBye's overlay sat at the highest one macOS offers — `screen-saver`. That's above almost everything, including the system's own Microphone and Screen Recording permission dialogs. So during onboarding, at the exact moment macOS asked for permission, the dialog could render _underneath_ the overlay and be invisible. The fix, `setPermissionMode`, is macOS-only and does the un-intuitive thing: it _lowers_ the overlay to the `floating` level for the duration of a permission request, then restores it afterward. And restoring it needed a specific trick, documented right in the commit: macOS "doesn't always visually re-raise a window when setting the same level it was previously at," so the code steps the window through an intermediate level — `floating → status → screen-saver` — to force the window server to recalculate the stacking order. You don't set the level you want; you walk to it. **The overlay was eating the dialog's clicks.** Onboarding paints a dark full-window backdrop, and the passthrough hook special-cases it: while that backdrop is up, it forces click-capture on the whole window so the wizard is fully interactive. But "capture every click" is exactly wrong when a system dialog is trying to collect one. The fix strips the backdrop's marker attribute during a permission request, so the hook drops out of capture mode and the dialog's buttons get their clicks. The overlay keeps its own controls interactive; it just stops pretending the empty glass is a button. **The hole in the optimization.** Remember that the hook only acts on transitions. When the window left capture mode, it re-ran the hover test — but if your cursor was _already_ sitting on a control at that moment, the "are we over UI?" answer was already "yes," so no transition fired, and click-through was never disabled. The control looked alive and was dead. The fix (`1b6eb20`, quoted) stops trusting the transition: "when exiting onboarding mode, directly evaluate whether cursor is over a UI region and make the correct IPC call." When you change modes, you don't wait for the next move to tell you where the cursor is — you look. **And make the cession reversible.** The subtle risk in all of this is that the overlay is now temporarily giving up its z-order and its click-capture, and something has to give them _back_. If the restore call throws — an IPC hiccup, a window torn down mid-request — the app could be stranded with `isRequestingPermission` stuck true and a dark backdrop hiding your whole screen, permanently. So the exit path got wrapped so the state-reset "always executes," plus a multi-minute safety timeout and a cleanup on window destroy. The rule that falls out: **every time an overlay cedes input, the un-ceding has to be in a `finally`, not a happy path.** ## v1.8.14: when the capture side breaks The other side of the contract is "receive input where there's UI." v1.8.14 is what it looks like when the overlay is _too grabby_ — when interacting with it costs you the input focus of the app you actually care about. The symptom in the changelog is focus-stealing, and here's the honest version, because the git history is more instructive than the release note. The overlay's shortcut handlers — toggle visibility on Cmd+B, open the chat window, scroll it — each used to call `mainWindow.focus()`, and on macOS `app.focus({ steal: true })`, to make sure the overlay was frontmost when you triggered it. The problem is that focusing the overlay _deactivates whatever was frontmost before_ — your meeting, your editor — so a keystroke meant for them could land nowhere, or in the wrong place. The overlay was grabbing a thing it didn't actually need. That last part is the whole lesson, and the team found it the hard way. The branch that became v1.8.14 first tried to solve _dragging_ specifically — because dragging has its own beautiful version of this bug: when you drag a full-screen overlay, the window moves under a stationary cursor, so the hover model thinks the pointer "left" the control and flips to click-through _mid-drag_, dropping the drag. So they added a `dragLock`. Then they tried making the whole window non-focusable — which broke typing and dragging, so they restored focusability, then narrowed non-focusability to just text inputs, then ripped the toggle out. They built a custom drag IPC to replace Electron's native drag. They added blur-on-unexpected-focus. A week of this. Then they deleted almost all of it. The commit that shipped reverted the custom drag, the focusable toggle, and the blur handler, and kept exactly one idea: **stop calling `focus()`.** The realization, written into a code comment, is that the overlay never needed OS focus in the first place — "IPC messages (`webContents.send`) work without window focus." The overlay drives its own renderer directly; focus was pure collateral damage. The shippable fix wasn't the clever custom-drag rewrite built in a day. It was subtracting the focus calls that shouldn't have been there. (Worth saying plainly: the changelog says "while dragging it," but what actually shipped was removing focus-steal from the keyboard-shortcut handlers — the drag-specific machinery was explored and reverted the same week.) ## The stakes, made literal If you want proof that this contract is not academic, look at what happened months later when the overlay got stuck on the _wrong_ side of it. In a much later release, the renderer process crashed while the overlay was in capture mode — `setIgnoreMouseEvents(false)`, window grabbing all clicks — and with no living UI to release it, the invisible full-desktop window sat there eating every click on the user's machine until they force-quit. A transparent window stuck in capture mode isn't a dead button; it's a desktop-wide click trap. (That one got its own diagnosis — see [when your AI notetaker stops recording mid-meeting](/blog/why-ai-notetaker-stops-recording-mid-meeting), which pairs it with a very different bug in the same release.) It's the sharpest statement of the whole problem: get the toggle stuck one way and your UI is dead; stuck the other way and your whole desktop is. ## Three things these releases taught us 1. **A click-through overlay is a two-sided contract, and both sides fail differently.** Too transparent and your own controls go dead; too grabby and you swallow other apps' input. Most overlay bugs are just one of those two, and naming which side you're on tells you where to look. 2. **The highest always-on-top level is not a prize.** Sitting above _everything_ means sitting above the system dialogs you need the user to see. Design for when your overlay should politely step down — and make stepping back up reversible in a `finally`, because a half-completed cession is worse than never ceding. 3. **Subtraction beats cleverness.** The winning v1.8.14 fix was deleting `focus()` calls the overlay never needed, found only after a week of building and reverting custom drag handling. When a feature keeps fighting the platform, the question to ask first is whether you can remove the thing doing the fighting. For the previous chapter in the v1 story, [putting the release in CI, twice](/blog/putting-the-release-in-ci-twice) (v1.8.4); for the next, [from OCR to pixels without losing the fallback](/blog/from-ocr-to-pixels-without-losing-the-fallback) (v1.8.6); and for the whole arc, [the anatomy of shipping software to perfection](/blog/anatomy-of-perfection).