fix(dictation): PR 1.4 review polish — banner re-prompt, nav flush, language restore
- Track draft-handled-this-session flag so Restore/Discard suppresses the recovery banner on page-nav round-trips within one session; resets cleanly on the next successful save. - Flush pending autosave in onDestroy so Tauri SPA navigation preserves the last <1.5s of input. - Round-trip language on Restore so a draft captured under one language doesn't silently inherit the current setting on revival. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,23 @@
|
||||
<script lang="ts" module>
|
||||
// PR 1.4 review polish: module-scoped (NOT component-scoped) so the
|
||||
// flag survives Tauri SPA navigation re-mounting this page. Once the
|
||||
// user has explicitly Restored or Discarded the recovery banner this
|
||||
// session, suppress re-prompting on subsequent in-app navigations
|
||||
// (Dictation → History → Dictation). Without this, the autosave that
|
||||
// immediately follows a Restore would re-trigger the banner on the
|
||||
// next visit. Reset to false on the next successful save (or Clear)
|
||||
// so a fresh capture-and-recovery cycle still works within the same
|
||||
// session without needing a full app restart.
|
||||
let draftHandledThisSession = false;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { Channel, invoke } from "@tauri-apps/api/core";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { page, settings, templates, profiles, addToHistory, addTask, tasks } from "$lib/stores/page.svelte.js";
|
||||
import { page, settings, saveSettings, templates, profiles, addToHistory, addTask, tasks } from "$lib/stores/page.svelte.js";
|
||||
import { markError, markGenerating, markGenerationDone, markLoading, refreshLlmStatus } from "$lib/stores/llmStatus.svelte.js";
|
||||
import { playStartCue, playStopCue, playCompleteCue } from "$lib/utils/sounds.js";
|
||||
import { profilesStore } from "$lib/stores/profiles.svelte.ts";
|
||||
@@ -91,11 +104,15 @@
|
||||
// Recovery check — runs even in non-Tauri preview so the browser
|
||||
// shell can also offer to restore. Only shows the banner if there's
|
||||
// nothing currently in the textarea (otherwise the user has live
|
||||
// content and a banner would be confusing).
|
||||
const draft = loadRecoverableDraft();
|
||||
if (draft && !transcript.trim()) {
|
||||
recoverableDraft = draft;
|
||||
recoverableDraftAgo = formatTimeAgo(draft.updatedAt);
|
||||
// content and a banner would be confusing). Also skipped if the
|
||||
// user already Restored/Discarded this session — see
|
||||
// draftHandledThisSession above.
|
||||
if (!draftHandledThisSession) {
|
||||
const draft = loadRecoverableDraft();
|
||||
if (draft && !transcript.trim()) {
|
||||
recoverableDraft = draft;
|
||||
recoverableDraftAgo = formatTimeAgo(draft.updatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
// visibilitychange + pagehide flush the autosave immediately so
|
||||
@@ -125,10 +142,11 @@
|
||||
visibilityHandler = null;
|
||||
}
|
||||
window.removeEventListener("pagehide", flushAutosave);
|
||||
if (autosaveTimer) {
|
||||
clearTimeout(autosaveTimer);
|
||||
autosaveTimer = null;
|
||||
}
|
||||
// Tauri SPA navigation (Dictation → History → Files) doesn't fire
|
||||
// pagehide or visibilitychange, so without this flush the last
|
||||
// <1.5s of pre-nav input would be lost from the draft. flushAutosave
|
||||
// also clears the timer, so no need to clear it again below.
|
||||
flushAutosave();
|
||||
clearInterval(timerInterval);
|
||||
if (page.recording) {
|
||||
page.recording = false;
|
||||
@@ -194,8 +212,16 @@
|
||||
segments = Array.isArray(recoverableDraft.segments)
|
||||
? [...recoverableDraft.segments]
|
||||
: [];
|
||||
// Round-trip the language so a draft captured under a different
|
||||
// language (e.g. user was in Spanish, switched away, came back)
|
||||
// doesn't silently inherit the current setting on revival.
|
||||
if (recoverableDraft.language) {
|
||||
settings.language = recoverableDraft.language;
|
||||
saveSettings();
|
||||
}
|
||||
recoverableDraft = null;
|
||||
recoverableDraftAgo = "";
|
||||
draftHandledThisSession = true;
|
||||
// Don't clear the localStorage entry — the autosave $effect will
|
||||
// immediately rewrite it with a fresh `updatedAt` reflecting the
|
||||
// restored state. If the app crashes between restore and next
|
||||
@@ -205,6 +231,7 @@
|
||||
function discardDraft() {
|
||||
recoverableDraft = null;
|
||||
recoverableDraftAgo = "";
|
||||
draftHandledThisSession = true;
|
||||
clearDraft();
|
||||
}
|
||||
|
||||
@@ -734,6 +761,10 @@
|
||||
return;
|
||||
}
|
||||
clearDraft();
|
||||
// Successful save closes the current draft cycle; clear the
|
||||
// session flag so a fresh capture-and-recovery cycle works
|
||||
// normally without needing a full app restart.
|
||||
draftHandledThisSession = false;
|
||||
|
||||
// Extract tasks from transcript
|
||||
const extracted = await extractTasksForTranscript(transcript);
|
||||
@@ -799,6 +830,7 @@
|
||||
// throw away.
|
||||
recoverableDraft = null;
|
||||
recoverableDraftAgo = "";
|
||||
draftHandledThisSession = false;
|
||||
clearDraft();
|
||||
}
|
||||
|
||||
@@ -876,6 +908,7 @@
|
||||
return;
|
||||
}
|
||||
clearDraft();
|
||||
draftHandledThisSession = false;
|
||||
saved = true;
|
||||
setTimeout(() => { saved = false; }, FEEDBACK_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user