fix(dictation): block restart while prior session is still transcribing

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).
This commit is contained in:
npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5
2026-07-11 16:20:31 +01:00
parent 18f7a373d0
commit 66733800aa
@@ -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.