perf+persistence: the durable reconcile mirror (cache + disk) #47
Reference in New Issue
Block a user
No description provided.
Delete Branch "infra/reconcile-cache"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The mirror layer — cached reads and durable persistence. Two commits:
1. In-memory reconcile cache
A full reconcile is ~2N gitea calls (deps + timelines per issue); every agent tool call was doing a fresh one. Now one shared snapshot cache backs the UI reconcile and the agent: tool calls reuse a snapshot within a 30s TTL; the explicit UI reconcile forces fresh; writes invalidate so the board reflects them immediately.
2. Persist to disk — instant boot + offline reads
snapshot-store.ts: the snapshot is written to app userData as JSON on every successful reconcile (never throws; corrupt/absent → "no cache").gitea:boot): the app shows the persisted snapshot immediately, then a fresh reconcile supersedes it (stale-while-revalidate) — no blank board while ~2N calls run.gitea:reconcilefallback): if gitea is unreachable, reads serve the persisted snapshot (stale: true) instead of erroring.useBacklogdoes boot → show → reconcile → replace; a reconcile error keeps the shown snapshot.Why JSON, not SQLite (yet)
At this scale (~34 issues, a 37KB snapshot) the whole thing fits in memory and the scheduler/forecast operate on arrays — indexed SQL buys nothing yet, and it avoids a native
better-sqlite3+ electron-rebuild (Electron 34 ships Node 20, nonode:sqlite) or a WASM dependency. Real SQL tables become worthwhile only when the mirror needs indexed queries over larger data; that's the clean next step from here.Verified
Desktop typecheck clean · 14 fixture e2e green · live Reginald answers correctly from the cache.
Persistence live-verified: the snapshot persists (34 issues / 44 deps / 34 timelines / 5 milestones to disk); a second launch with gitea pointed at an unreachable host renders the full real board — NOW/NEXT/LATER + the Monte Carlo cone — entirely from the cache (new
live-persistencee2e, screenshot attached in the run).🤖 Generated with Claude Code
A full reconcile is ~2N gitea calls (deps + timelines per issue). Every agent tool call (query_project) was doing a fresh one; the UI reconcile and the agent didn't share anything. Now a single in-memory snapshot cache backs both. - gitea.ts: getSnapshot(client, { maxAgeMs }) — reads within the window reuse the cache; maxAgeMs:0 forces fresh. invalidateSnapshot() drops it. The explicit UI reconcile forces fresh (and warms the cache); agent tool calls tolerate a 30s TTL to stay responsive; applyChange + createIssues invalidate so the board and forecast reflect the write immediately. - model.ts: query_project reads getSnapshot (30s TTL) instead of reconciling live. This is the SQLite mirror's cache semantics in memory — rebuildable, the durable truth stays in gitea (purity split, D4). Persistent SQLite (offline + instant boot) is a separate slice: Electron 34's Node 20 has no node:sqlite, so it needs better-sqlite3 + electron-rebuild or sql.js/WASM — deferred as its own decision. Verified: desktop typecheck clean, 14 fixture e2e green, live Reginald still answers correctly from the cache (writes invalidate → board stays correct). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>perf: main-process reconcile cache (the mirror's cache layer)to perf+persistence: the durable reconcile mirror (cache + disk)