ui: Day 4 frontend — dual-write history to SQLite + persist History rename
addToHistory, renameHistoryEntry, deleteFromHistory in
src/lib/stores/page.svelte.js now dual-write to:
- the in-memory `history` array (UI snappiness, unchanged)
- localStorage (offline / browser-preview fallback, unchanged)
- SQLite via the new Tauri commands from 1cce567 (the canonical store)
The SQLite write is best-effort: failure does not lose the in-memory
copy. Console-warns for diagnostic visibility. Browser-preview path
correctly skips the Tauri call.
HistoryPage rename flow (renameItem) now calls renameHistoryEntry, which
goes through update_transcript. Closes the long-standing TODO from
architecture-review.md §13: rename was UI-only, never persisted, lost
on reload.
On rename failure a warn-toast surfaces "your change is visible now but
did not save" so the user is not surprised on next launch.
NOT WIRED YET (deferred):
- HistoryPage.searchQuery still filters in-memory rather than calling
search_transcripts. Fine for small histories; FTS5 infrastructure is
in place to upgrade when needed.
- Reading initial history from SQLite on session boot. localStorage
remains the cold-start source for now; SQLite catches up via dual-
write. A backfill / one-time sync command can land later.
This commit is contained in:
@@ -75,7 +75,17 @@ export function saveProfiles() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// ---- History (persisted to localStorage) ----
|
||||
// ---- History (dual-write: SQLite via Tauri = canonical, localStorage = read-cache) ----
|
||||
//
|
||||
// As of Day 4 of the upgrade plan, the canonical store is the SQLite
|
||||
// transcripts table reachable via the `add_transcript`, `list_transcripts`,
|
||||
// `update_transcript`, `delete_transcript`, `search_transcripts` Tauri
|
||||
// commands. localStorage continues to back the in-memory `history` array
|
||||
// for UI snappiness and offline browser-preview support, but the source
|
||||
// of truth is SQLite. Browser preview falls back to localStorage only.
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { hasTauriRuntime } from "$lib/utils/runtime.js";
|
||||
|
||||
const HISTORY_KEY = "kon_history";
|
||||
|
||||
@@ -95,15 +105,86 @@ export function saveHistory() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function addToHistory(entry) {
|
||||
/**
|
||||
* Add a transcript to history. Dual-writes to SQLite (canonical) and
|
||||
* localStorage (cache). The SQLite write is best-effort: if it fails,
|
||||
* we still update the in-memory + localStorage copy so the UI stays
|
||||
* responsive. The next session boot reconciles by reading SQLite first.
|
||||
*
|
||||
* `entry` shape:
|
||||
* { id, text, source?, title?, audioPath?, duration?, engine?, modelId?,
|
||||
* inferenceMs?, sampleRate?, audioChannels?, formatMode?,
|
||||
* removeFillers?, britishEnglish?, antiHallucination?, ...uiOnlyFields }
|
||||
*/
|
||||
export async function addToHistory(entry) {
|
||||
history.unshift(entry);
|
||||
if (history.length > 100) history.length = 100;
|
||||
saveHistory();
|
||||
|
||||
if (!hasTauriRuntime()) return; // Browser preview: localStorage only.
|
||||
|
||||
try {
|
||||
await invoke("add_transcript", {
|
||||
transcript: {
|
||||
id: String(entry.id),
|
||||
text: entry.text ?? "",
|
||||
source: entry.source ?? "microphone",
|
||||
title: entry.title ?? null,
|
||||
audioPath: entry.audioPath ?? null,
|
||||
duration: Number(entry.duration ?? 0),
|
||||
engine: entry.engine ?? null,
|
||||
modelId: entry.modelId ?? null,
|
||||
inferenceMs: entry.inferenceMs ?? null,
|
||||
sampleRate: entry.sampleRate ?? null,
|
||||
audioChannels: entry.audioChannels ?? null,
|
||||
formatMode: entry.formatMode ?? null,
|
||||
removeFillers: !!entry.removeFillers,
|
||||
britishEnglish: !!entry.britishEnglish,
|
||||
antiHallucination: !!entry.antiHallucination,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("addToHistory: SQLite dual-write failed, kept in localStorage", err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update text and/or title of an existing transcript. Persists to SQLite
|
||||
* via update_transcript; updates the in-memory + localStorage cache.
|
||||
* Closes the historic "rename never persists" bug from
|
||||
* architecture-review.md §13.
|
||||
*/
|
||||
export async function renameHistoryEntry(id, updates) {
|
||||
const idx = history.findIndex(h => String(h.id) === String(id));
|
||||
if (idx >= 0) {
|
||||
if (updates.title !== undefined) history[idx].title = updates.title;
|
||||
if (updates.text !== undefined) history[idx].text = updates.text;
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
if (!hasTauriRuntime()) return;
|
||||
|
||||
try {
|
||||
await invoke("update_transcript", {
|
||||
id: String(id),
|
||||
text: updates.text ?? null,
|
||||
title: updates.title ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("renameHistoryEntry: SQLite update failed", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteFromHistory(index) {
|
||||
const entry = history[index];
|
||||
history.splice(index, 1);
|
||||
saveHistory();
|
||||
|
||||
if (!hasTauriRuntime() || !entry?.id) return;
|
||||
|
||||
invoke("delete_transcript", { id: String(entry.id) })
|
||||
.catch(err => console.warn("deleteFromHistory: SQLite delete failed", err));
|
||||
}
|
||||
|
||||
// ---- Tasks (persisted to localStorage) ----
|
||||
|
||||
Reference in New Issue
Block a user