refactor(state): drop kon_history localStorage cache — SQLite canonical
This commit is contained in:
@@ -75,55 +75,79 @@ export function saveProfiles() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// ---- History (dual-write: SQLite via Tauri = canonical, localStorage = read-cache) ----
|
||||
// ---- History (SQLite via Tauri = canonical; no localStorage 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.
|
||||
// The canonical store is the SQLite transcripts table reachable via the
|
||||
// `add_transcript`, `list_transcripts`, `update_transcript`,
|
||||
// `delete_transcript`, `search_transcripts` Tauri commands. The history
|
||||
// array is seeded empty on module init and hydrated from SQLite on boot
|
||||
// in the desktop runtime. Browser preview (no Tauri) shows an empty list.
|
||||
//
|
||||
// The previous `kon_history` localStorage read-cache was removed to kill
|
||||
// the cold-start flicker where the UI painted stale localStorage data and
|
||||
// then overwrote it from SQLite a tick later.
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { hasTauriRuntime } from "$lib/utils/runtime.js";
|
||||
import { toasts } from "$lib/stores/toasts.svelte.js";
|
||||
|
||||
const HISTORY_KEY = "kon_history";
|
||||
export const history = $state([]);
|
||||
|
||||
function loadHistory() {
|
||||
/** Shape a TranscriptDto row from `list_transcripts` into the UI entry shape
|
||||
* that HistoryPage expects. UI-only fields that were never persisted to
|
||||
* SQLite (segments, starred, manualTags, language, template) are absent. */
|
||||
function mapTranscriptRow(row) {
|
||||
const text = row.text ?? "";
|
||||
return {
|
||||
id: row.id,
|
||||
text,
|
||||
source: row.source ?? "",
|
||||
title: row.title ?? "",
|
||||
audioPath: row.audioPath ?? null,
|
||||
duration: Number(row.duration ?? 0),
|
||||
engine: row.engine ?? null,
|
||||
modelId: row.modelId ?? null,
|
||||
createdAt: row.createdAt ?? null,
|
||||
date: row.createdAt ? new Date(row.createdAt).toLocaleString("en-GB") : "",
|
||||
preview: text.slice(0, 120),
|
||||
};
|
||||
}
|
||||
|
||||
async function loadHistory() {
|
||||
if (!hasTauriRuntime()) {
|
||||
history.length = 0;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const raw = localStorage.getItem(HISTORY_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return [];
|
||||
const rows = await invoke("list_transcripts", { limit: 500, offset: 0 });
|
||||
const mapped = Array.isArray(rows) ? rows.map(mapTranscriptRow) : [];
|
||||
history.splice(0, history.length, ...mapped);
|
||||
} catch (err) {
|
||||
console.error("loadHistory failed", err);
|
||||
history.length = 0;
|
||||
toasts.error("Failed to load history", err?.message ?? String(err));
|
||||
}
|
||||
}
|
||||
|
||||
export const history = $state(loadHistory());
|
||||
|
||||
// Keep the in-memory history in sync with edits made by sibling windows
|
||||
// (e.g. the viewer saving a cleaned-up transcript). Storage events fire
|
||||
// only on *other* windows than the writer, so we won't re-enter our own
|
||||
// writes.
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("storage", (e) => {
|
||||
if (e.key !== HISTORY_KEY || !e.newValue) return;
|
||||
try {
|
||||
const next = JSON.parse(e.newValue);
|
||||
history.splice(0, history.length, ...next);
|
||||
} catch {}
|
||||
});
|
||||
// Hydrate history from SQLite on boot. Mirrors the tasks hydration pattern
|
||||
// further down this file.
|
||||
if (typeof window !== "undefined" && hasTauriRuntime()) {
|
||||
loadHistory().catch(() => {});
|
||||
}
|
||||
|
||||
export function saveHistory() {
|
||||
try {
|
||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
||||
} catch {}
|
||||
}
|
||||
// `saveHistory` is retained as a no-op for callers in HistoryPage that use
|
||||
// it as a mutation hook (tag add/remove, clear-all). SQLite writes happen
|
||||
// via the specific `add_transcript` / `update_transcript` /
|
||||
// `delete_transcript` commands in the functions below; ad-hoc UI-only
|
||||
// mutations (e.g. manualTags) are session-scoped because they are not
|
||||
// backed by a SQLite column.
|
||||
export function saveHistory() {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Add a transcript to history. Writes to SQLite (canonical) and updates
|
||||
* the in-memory `history` array. SQLite is best-effort: if it fails we
|
||||
* keep the in-memory copy so the UI stays responsive for the session,
|
||||
* but the entry will not survive a restart.
|
||||
*
|
||||
* `entry` shape:
|
||||
* { id, text, source?, title?, audioPath?, duration?, engine?, modelId?,
|
||||
@@ -132,10 +156,9 @@ export function saveHistory() {
|
||||
*/
|
||||
export async function addToHistory(entry) {
|
||||
history.unshift(entry);
|
||||
if (history.length > 100) history.length = 100;
|
||||
saveHistory();
|
||||
if (history.length > 500) history.length = 500;
|
||||
|
||||
if (!hasTauriRuntime()) return; // Browser preview: localStorage only.
|
||||
if (!hasTauriRuntime()) return; // Browser preview: in-memory only.
|
||||
|
||||
try {
|
||||
await invoke("add_transcript", {
|
||||
@@ -158,22 +181,20 @@ export async function addToHistory(entry) {
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("addToHistory: SQLite dual-write failed, kept in localStorage", err);
|
||||
console.warn("addToHistory: SQLite write failed, entry will not persist past restart", 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.
|
||||
* via update_transcript; updates the in-memory entry. 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;
|
||||
@@ -193,7 +214,6 @@ export async function renameHistoryEntry(id, updates) {
|
||||
export function deleteFromHistory(index) {
|
||||
const entry = history[index];
|
||||
history.splice(index, 1);
|
||||
saveHistory();
|
||||
|
||||
if (!hasTauriRuntime() || !entry?.id) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user