From 66733800aabed782913479bd65f09e1757a83e39 Mon Sep 17 00:00:00 2001 From: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co> Date: Fri, 10 Jul 2026 16:56:57 +0100 Subject: [PATCH] fix(dictation): block restart while prior session is still transcribing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex P2 on useLocalDictation.ts: the startRecording guard only checked isStarting/isRecording, so a new session could start during the previous session's isTranscribing window — after stopRecording() clears isRecording but before the native stopped event delivers the final transcript. The new start unlistens the old session's handlers before its last words arrive, dropping them on a quick re-press of the mic / hold of Cmd+D. Add isTranscribing to the start guard (and to the useCallback deps) so a restart waits for the prior session to fully stop. This is the canonical guard, so it covers every entry point (toggle button, Cmd+D push-to-talk, useDictation). --- .../dictation/hooks/useLocalDictation.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/desktop/src/features/dictation/hooks/useLocalDictation.ts b/desktop/src/features/dictation/hooks/useLocalDictation.ts index 0f8394551..f25a4e05d 100644 --- a/desktop/src/features/dictation/hooks/useLocalDictation.ts +++ b/desktop/src/features/dictation/hooks/useLocalDictation.ts @@ -279,7 +279,13 @@ export function useLocalDictation({ useEffect(() => cleanup, [cleanup]); const startRecording = useCallback(async () => { - if (!isEnabled || isStarting || isRecording) return; + // Also guard on `isTranscribing`: after `stopRecording()` the previous + // session has cleared `isRecording` but is still awaiting its native + // `stopped` event, which delivers the final transcript before its + // listeners are unregistered. Starting a new session in that window would + // unlisten the old session's handlers (below) before its last words + // arrived, dropping them. Wait for the prior session to fully stop. + if (!isEnabled || isStarting || isRecording || isTranscribing) return; // Clear abort flag for this new start attempt. startAbortedRef.current = false; @@ -481,7 +487,14 @@ export function useLocalDictation({ } finally { setIsStarting(false); } - }, [cleanup, flushAudioBatch, isEnabled, isRecording, isStarting]); + }, [ + cleanup, + flushAudioBatch, + isEnabled, + isRecording, + isStarting, + isTranscribing, + ]); const stopRecording = useCallback(() => { // Signal any in-flight startRecording to bail after its next await.