fix(#3169): pin capture backend at mic start (_activeCaptureMode) — toggle-mid-record safety (Codex review #2)
Codex round-2: _stopMic and mediaRecorder.onstop read the CURRENT _rawAudioMode to choose backend/dispatch, but the recording was started on the OLD mode — so toggling Settings→Sound mid-recording could stop the wrong backend (orphaning the other) or dispatch raw-vs-transcribe wrongly. Pin _activeCaptureMode (speech | media-raw | media-transcribe) at start; _stopMic + onstop use it. Adds front-end source-invariant regression tests. Co-authored-by: lucasrc <lrclucas@gmail.com>
This commit is contained in:
@@ -444,6 +444,10 @@ $('btnAttach').onclick=e=>{if(e&&e.preventDefault)e.preventDefault();$('fileInpu
|
||||
|
||||
// Raw audio mode preference: send audio file instead of transcribing
|
||||
let _rawAudioMode = localStorage.getItem('hermes-raw-audio-mode') === 'true';
|
||||
// Capture backend pinned at recording start ('speech' | 'media' | null) so
|
||||
// _stopMic / onstop act on the backend that actually started, even if the
|
||||
// raw-audio toggle changes mid-recording (#3169 Codex review).
|
||||
let _activeCaptureMode = null;
|
||||
|
||||
const btn=$('btnMic');
|
||||
const status=$('micStatus');
|
||||
@@ -549,7 +553,11 @@ $('btnAttach').onclick=e=>{if(e&&e.preventDefault)e.preventDefault();$('fileInpu
|
||||
|
||||
function _stopMic(){
|
||||
if(!window._micActive) return;
|
||||
if(recognition && !_rawAudioMode){
|
||||
// Stop the backend that was ACTIVE WHEN RECORDING STARTED — not whatever
|
||||
// _rawAudioMode says now. The user can toggle Settings → Sound mid-recording,
|
||||
// which would otherwise make us stop the wrong backend and orphan the other
|
||||
// (#3169 Codex review). _activeCaptureMode is pinned at start.
|
||||
if(recognition && _activeCaptureMode==='speech'){
|
||||
recognition.stop();
|
||||
return;
|
||||
}
|
||||
@@ -630,6 +638,7 @@ $('btnAttach').onclick=e=>{if(e&&e.preventDefault)e.preventDefault();$('fileInpu
|
||||
_finalText='';
|
||||
_prefix=ta.value;
|
||||
if(recognition && !_forceMediaRecorder && !_rawAudioMode){
|
||||
_activeCaptureMode='speech';
|
||||
recognition.start();
|
||||
_setRecording(true);
|
||||
return;
|
||||
@@ -659,7 +668,7 @@ $('btnAttach').onclick=e=>{if(e&&e.preventDefault)e.preventDefault();$('fileInpu
|
||||
_setRecording(false);
|
||||
_stopTracks();
|
||||
if(blob.size){
|
||||
if(_rawAudioMode){
|
||||
if(_activeCaptureMode==='media-raw'){
|
||||
await _sendRawAudio(blob);
|
||||
}else{
|
||||
await _transcribeBlob(blob);
|
||||
@@ -669,6 +678,7 @@ $('btnAttach').onclick=e=>{if(e&&e.preventDefault)e.preventDefault();$('fileInpu
|
||||
window._micPendingSend=false;
|
||||
}
|
||||
};
|
||||
_activeCaptureMode=_rawAudioMode?'media-raw':'media-transcribe';
|
||||
mediaRecorder.start();
|
||||
_setRecording(true);
|
||||
}catch(err){
|
||||
|
||||
@@ -141,3 +141,36 @@ def test_raw_audio_upload_different_formats():
|
||||
except KeyError:
|
||||
pass
|
||||
assert handler.status is not None, f"Failed for {filename} ({mime})"
|
||||
|
||||
|
||||
# ── Front-end: raw-audio mic backend pinning (boot.js source invariants) ──────
|
||||
# Regression guard for the #3169 Codex-review fix: _stopMic / onstop must act on
|
||||
# the capture backend that was ACTIVE WHEN RECORDING STARTED (pinned in
|
||||
# _activeCaptureMode), not whatever _rawAudioMode says now — otherwise toggling
|
||||
# Settings → Sound mid-recording orphans the wrong backend. Also: an explicit
|
||||
# Send click (_micPendingSend) must send even with text in the composer.
|
||||
import pathlib as _pathlib
|
||||
|
||||
_BOOT_JS = (_pathlib.Path(__file__).parent.parent / "static" / "boot.js").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_stop_mic_uses_pinned_active_capture_mode_not_current_rawmode():
|
||||
assert "let _activeCaptureMode" in _BOOT_JS, "_activeCaptureMode must be declared"
|
||||
# _stopMic decides backend from the pinned mode, not _rawAudioMode.
|
||||
assert "_activeCaptureMode==='speech'" in _BOOT_JS
|
||||
# onstop dispatches raw-vs-transcribe from the pinned mode too.
|
||||
assert "_activeCaptureMode==='media-raw'" in _BOOT_JS
|
||||
# the mode is pinned at both start branches.
|
||||
assert "_activeCaptureMode='speech'" in _BOOT_JS
|
||||
assert "_activeCaptureMode=_rawAudioMode?'media-raw':'media-transcribe'" in _BOOT_JS
|
||||
|
||||
|
||||
def test_send_raw_audio_honors_explicit_pending_send():
|
||||
# An explicit Send-button click (sets _micPendingSend) must send the raw
|
||||
# audio even when the composer already has text.
|
||||
assert "if(window._micPendingSend){" in _BOOT_JS
|
||||
# and it lives inside _sendRawAudio (before the empty-composer fallback).
|
||||
idx = _BOOT_JS.index("async function _sendRawAudio")
|
||||
end = _BOOT_JS.index("function _commitTranscript", idx)
|
||||
body = _BOOT_JS[idx:end]
|
||||
assert "window._micPendingSend" in body and "send()" in body
|
||||
|
||||
Reference in New Issue
Block a user