josh 0b1425d4f6 feat(01-03): idb DB + localStorage fallback adapter (CORE-04) + last-3 snapshot retention + persist API [GREEN]
- db.ts: openSaveDB() opens IndexedDB ('tlg-save', v1) with two object
  stores (saves singleton + save_snapshots keyed); on openDB rejection
  (private mode, blocked, quota exceeded) falls back to LocalStorageDBAdapter
  per CORE-04 contract
- db-localstorage-adapter.ts: ~110-LoC adapter exposing the same minimal
  get/put/delete/getAll/transaction surface as idb's IDBPDatabase, namespaced
  under tlg.saves.<id> and tlg.save_snapshots.<id>; transaction() shim
  proxies straight through (localStorage has no real transactions)
- snapshots.ts: snapshot(envelope) writes to save_snapshots and prunes to
  RETAIN=3 newest by savedAt descending (CORE-08); listSnapshots() returns
  newest-first; entropy suffix on snapshot IDs avoids same-ms collisions
- persist.ts: requestPersistence() returns {granted, apiAvailable} for all
  4 navigator.storage scenarios per CORE-05 + RESEARCH Pitfall 2

Test infra fixes: snapshots.test.ts and db.test.ts cannot deleteDatabase
between tests because openSaveDB leaves an open connection that idb caches
(deleteDatabase blocks indefinitely). beforeEach instead clears store
contents directly. The fallback test calls vi.resetModules() BEFORE
vi.doMock('idb') so the freshly-imported db.ts picks up the rejecting
openDB stub, and re-imports LocalStorageDBAdapter from the same module
graph so instanceof checks against the same class identity.

Tests: 12/12 pass (npx vitest run src/save/db.test.ts
src/save/snapshots.test.ts src/save/persist.test.ts).
Full save suite: 33/33 pass (Task 1 + Task 2 combined).
TypeScript-strict; no 'any' in production code (CLAUDE.md).
2026-05-08 23:36:20 -04:00
2026-05-08 21:37:11 -04:00
S
Description
No description provided
1.4 MiB
Languages
TypeScript 91.2%
JavaScript 6.7%
Ink 1.8%
CSS 0.2%
HTML 0.1%