refactor(state): drop kon_history localStorage cache — SQLite canonical
This commit is contained in:
@@ -75,55 +75,79 @@ export function saveProfiles() {
|
|||||||
} catch {}
|
} 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
|
// The canonical store is the SQLite transcripts table reachable via the
|
||||||
// transcripts table reachable via the `add_transcript`, `list_transcripts`,
|
// `add_transcript`, `list_transcripts`, `update_transcript`,
|
||||||
// `update_transcript`, `delete_transcript`, `search_transcripts` Tauri
|
// `delete_transcript`, `search_transcripts` Tauri commands. The history
|
||||||
// commands. localStorage continues to back the in-memory `history` array
|
// array is seeded empty on module init and hydrated from SQLite on boot
|
||||||
// for UI snappiness and offline browser-preview support, but the source
|
// in the desktop runtime. Browser preview (no Tauri) shows an empty list.
|
||||||
// of truth is SQLite. Browser preview falls back to localStorage only.
|
//
|
||||||
|
// 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 { invoke } from "@tauri-apps/api/core";
|
||||||
import { hasTauriRuntime } from "$lib/utils/runtime.js";
|
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 {
|
try {
|
||||||
const raw = localStorage.getItem(HISTORY_KEY);
|
const rows = await invoke("list_transcripts", { limit: 500, offset: 0 });
|
||||||
if (raw) return JSON.parse(raw);
|
const mapped = Array.isArray(rows) ? rows.map(mapTranscriptRow) : [];
|
||||||
} catch {}
|
history.splice(0, history.length, ...mapped);
|
||||||
return [];
|
} 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());
|
// Hydrate history from SQLite on boot. Mirrors the tasks hydration pattern
|
||||||
|
// further down this file.
|
||||||
// Keep the in-memory history in sync with edits made by sibling windows
|
if (typeof window !== "undefined" && hasTauriRuntime()) {
|
||||||
// (e.g. the viewer saving a cleaned-up transcript). Storage events fire
|
loadHistory().catch(() => {});
|
||||||
// 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 {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveHistory() {
|
// `saveHistory` is retained as a no-op for callers in HistoryPage that use
|
||||||
try {
|
// it as a mutation hook (tag add/remove, clear-all). SQLite writes happen
|
||||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
// via the specific `add_transcript` / `update_transcript` /
|
||||||
} catch {}
|
// `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
|
* Add a transcript to history. Writes to SQLite (canonical) and updates
|
||||||
* localStorage (cache). The SQLite write is best-effort: if it fails,
|
* the in-memory `history` array. SQLite is best-effort: if it fails we
|
||||||
* we still update the in-memory + localStorage copy so the UI stays
|
* keep the in-memory copy so the UI stays responsive for the session,
|
||||||
* responsive. The next session boot reconciles by reading SQLite first.
|
* but the entry will not survive a restart.
|
||||||
*
|
*
|
||||||
* `entry` shape:
|
* `entry` shape:
|
||||||
* { id, text, source?, title?, audioPath?, duration?, engine?, modelId?,
|
* { id, text, source?, title?, audioPath?, duration?, engine?, modelId?,
|
||||||
@@ -132,10 +156,9 @@ export function saveHistory() {
|
|||||||
*/
|
*/
|
||||||
export async function addToHistory(entry) {
|
export async function addToHistory(entry) {
|
||||||
history.unshift(entry);
|
history.unshift(entry);
|
||||||
if (history.length > 100) history.length = 100;
|
if (history.length > 500) history.length = 500;
|
||||||
saveHistory();
|
|
||||||
|
|
||||||
if (!hasTauriRuntime()) return; // Browser preview: localStorage only.
|
if (!hasTauriRuntime()) return; // Browser preview: in-memory only.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke("add_transcript", {
|
await invoke("add_transcript", {
|
||||||
@@ -158,22 +181,20 @@ export async function addToHistory(entry) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} 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
|
* Update text and/or title of an existing transcript. Persists to SQLite
|
||||||
* via update_transcript; updates the in-memory + localStorage cache.
|
* via update_transcript; updates the in-memory entry. Closes the historic
|
||||||
* Closes the historic "rename never persists" bug from
|
* "rename never persists" bug from architecture-review.md §13.
|
||||||
* architecture-review.md §13.
|
|
||||||
*/
|
*/
|
||||||
export async function renameHistoryEntry(id, updates) {
|
export async function renameHistoryEntry(id, updates) {
|
||||||
const idx = history.findIndex(h => String(h.id) === String(id));
|
const idx = history.findIndex(h => String(h.id) === String(id));
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
if (updates.title !== undefined) history[idx].title = updates.title;
|
if (updates.title !== undefined) history[idx].title = updates.title;
|
||||||
if (updates.text !== undefined) history[idx].text = updates.text;
|
if (updates.text !== undefined) history[idx].text = updates.text;
|
||||||
saveHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasTauriRuntime()) return;
|
if (!hasTauriRuntime()) return;
|
||||||
@@ -193,7 +214,6 @@ export async function renameHistoryEntry(id, updates) {
|
|||||||
export function deleteFromHistory(index) {
|
export function deleteFromHistory(index) {
|
||||||
const entry = history[index];
|
const entry = history[index];
|
||||||
history.splice(index, 1);
|
history.splice(index, 1);
|
||||||
saveHistory();
|
|
||||||
|
|
||||||
if (!hasTauriRuntime() || !entry?.id) return;
|
if (!hasTauriRuntime() || !entry?.id) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user