BRUCE BLOG / INTERACTION
Where did the mouse-up go? Investigating a missing click
A light tap failed, while a firm press restored it. Following the event chain revealed a missing release before the page, and a recovery policy that shaped everything after it.
Yang Yong’an + Claude Code · Published · Originally recorded
A click with one half missing
The report sounded like a frontend issue: some settings tabs ignored a light trackpad tap, yet a firm press made subsequent taps work again. We had previously blamed similar behavior on React click compatibility and moved some actions to the press. That explanation had never been measured all the way through.
This time we recorded events in the actual window. A failure contained pointerdown and mousedown, then no release. The next action lacked a fresh pointerdown. WebKit still believed the previous button press was held. The question changed from an unreliable click to a missing release somewhere in the pipeline.
pointerdown → mousedown → …
Next action: mousedown → pointerup → mouseup → clickAsk each layer whether it received the event
First we confirmed that hit testing reached the intended button, without an overlay or drag region intercepting it. We then checked the WebView, window, application and events delivered to the process. The missing mouseUp appeared in none of them. Even the system continued reporting the left button as pressed. Changing the page handler could no longer explain this evidence.
The test machine ran Magnet, a window-management utility with an active event interceptor watching mouse presses and releases. Quitting it changed the comparison: two recorded groups of eight clicks lost two to three releases; twelve clicks of valid duration after quitting it lost none. This was a result for the device and utility state tested then, not a finding about every installation.
Synthetic input needed its own control. A 0ms press-and-release could fail in a minimal native window too, so it could not stand in for a real tap. An instrument defect nearly became our product diagnosis.
The same missing event can produce different experiences
We compared the same class of events in Chromium and WKWebView. Both could receive incomplete sequences; their behavior after the loss differed. In the measurement, Chromium ended a stale press before accepting a new one, while WebKit could continue with the incorrect state.
Title-bar ownership mattered as well. A native title-bar click need not enter the webpage, whereas Bruce's web-based drag region involves the WebView. A report that Electron feels better may involve recovery and event boundaries; it does not establish that one framework's click handler is more reliable.
Repair state without inventing intent
Bruce cannot repair another application's event interceptor, but it can maintain press/release pairing at its own WebView entry points. The implementation combines system button-change signals, the event queue and a subsequent real press to decide whether a repair is needed.
Location matters as much as timing. An early repair with a trustworthy target can finish the tap at its original press location. A late repair resets state without carrying out an action the person has abandoned. Releasing at the pointer's new location can instead give a common ancestor a ghost click.
Follow-up work distinguished pointer drags from native drag sessions and tracked late real releases to avoid settling one action twice. Repairing a missing event should not mean synthesizing clicks indiscriminately across targets.
Leave a route the next developer can follow
These notes can help developers working with WebViews, native windows or hybrid interfaces, as well as Agents investigating on their behalf. A specific repair may change with platform versions. Observability and controlled comparisons remain useful.
- Separate hit testing, press, release, dispatch and state recovery.
- Check whether each layer received the event, and stop blaming boundaries already ruled out.
- Keep failing and successful controls, with the synthetic-input conditions recorded.
- Confirm the target window before an instrument posts input.
- Reset stale state without completing an intent the person has already abandoned.