@@ -1125,8 +1125,10 @@ async fn begin_session(inner: &Arc<Inner>) -> Result<(), String> {
11251125 return Err ( message) ;
11261126 }
11271127
1128- emit_capsule ( inner, CapsuleState :: Recording , 0.0 , 0 , None , None ) ;
1129-
1128+ // 不在这里 emit Recording capsule —— 让 start_recorder_for_starting 在
1129+ // Recorder::start 成功后再发,确保「用户看到录音条」时 mic 已经在 capture。
1130+ // 之前在这一行就 emit 会让用户看到录音条后立刻开口,但 mic 还在 cpal init
1131+ // 窗口(50-200ms)内 → 开头几个字物理上录不到。详见 issue 备注。
11301132 let active_asr = CredentialsVault :: get_active_asr ( ) ;
11311133
11321134 #[ cfg( target_os = "macos" ) ]
@@ -1299,6 +1301,26 @@ fn start_recorder_for_starting(
12991301 Ok ( ( rec, runtime_errors) ) => {
13001302 store_recorder_for_session ( inner, session_id, rec) ;
13011303 spawn_recorder_error_monitor ( inner, runtime_errors) ;
1304+ // ★ 录音器实际启动后再发 Recording capsule —— 避免用户「看到录音条但
1305+ // mic 还没开」的 50-200ms 窗口里开口讲话被吞(三条 ASR 路径共享)。
1306+ // ASR 连接慢的间隙由 DeferredAsrBridge 缓存 PCM,按顺序后送,不丢字。
1307+ //
1308+ // 竞态保护:必须在 stop_recorder_if_pending_start_stop 之前 emit,
1309+ // 并且仅当 recorder 真的会继续运行(phase 仍是 Starting、无待处理的
1310+ // stop / cancel)时才 emit。否则用户在 cpal init 期间松开热键时,
1311+ // stop / cancel 路径可能已经发出 Transcribing / Cancelled,本行
1312+ // 再无条件覆盖回 Recording 会让 UI 短暂闪烁错误状态(短按尤其明显)。
1313+ // Codex review (PR #289 P2) 指出。
1314+ let should_emit_recording = {
1315+ let state = inner. state . lock ( ) ;
1316+ state. session_id == session_id
1317+ && state. phase == SessionPhase :: Starting
1318+ && !state. pending_stop
1319+ && !state. cancelled
1320+ } ;
1321+ if should_emit_recording {
1322+ emit_capsule ( inner, CapsuleState :: Recording , 0.0 , 0 , None , None ) ;
1323+ }
13021324 stop_recorder_if_pending_start_stop ( inner) ;
13031325 log:: info!( "[coord] recorder started (asr={active_asr}, phase=Starting)" ) ;
13041326 }
0 commit comments