Skip to content

Desktop quality pass: hot-path perf, hands-free races, Windows compatibility#27

Merged
metaforismo merged 2 commits into
mainfrom
fix/desktop-quality-pass
Jun 11, 2026
Merged

Desktop quality pass: hot-path perf, hands-free races, Windows compatibility#27
metaforismo merged 2 commits into
mainfrom
fix/desktop-quality-pass

Conversation

@metaforismo

Copy link
Copy Markdown
Owner

Multi-reviewer quality pass over the merged desktop code (3 review agents: performance, Windows compatibility, recent-feature correctness), with every applied fix verified against the code first.

Performance (per-dictation hot path)

  • text-writer: the clipboard restore ran a thread::sleep(1s) on the main thread of the one-shot binary (the comment claimed a background thread) — every dictation held the "processing" pill ~1s longer. The binary now pastes and exits; the Electron main process restores the user's clipboard on a timer, with correct handling of back-to-back dictations (Windows writer had the same bug — also fixed).
  • key events: every OS keystroke was broadcast over IPC to all renderer windows, always (2 msgs/keystroke/window, plus a privacy surface). Forwarding is now opt-in per webContents, toggled by the preload around onKeyEvent — only the shortcut editors receive events, only while mounted.
  • interactions list: findAll shipped the raw_audio BLOB for every row over IPC on every dictation. The list now uses a summary query (has_audio flag) and the renderer fetches audio lazily by id for play/download.
  • audio: volume-meter RMS+emit throttled to ~15Hz at the source (was ~47/s); awaitDrainComplete no longer orphans the first caller and leaks error listeners on overlapping stops.

Hands-free state machine (follow-up to #62 / 434df45)

  • Toggling hands-free off mid-session no longer orphans the recording: pending taps settle, an active hands-free session completes on the next key event, and a still-held double-tap downgrades to push-to-talk.
  • Stop→start serialization: a pending tap interrupted by a different shortcut issued completeSession() + startSession() in the same tick, and the new session's initialize() could interleave with the old session's teardown (dropping the audio-duration check or cancelling the new stream). startSession now waits for the in-flight stop's teardown phase (released before the server-response wait, so no added latency).
  • An accidental double-tap on the hands-free stop tap no longer starts a spurious sub-400ms recording ("No speech detected" toast).
  • Globally disabling dictation now cancels the in-flight session instead of completing it (no surprise text insertion); removed dead activeShortcutId assignments.

Windows compatibility

  • Ctrl+V / Ctrl+C with raw virtual keys (VK_V/VK_C) instead of Key::Unicode: on layouts without v/c keys (Cyrillic, Greek, …) enigo fell back to sending text, the chord was never delivered, and paste/copy — the app's core function — silently failed.
  • Unknown(179) is only the fn key on macOS; on Windows it's media Play/Pause and was corrupting the pressed-keys set (gated in Rust and TS).
  • Mic privacy: checkMicrophonePermission hardcoded true on Windows; it now reads getMediaAccessStatus, so a denied mic no longer passes onboarding and records silence.
  • Packaging: local cross-compile (GNU) now mirrors .exes into the MSVC target dir that electron-builder and the dev resolver expect — bun build:app:windows produced a broken layout before.
  • Closing the dashboard on Windows hides to tray (like macOS) instead of quitting the whole app mid-dictation; Quit stays in the tray menu.
  • The macOS-only "Mute audio when dictating" toggle is hidden on other platforms (was a silent no-op).

Tests

  • +7 regression tests for the new behaviors (forwarding gate, refractory window, toggle-off reconciliation, Esc-during-pending, stop-with-pending-tap, stop→start barrier).
  • Full suite green: lib (incl. keyboard 55, session manager 31), server, app, native (cargo test --workspace), clippy clean, and cargo check --target x86_64-pc-windows-gnu passes for the three changed crates.

🤖 Generated with Claude Code

metaforismo and others added 2 commits June 11, 2026 19:10
- text-writer: stop blocking ~1s on clipboard restore; the one-shot binary
  pastes and exits, the Electron main process restores the user clipboard
  on a timer (with correct handling of back-to-back dictations)
- keyboard: forward global key events only to webContents that subscribed
  (shortcut editors) instead of broadcasting every OS keystroke to all
  windows (perf + privacy)
- interactions list: stop shipping raw_audio BLOBs on every list refresh;
  rows carry has_audio and the renderer fetches audio lazily by id
- audio: throttle volume-meter emissions to ~15Hz at the source and skip
  the RMS pass when throttled; fix awaitDrainComplete leaking listeners
  and orphaning the first caller on overlapping stops

fix(desktop): hands-free state machine edge cases

- reconcile pending-tap/hands-free state when the setting is toggled off
  mid-session (complete or downgrade to push-to-talk instead of orphaning
  the recording)
- serialize stop→start: startSession now waits for an in-flight stop's
  teardown of the shared controller/recorder (pending tap interrupted by a
  new shortcut could interleave initialize() with teardown and drop audio)
- swallow an accidental double-tap on the hands-free stop tap (refractory
  window) instead of starting a spurious sub-400ms recording
- cancel (not complete) the in-flight session when dictation is globally
  disabled; remove dead activeShortcutId assignments
- +7 regression tests (keyboard 55 green, session manager 31 green)

Co-Authored-By: Claude Fable 5 <[email protected]>
Confirmed findings from a Windows-compatibility review:

- text-writer/selected-text-reader: send Ctrl+V / Ctrl+C with raw virtual
  keys (VK_V/VK_C) instead of Key::Unicode — Unicode resolves through the
  active keyboard layout, so on layouts without v/c keys (Cyrillic, Greek,
  ...) the chord was never delivered and paste/copy silently failed
- global-key-listener: only treat Unknown(179) as the macOS fast-path fn
  key; on Windows it is the media Play/Pause key and was corrupting the
  pressed-keys set and getting swallowed when an fn hotkey was registered
  (same gate on the TS side when registering hotkeys)
- permissions: detect the Windows Settings > Privacy > Microphone toggle
  via getMediaAccessStatus instead of hardcoding true — a denied mic used
  to pass onboarding and record silence with no diagnostics
- packaging: mirror GNU cross-compiled .exes into the MSVC target dir so
  the local bun build:app:windows flow produces the layout
  electron-builder and the dev binary resolver expect
- app lifecycle: closing the dashboard on Windows now hides to tray (like
  macOS) instead of quitting — closing settings used to kill background
  dictation entirely; Quit remains in the tray menu
- settings UI: hide the macOS-only 'Mute audio when dictating' toggle on
  other platforms instead of showing a silent no-op

Verified: cargo test workspace green, clippy clean, cross-compile check
for x86_64-pc-windows-gnu passes for all three changed crates.

Co-Authored-By: Claude Fable 5 <[email protected]>
@metaforismo
metaforismo merged commit 1a4c827 into main Jun 11, 2026
1 check failed
@metaforismo
metaforismo deleted the fix/desktop-quality-pass branch June 11, 2026 19:00
metaforismo added a commit that referenced this pull request Jun 11, 2026
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