From 869a5b103535aaca58e14975ff0bb4fef5769a38 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:30:30 +0200 Subject: [PATCH] fix: stop voice mode listening after leaving the chat page (#30405) (#30417) Leaving the chat with voice mode active (Admin Panel, Workspace, Notes) left a hidden recorder running in the tab. The voice mode flag stays on when the chat page unmounts, so the overlay's teardown restarted recording on the destroyed component, which kept transcribing speech and sending prompts from other pages. Returning to the chat also opened an extra microphone stream that was never closed. The overlay now marks itself destroyed on teardown and never starts or restarts recording after that, so leaving the chat shuts the microphone down fully. Resetting the voice mode flag in the chat page cleanup was considered, but it changes behaviour for the controls panel on return and still runs after the overlay's own teardown. Verified in Chromium with a fake microphone (14s per phase, transcription requests / live mic tracks): | Phase | before | after | |---|---|---| | Call on chat | 3 / 1 | 3 / 1 | | After moving to Workspace | 3 / 1 | 0 / 0 | | Voice mode reopened | 3 / 3 | 3 / 1 | Fixes #30405 --- src/lib/components/chat/MessageInput/CallOverlay.svelte | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/MessageInput/CallOverlay.svelte b/src/lib/components/chat/MessageInput/CallOverlay.svelte index b4379b57f9..1340050a4a 100644 --- a/src/lib/components/chat/MessageInput/CallOverlay.svelte +++ b/src/lib/components/chat/MessageInput/CallOverlay.svelte @@ -44,6 +44,7 @@ let mediaRecorder; let audioStream = null; let audioChunks = []; + let destroyed = false; let videoInputDevices = []; let selectedVideoInputDeviceId = null; @@ -184,7 +185,8 @@ }; const stopRecordingCallback = async (_continue = true) => { - if ($showCallOverlay) { + // $showCallOverlay stays true when the chat page unmounts + if ($showCallOverlay && !destroyed) { console.log('%c%s', 'color: red; font-size: 20px;', '🚨 stopRecordingCallback 🚨'); // deep copy the audioChunks array @@ -231,7 +233,7 @@ }; const startRecording = async () => { - if ($showCallOverlay) { + if ($showCallOverlay && !destroyed) { if (!audioStream) { audioStream = await navigator.mediaDevices.getUserMedia({ audio: { @@ -776,6 +778,7 @@ }); onDestroy(async () => { + destroyed = true; await stopAllAudio(); await stopRecordingCallback(false); await stopCamera();