From 8e046c0ed3f58448c647e6f19a480a0f436db2b1 Mon Sep 17 00:00:00 2001 From: npub13fn4ahfnvaa2qwylvegdgeajqs0mph6v4qsw4jcqnw4mjh3hzh2quuucm5 <8a675edd33677aa0389f6650d467b2041fb0df4ca820eacb009babb95e3715d4@sprout-oss.stage.blox.sqprod.co> Date: Thu, 9 Jul 2026 17:48:15 +0100 Subject: [PATCH] fix(dictation): cancel pending starts when composer becomes disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The disabled-state cleanup only checked isRecording, so it never aborted a start still in isStarting (mic permission / AudioWorklet setup) or the isTranscribing grace window. If the composer became disabled mid-start (channel read-only, parent send-state disable), the pending start could finish and begin microphone capture in a disabled composer. Broaden the guard to the full ownership window (isRecording || isStarting || isTranscribing) and re-run when any flag flips, so cancelRecording() — which sets startAbortedRef to bail an in-flight start — fires for a start that completes after the disable. --- .../dictation/hooks/useComposerDictation.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/desktop/src/features/dictation/hooks/useComposerDictation.ts b/desktop/src/features/dictation/hooks/useComposerDictation.ts index 348f00ea7..9a75d6be0 100644 --- a/desktop/src/features/dictation/hooks/useComposerDictation.ts +++ b/desktop/src/features/dictation/hooks/useComposerDictation.ts @@ -110,14 +110,30 @@ export function useComposerDictation({ } }, [draftKey]); - // Auto-cancel dictation when the composer becomes disabled mid-recording + // Auto-cancel dictation when the composer becomes disabled mid-session // (e.g. channel becomes read-only, parent send state disables thread composer). // Without this, the STT session keeps running with no way to stop it. + // + // Covers the full ownership window — `isStarting` and `isTranscribing`, not + // just `isRecording`. If the composer is disabled while `startRecording()` is + // still resolving (mic permission / AudioWorklet setup), the pending start + // would otherwise finish and begin microphone capture in a disabled composer. + // `cancelRecording()` aborts the in-flight start as well as a live recording, + // and re-running when any of these flags flip catches a start that completes + // after the disable. useEffect(() => { - if (disabled && dictation.isRecording) { + const owningSession = + dictation.isRecording || dictation.isStarting || dictation.isTranscribing; + if (disabled && owningSession) { dictation.cancelRecording(); } - }, [disabled, dictation.isRecording, dictation.cancelRecording]); + }, [ + disabled, + dictation.isRecording, + dictation.isStarting, + dictation.isTranscribing, + dictation.cancelRecording, + ]); // ⌘D push-to-talk — hold to record, release to stop. // Dispatched from AppShell's keydown/keyup handlers.