fix(recording): replace blocking dialog with toast for mic not available errors#1974
Conversation
| let is_device_not_found = message.contains("no longer available") | ||
| || message.contains("DeviceNotFound"); |
There was a problem hiding this comment.
The substring match here feels a bit brittle (casing/format can vary). Normalizing once keeps it from missing variants.
| let is_device_not_found = message.contains("no longer available") | |
| || message.contains("DeviceNotFound"); | |
| let normalized_message = message.to_lowercase(); | |
| let is_device_not_found = normalized_message.contains("no longer available") | |
| || normalized_message.contains("devicenotfound"); |
| const msg = | ||
| e instanceof Error ? e.message : String(e); |
There was a problem hiding this comment.
Worth logging the underlying error here too (like the screenshot path does) so we don’t lose stack/context when users report this.
| const msg = | |
| e instanceof Error ? e.message : String(e); | |
| const msg = e instanceof Error ? e.message : String(e); | |
| console.error("Failed to start recording", e); |
| toast.error( | ||
| "Selected microphone is not available. Please select a different microphone in settings.", | ||
| ); |
There was a problem hiding this comment.
Given the original report mentions repeated clicks/loops, adding a stable id prevents a stack of identical toasts.
| toast.error( | |
| "Selected microphone is not available. Please select a different microphone in settings.", | |
| ); | |
| toast.error( | |
| "Selected microphone is not available. Please select a different microphone in settings.", | |
| { id: "mic-device-not-found" }, | |
| ); |
| toast.error( | ||
| `Failed to start recording: ${msg}`, | ||
| ); |
There was a problem hiding this comment.
Same idea for the generic failure path — stable id keeps the UI from getting flooded if this happens repeatedly.
| toast.error( | |
| `Failed to start recording: ${msg}`, | |
| ); | |
| toast.error( | |
| `Failed to start recording: ${msg}`, | |
| { id: "start-recording-failed" }, | |
| ); |
| // DeviceNotFound errors are surfaced to the user via the frontend toast; skip the | ||
| // blocking native dialog so the overlay stays responsive and the error isn't repeated. | ||
| let is_device_not_found = message.contains("no longer available") | ||
| || message.contains("DeviceNotFound"); |
There was a problem hiding this comment.
Duplicate string-matching across Rust and TypeScript
The same two substrings ("no longer available" and "DeviceNotFound") are now hard-coded in both recording.rs (to suppress the native dialog) and target-select-overlay.tsx (to show the friendly toast). If the underlying audio library changes either error message, the two guards will diverge: the Rust side might still suppress the dialog while the TypeScript side falls through to the generic "Failed to start recording: …" message, or vice-versa. Centralising the classification — e.g., via a dedicated Tauri error variant instead of string matching — would make both sites resilient to message changes.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/recording.rs
Line: 2373-2376
Comment:
**Duplicate string-matching across Rust and TypeScript**
The same two substrings (`"no longer available"` and `"DeviceNotFound"`) are now hard-coded in both `recording.rs` (to suppress the native dialog) and `target-select-overlay.tsx` (to show the friendly toast). If the underlying audio library changes either error message, the two guards will diverge: the Rust side might still suppress the dialog while the TypeScript side falls through to the generic "Failed to start recording: …" message, or vice-versa. Centralising the classification — e.g., via a dedicated Tauri error variant instead of string matching — would make both sites resilient to message changes.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| commands | ||
| .startRecording({ | ||
| capture_target: props.target, | ||
| mode: rawOptions.mode, | ||
| capture_system_audio: rawOptions.captureSystemAudio, | ||
| }) | ||
| .catch((e: unknown) => { | ||
| const msg = | ||
| e instanceof Error ? e.message : String(e); | ||
| if ( | ||
| msg.includes("no longer available") || | ||
| msg.includes("DeviceNotFound") | ||
| ) { | ||
| toast.error( | ||
| "Selected microphone is not available. Please select a different microphone in settings.", | ||
| ); | ||
| } else { | ||
| toast.error( | ||
| `Failed to start recording: ${msg}`, | ||
| ); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Add
console.error to match the pattern used by the screenshot error handler and other catch blocks in this file, so there is always a log trail even when the toast copy differs from the raw error.
| commands | |
| .startRecording({ | |
| capture_target: props.target, | |
| mode: rawOptions.mode, | |
| capture_system_audio: rawOptions.captureSystemAudio, | |
| }) | |
| .catch((e: unknown) => { | |
| const msg = | |
| e instanceof Error ? e.message : String(e); | |
| if ( | |
| msg.includes("no longer available") || | |
| msg.includes("DeviceNotFound") | |
| ) { | |
| toast.error( | |
| "Selected microphone is not available. Please select a different microphone in settings.", | |
| ); | |
| } else { | |
| toast.error( | |
| `Failed to start recording: ${msg}`, | |
| ); | |
| } | |
| }); | |
| commands | |
| .startRecording({ | |
| capture_target: props.target, | |
| mode: rawOptions.mode, | |
| capture_system_audio: rawOptions.captureSystemAudio, | |
| }) | |
| .catch((e: unknown) => { | |
| console.error("Failed to start recording", e); | |
| const msg = | |
| e instanceof Error ? e.message : String(e); | |
| if ( | |
| msg.includes("no longer available") || | |
| msg.includes("DeviceNotFound") | |
| ) { | |
| toast.error( | |
| "Selected microphone is not available. Please select a different microphone in settings.", | |
| ); | |
| } else { | |
| toast.error( | |
| `Failed to start recording: ${msg}`, | |
| ); | |
| } | |
| }); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/target-select-overlay.tsx
Line: 1745-1766
Comment:
Add `console.error` to match the pattern used by the screenshot error handler and other catch blocks in this file, so there is always a log trail even when the toast copy differs from the raw error.
```suggestion
commands
.startRecording({
capture_target: props.target,
mode: rawOptions.mode,
capture_system_audio: rawOptions.captureSystemAudio,
})
.catch((e: unknown) => {
console.error("Failed to start recording", e);
const msg =
e instanceof Error ? e.message : String(e);
if (
msg.includes("no longer available") ||
msg.includes("DeviceNotFound")
) {
toast.error(
"Selected microphone is not available. Please select a different microphone in settings.",
);
} else {
toast.error(
`Failed to start recording: ${msg}`,
);
}
});
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
When the selected mic isn't available, a blocking native dialog caused a loop on multi-monitor setups, user dismisses, overlay re-appears, clicks again, repeat 5-7 times.
Fix: catch startRecording errors in the overlay and show a toast instead.
Skip the blocking dialog in handle_spawn_failure for DeviceNotFound so the overlay stays interactive.
Greptile Summary
This PR fixes a UX loop on multi-monitor setups where a blocking native dialog for mic-unavailable errors would repeatedly re-appear after dismissal. It replaces that dialog with a non-blocking toast on the frontend and suppresses the native dialog in the Rust backend for
DeviceNotFounderrors.recording.rs:handle_spawn_failurenow skips theMessageDialogBuilder/blocking_showpath when the error message contains"no longer available"or"DeviceNotFound", keeping the overlay interactive.target-select-overlay.tsx: A.catchhandler is added to thestartRecordingcall that shows a friendly toast for mic errors and a generic fallback for any other rejection; the same two substrings are checked independently on this side, creating a duplicated classification that could drift if either side is updated.Confidence Score: 4/5
Safe to merge — the change addresses a real, reproducible loop and the fallback path (generic toast) is still shown for any unrecognised error.
Both changed files have contained, low-risk edits. The only concerns are that the two substring guards are independently maintained in Rust and TypeScript, and the new catch block lacks a console.error call. Neither affects correctness today.
The string-matching in recording.rs and target-select-overlay.tsx should stay in sync; if the underlying audio library changes its error messages, one side will diverge silently.
Important Files Changed
.catchhandler tostartRecordingthat shows a user-friendly toast for mic-unavailable errors and a generic fallback for others; missingconsole.errorlogging and duplicates the Rust-side string matching.Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix(recording): show toast instead of bl..." | Re-trigger Greptile