fix(coordinator): block QA edge routing while dictation runs [高]#390
Merged
Conversation
handle_pressed_edge previously routed the dictation hotkey to the QA panel whenever qa_state.panel_visible was true, regardless of whether a main dictation session was already active. If the user opened the QA panel mid-dictation (or while polishing/inserting), the next dictation- hotkey edge was routed into begin_qa_session, which calls Recorder::start a second time on the same mic device. cpal usually rejects the second build_input_stream on macOS/Windows but the dictation session keeps running with no UX path to stop it from the QA panel; on Linux/PipeWire the second open can succeed and you get two concurrent capture streams competing for the audio device. Symmetrically, handle_released_edge swallowed Released entirely when the panel was visible — fine when QA owned the press, but if dictation owned the press, Hold-mode dictation could never end because the Released edge was eaten. Fix: read inner.state.phase alongside panel_visible. If a dictation session is non-Idle (Starting / Listening / Processing / Inserting), both edges go to dictation regardless of panel_visible. The QA panel stays open, just doesn't capture this hotkey. 3.3.4 (same PR, same files): open_qa_panel always emit'd CapsuleState::Idle to sweep stale Done residue, but if dictation was mid-flight that clobbered the in-flight capsule (Recording bar, Polishing progress, the brief Done toast). Guard the sweep on dictation phase == Idle. Audit IDs 3.3.1 + 3.3.4 (CONFIRMED 高 + 中). Test: 183/183 lib tests pass. Manual verification (open QA panel during dictation, press dictation hotkey — should stop dictation, not begin QA; also Hold-mode dictation while panel visible should still stop on release) requires the running app, to be done after merge.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
H-Chris233
approved these changes
May 9, 2026
5 tasks
pull Bot
pushed a commit
to yimmy23/openless
that referenced
this pull request
May 10, 2026
10 PRs landed on beta this cycle: - Open-Less#377 paste shortcut configurable (issue Open-Less#360) - Open-Less#386 TS UserPreferences updateChannel alignment - Open-Less#387 focus_target leak on Processing-phase cancel - Open-Less#388 [严重] MacHotkeyAdapter::shutdown stops CFRunLoop + tap - Open-Less#389 emit_capsule window.show/hide off audio thread - Open-Less#390 QA / dictation hotkey routing race - Open-Less#391 audio-mute spawn_blocking (async hygiene) - Open-Less#392 hotkey supervisor + global dispatcher exit signal - Open-Less#393 post-audit logic-review hotfixes (QA mute .await + focus_target Processing branch) - Open-Less#394 in-process credentials cache (kills repeated Keychain prompts) Bump 4 files: package.json, tauri.conf.json, Cargo.toml, Cargo.lock.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
User description
Summary
Two related state-machine race fixes between dictation and the Q&A panel.
3.3.1 — Hotkey edge routing race
`handle_pressed_edge` routed the dictation hotkey edge to the QA panel whenever `qa_state.panel_visible` was true, regardless of whether a main dictation session was already running. If the user opened the QA panel mid-dictation, the next dictation-hotkey edge was routed into `begin_qa_session`, which calls `Recorder::start` a second time on the same mic device.
Symmetrically, `handle_released_edge` swallowed Released whenever the panel was visible — fine when QA owned the press, but if dictation owned the press, Hold-mode dictation could never end because the Released edge was eaten.
Fix: read `inner.state.phase` alongside `panel_visible`. If a dictation session is non-Idle, both edges go to dictation regardless of `panel_visible`. QA panel stays open, just doesn't capture this hotkey.
3.3.4 — open_qa_panel clobbers in-flight capsule
`open_qa_panel` always emitted `CapsuleState::Idle` to sweep stale "已粘贴 N 字" Done residue. But if dictation was mid-flight (Recording bar / Polishing progress / Done toast still visible inside the ~1.5s auto-hide window), the sweep clobbered live UI.
Fix: guard the sweep on dictation phase == Idle. The original Done-residue sweep semantic still applies the common case (dictation finished, capsule auto-hid, user opens QA later) since by then phase is Idle.
Why one PR
Both fixes are about the same logical seam — "QA panel ↔ dictation session interaction" — and touch the two adjacent files (`coordinator/dictation.rs` + `coordinator/qa.rs`). Splitting them across PRs would just create a merge conflict on the same code review boundary.
Audit linkage
Audit IDs 3.3.1 + 3.3.4 (both CONFIRMED, 高 + 中). See `docs/audit-2026-05-10-validated.md` (local).
Test plan
PR Type
Bug fix
Description
Prevent QA hotkey routing during active dictation
Fix Hold-mode stuck by proper release edge routing
Conditionally clear capsule to protect in-flight UI
Diagram Walkthrough
flowchart LR A["Hotkey Edge"] --> B{"dictation active?"} B -- "Yes" --> C["Route to Dictation"] B -- "No, QA visible?" --> D["Route to QA"] E["open_qa_panel"] --> F{"dictation idle?"} F -- "Yes" --> G["Emit Idle Capsule"] F -- "No" --> H["Keep Current Capsule"]File Walkthrough
dictation.rs
Guard hotkey routing with dictation phase checkopenless-all/app/src-tauri/src/coordinator/dictation.rs
dictation_activeguard inhandle_pressed_edgeto route todictation when session is non-Idle, even if QA panel is visible.
handle_released_edgeto allow release to stopdictation when session is active.
qa.rs
Conditional capsule reset on QA panel openopenless-all/app/src-tauri/src/coordinator/qa.rs
open_qa_panel, emitCapsuleState::Idleonly when dictation is Idle,preventing capsule overwrite of in-progress UI.
SessionPhaseto perform the state check.