qa: restore boot, wire dead error rx, harden storage and config
Front-to-back QC pass turned up two independent missing-module
showstoppers (workspace did not compile; frontend did not load) plus a
handful of HANDOVER-claimed features that were wired but dead. Fixes:
P0 — gets the app booting again:
- Add the never-committed src/lib/utils/runtime.js (hasTauriRuntime
detection); 5 imports were resolving to nothing.
- Add the never-committed crates/audio/src/streaming_resample.rs
(rubato-backed StreamingResampler with new/push_samples/flush);
declared in lib.rs and used 3x by live.rs but had no impl.
- Drop the duplicate hasTauriRuntime import in routes/+layout.svelte.
- Allow the transcript-viewer window to use the default capability
(was missing from capabilities/default.json:windows, so the viewer
window could open but not invoke any Tauri command).
P1 — features documented as working but actually dead:
- Pump MicrophoneCapture::take_error_rx() into LiveStatusMessage::
Warning each loop iteration in commands/live.rs. The HANDOVER
promised cpal stream errors would surface as toasts; the channel
was created and never read.
- Replace .expect() on the WebKit media-permission setup with a
logged warning. Failure no longer aborts the whole process.
- Toast on save_preferences failure (preferences.svelte.js had a
silent console.error — now warns once per failure run via the
existing toasts store).
P2 — correctness/robustness:
- add_dictionary_entry: switch INSERT OR IGNORE to ON CONFLICT
DO UPDATE ... RETURNING id so duplicate terms get the real row id
instead of a stale auto-increment.
- search_transcripts: qualify ORDER BY fts.rank.
- InsertTranscriptParams + TranscriptRow: bump sample_rate /
audio_channels from i32 to i64 to match the Tauri DTO and avoid
silent truncation at the boundary.
- Drop the unused tauri-plugin-mcp dependency.
- Promote sqlx in src-tauri/Cargo.toml from linux-only to
unconditional (lib.rs names sqlx::SqlitePool unconditionally —
macOS/Windows builds were latently broken).
- hotkey/linux.rs: stop panicking the hotplug task on inotify
failure; degrade to "no hotplug" with a stderr warning.
- layout.svelte: store the global error/unhandledrejection handler
refs and remove them in onDestroy so HMR/window teardown doesn't
leak listeners.
Verified: cargo check -p kon-core -p kon-storage -p kon-cloud-providers
passes. cargo check on src-tauri/kon-audio/kon-hotkey requires alsa +
gtk system libs not present in this sandbox; their changes are
syntactically and type-checked against the rest of the workspace.
svelte-check requires npm install which is not available here.
https://claude.ai/code/session_018ozAs4UcRC8jbJbddqJtEw
This commit is contained in:
@@ -254,11 +254,16 @@ fn run_live_session(
|
||||
status_channel: Channel<LiveStatusMessage>,
|
||||
stop_flag: Arc<AtomicBool>,
|
||||
) -> Result<LiveSessionSummary, String> {
|
||||
let (capture, rx) = match config.microphone_device.as_deref() {
|
||||
let (mut capture, rx) = match config.microphone_device.as_deref() {
|
||||
Some(name) if !name.is_empty() => MicrophoneCapture::start_with_device(name),
|
||||
_ => MicrophoneCapture::start(),
|
||||
}
|
||||
.map_err(|e| e.to_string())?;
|
||||
// Drain runtime stream errors into the status channel so the user
|
||||
// gets a toast when the device disconnects mid-recording instead of
|
||||
// silently producing empty transcripts. The `_capture` binding keeps
|
||||
// the cpal stream alive for the duration of the session.
|
||||
let mic_error_rx = capture.take_error_rx();
|
||||
let _capture = capture;
|
||||
|
||||
let mut resampler: Option<StreamingResampler> = None;
|
||||
@@ -283,6 +288,22 @@ fn run_live_session(
|
||||
&status_channel,
|
||||
)? {}
|
||||
|
||||
// Surface any cpal runtime errors as warnings. Non-fatal: a hard
|
||||
// disconnect will also drop the audio sender and be caught by
|
||||
// the `Disconnected` arm below. This lets the user see a toast
|
||||
// even when cpal recovers without tearing the stream down.
|
||||
if let Some(err_rx) = &mic_error_rx {
|
||||
while let Ok(err) = err_rx.try_recv() {
|
||||
let _ = status_channel.send(LiveStatusMessage::Warning {
|
||||
session_id,
|
||||
message: format!(
|
||||
"Microphone '{}' reported an error: {}",
|
||||
err.device_name, err.message
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
match rx.recv_timeout(Duration::from_millis(25)) {
|
||||
Ok(chunk) => {
|
||||
let mono = downmix_chunk(chunk.samples, chunk.channels as usize);
|
||||
|
||||
@@ -174,7 +174,18 @@ pub fn run() {
|
||||
request.allow();
|
||||
true
|
||||
});
|
||||
}).expect("Failed to configure webview media permissions");
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
// Non-fatal: WebKitGTK may already have media
|
||||
// capture wired by some compositors, or the
|
||||
// signal binding may fail on unusual builds.
|
||||
// Falling back means getUserMedia() prompts (or
|
||||
// silently denies) instead of auto-granting,
|
||||
// which is degraded but recoverable.
|
||||
eprintln!(
|
||||
"[startup] failed to configure webview media permissions: {e}",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Close-to-tray: hide window instead of exiting
|
||||
|
||||
Reference in New Issue
Block a user