fix(coordinator): post-audit logic-review hotfixes

Two real bugs surfaced by the 2026-05-10 end-to-end logic review of
beta after the 8-PR audit-fix wave landed.

1. coordinator.rs:2313 — `acquire_recording_mute(inner, "qa");` was
   missing `.await`. PR #391 (`6171df61`) made the function `async fn`
   and updated the dictation call site at coordinator/dictation.rs:451,
   but the QA call site was missed. Effect: when a user has
   `mute_during_recording = true` and triggers QA via Right Option, the
   returned Future is dropped on the next line, `spawn_blocking` is
   never scheduled, holders never increments, and system audio is NOT
   muted (e.g. YouTube playback continues into the QA recording). Both
   the matching `release_recording_mute(inner, "qa")` calls become
   no-ops (early return at holders == 0). The compiler was emitting
   `unused_must_use` for this site (verified before this commit).
   Fix: add `.await`.

2. coordinator/dictation.rs:843-849 — PR #387 (`ce82fcd9`) was framed
   as "clear focus_target on cancel regardless of phase", but the only
   code path that gained the unconditional clear was
   `finish_cancel_session_state`. cancel_session deliberately skips
   that helper for Processing (so end_session can drive its own
   teardown), and end_session's "ASR-finished, cancelled" exit at
   dictation.rs:843-849 set phase=Idle but never touched focus_target.
   Result: cancel-during-Processing leaves a stale `usize` focus slot
   in state.focus_target until the next begin_session_state overwrites
   it. Today the leak is bounded (no documented reader between cancel
   and next begin), but it violates PR #387's stated contract and is a
   silent footgun for any future reader of focus_target on that
   interval. Fix: in the cancelled-after-ASR exit branch, take the
   state lock once and clear both phase and focus_target together.

Source: docs/logic-review-2026-05-10.md (subagent end-to-end review).
185/185 lib tests pass; cargo check clean (unused_must_use gone).
Manual verification checklist for both fixes in the review doc.
This commit is contained in:
baiqing committed 2026-05-10 08:42:23 +08:00
1 parent 892f3c4bfe
commit d40b08ab44
2 files changed
+10 -2

No files matched your search

@@ -2310,7 +2310,7 @@ async fn begin_qa_session(inner: &Arc<Inner>) -> Result<(), String> {
let microphone_device_name = selected_microphone_device_name(inner);
stop_microphone_preview_monitor(inner, "QA recorder");
acquire_recording_mute(inner, "qa");
acquire_recording_mute(inner, "qa").await;
match Recorder::start(microphone_device_name, consumer, level_handler) {
Ok((rec, runtime_errors)) => {
*inner.qa_recorder.lock() = Some(rec);
@@ -845,7 +845,15 @@ pub(super) async fn end_session(inner: &Arc<Inner>) -> Result<(), String> {
if inner.state.lock().cancelled {
log::info!("[coord] cancel detected after ASR — discarding transcript");
restore_prepared_windows_ime_session(inner, current_session_id);
inner.state.lock().phase = SessionPhase::Idle;
// PR #387 的「cancel 后清 focus_target」契约要在 Processing 路径上也成立。
// cancel_session 在 Processing 阶段故意跳过 finish_cancel_session_state(让
// 这里收尾),但此前的 end_session 没把 focus_target 清掉。logic-review
// 2026-05-10 P3 (🚩) 把这条补完。
{
let mut state = inner.state.lock();
state.phase = SessionPhase::Idle;
state.focus_target = None;
}
return Ok(());
}