Skip to content

[5/12] feat: capture & dictation core — storage/privacy, hotkey state machine - #14

Open
Mvkd108 wants to merge 2 commits into
feat/transcription-benchmarkingfrom
feat/dictation-hotkey-model
Open

[5/12] feat: capture & dictation core — storage/privacy, hotkey state machine#14
Mvkd108 wants to merge 2 commits into
feat/transcription-benchmarkingfrom
feat/dictation-hotkey-model

Conversation

@Mvkd108

@Mvkd108 Mvkd108 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Status: REVIEW ONLY — do not merge. PR 5 of 12 in a stacked series; stacked on #13. This PR shows only its own chunk. Series intro, divergence notes and full map: #10. Next: #15.

This PR (5/12): capture & dictation core — storage/privacy, hotkey state machine

Commits:

  • f9a8d68 feat: capture storage, temp-audio policy and microphone access checks — Services/CaptureStorageService.cs, Services/DictationTemporaryAudioPolicy.cs, Services/WindowsMicrophoneAccessService.cs, Services/WindowsProcessLoopbackCapture.cs
  • 3673024 feat: hotkey gesture model, conflict probe and dictation state machine — Services/HotkeyGesture.cs, Services/HotkeyConflictProbe.cs, Services/DictationHotkeyStateMachine.cs

7 files, +870.

Review focus

  • DictationHotkeyStateMachine — press/hold/release/cancel transitions, debounce, and edge cases (focus loss mid-dictation, rapid re-press).
  • HotkeyConflictProbe — how conflicts with other apps/OS shortcuts are detected and surfaced.
  • DictationTemporaryAudioPolicy + CaptureStorageService — temp audio lifecycle and deletion guarantees (privacy-sensitive: nothing may linger after paste/discard).
  • WindowsMicrophoneAccessService — Windows privacy-setting detection (mic disabled at OS level vs no device).

Expected — do not flag: wiring into the app shell arrives in PR 9; dictation end-to-end runs through PR 9/10 code.

Known limits

  • A muted Windows microphone currently reports "No speech detected" instead of "Microphone is muted" — root cause diagnosed (2026-08-03), fix not yet implemented. Tracked as follow-up.
  • Paste-target qualification (which apps accept injected text correctly) is a human gate; see qualification scripts in PR 12.

Test evidence

  • Full stack at tip: build clean (--no-restore); 484/484 passing.
  • Suite lands in PR 11; classes covering this chunk: Phase2DictationTests, HotkeyAndPasteTests, CapturePrivacyAndRuntimeTests.

Previous: #13 | Next: #15

Copilot AI lite review requested due to automatic review settings August 4, 2026 09:04
@coderabbitai

coderabbitai Bot commented Aug 4, 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: dc81b818-80bc-4acb-b62f-40538bd644c4

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Mvkd108 and others added 2 commits August 18, 2026 20:09
…ecks

Centralises where captured audio lives and when it is deleted, adds an
explicit retention policy for dictation scratch audio, surfaces the
Windows microphone privacy setting, and adds per-process loopback capture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…chine

Replaces ad-hoc hotkey handling with an explicit gesture type, a probe
that detects conflicts with other applications, and a state machine that
models push-to-talk and toggle dictation deterministically.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Mvkd108
Mvkd108 force-pushed the feat/dictation-hotkey-model branch from 3673024 to f69bae7 Compare August 18, 2026 14:39
@claude

claude Bot commented Aug 18, 2026

Copy link
Copy Markdown

Claude finished @Mvkd108's task in 5m 59s —— View job


Review: capture & dictation core — storage/privacy, hotkey state machine

Todo list

  • Gather context (PR description, changed files, CLAUDE.md)
  • Review DictationHotkeyStateMachine (press/hold/release/cancel transitions, debounce, edge cases)
  • Review HotkeyConflictProbe (conflict detection/surfacing)
  • Review DictationTemporaryAudioPolicy + CaptureStorageService (temp audio lifecycle, deletion guarantees)
  • Review WindowsMicrophoneAccessService (privacy-setting detection)
  • Review remaining files (HotkeyGesture, WindowsProcessLoopbackCapture, AudioCaptureService diff)
  • Post final review

🔴 High: HotkeyConflictProbe always fails — the probe can never confirm a hotkey is available

windows-native/Muesli.Windows/Services/HotkeyConflictProbe.cs:38-41

var id = unchecked((int)(0x4D550000 | (Environment.TickCount & 0x7fff)));
...
try { return RegisterHotKey(IntPtr.Zero, id, modifiers, (uint)KeyInterop.VirtualKeyFromKey(parsed.Key)); }
finally { UnregisterHotKey(IntPtr.Zero, id); }

Win32's RegisterHotKey requires id to be in the range 0x00000xBFFF when hWnd is NULL (0xC0000xFFFF is reserved for shared-DLL hotkeys registered via GlobalAddAtom). 0x4D550000 | (TickCount & 0x7fff) always produces a value around 0x4D5500000x4D557FFF, far outside that range. RegisterHotKey will therefore always fail with ERROR_INVALID_PARAMETER, and IsAdvisoryRegistrationAvailable will always return false, regardless of whether the candidate gesture actually conflicts with anything. Since this is the whole point of the conflict probe (a review-focus item called out in the PR description), this looks like it would make the feature non-functional (always reporting "unavailable"/conflicting) once wired up.

Fix: keep id within 0x00000xBFFF, e.g. Environment.TickCount & 0xBFFF (guard against 0, since some implementations treat id == 0 specially).

Fix this →


🟠 Medium: last-dictation.wav is never cleaned up — raw dictation audio lingers indefinitely after paste

windows-native/Muesli.Windows/Services/CaptureStorageService.cs:154-163 (IsLegacyOrTransientCapture):

return (name.StartsWith("native-", ...) ||
        name.StartsWith(DictationTemporaryAudioPolicy.SegmentPrefix, ...) ||
        name.StartsWith(DictationTemporaryAudioPolicy.MergedPrefix, ...) ||
        name.StartsWith("meeting-system-", ...) ||
        name.Equals("last-meeting-system.wav", ...)) &&
       !name.Equals("last-dictation.wav", ...);

AudioCaptureService.StopAsync (AudioCaptureService.cs:85) overwrites captures\last-dictation.wav with the raw audio of the just-finished dictation on every successful stop. That file is:

  • Explicitly excluded from DeleteLegacyAndTransientCaptures/InspectLegacyAndTransientCaptures (line 162 above).
  • Not touched by DictationTemporaryAudioPolicy.CleanupInterruptedFiles, which only targets dictation-tmp-*/dictation-merged-*.

So the most recent dictation's raw audio persists on disk indefinitely (until the next dictation overwrites it) — even after the transcript is pasted or discarded. This directly contradicts this PR's own stated privacy bar: "nothing may linger after paste/discard." Notably the analogous file for meetings, last-meeting-system.wav, is cleaned by this same routine (line 161), so the asymmetry looks unintentional rather than deliberate.

(I did find one plausible reason for the exclusion: TranscriptionBenchmarkService.FindBenchmarkAudioPath reads last-dictation.wav as a benchmark input — but that's a developer/benchmarking convenience, not a stated product requirement, and doesn't obviously justify keeping raw speech audio around indefinitely for a privacy-sensitive dictation flow.)

Worth confirming with the author whether this is intentional; if not, either delete/rotate last-dictation.wav once transcription completes (mirroring last-meeting-system.wav's handling) or make the benchmark path opt-in via an env var like the other MUESLI_* gates in this repo.

Fix this →


🟡 Minor / question: DictationHotkeyStateMachine.Reset() doesn't return an action

windows-native/Muesli.Windows/Services/DictationHotkeyStateMachine.cs:50: Reset() returns void and just forces State = Idle, unlike every other transition, which returns the DictationHotkeyAction the caller must perform. The PR description calls out "focus loss mid-dictation" as a case to check for this class, so presumably PR 9's wiring will call Reset() on focus loss and separately call DictationCoordinator.CancelAsync() — but that means the "must cancel any in-flight capture" invariant lives entirely in the (not-yet-written) caller rather than being enforced by this state machine. Not a bug in this PR as committed (no caller exists yet), but worth double-checking in PR 9 that every Reset() call site pairs it with an explicit cancel — nothing here prevents a caller from calling Reset() alone and leaving a capture running with no way to stop it via the hotkey (since a subsequent physical key-up would land on State == Idle and be ignored).


Other files reviewed, no issues found

  • HotkeyGesture.cs — parsing/matching logic looks correct; ModifierKeys values happen to line up with Win32 MOD_* flags, so the (uint)parsed.Modifiers cast used elsewhere is valid.
  • WindowsMicrophoneAccessService.cs — probe/classification logic looks reasonable; the muted-mic-reports-"No speech detected" gap is already called out as a known, tracked limitation in the PR description, so not re-flagged here.
  • WindowsProcessLoopbackCapture.cs — COM activation/capture-loop/teardown sequencing (cancellation, GC.KeepAlive, buffer release in finally) looks careful and correct on read-through.
  • AudioCaptureService.cs diff — trivial constant addition, no issues.

Not run

No dotnet/build tooling was available in this environment, so I did a read-through review only (no build or test-windows-package.ps1 run). Findings above are based on static reading of the Win32 API contract and the file lifecycle across these files.
· branch feat/dictation-hotkey-model

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.

2 participants