summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/cache-warm-storage.ts
blob: 66a8c3177a584a4b5ff3cb3544e383dcd84f734d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
 * LocalStorage persistence for the per-tab "prompt cache warming" toggle.
 *
 * Cache warming keeps a tab's provider prompt-cache warm while the tab is idle
 * by periodically replaying its exact cached conversation plus a trivial
 * throwaway turn (see `cache-warming.svelte.ts`). Whether warming is enabled is
 * a per-tab preference that must survive a browser reload.
 *
 * Why localStorage and not the backend `settings` table:
 *   - It's a per-device UI preference, not domain state — the same precedent as
 *     `dispatch-sidebar-panels`, `dispatch-theme`, and `dispatch-api-url`.
 *   - No backend round-trip on every toggle.
 *   - Warming itself is a frontend-driven timer; keeping its on/off flag on the
 *     frontend keeps the whole feature self-contained.
 *
 * Shape: a single JSON object mapping `tabId -> boolean` under one key, so a
 * closed tab's stale entry is cheap and easy to prune.
 */

const LS_KEY = "dispatch-cache-warming";

type WarmMap = Record<string, boolean>;

/** Read the whole tab→enabled map. Never throws; returns {} on any failure. */
function readMap(): WarmMap {
	try {
		const raw = localStorage.getItem(LS_KEY);
		if (!raw) return {};
		const parsed: unknown = JSON.parse(raw);
		if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
		const out: WarmMap = {};
		for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
			if (typeof v === "boolean") out[k] = v;
		}
		return out;
	} catch {
		// localStorage unavailable (private mode / restricted context) or corrupt
		// write from a prior session. Degrade to "nothing persisted".
		return {};
	}
}

/** Persist the whole map. Best-effort — swallows quota / access errors. */
function writeMap(map: WarmMap): void {
	try {
		localStorage.setItem(LS_KEY, JSON.stringify(map));
	} catch {
		// Best-effort: the in-memory store remains the source of truth for the
		// current session even if persistence fails.
	}
}

/** Whether cache warming is enabled for `tabId` (default: false). */
export function loadCacheWarmEnabled(tabId: string): boolean {
	return readMap()[tabId] === true;
}

/**
 * Persist the warming toggle for one tab. Writing `false` removes the entry
 * (default is off, so absence is the natural "disabled" representation and
 * keeps stale tabs from accumulating).
 */
export function saveCacheWarmEnabled(tabId: string, enabled: boolean): void {
	const map = readMap();
	if (enabled) map[tabId] = true;
	else delete map[tabId];
	writeMap(map);
}

/** Drop a tab's persisted toggle entirely (called when the tab is closed). */
export function clearCacheWarmEnabled(tabId: string): void {
	const map = readMap();
	if (tabId in map) {
		delete map[tabId];
		writeMap(map);
	}
}