Merge pull request #390 from appergb/fix/audit-qa-dictation-routing

fix(coordinator): block QA edge routing while dictation runs [高]
This commit is contained in:
Chris233 authored and GitHub committed 2026-05-10 07:34:32 +08:00
commit 033e5724ca
2 files changed
+23 -6

No files matched your search

@@ -12,8 +12,14 @@ pub(super) async fn handle_pressed_edge(inner: &Arc<Inner>) {
let was_held = inner.hotkey_trigger_held.swap(true, Ordering::SeqCst);
if !was_held {
// 路由:QA 浮窗可见时,rightOption 边沿走 QA;否则走主听写。详见 issue #118 v2。
// 例外:dictation session 已经在跑(Starting / Listening / Processing / Inserting),
// 即使 QA 浮窗被打开了,这条边沿也必须先走 dictation。否则 begin_qa_session 会
// 第二次抢同一个麦克风 device —— 在 Linux/PipeWire 上甚至会成功打开两路捕获,
// dictation 的 recorder 没人停;在 macOS/Windows 上 cpal 会拒绝第二次 build_input_stream
// 但 dictation session 仍在跑、用户找不到从 QA 面板停掉它的入口。审计 3.3.1。
let dictation_active = !matches!(inner.state.lock().phase, SessionPhase::Idle);
let panel_visible = inner.qa_state.lock().panel_visible;
if panel_visible {
if panel_visible && !dictation_active {
handle_qa_option_edge(inner).await;
} else {
handle_pressed(inner).await;
@@ -48,8 +54,12 @@ pub(super) async fn handle_released_edge(inner: &Arc<Inner>) {
let was_held = inner.hotkey_trigger_held.swap(false, Ordering::SeqCst);
if was_held {
// QA 浮窗可见时,Option 行为是 press-toggle(不分 hold/release),release 边沿忽略。
// 与 handle_pressed_edge 的路由对称:dictation session 在跑时 Pressed 已经被路由到
// dictation,那 Released 必须也路由到 dictation —— 否则 Hold 模式松开热键时
// end_session 不会触发,dictation 永远停不下来。审计 3.3.1。
let dictation_active = !matches!(inner.state.lock().phase, SessionPhase::Idle);
let panel_visible = inner.qa_state.lock().panel_visible;
if panel_visible {
if panel_visible && !dictation_active {
return;
}
handle_released(inner).await;
@@ -2,7 +2,7 @@ use std::sync::Arc;
use tauri::Emitter;
use crate::coordinator_state::{initial_session_id, SessionId};
use crate::coordinator_state::{initial_session_id, SessionId, SessionPhase};
use crate::selection::SelectionContext;
use crate::types::CapsuleState;
@@ -86,9 +86,16 @@ pub(super) fn open_qa_panel(inner: &Arc<Inner>) {
state.selection = None;
state.front_app = capture_frontmost_app();
}
// 先把胶囊清干净,避免主听写上一次 Done 状态残留的 message/insertedChars
// 在 QA Done 阶段被 capsule UI 错误复用("已之一粘贴这个 0" 那种)。
emit_capsule(inner, CapsuleState::Idle, 0.0, 0, None, None);
// 主听写 phase 是 Idle 才需要 sweep capsule —— 这里的语义是清掉「上一次 dictation
// Done 状态残留」的 message / insertedChars,让 QA 自己的 capsule 状态从干净起跑
// (否则 capsule UI 会出现 "已粘贴这个 0" 之类把上一次 inserted_chars 错误复用的
// 显示)。但如果 dictation 当前正处于 Recording / Polishing / Inserting / Done toast
// 显示中,强行 emit Idle 会把用户没看完的反馈抹掉、或者把 Polishing 中的进度条
// 卡死。审计 3.3.4。
let dictation_idle = matches!(inner.state.lock().phase, SessionPhase::Idle);
if dictation_idle {
emit_capsule(inner, CapsuleState::Idle, 0.0, 0, None, None);
}
if let Some(app) = inner.app.lock().clone() {
crate::show_qa_window(&app, "idle");
let _ = app.emit_to(