Skip to content

fix(whisper): fix recording restart and stuck transcribing#51

Merged
wilcorrea merged 2 commits intomainfrom
fix/whisper-recording-restart
Mar 11, 2026
Merged

fix(whisper): fix recording restart and stuck transcribing#51
wilcorrea merged 2 commits intomainfrom
fix/whisper-recording-restart

Conversation

@wilcorrea
Copy link
Copy Markdown
Contributor

@wilcorrea wilcorrea commented Mar 11, 2026

Summary

  • Prevent recording restart during transcription: Add IsTranscribing state flag checked by the toggle shortcut, so pressing the shortcut while transcription is running is a no-op instead of starting a new recording
  • Surface transcription errors: handleStop now shows the error state with retry/close options when stop_and_transcribe fails, instead of leaving the UI stuck on "transcribing" forever

Test plan

  • Start recording via shortcut → press shortcut again → should stop and transcribe (not restart)
  • During transcription, press shortcut → nothing happens (no restart)
  • Start recording with no model loaded → press stop → should show error (not stuck spinner)
  • After transcription completes/fails, shortcut works normally to start new recording
  • Cancel recording still works as before

Summary by CodeRabbit

Release Notes

  • New Features

    • Added safeguards to prevent recording from starting while transcription is in progress, avoiding potential conflicts.
  • Bug Fixes

    • Transcription errors are now properly displayed as user-friendly messages in the app interface instead of being silently logged.

Add IsTranscribing state flag that blocks the toggle shortcut from
starting a new recording while transcription is still in progress.
Show error state with retry/close options when stop_and_transcribe
fails, instead of leaving the UI stuck on "transcribing" forever.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

A new transcription state tracking mechanism is introduced to prevent concurrent recording and transcription operations. The IsTranscribing state is initialized, managed through the transcription lifecycle, and guards prevent recording start during active transcription. Error handling in the UI is updated to display trimmed error messages.

Changes

Cohort / File(s) Summary
Backend State Management
apps/tauri/src-tauri/src/lib.rs
Introduces new IsTranscribing public state type wrapping an Arc<AtomicBool> and initializes it in the Tauri app builder.
Transcription Command Handlers
apps/tauri/src-tauri/src/whisper/commands.rs
Updates cancel_recording to clear IsTranscribing state; refactors stop_and_transcribe to set IsTranscribing true at start and false at end, with added guard preventing recording while transcription is active.
Frontend Error Handling
apps/tauri/src/WhisperApp.tsx
Modifies stop_and_transcribe error handler to store trimmed error message and switch UI to error view instead of console logging.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as Frontend UI
    participant RecordHandler as Record Handler
    participant StateManager as State Manager
    participant TranscribeHandler as Transcribe Handler

    User->>UI: Click stop & transcribe
    UI->>StateManager: Check IsTranscribing state
    StateManager-->>UI: IsTranscribing = false
    UI->>RecordHandler: Verify not currently recording
    RecordHandler->>StateManager: Set IsTranscribing = true
    StateManager-->>RecordHandler: State updated
    RecordHandler->>TranscribeHandler: Start transcription
    TranscribeHandler->>TranscribeHandler: Process audio
    TranscribeHandler-->>RecordHandler: Transcription complete/error
    RecordHandler->>StateManager: Set IsTranscribing = false
    StateManager-->>RecordHandler: State reset
    RecordHandler-->>UI: Result (success/error)
    alt Transcription Success
        UI->>UI: Display transcribed text
    else Transcription Error
        UI->>UI: Display trimmed error message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A transcriber's dance, so synchronized and true,
No double recordings to muddy the queue!
With state guards standing watch, preventing the clash,
One task at a time—no more audio hash! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main changes: preventing recording restart during transcription and fixing the stuck transcribing state by surfacing errors.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/whisper-recording-restart

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
apps/tauri/src-tauri/src/whisper/commands.rs (1)

107-142: Good cleanup pattern; however, error path emits event AND returns Err causing duplicate frontend handling.

The closure-based approach with guaranteed cleanup at line 141 is solid—IsTranscribing resets regardless of success or failure.

However, lines 134-136 emit transcription-error AND return Err(e). The frontend both listens for the event (line 130-134 in WhisperApp.tsx) and catches the invoke error (lines 79-84). Both handlers set the same state, which is redundant. Consider either:

  1. Only emit the event and return Ok(String::new()) on error, or
  2. Only return Err and let the catch block handle it, removing the event emission.

This isn't breaking—React batches the duplicate state updates—but it creates unnecessary coupling.

♻️ Option: Remove event emission on error path
         match transcriber.transcribe(&audio) {
             Ok(text) => {
                 let _ = app.emit("transcription-complete", text.clone());
                 Ok(text)
             }
             Err(e) => {
-                let _ = app.emit("transcription-error", e.clone());
                 Err(e)
             }
         }

Then the frontend relies solely on the catch block for error handling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src-tauri/src/whisper/commands.rs` around lines 107 - 142, The
error path currently both emits the "transcription-error" event and returns Err
from the closure (see transcriber.transcribe() match and the
emit("transcription-error") call), causing duplicate frontend handling; remove
the event emission on the Err branch and only return Err(e) so the
caller/invoker handles the error (keep the existing IsTranscribing reset logic
and the successful-path emit("transcription-complete") intact).
apps/tauri/src-tauri/src/lib.rs (1)

759-762: Guard only protects the shortcut path; direct command calls bypass it.

The IsTranscribing check prevents restarting via keyboard shortcut, but start_recording, start_recording_button_mode, and start_recording_field_mode Tauri commands (see apps/tauri/src-tauri/src/whisper/commands.rs lines 16-78) do not check this flag. If any caller invokes these commands directly while transcription is active, recording will start anyway.

The current UI flow naturally prevents this (no record buttons during transcription), so this may be acceptable. Consider adding the guard to the command functions for API consistency if other consumers may call them.

♻️ Proposed fix to add guard in start_recording

In apps/tauri/src-tauri/src/whisper/commands.rs, add the check:

 #[tauri::command]
 pub fn start_recording(
     state: tauri::State<RecorderState>,
     app: tauri::AppHandle,
 ) -> Result<(), String> {
     let is_recording = app.state::<crate::IsRecording>();
     if is_recording.0.load(Ordering::Relaxed) {
         return Err("Already recording".to_string());
     }
+    let is_transcribing = app.state::<crate::IsTranscribing>();
+    if is_transcribing.0.load(Ordering::Relaxed) {
+        return Err("Transcription in progress".to_string());
+    }

     let app_data_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src-tauri/src/lib.rs` around lines 759 - 762, The IsTranscribing
guard in lib.rs only protects the keyboard shortcut path; add the same guard to
the Tauri command handlers so direct API calls cannot start a new recording
while transcription is active: in apps/tauri/src-tauri/src/whisper/commands.rs
update start_recording, start_recording_button_mode, and
start_recording_field_mode to read the IsTranscribing state
(handle.state::<IsTranscribing>() or via a passed tauri::State<IsTranscribing>),
check its AtomicBool with .load(Ordering::Relaxed), and return early (no-op)
when true before proceeding to start recording.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/tauri/src-tauri/src/lib.rs`:
- Around line 759-762: The IsTranscribing guard in lib.rs only protects the
keyboard shortcut path; add the same guard to the Tauri command handlers so
direct API calls cannot start a new recording while transcription is active: in
apps/tauri/src-tauri/src/whisper/commands.rs update start_recording,
start_recording_button_mode, and start_recording_field_mode to read the
IsTranscribing state (handle.state::<IsTranscribing>() or via a passed
tauri::State<IsTranscribing>), check its AtomicBool with
.load(Ordering::Relaxed), and return early (no-op) when true before proceeding
to start recording.

In `@apps/tauri/src-tauri/src/whisper/commands.rs`:
- Around line 107-142: The error path currently both emits the
"transcription-error" event and returns Err from the closure (see
transcriber.transcribe() match and the emit("transcription-error") call),
causing duplicate frontend handling; remove the event emission on the Err branch
and only return Err(e) so the caller/invoker handles the error (keep the
existing IsTranscribing reset logic and the successful-path
emit("transcription-complete") intact).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7fd58c1e-d6f6-48ea-a10d-484ea3766fec

📥 Commits

Reviewing files that changed from the base of the PR and between 21cff37 and 3ac0ba9.

📒 Files selected for processing (3)
  • apps/tauri/src-tauri/src/lib.rs
  • apps/tauri/src-tauri/src/whisper/commands.rs
  • apps/tauri/src/WhisperApp.tsx

@wilcorrea wilcorrea merged commit e09f359 into main Mar 11, 2026
1 check passed
@wilcorrea wilcorrea deleted the fix/whisper-recording-restart branch March 11, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant