Commit Graph

2 Commits

Author SHA1 Message Date
josh 31f8ede9ac feat(02-05): wire compost beat toast (Plan 02-04 deferral)
- src/ui/settings/compost-toast.tsx: thin transient toast (D-07,
  GARD-04). Reads a rotating line from uiStrings[1].post_harvest_beat
  on each compost dispatch; fades out after 3.5s. Co-located with
  PersistenceToast as the plan specified ('folded into the persistence-
  toast UI surface').
- src/store/session-slice.ts: compostBeatTick monotonic counter +
  bumpCompostBeat action. Counter (vs boolean) ensures consecutive
  composts re-fire the toast without dedup.
- src/game/scenes/Garden.ts: handleTilePointerDown's compost branch
  calls bumpCompostBeat after enqueueCommand.
- src/App.tsx: mounts <CompostToast />.
- 4 new compost-toast tests; 312/312 vitest green; e2e still 1.6s
  green; npm run ci exits 0.

Implementation choice (per plan 'surfaced in SUMMARY'): minimum-viable
toast surface chosen over the Ink runtime path. The Ink-authored
compost-acknowledgements.ink content remains compiled + runtime-
loadable via loadInkStory + InkRenderer, so a future plan can swap
this component for the richer voice without touching sim or store.
2026-05-09 11:07:43 -04:00
josh 5d58d6cc7b feat(02-05): letter overlay + settings UI + boot save lifecycle + clock injection
- src/save/payload.ts (W2): shared buildPayloadFromStore (state, nowMs)
  + hydrateStoreFromPayload (state, payload). Two-arg signature unifies
  Settings.tsx (passes Date.now()) and PhaserGame.tsx saveSync (passes
  clock.now()). BLOCKER 3 — lastTickAt is wall-clock ms, owned by the
  application layer; the sim never writes it.
- src/ui/letter/Letter.tsx + test: D-20 full-screen overlay (UX-02);
  loads compiled letter Ink, binds plants_bloomed/fragment_titles/
  lura_was_here slots from session.pendingLetterEventBlock, dismisses
  via Tend the garden button or backdrop click. Pitfall 9 — dismiss
  calls bootstrapAudioContext synchronously inside the click handler.
- src/ui/settings/Settings.tsx + test: D-28 save-management UI
  (Export/Import/Restore). BLOCKER 2 — Import pipeline is
  importFromBase64 -> unwrap (CRC verify) -> migrate -> hydrate. No
  audio sliders, no a11y polish (Phase 8 owns those).
- src/ui/settings/persistence-toast.tsx: D-30 one-time soft toast in
  voice when navigator.storage.persist() denies. Reads
  showPersistenceToast from session slice; sets persistenceToastShown
  after the timeout fires.
- src/PhaserGame.tsx: full boot path rewrite. Clock selection
  (?devtime=fake URL flag, production-guarded by import.meta.env.PROD),
  save load (BLOCKER 1 — unwrap then migrate), silent offline catchup
  via drainTicks(silent=true), letter overlay open at >=5min absence,
  requestPersistence + showPersistenceToast wiring, Phaser start AFTER
  hydration, registerSaveLifecycleHooks with synchronous LocalStorage
  saveSync (Pitfall 7) + best-effort IDB write. W5 — lifecycle handle
  held in ref so outer cleanup detaches.
- src/store/session-slice.ts: showPersistenceToast transient flag +
  setShowPersistenceToast action.
- src/ui/journal/journal-icon.tsx: 'j' hotkey listener via
  tlg:toggle-journal CustomEvent (D-29).
- src/game/scenes/Garden.ts: formalized clock read via readClockSlot()
  helper; falls back to wallClock if window.__tlgClock missing.
- src/App.tsx: mount Letter, Settings, PersistenceToast, SettingsIcon
  (corner button); D-29 keyboard shortcuts (',' toggles Settings, 'j'
  toggles Journal via window event).
- 308/308 tests green (was 295; +13 new — 7 Letter + 6 Settings).
  npm run ci exits 0; Vite emits letter Ink as a separate lazy chunk.
2026-05-09 10:57:09 -04:00