Commit Graph

2 Commits

Author SHA1 Message Date
Croissant Le Doux
73643efcbc Purity / rebuild guarantee test (#30)
The cache is a rebuildable index over gitea, never a source of truth (D4).
Two tests lock that invariant where the durable cache actually lives:

- packages/core: cache-purity-v0.test.ts — file-backed node:sqlite. Build the
  SQLite mirror from a gitea snapshot, capture every re-derived field, delete the
  .sqlite file, rebuild from the same snapshot, assert byte-identical. Plus a
  structural D4 guard: every issues-table column must map to a gitea field, so a
  future user-authored column can't silently break rebuild-ability.

- apps/desktop: snapshot-store.test.ts — the shipped durable cache is the JSON
  snapshot-store. Delete the file → loadSnapshot returns null (degrades to
  no-cache, never throws), which is what forces the next getSnapshot to reconcile
  fresh from gitea. Corrupt/partial files are likewise treated as no-cache.
  Stands up vitest for the desktop main process (first unit tests there);
  electron is mocked, snapshot path is injected.

No native better-sqlite3 shipped: the SQLite mirror has no consumer on any hot
path yet, so wiring it into main (native module + asarUnpack + dmg re-verify)
would add packaging risk for no runtime benefit. The purity invariant is proven
at the seam for both caches; the native driver migration is deferred until
SQLite becomes load-bearing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 18:02:50 -04:00
Croissant Le Doux
2f6636684e feat: persist the reconcile cache to disk — instant boot + offline reads
Extends the in-memory cache into a durable mirror. The reconcile snapshot is
written to disk on every successful reconcile; on boot the app shows it instantly
(stale-while-revalidate) instead of a blank board, and if gitea is unreachable,
reads fall back to it (offline). Rebuildable — the durable truth stays in gitea.

- snapshot-store.ts: load/save the snapshot as JSON in app userData (never throws;
  corrupt/absent → "no cache"). At this scale (~34 issues, 37KB) the whole snapshot
  fits in memory, so a JSON file beats indexed SQL — no query benefit yet, no
  native-module (better-sqlite3/electron-rebuild) or WASM dependency. That's the
  next step if the mirror ever needs indexed queries over larger data.
- gitea.ts: getSnapshot persists on a fresh pull; bootSnapshot() returns the
  persisted snapshot (without seeding the cache — agents still reconcile fresh);
  gitea:boot serves it; gitea:reconcile falls back to it on failure (stale:true).
- useBacklog: stale-while-revalidate — boot instantly, then a fresh reconcile
  supersedes; a reconcile error keeps the shown snapshot instead of erroring.

Verified: desktop typecheck clean, 14 fixture e2e green. Live: the snapshot
persists (34 issues / 44 deps / 34 timelines / 5 milestones written to disk); a
second launch with gitea unreachable renders the full real board — NOW/NEXT/LATER
+ the Monte Carlo cone — entirely from the cache (new live-persistence e2e).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 23:43:44 -04:00