diff --git a/src/lib/pages/FirstRunPage.svelte b/src/lib/pages/FirstRunPage.svelte index 28cfaf6..ae4955f 100644 --- a/src/lib/pages/FirstRunPage.svelte +++ b/src/lib/pages/FirstRunPage.svelte @@ -43,15 +43,23 @@ downloadProgress = 0; downloadStartedAt = Date.now(); - const unlisten = await listen("model-download-progress", (event) => { - downloadProgress = event.payload.percent; - }); - - const unlistenParakeet = await listen("parakeet-download-progress", (event) => { - downloadProgress = event.payload.percent; - }); + // Tauri unlisten handles must be released on every exit path — + // declared outside the try so the finally can always call them, + // but assigned inside so a throw on the second listen() doesn't + // leak the first one. SettingsPage.svelte:864-880 is the + // long-form reference pattern (onMount + onDestroy). + let unlisten: (() => void) | undefined; + let unlistenParakeet: (() => void) | undefined; try { + unlisten = await listen("model-download-progress", (event) => { + downloadProgress = event.payload.percent; + }); + + unlistenParakeet = await listen("parakeet-download-progress", (event) => { + downloadProgress = event.payload.percent; + }); + if (modelId.startsWith("whisper-")) { // backend's whisper_model_id accepts the full model id via its // `other => ModelId::new(other)` fallback, so pass the id through @@ -90,8 +98,11 @@ error = `Download failed: ${e}`; downloading = false; } finally { - unlisten(); - unlistenParakeet(); + // Guard against listen() throwing before assigning the unlisten + // handle — without these checks the finally itself would throw + // and mask the original error. + if (unlisten) unlisten(); + if (unlistenParakeet) unlistenParakeet(); } }