refactor(state): drop kon_tasks localStorage cache — Tauri-first, UI-on-success
This commit is contained in:
@@ -221,65 +221,85 @@ export function deleteFromHistory(index) {
|
||||
.catch(err => console.warn("deleteFromHistory: SQLite delete failed", err));
|
||||
}
|
||||
|
||||
// ---- Tasks (persisted to localStorage) ----
|
||||
// ---- Tasks (SQLite via Tauri = canonical; no localStorage cache) ----
|
||||
//
|
||||
// The canonical store is the SQLite tasks table reachable via the
|
||||
// `create_task_cmd`, `list_tasks_cmd`, `delete_task_cmd`,
|
||||
// `complete_task_cmd`, `uncomplete_task_cmd` Tauri commands. The tasks
|
||||
// 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 localStorage tasks cache was removed to kill the cold-
|
||||
// start flicker and the dual-write drift between localStorage and SQLite.
|
||||
|
||||
const TASKS_KEY = "kon_tasks";
|
||||
export const tasks = $state([]);
|
||||
|
||||
function loadTasks() {
|
||||
try {
|
||||
const raw = localStorage.getItem(TASKS_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return [];
|
||||
}
|
||||
|
||||
export const tasks = $state(loadTasks());
|
||||
|
||||
// On boot in the desktop runtime, load canonical tasks from SQLite.
|
||||
// localStorage remains the warm cache and fallback.
|
||||
if (typeof window !== "undefined" && hasTauriRuntime()) {
|
||||
invoke("list_tasks_cmd")
|
||||
.then((sqliteTasks) => {
|
||||
tasks.length = 0;
|
||||
tasks.push(...sqliteTasks);
|
||||
saveTasks();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
export function saveTasks() {
|
||||
try {
|
||||
localStorage.setItem(TASKS_KEY, JSON.stringify(tasks));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function addTask(task) {
|
||||
const newTask = {
|
||||
id: crypto.randomUUID(),
|
||||
text: task.text,
|
||||
bucket: task.bucket || "inbox",
|
||||
listId: task.listId || null,
|
||||
context: task.context || "",
|
||||
effort: task.effort || "",
|
||||
done: false,
|
||||
doneAt: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
sourceTranscriptId: task.sourceTranscriptId || null,
|
||||
parentTaskId: task.parentTaskId || null,
|
||||
notes: "",
|
||||
/** Shape a TaskDto row from the tasks commands into the UI entry shape
|
||||
* that TasksPage / WipTaskList / TaskSidebar expect. Mirrors
|
||||
* mapTranscriptRow's placement and field-defaulting pattern. */
|
||||
function mapTaskRow(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
text: row.text ?? "",
|
||||
bucket: row.bucket ?? "inbox",
|
||||
listId: row.listId ?? null,
|
||||
effort: row.effort ?? "",
|
||||
done: !!row.done,
|
||||
doneAt: row.doneAt ?? null,
|
||||
createdAt: row.createdAt ?? new Date().toISOString(),
|
||||
sourceTranscriptId: row.sourceTranscriptId ?? null,
|
||||
parentTaskId: row.parentTaskId ?? null,
|
||||
};
|
||||
tasks.unshift(newTask);
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
if (hasTauriRuntime()) {
|
||||
invoke("create_task_cmd", {
|
||||
}
|
||||
|
||||
async function loadTasks() {
|
||||
if (!hasTauriRuntime()) {
|
||||
tasks.length = 0;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const rows = await invoke("list_tasks_cmd");
|
||||
const mapped = Array.isArray(rows) ? rows.map(mapTaskRow) : [];
|
||||
tasks.splice(0, tasks.length, ...mapped);
|
||||
} catch (err) {
|
||||
console.error("loadTasks failed", err);
|
||||
tasks.length = 0;
|
||||
toasts.error("Failed to load tasks", err?.message ?? String(err));
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate tasks from SQLite on boot. Mirrors the history hydration pattern
|
||||
// earlier in this file.
|
||||
if (typeof window !== "undefined" && hasTauriRuntime()) {
|
||||
loadTasks().catch(() => {});
|
||||
}
|
||||
|
||||
// `saveTasks` is retained as a no-op for callers in the Task Lists section
|
||||
// further down (removeProfileTaskList, deleteTaskList) that mutate
|
||||
// `t.listId` to null when a list is deleted and previously flushed via
|
||||
// localStorage. SQLite writes happen via the specific `create_task_cmd`
|
||||
// / `delete_task_cmd` / `complete_task_cmd` / `uncomplete_task_cmd`
|
||||
// commands in the functions below; the listId-clearing mutation is
|
||||
// currently session-scoped because listId is not round-tripped through
|
||||
// those commands. See report note.
|
||||
export function saveTasks() {}
|
||||
|
||||
export async function addTask(task) {
|
||||
if (!hasTauriRuntime()) return;
|
||||
const id = crypto.randomUUID();
|
||||
try {
|
||||
const row = await invoke("create_task_cmd", {
|
||||
request: {
|
||||
id: newTask.id,
|
||||
text: newTask.text,
|
||||
bucket: newTask.bucket,
|
||||
sourceTranscriptId: newTask.sourceTranscriptId,
|
||||
id,
|
||||
text: task.text,
|
||||
bucket: task.bucket || "inbox",
|
||||
sourceTranscriptId: task.sourceTranscriptId || null,
|
||||
},
|
||||
}).catch(() => {});
|
||||
});
|
||||
tasks.unshift(mapTaskRow(row));
|
||||
broadcastTasks();
|
||||
} catch (err) {
|
||||
toasts.error("Couldn't add task", err?.message ?? String(err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,41 +307,48 @@ export function updateTask(id, updates) {
|
||||
const idx = tasks.findIndex((t) => t.id === id);
|
||||
if (idx >= 0) {
|
||||
Object.assign(tasks[idx], updates);
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteTask(id) {
|
||||
const idx = tasks.findIndex((t) => t.id === id);
|
||||
if (idx >= 0) {
|
||||
tasks.splice(idx, 1);
|
||||
saveTasks();
|
||||
broadcastTasks();
|
||||
if (hasTauriRuntime()) {
|
||||
invoke("delete_task_cmd", { id }).catch(() => {});
|
||||
export async function deleteTask(id) {
|
||||
if (!hasTauriRuntime()) return;
|
||||
try {
|
||||
await invoke("delete_task_cmd", { id });
|
||||
const idx = tasks.findIndex((t) => t.id === id);
|
||||
if (idx >= 0) {
|
||||
tasks.splice(idx, 1);
|
||||
broadcastTasks();
|
||||
}
|
||||
} catch (err) {
|
||||
toasts.error("Couldn't delete task", err?.message ?? String(err));
|
||||
}
|
||||
}
|
||||
|
||||
export function completeTask(id) {
|
||||
updateTask(id, { done: true, doneAt: new Date().toISOString() });
|
||||
if (hasTauriRuntime()) {
|
||||
invoke("complete_task_cmd", { id }).catch(() => {});
|
||||
export async function completeTask(id) {
|
||||
if (!hasTauriRuntime()) return;
|
||||
try {
|
||||
await invoke("complete_task_cmd", { id });
|
||||
updateTask(id, { done: true, doneAt: new Date().toISOString() });
|
||||
} catch (err) {
|
||||
toasts.error("Couldn't complete task", err?.message ?? String(err));
|
||||
}
|
||||
}
|
||||
|
||||
export function uncompleteTask(id) {
|
||||
updateTask(id, { done: false, doneAt: null });
|
||||
if (hasTauriRuntime()) {
|
||||
invoke("uncomplete_task_cmd", { id }).catch(() => {});
|
||||
export async function uncompleteTask(id) {
|
||||
if (!hasTauriRuntime()) return;
|
||||
try {
|
||||
await invoke("uncomplete_task_cmd", { id });
|
||||
updateTask(id, { done: false, doneAt: null });
|
||||
} catch (err) {
|
||||
toasts.error("Couldn't uncomplete task", err?.message ?? String(err));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- BroadcastChannel for multi-window task sync ----
|
||||
|
||||
const taskChannel = typeof BroadcastChannel !== "undefined"
|
||||
? new BroadcastChannel("kon_tasks")
|
||||
? new BroadcastChannel("kon_task_sync")
|
||||
: null;
|
||||
|
||||
if (taskChannel) {
|
||||
|
||||
Reference in New Issue
Block a user