1
0 Comments

A WebView2 renderer process broke my desktop app's “safe” focus check

I am building VocalCode, a small $4.99 push-to-talk desktop app for people who talk through coding prompts. The workflow is intentionally narrow: hold a key, speak, release, and local recognition types into the field already in focus.

The hard part turned out not to be speech recognition. It was safely deciding where text may be inserted after transcription finishes.

My Windows implementation captured the foreground window and the exact UI Automation element when recording started, then checked them again before inserting text. I also required the focused element PID to equal the foreground-window PID. That looked conservative and worked in ordinary Win32 apps.

It was wrong for WebView2. The foreground window belongs to the host process while the focused input can belong to a renderer process, so valid Claude Desktop inputs were rejected.

The fix was to stop treating PID equality as window membership. I now walk the focused UIA element's raw-view ancestors until one exposes a native HWND, resolve GetAncestor(hwnd, GA_ROOT), and require that root to equal GetForegroundWindow(). I still retain the foreground HWND, element runtime ID, and exact element comparison, so switching windows or fields during recognition is rejected.

Tests cover ordinary same-process Win32 input, cross-process WebView2 input, an element rooted in another window, and a field/window change during recording.

The lesson for me: process identity is an implementation detail in modern desktop UI stacks; window ancestry plus exact element identity is the safer boundary.

I am looking for Windows and Apple-silicon Mac users willing to try the 30-day no-card build in Claude Desktop, Cursor, terminals, and odd text controls. I am especially interested in failures, not compliments: https://vocalcode.app/

on August 26, 2026