fix(history): bulk delete + clear-all now persist to SQLite

The Phase 9 bulk-delete path passed UUID strings to deleteFromHistory(index),
which expected an integer; JS coerced the string to NaN and splice(NaN, 1)
collapsed to splice(0, 1), so bulk-delete silently removed the first N visible
rows instead of the selected ones, then fired delete_transcript against the
wrong IDs.

Clear-all called saveHistory(), which was a no-op stub left over from the
same incomplete-refactor pattern that produced the manualTags persistence bug
fixed in 7eb52d9. The in-memory array was emptied, but SQLite still held
every transcript, so they reappeared on next loadHistory().

- Add deleteFromHistoryById(id) next to the index-keyed deleteFromHistory.
- bulkDelete now calls deleteFromHistoryById.
- clearAll now awaits an explicit per-id delete loop and surfaces a toast on
  partial failure.
- Remove the saveHistory() stub and its sole caller's import.

https://claude.ai/code/session_0189xUb6ie6t9qHkzatGZ9Rb
This commit is contained in:
Claude
2026-04-25 06:54:01 +00:00
parent cc77befda8
commit dfa6457f1f
2 changed files with 25 additions and 6 deletions

View File

@@ -4,9 +4,9 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { import {
history, history,
saveHistory,
saveTranscriptMeta, saveTranscriptMeta,
deleteFromHistory, deleteFromHistory,
deleteFromHistoryById,
renameHistoryEntry, renameHistoryEntry,
} from "$lib/stores/page.svelte.js"; } from "$lib/stores/page.svelte.js";
import { toasts } from "$lib/stores/toasts.svelte.js"; import { toasts } from "$lib/stores/toasts.svelte.js";
@@ -228,12 +228,27 @@
return items; return items;
}); });
function clearAll() { async function clearAll() {
if (!confirm("Delete all history? This can't be undone.")) return; if (!confirm("Delete all history? This can't be undone.")) return;
const ids = history.map((entry) => entry.id);
history.splice(0); history.splice(0);
saveHistory();
expandedId = null; expandedId = null;
stopPlayback(); stopPlayback();
let failed = 0;
for (const id of ids) {
try {
await invoke("delete_transcript", { id: String(id) });
} catch (err) {
failed++;
console.warn("clearAll: SQLite delete failed", id, err);
}
}
if (failed > 0) {
toasts.error(
"Some history entries could not be deleted",
`${failed} of ${ids.length} failed to delete from disk and may reappear after restart.`,
);
}
} }
function toggleExpand(id) { function toggleExpand(id) {
@@ -453,7 +468,7 @@
`Delete ${selected.size} transcript${selected.size === 1 ? "" : "s"}?`, `Delete ${selected.size} transcript${selected.size === 1 ? "" : "s"}?`,
); );
if (!yes) return; if (!yes) return;
for (const id of selected) deleteFromHistory(id); for (const id of selected) deleteFromHistoryById(id);
clearSelection(); clearSelection();
} }

View File

@@ -223,8 +223,6 @@ if (typeof window !== "undefined" && hasTauriRuntime()) {
loadHistory().catch(() => {}); loadHistory().catch(() => {});
} }
export function saveHistory() {}
export async function addToHistory(entry: TranscriptWriteEntry) { export async function addToHistory(entry: TranscriptWriteEntry) {
const normalised = normaliseTranscriptEntry(entry); const normalised = normaliseTranscriptEntry(entry);
history.unshift(normalised); history.unshift(normalised);
@@ -323,6 +321,12 @@ export function deleteFromHistory(index: number) {
.catch((err) => console.warn("deleteFromHistory: SQLite delete failed", err)); .catch((err) => console.warn("deleteFromHistory: SQLite delete failed", err));
} }
export function deleteFromHistoryById(id: string) {
const idx = history.findIndex((entry) => String(entry.id) === String(id));
if (idx === -1) return;
deleteFromHistory(idx);
}
export const tasks = $state<TaskEntry[]>([]); export const tasks = $state<TaskEntry[]>([]);
function mapTaskRow(row: TaskDto): TaskEntry { function mapTaskRow(row: TaskDto): TaskEntry {