feat(field-guide): album state module + recordOnce primitive (FG-2) #94

Merged
manlycucumber merged 1 commit from feat/field-guide-album-state into develop 2026-07-11 18:23:47 +00:00
manlycucumber commented 2026-07-11 18:22:55 +00:00 (Migrated from github.com)

FG-2 of v0.4.0 — the album's storage layer. Part of #92. Independent of FG-1. Architecture: docs/architecture/field-guide.mdThe album).

What's here

  • BoscoStore.recordOnce(collection, id, data) — a generic insert-if-absent primitive. A live record with that id returns unchanged: no put(), no fresh updatedAt. So calling it repeatedly (the album's "record on read") is idempotent — it never re-marks the record pending and never re-bumps its timestamp. A tombstoned id (deleted: true, e.g. synced in from another device) counts as absent, so a later read can revive it. Reusable later by mastery / high-score / saved-art collections.
  • src/lib/state/album.ts — a thin wrapper: recordCard / hasCard / listCards. A card stores only { v: 1 } keyed by the creature slug; title/art/summary are joined from live gated frontmatter at view time, so a later-unapproved creature resolves to an inert frame and can never leak a title. Neutral records-not-rewards vocabulary (no earn/claim), and every function is profileless-safe — a reader with no profile browses without an error.

Pure state: no wiring, no UI. Record-on-read wiring is FG-4; the album view is FG-5.

The new album collection is picked up for free by pendingChanges / sync (union-merge) / export / import / deleteProfile — all enumerate collections dynamically. No migration, no SCHEMA_VERSION bump.

Verification (local gauntlet, all green)

check (0 errors) · lint · guard:colour · build · guard:external · guard:content · guard:provenance · guard:offline · 159 unit tests (+9). New tests via the deterministic makeStore() fakes: double-record freezes updatedAt + stays out of pendingChanges after a sync; tombstoned-id revives; profileless returns []/false/no-op; profile-scoped albums; export/import round-trip. deleteProfile clearing the album is already covered by the existing store test (it uses the album collection). No rendered surface — e2e n/a.

**FG-2 of v0.4.0** — the album's storage layer. Part of #92. Independent of FG-1. Architecture: [`docs/architecture/field-guide.md`](docs/architecture/field-guide.md) (§ *The album*). ## What's here - **`BoscoStore.recordOnce(collection, id, data)`** — a generic insert-if-absent primitive. A **live** record with that id returns unchanged: no `put()`, no fresh `updatedAt`. So calling it repeatedly (the album's "record on read") is **idempotent** — it never re-marks the record pending and never re-bumps its timestamp. A **tombstoned** id (`deleted: true`, e.g. synced in from another device) counts as absent, so a later read can revive it. Reusable later by mastery / high-score / saved-art collections. - **`src/lib/state/album.ts`** — a thin wrapper: `recordCard` / `hasCard` / `listCards`. A card stores only `{ v: 1 }` keyed by the creature **slug**; title/art/summary are joined from **live gated frontmatter at view time**, so a later-unapproved creature resolves to an inert frame and can never leak a title. Neutral **records-not-rewards** vocabulary (no `earn`/`claim`), and every function is **profileless-safe** — a reader with no profile browses without an error. Pure state: **no wiring, no UI.** Record-on-read wiring is FG-4; the album view is FG-5. The new `album` collection is picked up **for free** by `pendingChanges` / `sync` (union-merge) / `export` / `import` / `deleteProfile` — all enumerate collections dynamically. **No migration, no `SCHEMA_VERSION` bump.** ## Verification (local gauntlet, all green) `check` (0 errors) · `lint` · `guard:colour` · `build` · `guard:external` · `guard:content` · `guard:provenance` · `guard:offline` · **159 unit tests (+9)**. New tests via the deterministic `makeStore()` fakes: double-record freezes `updatedAt` + stays out of `pendingChanges` after a sync; tombstoned-id revives; profileless returns `[]`/`false`/no-op; profile-scoped albums; export/import round-trip. `deleteProfile` clearing the album is already covered by the existing store test (it uses the `album` collection). No rendered surface — e2e n/a.
Sign in to join this conversation.
No reviewers
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Bosco/bosco!94
No description provided.