docs(02-05): complete letter-settings-e2e plan
- 02-05-letter-settings-e2e-SUMMARY.md: full plan summary (frontmatter + decisions + REQ table + self-check). All 24 Phase-2 REQ-IDs structurally satisfied across the 5-plan set. - STATE.md: marked Plan 02-05 complete; Phase 2 ready for /gsd-verify-work; progress 19% → 22%; next action set to verifier. - ROADMAP.md: Plan 02-05 row marked [x] with duration + SUMMARY ref. - REQUIREMENTS.md: UX-02 / UX-10 / CORE-03 / PIPE-07 marked complete with traceability annotations citing Plan 02-05's contribution; per-row Plan 02-05 references added to UX-02, UX-10, CORE-03; PIPE-07 traceability table row updated.
This commit is contained in:
@@ -12,7 +12,7 @@ Requirements for initial release. Each maps to roadmap phases. All are user-cent
|
|||||||
- [x] **CORE-01**: Player loads the game in a modern browser (Chrome, Firefox, Safari, Edge — last 2 stable releases) and reaches a playable state in under 5 seconds on a 25 Mbps connection. <!-- Plan 01-01: scaffold builds (`npm run build` green); end-to-end <5s wall-clock measurement is Phase 2 PIPE-07. -->
|
- [x] **CORE-01**: Player loads the game in a modern browser (Chrome, Firefox, Safari, Edge — last 2 stable releases) and reaches a playable state in under 5 seconds on a 25 Mbps connection. <!-- Plan 01-01: scaffold builds (`npm run build` green); end-to-end <5s wall-clock measurement is Phase 2 PIPE-07. -->
|
||||||
|
|
||||||
- [x] **CORE-02**: Game runs a deterministic, fixed-timestep simulation that advances by elapsed real time (not `setInterval` ticks), so a player who switches tabs or sleeps their device returns to a correctly-advanced garden. <!-- Plan 02-01: drainTicks fixed-timestep accumulator + Clock injection contract; TICK_MS=200 (5Hz); 7 scheduler tests green. ESLint sim-purity rule (D-33) bans setInterval inside src/sim/**. Scene-driven tick wiring + visibility-pause is Plan 02-02. -->
|
- [x] **CORE-02**: Game runs a deterministic, fixed-timestep simulation that advances by elapsed real time (not `setInterval` ticks), so a player who switches tabs or sleeps their device returns to a correctly-advanced garden. <!-- Plan 02-01: drainTicks fixed-timestep accumulator + Clock injection contract; TICK_MS=200 (5Hz); 7 scheduler tests green. ESLint sim-purity rule (D-33) bans setInterval inside src/sim/**. Scene-driven tick wiring + visibility-pause is Plan 02-02. -->
|
||||||
- [x] **CORE-03**: Player who closes the game and returns finds the garden has progressed by the elapsed time (capped at 24 hours) — *no progression resumes from a stale snapshot*. <!-- Plan 02-01: drainTicks clamps at MAX_OFFLINE_MS=24h; computeOfflineCatchup reports hitOfflineCap=true on excess; both paths covered by Vitest. Letter-overlay (returning-player surface) is Plan 02-05. -->
|
- [x] **CORE-03**: Player who closes the game and returns finds the garden has progressed by the elapsed time (capped at 24 hours) — *no progression resumes from a stale snapshot*. <!-- Plan 02-01: drainTicks clamps at MAX_OFFLINE_MS=24h; computeOfflineCatchup reports hitOfflineCap=true on excess; both paths covered by Vitest. Plan 02-05: src/PhaserGame.tsx boot path threads computeOfflineCatchup → drainTicks(silent=true) → autoHarvestReadyPlants → letter overlay opens at ≥5min absence; auto-harvest accumulates plantsBloomedCount + harvestedFragmentIds + luraBeatPending into the OfflineEventBlock the letter Ink renders. PIPE-07 e2e exercises offline catchup structurally via FakeClock advance + reload. -->
|
||||||
- [x] **CORE-04**: Player's progress saves to IndexedDB (with localStorage fallback), surviving browser refresh, browser updates, and at least 30 days of inactivity on Chrome and Firefox. <!-- Plan 01-03: idb DB + LocalStorageDBAdapter fallback; 4 db tests green; round-trip test green. Settings UI surface is Phase 2. -->
|
- [x] **CORE-04**: Player's progress saves to IndexedDB (with localStorage fallback), surviving browser refresh, browser updates, and at least 30 days of inactivity on Chrome and Firefox. <!-- Plan 01-03: idb DB + LocalStorageDBAdapter fallback; 4 db tests green; round-trip test green. Settings UI surface is Phase 2. -->
|
||||||
- [x] **CORE-05**: Game requests persistent storage via `navigator.storage.persist()` on first save and surfaces the result respectfully if the browser declines. <!-- Plan 01-03: requestPersistence() all 4 API scenarios covered by Vitest; Settings UI surface is Phase 2. -->
|
- [x] **CORE-05**: Game requests persistent storage via `navigator.storage.persist()` on first save and surfaces the result respectfully if the browser declines. <!-- Plan 01-03: requestPersistence() all 4 API scenarios covered by Vitest; Settings UI surface is Phase 2. -->
|
||||||
- [x] **CORE-06**: Saves are versioned (`{schemaVersion, payload, checksum}`) and the game refuses to load a save with a checksum mismatch, presenting the player with a recovery option. <!-- Plan 01-03: wrap/unwrap + SaveCorruptError + CRC-32; 9 envelope tests green. -->
|
- [x] **CORE-06**: Saves are versioned (`{schemaVersion, payload, checksum}`) and the game refuses to load a save with a checksum mismatch, presenting the player with a recovery option. <!-- Plan 01-03: wrap/unwrap + SaveCorruptError + CRC-32; 9 envelope tests green. -->
|
||||||
@@ -89,7 +89,8 @@ Requirements for initial release. Each maps to roadmap phases. All are user-cent
|
|||||||
|
|
||||||
- [x] **UX-01**: First-time player sees a single, painted "Begin" screen with no UI clutter; the garden reveals itself as the player interacts (A Dark Room rule). <!-- Plan 02-02: BeginScreen mounts as a fixed-position dialog covering the canvas with only title + subtitle + Begin CTA; no HUD, no journal, no settings. Dismissed via session.beginGateDismissed (D-22). Phase 3 paints the treatment. -->
|
- [x] **UX-01**: First-time player sees a single, painted "Begin" screen with no UI clutter; the garden reveals itself as the player interacts (A Dark Room rule). <!-- Plan 02-02: BeginScreen mounts as a fixed-position dialog covering the canvas with only title + subtitle + Begin CTA; no HUD, no journal, no settings. Dismissed via session.beginGateDismissed (D-22). Phase 3 paints the treatment. -->
|
||||||
|
|
||||||
- [ ] **UX-02**: Player who returns after time away receives a "while you were away" *letter from the garden* — written in voice, not a stat dump — describing what grew, what bloomed, what the wind brought.
|
- [x] **UX-02**: Player who returns after time away receives a "while you were away" *letter from the garden* — written in voice, not a stat dump — describing what grew, what bloomed, what the wind brought. <!-- Plan 02-05: content/dialogue/season1/letter-from-the-garden.ink authored skeleton (bible voice, anti-FOMO compliant, 24h cap silent in voice per D-11) + slot vocabulary plants_bloomed/fragment_titles/lura_was_here from offlineEvents block; src/ui/letter/Letter.tsx full-screen overlay (D-20: opens at ≥5min absence, dismisses on tap with Pitfall 9 audio bootstrap); buildLetterSlots pure helper + 10 tests; Letter overlay 7 tests. Boot path in src/PhaserGame.tsx threads silent catchup → offlineEvents → openLetter. -->
|
||||||
|
|
||||||
- [ ] **UX-03**: Player can buy plants/upgrades in multi-buy increments (×1 / ×10 / ×100 / Max) when the option is meaningful for the current scaling.
|
- [ ] **UX-03**: Player can buy plants/upgrades in multi-buy increments (×1 / ×10 / ×100 / Max) when the option is meaningful for the current scaling.
|
||||||
- [ ] **UX-04**: Player can adjust separate Music, Ambient, and SFX volume sliders, with a master mute keybind; settings persist in saves.
|
- [ ] **UX-04**: Player can adjust separate Music, Ambient, and SFX volume sliders, with a master mute keybind; settings persist in saves.
|
||||||
- [ ] **UX-05**: Player can toggle a reduced-motion option (respects `prefers-reduced-motion` system setting by default) that disables non-essential particles and animation.
|
- [ ] **UX-05**: Player can toggle a reduced-motion option (respects `prefers-reduced-motion` system setting by default) that disables non-essential particles and animation.
|
||||||
@@ -97,7 +98,7 @@ Requirements for initial release. Each maps to roadmap phases. All are user-cent
|
|||||||
- [ ] **UX-07**: All UI text is selectable, copy-pasteable, and supports browser zoom up to 200% without breaking layout.
|
- [ ] **UX-07**: All UI text is selectable, copy-pasteable, and supports browser zoom up to 200% without breaking layout.
|
||||||
- [ ] **UX-08**: Color is never the sole carrier of information — icons, labels, or patterns provide a redundant channel for color-blind players.
|
- [ ] **UX-08**: Color is never the sole carrier of information — icons, labels, or patterns provide a redundant channel for color-blind players.
|
||||||
- [ ] **UX-09**: Tab title and favicon update to reflect a backgrounded state (e.g., a small bloom appears when a fragment is ready).
|
- [ ] **UX-09**: Tab title and favicon update to reflect a backgrounded state (e.g., a small bloom appears when a fragment is ready).
|
||||||
- [x] **UX-10**: Game saves state on `visibilitychange` to hidden, on `beforeunload`, and on Season transitions; behavior is identical between "tab backgrounded" and "tab closed." <!-- Plan 02-01: registerSaveLifecycleHooks attaches synchronous handlers for visibilitychange→hidden + beforeunload + saveOnSeasonTransition() callable; 6 lifecycle tests green covering every trigger + detach. Boot-path saveSync wiring is Plan 02-05. -->
|
- [x] **UX-10**: Game saves state on `visibilitychange` to hidden, on `beforeunload`, and on Season transitions; behavior is identical between "tab backgrounded" and "tab closed." <!-- Plan 02-01: registerSaveLifecycleHooks attaches synchronous handlers for visibilitychange→hidden + beforeunload + saveOnSeasonTransition() callable; 6 lifecycle tests green covering every trigger + detach. Plan 02-05: PhaserGame.tsx boot path now wires saveSync via clock.now() (BLOCKER 3 — wall-clock anchor) + synchronous LocalStorage write (Pitfall 7) + best-effort IDB write; lifecycle handle held in a ref so the outer useLayoutEffect cleanup detaches across the async IIFE boundary (W5). -->
|
||||||
- [x] **UX-11**: Numbers display in human-readable formats (1.2K, 4.5M, 8.9B, scientific notation past notation thresholds). <!-- Plan 02-01: formatHumanReadable handles every K/M/B/T threshold + 1e15 scientific + negative-sign branch; 11 format tests green. BigQty.format() delegates so all currency-grade numbers in the HUD route through this. -->
|
- [x] **UX-11**: Numbers display in human-readable formats (1.2K, 4.5M, 8.9B, scientific notation past notation thresholds). <!-- Plan 02-01: formatHumanReadable handles every K/M/B/T threshold + 1e15 scientific + negative-sign branch; 11 format tests green. BigQty.format() delegates so all currency-grade numbers in the HUD route through this. -->
|
||||||
|
|
||||||
- [ ] **UX-12**: Game surfaces *what Lura said yesterday* in returning-player UI affordances — never *fragments per hour* or *optimization metrics* (mechanic-as-metaphor doctrine).
|
- [ ] **UX-12**: Game surfaces *what Lura said yesterday* in returning-player UI affordances — never *fragments per hour* or *optimization metrics* (mechanic-as-metaphor doctrine).
|
||||||
@@ -112,7 +113,7 @@ Requirements for initial release. Each maps to roadmap phases. All are user-cent
|
|||||||
- [ ] **PIPE-04**: Project ships visual regression testing for the asset library that flags style drift before any model migration is merged.
|
- [ ] **PIPE-04**: Project ships visual regression testing for the asset library that flags style drift before any model migration is merged.
|
||||||
- [x] **PIPE-05**: Project ships an `anti-FOMO doctrine` document and a `Season 7 end-state` design document in `.planning/` (or `docs/`) before economy code is written. <!-- Plan 01-06: both docs authored and doc-lint tested (8 Vitest assertions green). -->
|
- [x] **PIPE-05**: Project ships an `anti-FOMO doctrine` document and a `Season 7 end-state` design document in `.planning/` (or `docs/`) before economy code is written. <!-- Plan 01-06: both docs authored and doc-lint tested (8 Vitest assertions green). -->
|
||||||
- [x] **PIPE-06**: Project ships unit tests (Vitest) covering all save migrations and core economy formulas, run on every CI build. <!-- Plan 01-07: .github/workflows/ci.yml runs npm ci + npm run ci on push + PR; 53 tests / 12 files green. -->
|
- [x] **PIPE-06**: Project ships unit tests (Vitest) covering all save migrations and core economy formulas, run on every CI build. <!-- Plan 01-07: .github/workflows/ci.yml runs npm ci + npm run ci on push + PR; 53 tests / 12 files green. -->
|
||||||
- [ ] **PIPE-07**: Project ships an end-to-end smoke test (Playwright) that loads the game, plants a seed, harvests a fragment, and verifies persistence across a page reload.
|
- [x] **PIPE-07**: Project ships an end-to-end smoke test (Playwright) that loads the game, plants a seed, harvests a fragment, and verifies persistence across a page reload. <!-- Plan 02-05: tests/e2e/season1-loop.spec.ts covers load → Begin → plant rosemary → fast-forward FakeClock 3min → harvest → fragment-reveal modal → close → journal-icon visible → open journal → fragment present → reload → fragment persists. URL-flag FakeClock injection production-guarded by import.meta.env.PROD; window.__tlgStore exposed only when ?devtime=fake. 1.5s test runtime, ~4s end-to-end. npm run test:e2e (not in npm run ci per minimum-viable doctrine; runs separately before /gsd-verify-work and on release). -->
|
||||||
|
|
||||||
## v2 Requirements
|
## v2 Requirements
|
||||||
|
|
||||||
@@ -197,7 +198,7 @@ Populated by gsd-roadmapper during roadmap creation on 2026-05-08. Updated after
|
|||||||
|-------------|-------|--------|
|
|-------------|-------|--------|
|
||||||
| CORE-01 | Phase 1 — Foundations & Doctrine | Complete (scaffold builds; full E2E <5s measurement is Phase 2 PIPE-07) |
|
| CORE-01 | Phase 1 — Foundations & Doctrine | Complete (scaffold builds; full E2E <5s measurement is Phase 2 PIPE-07) |
|
||||||
| CORE-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; drainTicks fixed-timestep accumulator + Clock injection; scene-driven tick wiring is Plan 02-02) |
|
| CORE-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; drainTicks fixed-timestep accumulator + Clock injection; scene-driven tick wiring is Plan 02-02) |
|
||||||
| CORE-03 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; MAX_OFFLINE_MS=24h clamp + computeOfflineCatchup; letter overlay is Plan 02-05) |
|
| CORE-03 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01 + 02-05; MAX_OFFLINE_MS=24h clamp + computeOfflineCatchup + PhaserGame.tsx boot path threads catchup → silent drainTicks → letter overlay) |
|
||||||
| CORE-04 | Phase 1 — Foundations & Doctrine | Complete (IDB + localStorage fallback; codec + round-trip; Settings UI is Phase 2) |
|
| CORE-04 | Phase 1 — Foundations & Doctrine | Complete (IDB + localStorage fallback; codec + round-trip; Settings UI is Phase 2) |
|
||||||
| CORE-05 | Phase 1 — Foundations & Doctrine | Complete (navigator.storage.persist() all 4 scenarios; Settings UI surface is Phase 2) |
|
| CORE-05 | Phase 1 — Foundations & Doctrine | Complete (navigator.storage.persist() all 4 scenarios; Settings UI surface is Phase 2) |
|
||||||
| CORE-06 | Phase 1 — Foundations & Doctrine | Complete (wrap/unwrap + CRC-32 checksum + SaveCorruptError) |
|
| CORE-06 | Phase 1 — Foundations & Doctrine | Complete (wrap/unwrap + CRC-32 checksum + SaveCorruptError) |
|
||||||
@@ -253,7 +254,7 @@ Populated by gsd-roadmapper during roadmap creation on 2026-05-08. Updated after
|
|||||||
| AEST-08 | Phase 1 — Foundations & Doctrine | Complete (Zod ProvenanceSchema 6 fields + CI gate; north-star reference set deferred to Phase 5 per IOU) |
|
| AEST-08 | Phase 1 — Foundations & Doctrine | Complete (Zod ProvenanceSchema 6 fields + CI gate; north-star reference set deferred to Phase 5 per IOU) |
|
||||||
| AEST-09 | Phase 1 — Foundations & Doctrine | Complete (human curation gate mechanism in place; recorded human decision in 01-05-IOU.md) |
|
| AEST-09 | Phase 1 — Foundations & Doctrine | Complete (human curation gate mechanism in place; recorded human decision in 01-05-IOU.md) |
|
||||||
| UX-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-02; single fixed-position Begin overlay; no HUD/journal/settings; D-22 dismissal) |
|
| UX-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-02; single fixed-position Begin overlay; no HUD/journal/settings; D-22 dismissal) |
|
||||||
| UX-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| UX-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-05; letter-from-the-garden.ink + Letter overlay + boot path silent catchup → openLetter at ≥5min absence; Pitfall 9 audio bootstrap on dismiss) |
|
||||||
| UX-03 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| UX-03 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| UX-04 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| UX-04 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| UX-05 | Phase 3 — Watercolor & Cello Aesthetic | Pending |
|
| UX-05 | Phase 3 — Watercolor & Cello Aesthetic | Pending |
|
||||||
@@ -261,7 +262,7 @@ Populated by gsd-roadmapper during roadmap creation on 2026-05-08. Updated after
|
|||||||
| UX-07 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| UX-07 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| UX-08 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| UX-08 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| UX-09 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| UX-09 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| UX-10 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; registerSaveLifecycleHooks + saveOnSeasonTransition; boot-path saveSync wiring is Plan 02-05) |
|
| UX-10 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01 + 02-05; registerSaveLifecycleHooks + saveOnSeasonTransition; PhaserGame.tsx boot path wires saveSync via clock.now() with synchronous LocalStorage write + best-effort IDB) |
|
||||||
| UX-11 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; formatHumanReadable K/M/B/T/scientific; BigQty.format() delegates) |
|
| UX-11 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; formatHumanReadable K/M/B/T/scientific; BigQty.format() delegates) |
|
||||||
| UX-12 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| UX-12 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| UX-13 | Phase 1 — Foundations & Doctrine | Complete (anti-fomo-doctrine.md authored + doc-lint tested; review-enforced per CONTEXT D-07) |
|
| UX-13 | Phase 1 — Foundations & Doctrine | Complete (anti-fomo-doctrine.md authored + doc-lint tested; review-enforced per CONTEXT D-07) |
|
||||||
@@ -271,7 +272,7 @@ Populated by gsd-roadmapper during roadmap creation on 2026-05-08. Updated after
|
|||||||
| PIPE-04 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| PIPE-04 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| PIPE-05 | Phase 1 — Foundations & Doctrine | Complete (both doctrine docs authored + 8 doc-lint assertions green) |
|
| PIPE-05 | Phase 1 — Foundations & Doctrine | Complete (both doctrine docs authored + 8 doc-lint assertions green) |
|
||||||
| PIPE-06 | Phase 1 — Foundations & Doctrine | Complete (ci.yml runs npm run ci on push + PR; 53 tests / 12 files green) |
|
| PIPE-06 | Phase 1 — Foundations & Doctrine | Complete (ci.yml runs npm run ci on push + PR; 53 tests / 12 files green) |
|
||||||
| PIPE-07 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| PIPE-07 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-05; tests/e2e/season1-loop.spec.ts — full Phase-2 loop in Chromium with FakeClock injection, 1.5s test runtime, 4s end-to-end) |
|
||||||
|
|
||||||
**Per-Phase Counts:**
|
**Per-Phase Counts:**
|
||||||
|
|
||||||
@@ -294,4 +295,4 @@ Populated by gsd-roadmapper during roadmap creation on 2026-05-08. Updated after
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Requirements defined: 2026-05-08*
|
*Requirements defined: 2026-05-08*
|
||||||
*Last updated: 2026-05-09 after Phase 1 verification (16/16 REQ-IDs marked Complete)*
|
*Last updated: 2026-05-09 after Plan 02-05 execution (40/77 REQ-IDs marked Complete — Phase 1 + Phase 2 fully shipped pending /gsd-verify-work)*
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ Plans:
|
|||||||
- [x] 02-02-begin-plant-grow-PLAN.md — sim/garden core (4×4 grid, 3 plant types, growth state machine, plantSeed) + render layer (Phaser primitives, ready-pulse, tile-coords) + BeginScreen + audio bootstrap + SeedPicker + UI strings (Wave 1; AEST-07, UX-01, GARD-01, GARD-02) ✓ 2026-05-09 (18 min) — see 02-02-begin-plant-grow-SUMMARY.md
|
- [x] 02-02-begin-plant-grow-PLAN.md — sim/garden core (4×4 grid, 3 plant types, growth state machine, plantSeed) + render layer (Phaser primitives, ready-pulse, tile-coords) + BeginScreen + audio bootstrap + SeedPicker + UI strings (Wave 1; AEST-07, UX-01, GARD-01, GARD-02) ✓ 2026-05-09 (18 min) — see 02-02-begin-plant-grow-SUMMARY.md
|
||||||
- [x] 02-03-harvest-journal-fragments-PLAN.md — Season-1 17 authored fragments + sim/memory selector (deterministic mulberry32, gated, no-dup, sentinel fallback for Pitfall 8) + harvest + compost commands (Pitfall 10 post-commit unlock thresholds) + Memory Journal + FragmentRevealModal + JournalIcon + PIPE-02 structural verifier (Wave 1; GARD-03, GARD-04, MEMR-01..06, PIPE-02) ✓ 2026-05-09 (12 min) — see 02-03-harvest-journal-fragments-SUMMARY.md
|
- [x] 02-03-harvest-journal-fragments-PLAN.md — Season-1 17 authored fragments + sim/memory selector (deterministic mulberry32, gated, no-dup, sentinel fallback for Pitfall 8) + harvest + compost commands (Pitfall 10 post-commit unlock thresholds) + Memory Journal + FragmentRevealModal + JournalIcon + PIPE-02 structural verifier (Wave 1; GARD-03, GARD-04, MEMR-01..06, PIPE-02) ✓ 2026-05-09 (12 min) — see 02-03-harvest-journal-fragments-SUMMARY.md
|
||||||
- [x] 02-04-lura-gate-beats-PLAN.md — inklecate compile pipeline + 4 authored .ink files (3 Lura beats + compost acknowledgements) + sim/narrative tick-count gate (1st/4th/8th harvest) + LuraDialogue overlay + InkRenderer drip + Phaser gate visual indicator (Wave 2; STRY-01, STRY-06, STRY-07 vacuous, STRY-10) ✓ 2026-05-09 (24 min) — see 02-04-lura-gate-beats-SUMMARY.md
|
- [x] 02-04-lura-gate-beats-PLAN.md — inklecate compile pipeline + 4 authored .ink files (3 Lura beats + compost acknowledgements) + sim/narrative tick-count gate (1st/4th/8th harvest) + LuraDialogue overlay + InkRenderer drip + Phaser gate visual indicator (Wave 2; STRY-01, STRY-06, STRY-07 vacuous, STRY-10) ✓ 2026-05-09 (24 min) — see 02-04-lura-gate-beats-SUMMARY.md
|
||||||
- [ ] 02-05-letter-settings-e2e-PLAN.md — sim/offline + auto-harvest + letter Ink + Letter overlay + Settings (Export/Import/Restore) + persistence-toast + boot-path save lifecycle wiring + URL-flag FakeClock injection + Playwright PIPE-07 e2e (Wave 2; UX-02, UX-10, CORE-03, CORE-11, PIPE-07)
|
- [x] 02-05-letter-settings-e2e-PLAN.md — sim/offline + auto-harvest + letter Ink + Letter overlay + Settings (Export/Import/Restore) + persistence-toast + boot-path save lifecycle wiring + URL-flag FakeClock injection + Playwright PIPE-07 e2e (Wave 2; UX-02, UX-10, CORE-03, CORE-11, PIPE-07) ✓ 2026-05-09 (20 min) — see 02-05-letter-settings-e2e-SUMMARY.md
|
||||||
**UI hint**: yes
|
**UI hint**: yes
|
||||||
|
|
||||||
### Phase 3: Watercolor & Cello Aesthetic
|
### Phase 3: Watercolor & Cello Aesthetic
|
||||||
|
|||||||
+24
-18
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: in_progress
|
status: in_progress
|
||||||
stopped_at: "Phase 2 Wave 2 first plan (Plan 02-04 lura-gate-beats) executed in sequential mode. 3 atomic commits: c90f8f1 (ink compilation pipeline + 4 authored Season-1 .ink files + runtime loader), 7b79d11 (sim/narrative — Lura beat gating 1/4/8 harvest, STRY-10), 661f990 (Lura dialogue overlay + Ink runtime + gate visual + Garden scene wiring). 264/264 tests green (was 217; +47 new); npm run ci exits 0 end-to-end with compile:ink integrated into the chain BEFORE test. STRY-01 / STRY-06 / STRY-07 / STRY-10 requirements satisfied. Lura goes on the record as the warmth anchor — 3 authored Ink beats (lura-arrival/mid/farewell) gated at 1st/4th/8th harvest counts (D-14); STRY-10 mechanically defended by FakeClock-24h-no-harvest test. RESEARCH Assumption A6 verified first-try on Windows via the bundled inklecate binary at node_modules/inklecate/bin/. Vite emits 4 lazy code-split chunks for compiled Ink JSON. Sim purity firewall holds: src/sim/narrative/ contains zero inkjs imports. Compost-toast UI deferred to Plan 02-05's persistence-toast surface (compost-acknowledgements.ink rewritten in VAR-driven branch shape, ready for the runtime). Wave 2 final plan (02-05 letter-settings-e2e) is the only remaining Phase-2 work. Next: /gsd-execute-phase 2 to continue with Plan 02-05."
|
stopped_at: "Phase 2 Wave 2 final plan (Plan 02-05 letter-settings-e2e) executed in sequential mode. 4 atomic commits: 26eb77a (sim/offline + auto-harvest + letter Ink + letter-renderer), 5d58d6c (Letter overlay + Settings UI + boot save lifecycle + clock injection), dd48696 (Playwright e2e for PIPE-07), 31f8ede (compost beat toast — Plan 02-04 deferral). 312/312 vitest tests green (was 264; +48 new); Playwright PIPE-07 spec exits 0 in 1.5s test runtime / 4s end-to-end; npm run ci exits 0. UX-02 / UX-10 / CORE-03 / CORE-11 / PIPE-07 / GARD-02 / GARD-04 requirements satisfied. Phase 2 vertical slice closed end-to-end on real authored content + real save round-trip + real offline catchup — could plausibly ship as a free standalone Season-1 prologue. URL-flag FakeClock injection landed cleanly first-try, production-guarded by import.meta.env.PROD. Compost-beat UI wired as a thin transient CompostToast (minimum-viable; Ink runtime path remains compiled + loadable for Phase 4+ swap-in). gray-matter dep auto-removed (Rule 3 blocking issue surfaced via the e2e — Buffer not defined in browser; replaced with 15-line parseFrontmatter helper; bundle dropped 2.2MB → 1.9MB). All 24 Phase-2 REQ-IDs structurally satisfied across the 5-plan set. Phase 2 ready for /gsd-verify-work."
|
||||||
last_updated: "2026-05-09T14:32:00.000Z"
|
last_updated: "2026-05-09T15:08:00.000Z"
|
||||||
last_activity: 2026-05-09
|
last_activity: 2026-05-09
|
||||||
progress:
|
progress:
|
||||||
total_phases: 8
|
total_phases: 8
|
||||||
completed_phases: 1
|
completed_phases: 1
|
||||||
total_plans: 8
|
total_plans: 8
|
||||||
completed_plans: 11
|
completed_plans: 12
|
||||||
percent: 19
|
percent: 22
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -21,16 +21,16 @@ progress:
|
|||||||
See: .planning/PROJECT.md (updated 2026-05-08)
|
See: .planning/PROJECT.md (updated 2026-05-08)
|
||||||
|
|
||||||
**Core value:** Every idle mechanic must function as a metaphor that the player absorbs without being told. When economy and meaning conflict, meaning wins.
|
**Core value:** Every idle mechanic must function as a metaphor that the player absorbs without being told. When economy and meaning conflict, meaning wins.
|
||||||
**Current focus:** Phase 02 — Season 1 Vertical Slice (Soil) — Wave 0 done; Wave 1 done (02-02 + 02-03); Wave 2 first plan (02-04) DONE; 02-05 is the only remaining Phase-2 work
|
**Current focus:** Phase 02 — Season 1 Vertical Slice (Soil) — ALL 5 PLANS DONE; ready for /gsd-verify-work
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 02 (season-1-vertical-slice-soil) — 4/5 plans complete (Wave 0 + Wave 1 + first Wave 2 plan)
|
Phase: 02 (season-1-vertical-slice-soil) — 5/5 plans complete (all waves)
|
||||||
Plans: 5 of 5 created (3 waves); Wave 0 (02-01) DONE; Wave 1 (02-02 + 02-03) DONE; Wave 2 first plan (02-04 lura-gate-beats) DONE; Wave 2 final plan (02-05 letter-settings-e2e) queued
|
Plans: 5 of 5 created (3 waves); Wave 0 (02-01) DONE; Wave 1 (02-02 + 02-03) DONE; Wave 2 (02-04 + 02-05) DONE
|
||||||
Status: Plan 02-04 lura-gate-beats executed in sequential mode — 3 atomic commits, 47 new tests (264/264 total green), npm run ci exits 0 with compile:ink integrated into the chain BEFORE test. STRY-01 / STRY-06 / STRY-07 (vacuous) / STRY-10 satisfied end-to-end. Lura's 3 Season-1 beats authored in voice + ship via the inklecate compile pipeline + inkjs runtime; gate visual indicator + DOM dialogue overlay both green. RESEARCH Assumption A6 verified first-try on Windows. Sim purity firewall holds: src/sim/narrative/ contains zero inkjs imports.
|
Status: Plan 02-05 letter-settings-e2e executed in sequential mode — 4 atomic commits (26eb77a, 5d58d6c, dd48696, 31f8ede), 48 new tests (312/312 total vitest green), npm run ci exits 0, npx playwright test exits 0 in 1.5s. UX-02 / UX-10 / CORE-03 / CORE-11 / PIPE-07 / GARD-02 / GARD-04 satisfied end-to-end. Phase 2 vertical slice closed: a player can launch, plant, grow, harvest, meet Lura, leave the tab, return ≥5min later, see the letter from the garden in voice, dismiss to the live garden — and everything persists across reload (PIPE-07 e2e proves it). All 24 Phase-2 REQ-IDs structurally satisfied across the 5-plan set. The vertical slice could plausibly ship as a free standalone Season-1 prologue (banner concern #2's escape hatch realized).
|
||||||
Last activity: 2026-05-09 -- Plan 02-04 execute complete
|
Last activity: 2026-05-09 -- Plan 02-05 execute complete; Phase 2 ready for verification
|
||||||
|
|
||||||
Progress: [██░░░░░░░░] 19%
|
Progress: [██░░░░░░░░] 22%
|
||||||
|
|
||||||
## Verification Results
|
## Verification Results
|
||||||
|
|
||||||
@@ -61,21 +61,21 @@ Gates run: lint (exit 0), test (53/53 green, 12 files), validate:assets (2 asset
|
|||||||
|
|
||||||
**Velocity:**
|
**Velocity:**
|
||||||
|
|
||||||
- Total plans completed: 10 (1 partial — 01-05 Task 2 deferred via IOU)
|
- Total plans completed: 12 (1 partial — 01-05 Task 2 deferred via IOU)
|
||||||
- Average duration: ~5 min (Wave 1 baseline 6min; Wave 2 plans 4–8min; Plan 07 ~2min; Plan 02-01 ~12min; Plan 02-02 ~18min; Plan 02-03 ~12min — Phase-2 vertical-slice plans are heaviest)
|
- Average duration: ~7 min across all plans; Phase-2 plans are heavier (12-24min each)
|
||||||
- Total execution time: ~70 min across Phase 1 + Phase 2 Wave 0 + Wave 1 (both plans)
|
- Total execution time: ~106 min across Phase 1 + Phase 2 (all 12 plans)
|
||||||
|
|
||||||
**By Phase:**
|
**By Phase:**
|
||||||
|
|
||||||
| Phase | Plans | Total | Avg/Plan |
|
| Phase | Plans | Total | Avg/Plan |
|
||||||
|-------|-------|-------|----------|
|
|-------|-------|-------|----------|
|
||||||
| 1. Foundations & Doctrine | 7/7 (complete) | ~30 min | ~5 min |
|
| 1. Foundations & Doctrine | 7/7 (complete) | ~30 min | ~5 min |
|
||||||
| 2. Season 1 Vertical Slice (Soil) | 4/5 (Wave 0 + Wave 1 + first Wave 2 plan complete) | ~66 min | ~16 min |
|
| 2. Season 1 Vertical Slice (Soil) | 5/5 (complete; ready for /gsd-verify-work) | ~86 min | ~17 min |
|
||||||
|
|
||||||
**Recent Trend:**
|
**Recent Trend:**
|
||||||
|
|
||||||
- Last 5 plans: [01-07 ci-workflow · 02-01 foundations · 02-02 begin-plant-grow · 02-03 harvest-journal-fragments · 02-04 lura-gate-beats — all green]
|
- Last 5 plans: [02-01 foundations · 02-02 begin-plant-grow · 02-03 harvest-journal-fragments · 02-04 lura-gate-beats · 02-05 letter-settings-e2e — all green]
|
||||||
- Trend: → (02-04 was 24 min — heaviest Phase-2 plan to date; first real player-narrative integration in the project covers a build pipeline + runtime + sim gate + UI overlay + canvas indicator + Garden scene wiring + 4 authored Ink files all in one plan; +47 new tests for 264/264 total green)
|
- Trend: → (02-05 was 20min closing plan covering boot-path rewrite + 5 new components + Playwright e2e + Rule 3 auto-fix for gray-matter Buffer issue; +48 new tests for 312/312 total green)
|
||||||
|
|
||||||
*Updated after each plan completion*
|
*Updated after each plan completion*
|
||||||
|
|
||||||
@@ -106,6 +106,12 @@ Recent decisions affecting current work:
|
|||||||
- Plan 02-04 (Wave 2): STRY-07 satisfied vacuously for Phase 2 — zero .ink files contain Keeper-spoken lines. The gardener-keeper voice in compost beats acknowledges the player's actions but is never personified. Phase 7's binary choice surface (SEAS-09 / STRY-08) re-evaluates.
|
- Plan 02-04 (Wave 2): STRY-07 satisfied vacuously for Phase 2 — zero .ink files contain Keeper-spoken lines. The gardener-keeper voice in compost beats acknowledges the player's actions but is never personified. Phase 7's binary choice surface (SEAS-09 / STRY-08) re-evaluates.
|
||||||
- Plan 02-04 (Wave 2): Cadence values: DEFAULT_DELAY_MS=1500, PER_CHAR_MS=20, MAX_DELAY_MS=4000. Calibrated against typical 80-char line (3.1s) feeling close to a thoughtful texted reply, vs short "Oh." (1.56s) feeling like a beat. Tunable in playtest by editing src/ui/dialogue/ink-runtime.ts; constants exported for the Phase 8 UX-05 reduced-motion hook.
|
- Plan 02-04 (Wave 2): Cadence values: DEFAULT_DELAY_MS=1500, PER_CHAR_MS=20, MAX_DELAY_MS=4000. Calibrated against typical 80-char line (3.1s) feeling close to a thoughtful texted reply, vs short "Oh." (1.56s) feeling like a beat. Tunable in playtest by editing src/ui/dialogue/ink-runtime.ts; constants exported for the Phase 8 UX-05 reduced-motion hook.
|
||||||
- Plan 02-04 (Wave 2): Lura's `last_plant_type` derives from the most-recently-harvested fragment's tonal-register tag (warm → rosemary, contemplative → yarrow, heavy → winter-rose). The harvest pipeline doesn't currently store source plant type per harvest — Plan 02-05 may add that to offlineEvents. The tag-based proxy is sufficient for Phase 2's voice; Lura's branch on plant type is flavor, not a gate.
|
- Plan 02-04 (Wave 2): Lura's `last_plant_type` derives from the most-recently-harvested fragment's tonal-register tag (warm → rosemary, contemplative → yarrow, heavy → winter-rose). The harvest pipeline doesn't currently store source plant type per harvest — Plan 02-05 may add that to offlineEvents. The tag-based proxy is sufficient for Phase 2's voice; Lura's branch on plant type is flavor, not a gate.
|
||||||
|
- Plan 02-05 (Wave 2): URL-flag FakeClock injection landed cleanly first-try, production-guarded by import.meta.env.PROD. Window slots `__tlgClock` / `__tlgFakeClock` / `__tlgStore` are written ONLY when `!isProd && devtime === 'fake'`; production builds silently ignore the flag. Playwright PIPE-07 spec exploits this to dispatch sim commands without pixel-precise canvas clicks — the test runs in 1.5s.
|
||||||
|
- Plan 02-05 (Wave 2): Compost-beat UI wired as a thin transient CompostToast (D-07 + GARD-04). Implementation choice surfaced in SUMMARY: minimum-viable bias chosen over the Ink runtime path. The Ink-authored richer voice in compost-acknowledgements.ink remains compiled + runtime-loadable for Phase 4+ to swap in if branching is needed. compostBeatTick monotonic counter (vs. boolean) ensures consecutive composts re-fire the toast without dedup.
|
||||||
|
- Plan 02-05 (Wave 2): Save-payload helpers extracted to src/save/payload.ts (W2 fix). Two-arg signature buildPayloadFromStore(state, nowMs) unifies Settings.tsx (passes Date.now()) and PhaserGame.tsx saveSync (passes clock.now()) without arity divergence. BLOCKER 3 — lastTickAt is the wall-clock anchor; the application layer owns the value.
|
||||||
|
- Plan 02-05 (Wave 2): 5-minute absence threshold (D-20) lives as ABSENCE_LETTER_THRESHOLD_MS constant in src/PhaserGame.tsx. Below 5min: silent resume, no overlay. ≥5min: letter Ink loads + slots bind + overlay opens. The Letter overlay's dismiss path calls bootstrapAudioContext synchronously inside the click handler (Pitfall 9 — returning player needs an audio gesture to land in the live garden).
|
||||||
|
- Plan 02-05 (Wave 2): gray-matter package replaced with a 15-line parseFrontmatter regex helper (Rule 3 — Blocking auto-fix). gray-matter pulls in Node's Buffer global which is undefined under Vite's browser bundle; the build emitted a 'Module buffer externalized' warning that masked the runtime ReferenceError surfacing only in real browsers (caught by the e2e). Bundle size dropped 2.2MB → 1.9MB as a tree-shake side effect. The dep itself remains in package.json as a deferred-items cleanup task.
|
||||||
|
- Plan 02-05 (Wave 2): Playwright dev port pinned to 5273 + --strictPort because the user's machine has another Vite project bound to 5173. reuseExistingServer false ensures the spec always launches a fresh Vite against this project. Documented in playwright.config.ts comment block.
|
||||||
- Phases 4-7 deliver the remaining six Seasons in mechanic-introducing pairs (Season 2 alone with prestige, Seasons 3-4, Seasons 5-6, Season 7 alone) — at most one new mechanic per Season per the scope-defense doctrine.
|
- Phases 4-7 deliver the remaining six Seasons in mechanic-introducing pairs (Season 2 alone with prestige, Seasons 3-4, Seasons 5-6, Season 7 alone) — at most one new mechanic per Season per the scope-defense doctrine.
|
||||||
- Plan 01-01: scaffolded by hand (the official `npm create @phaserjs/game@latest` is interactive-only — `--template react-ts --yes` flags are silently ignored as of create-game v1.3.2); plan's documented fallback path was used. Vite 8 + TS 6 referenced-projects tsconfig layout adopted; `build` runs `tsc -b && vite build` so strict-TS gates every build. ESLint 9 installed → Plan 02 must use **flat config** (`eslint.config.js`), not legacy `.eslintrc.*`.
|
- Plan 01-01: scaffolded by hand (the official `npm create @phaserjs/game@latest` is interactive-only — `--template react-ts --yes` flags are silently ignored as of create-game v1.3.2); plan's documented fallback path was used. Vite 8 + TS 6 referenced-projects tsconfig layout adopted; `build` runs `tsc -b && vite build` so strict-TS gates every build. ESLint 9 installed → Plan 02 must use **flat config** (`eslint.config.js`), not legacy `.eslintrc.*`.
|
||||||
- Plan 01-01: pre-installed `fake-indexeddb@^6` here so Plan 03 doesn't have to re-edit `package.json`. All Phase-1 dep versions match RESEARCH.md exactly within their `^` ranges.
|
- Plan 01-01: pre-installed `fake-indexeddb@^6` here so Plan 03 doesn't have to re-edit `package.json`. All Phase-1 dep versions match RESEARCH.md exactly within their `^` ranges.
|
||||||
@@ -136,5 +142,5 @@ Items acknowledged and carried forward:
|
|||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-05-09
|
Last session: 2026-05-09
|
||||||
Stopped at: Phase 2 Wave 2 first plan (Plan 02-04 lura-gate-beats) executed in sequential mode — 3 atomic commits (c90f8f1, 7b79d11, 661f990), 47 new tests, 264/264 total green, npm run ci exits 0 (compile:ink integrated into the CI chain BEFORE test). STRY-01/STRY-06/STRY-07/STRY-10 satisfied end-to-end. Lura's 3 Season-1 beats authored in voice via the inklecate compile pipeline + inkjs runtime; gate visual indicator + DOM dialogue overlay both functional. RESEARCH Assumption A6 verified first-try on Windows via the bundled inklecate binary. Sim purity firewall holds: src/sim/narrative/ contains zero inkjs imports; ESLint sim-purity rule still green. Vite emits 4 lazy code-split chunks for compiled Ink JSON. SUMMARY at .planning/phases/02-season-1-vertical-slice-soil/02-04-lura-gate-beats-SUMMARY.md.
|
Stopped at: Phase 2 Wave 2 final plan (Plan 02-05 letter-settings-e2e) executed in sequential mode — 4 atomic commits (26eb77a, 5d58d6c, dd48696, 31f8ede), 48 new tests, 312/312 total vitest green, npm run ci exits 0, Playwright PIPE-07 spec exits 0 in 1.5s test runtime / 4s end-to-end. UX-02 / UX-10 / CORE-03 / CORE-11 / PIPE-07 / GARD-02 / GARD-04 satisfied end-to-end. Phase 2 vertical slice closed: a player can launch, plant, grow, harvest, meet Lura, leave the tab, return ≥5min later, see the letter from the garden in voice, dismiss to the live garden — and everything persists across reload. URL-flag FakeClock injection production-guarded; gray-matter dep auto-removed (bundle 2.2MB → 1.9MB); compost beat wired as thin transient toast. SUMMARY at .planning/phases/02-season-1-vertical-slice-soil/02-05-letter-settings-e2e-SUMMARY.md.
|
||||||
Next action: `/gsd-execute-phase 2` to continue with Plan 02-05 (offline catchup + letter + Settings + Playwright e2e — UX-02, UX-10, CORE-03, CORE-11, PIPE-07). Plan 02-05 will fold the compost-toast UI surface alongside the persistence-denied toast (compost-acknowledgements.ink runtime path is already wired; only the toast component is missing). Plan 02-05 is the final Phase-2 plan; completing it means Phase 2 is shippable as a free standalone Season-1 prologue.
|
Next action: `/gsd-verify-work` to UAT Phase 2. All 24 Phase-2 REQ-IDs structurally satisfied; the verifier consumes the e2e + SUMMARY for sign-off. After Phase 2 verification passes: `/gsd-discuss-phase 3` to begin the Watercolor & Cello Aesthetic phase (8 REQ-IDs: GARD-10, AEST-01..06, UX-05).
|
||||||
|
|||||||
+326
@@ -0,0 +1,326 @@
|
|||||||
|
---
|
||||||
|
phase: 02-season-1-vertical-slice-soil
|
||||||
|
plan: 05
|
||||||
|
subsystem: letter-settings-e2e-vertical-slice-closeout
|
||||||
|
tags: [vertical-slice, letter, settings, save-lifecycle, offline-catchup, playwright-e2e, compost-toast, mvp, wave-2]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 02-01
|
||||||
|
provides: Zustand store + V1Payload extension fields + tick scheduler (drainTicks/computeOfflineCatchup) + save lifecycle hooks (registerSaveLifecycleHooks) + Phaser EventBus singleton
|
||||||
|
- phase: 02-02
|
||||||
|
provides: sim/garden core + Garden Phaser scene (clock-via-window-slot read pattern) + BeginScreen + audio bootstrap + UI strings
|
||||||
|
- phase: 02-03
|
||||||
|
provides: 17 Season-1 fragments + sim/memory selector + harvest/compost commands + Memory Journal + JournalIcon (D-23 first-harvest gate)
|
||||||
|
- phase: 02-04
|
||||||
|
provides: inklecate compile pipeline + 4 authored Ink files + ink-loader (loadInkStory + INK_VARIABLE_MAP) + InkRenderer drip + LuraDialogue overlay + gate-renderer
|
||||||
|
provides:
|
||||||
|
- sim/offline module — OfflineEventBlockSchema (Zod) + EMPTY_OFFLINE_EVENTS + aggregateOfflineEvent pure aggregator (CONTEXT D-19)
|
||||||
|
- sim/garden/auto-harvest — autoHarvestReadyPlants silent-mode harvest branch (D-10) reusing the standard harvest() pipeline so selector + Pitfall 10 unlocks + STRY-10 Lura gate run identically; BLOCKER 3 invariant preserved (no lastTickAt writes)
|
||||||
|
- simulateOneTick silent mode — ctx.silent triggers auto-harvest sweep before tick increment; active-play path unchanged (silent defaults false)
|
||||||
|
- content/dialogue/season1/letter-from-the-garden.ink — authored Ink skeleton with VAR plants_bloomed / fragment_titles / lura_was_here per D-17/D-18; bible voice, anti-FOMO compliant, 24h cap silent in voice (D-11)
|
||||||
|
- ink-loader extended — loadInkStory union accepts 'letter-from-the-garden' (separate letterStoryGlob for lazy code-split chunk); INK_VARIABLE_MAP gains plants_bloomed / fragment_titles / lura_was_here slots reading from session.pendingLetterEventBlock
|
||||||
|
- src/save/payload.ts — buildPayloadFromStore(state, nowMs) + hydrateStoreFromPayload(state, payload). Two-arg signature (W2 fix) unifies Settings.tsx and PhaserGame.tsx saveSync without arity divergence.
|
||||||
|
- src/ui/letter/Letter.tsx — D-20 full-screen DOM overlay (UX-02). Loads compiled letter Ink, binds slots from offlineEvents, dismisses via Tend the garden button or backdrop click. Pitfall 9 — synchronous-inside-click bootstrapAudioContext call.
|
||||||
|
- src/ui/letter/letter-renderer.ts — pure buildLetterSlots helper (testable without happy-dom + Ink runtime).
|
||||||
|
- src/ui/settings/Settings.tsx — D-28 save-management modal (Export to Base64 / Import / Restore previous snapshot). BLOCKER 2 — Import pipeline is importFromBase64 → unwrap → migrate → hydrate.
|
||||||
|
- src/ui/settings/persistence-toast.tsx — D-30 one-time soft toast in voice when navigator.storage.persist() denies. Reads showPersistenceToast transient flag from session slice; sets persistenceToastShown=true after timeout.
|
||||||
|
- src/ui/settings/compost-toast.tsx — D-07 + GARD-04 thin transient compost beat toast (Plan 02-04 deferral). Cycles through uiStrings.post_harvest_beat lines on each compost dispatch; fades after 3.5s.
|
||||||
|
- PhaserGame.tsx full boot path rewrite — clock selection (?devtime=fake, production-guarded), save load (BLOCKER 1: unwrap → 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.
|
||||||
|
- App.tsx — mounts Letter, Settings, PersistenceToast, CompostToast, SettingsIcon (corner button); D-29 keyboard shortcuts (',' toggles Settings, 'j' toggles Journal via window event).
|
||||||
|
- tests/e2e/season1-loop.spec.ts — Playwright PIPE-07 smoke covering load → Begin → plant → fast-forward → harvest → reveal → journal → reload → persist. Sidesteps Phaser canvas pixel-clicking via window.__tlgStore command dispatch (production-guarded).
|
||||||
|
- playwright.config.ts — pinned port 5273 + --strictPort to avoid dev-server collisions; reuseExistingServer false; webServer timeout bumped 30s → 60s.
|
||||||
|
- src/content/loader.ts — gray-matter replaced with parseFrontmatter (15-line regex-based YAML frontmatter splitter). Rule 3 — Blocking auto-fix: gray-matter pulls in Node Buffer global which is undefined in the browser; the build emitted a 'Module buffer externalized' warning that masked the runtime ReferenceError surfacing only in real browsers (caught by the e2e). Bundle size dropped 2.2MB → 1.9MB as a side effect.
|
||||||
|
- PIPE-07 SATISFIED — full Phase-2 vertical slice exercised end-to-end in a real Chromium build under FakeClock injection.
|
||||||
|
affects: [/gsd-verify-work (Phase 2 verification consumes this plan's e2e + SUMMARY for sign-off), Phase 3 (Watercolor & Cello — paints over the working loop)]
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Boot path as the binding layer (src/PhaserGame.tsx): clock selection → save load → unwrap → migrate → hydrate → silent offline catchup → maybe-open-letter → start Phaser → register save lifecycle hooks. Two useLayoutEffect blocks; lifecycle handle held in a ref so the outer cleanup can detach across the async IIFE boundary (W5)."
|
||||||
|
- "Silent-mode simulate (D-10): ctx.silent flips on for the offline catchup loop; simulateOneTick auto-harvests every ready-stage tile via autoHarvestReadyPlants. The harvest pipeline is reused identically — selector + Pitfall 10 unlocks + STRY-10 Lura gate all run; the only difference is who initiates (sim vs. player command)."
|
||||||
|
- "OfflineEventBlock as the letter's slot vocabulary (D-17/D-19): the silent catchup accumulates plantsBloomedCount + harvestedFragmentIds + luraBeatPending; buildLetterSlots converts to Ink VAR slots; letter Ink renders the authored skeleton. Pure data flow; no Date.now leaks."
|
||||||
|
- "Save-payload helpers extracted to src/save/payload.ts (W2 fix): single source of truth for buildPayloadFromStore(state, nowMs) + hydrateStoreFromPayload(state, payload). Two-arg signature lets PhaserGame's saveSync pass clock.now() and Settings.tsx pass Date.now() — same shape, different value, BLOCKER 3 invariant preserved (lastTickAt is wall-clock ms, owned by the application layer)."
|
||||||
|
- "Test-only window slots (__tlgStore + __tlgFakeClock + __tlgClock) gated by import.meta.env.PROD. Production builds silently ignore the ?devtime=fake URL flag; the slots themselves are never assigned. Playwright e2e exploits this to dispatch sim commands without pixel-precise canvas clicks (which Phaser doesn't make easy in headless)."
|
||||||
|
- "Compost toast as a thin transient surface (Plan 02-04 deferral): bumpCompostBeat monotonic counter in session slice → CompostToast watches the tick value via useEffect → cycles through uiStrings.post_harvest_beat lines. The Ink-authored richer voice in compost-acknowledgements.ink stays compiled + runtime-loadable for Phase 4+ to swap in if branching is needed."
|
||||||
|
- "Frontmatter parsing without gray-matter (Rule 3 auto-fix): 15-line parseFrontmatter regex handles the strict '---<yaml>---<body>' shape under Vite's browser bundle without pulling in Node Buffer global. Bundle dropped 2.2MB → 1.9MB."
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- src/sim/offline/events.ts (OfflineEventBlockSchema + EMPTY_OFFLINE_EVENTS + aggregateOfflineEvent — D-19)
|
||||||
|
- src/sim/offline/events.test.ts (14 tests covering schema acceptance/rejection + aggregator immutability)
|
||||||
|
- src/sim/offline/index.ts (barrel)
|
||||||
|
- src/sim/garden/auto-harvest.ts (autoHarvestReadyPlants — D-10 silent-mode harvest)
|
||||||
|
- src/sim/garden/auto-harvest.test.ts (7 tests — single/multi-harvest, immature exclusion, BLOCKER 3 lastTickAt invariant, Lura gate threading)
|
||||||
|
- content/dialogue/season1/letter-from-the-garden.ink (authored letter Ink with VAR plants_bloomed / fragment_titles / lura_was_here)
|
||||||
|
- src/save/payload.ts (buildPayloadFromStore + hydrateStoreFromPayload shared helpers)
|
||||||
|
- src/ui/letter/Letter.tsx (D-20 full-screen overlay — loads letter Ink + binds slots + Pitfall 9 audio bootstrap on dismiss)
|
||||||
|
- src/ui/letter/Letter.test.tsx (7 tests — null-when-closed, dialog mounts, dismiss bootstraps audio + dismisses Begin gate, click-on-article does NOT dismiss, calls loadInkStory + ChoosePathString correctly)
|
||||||
|
- src/ui/letter/letter-renderer.ts (buildLetterSlots pure helper)
|
||||||
|
- src/ui/letter/letter-renderer.test.ts (10 tests — empty / single / multi / long-line slug fallback / missing-fragment fallback / lura_was_here flag / 50-bloom edge / zero-bloom path)
|
||||||
|
- src/ui/letter/index.ts (barrel)
|
||||||
|
- src/ui/settings/Settings.tsx (D-28 save-management modal)
|
||||||
|
- src/ui/settings/Settings.test.tsx (6 tests — null-when-closed, all 4 buttons mount, Close fires onClose, Export populates textarea + status, Import on bad payload shows soft error, Export→Import round-trip)
|
||||||
|
- src/ui/settings/persistence-toast.tsx (D-30 one-time soft toast)
|
||||||
|
- src/ui/settings/compost-toast.tsx (D-07 transient compost beat toast — Plan 02-04 deferral)
|
||||||
|
- src/ui/settings/compost-toast.test.tsx (4 tests — null at initial state, appears on bump, fades after timeout, re-fires on second bump)
|
||||||
|
- src/ui/settings/index.ts (barrel)
|
||||||
|
- tests/e2e/season1-loop.spec.ts (Playwright PIPE-07 full-loop smoke)
|
||||||
|
- .planning/phases/02-season-1-vertical-slice-soil/deferred-items.md (gray-matter package.json cleanup tracked)
|
||||||
|
modified:
|
||||||
|
- src/sim/garden/commands.ts (SimContext extended with `silent?: boolean`; simulateOneTick calls autoHarvestReadyPlants when ctx.silent; benign circular import with auto-harvest.ts is ESM-safe — neither needs the other at module-init time)
|
||||||
|
- src/sim/garden/index.ts (re-export autoHarvestReadyPlants)
|
||||||
|
- src/sim/index.ts (re-export ./offline)
|
||||||
|
- src/content/ink-loader.ts (extended union with 'letter-from-the-garden'; separate letterStoryGlob for lazy code-split; INK_VARIABLE_MAP gains plants_bloomed / fragment_titles / lura_was_here)
|
||||||
|
- src/save/index.ts (re-export buildPayloadFromStore + hydrateStoreFromPayload)
|
||||||
|
- src/store/session-slice.ts (showPersistenceToast + setShowPersistenceToast + compostBeatTick + bumpCompostBeat)
|
||||||
|
- src/ui/index.ts (re-export ./letter and ./settings)
|
||||||
|
- src/ui/journal/journal-icon.tsx (window 'tlg:toggle-journal' CustomEvent listener for D-29 'j' hotkey)
|
||||||
|
- src/PhaserGame.tsx (full boot path rewrite — clock selection + save load + silent catchup + lifecycle hooks)
|
||||||
|
- src/game/scenes/Garden.ts (formalized clock read via readClockSlot helper; compost branch calls bumpCompostBeat)
|
||||||
|
- src/App.tsx (mounts Letter, Settings, PersistenceToast, CompostToast, SettingsIcon; D-29 keyboard shortcuts)
|
||||||
|
- src/content/loader.ts (gray-matter replaced with parseFrontmatter; Rule 3 blocking-issue auto-fix)
|
||||||
|
- playwright.config.ts (port 5273 + strictPort; reuseExistingServer false; webServer timeout 60s)
|
||||||
|
- package.json (test:e2e script)
|
||||||
|
removed: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "URL-flag FakeClock injection landed cleanly first-try via window.__tlgClock + __tlgFakeClock + __tlgStore slots, all gated by import.meta.env.PROD. Production builds silently ignore ?devtime=fake. Verified by Playwright running successfully with the flag and structurally by the production guard in PhaserGame.tsx's first useLayoutEffect."
|
||||||
|
- "Compost-beat UI wired as a thin transient toast (CompostToast) rather than the full Ink runtime surface. Implementation choice surfaced per the plan's must_have: minimum-viable bias keeps Phase 2 closing tight; the Ink-authored compost-acknowledgements.ink content stays compiled + runtime-loadable so Phase 4+ can swap in richer voice without touching sim or store."
|
||||||
|
- "Save-payload helpers extracted to src/save/payload.ts (W2 fix). Two-arg signature buildPayloadFromStore(state, nowMs) unifies Settings.tsx (passes Date.now()) and PhaserGame.tsx saveSync (passes clock.now()) without arity divergence. BLOCKER 3 — lastTickAt is the wall-clock anchor; the application layer owns the value."
|
||||||
|
- "5-minute absence threshold (D-20) lives as ABSENCE_LETTER_THRESHOLD_MS in src/PhaserGame.tsx (line ~76 of the constants block, exported via grep-able literal). Below 5min: silent resume, no overlay. ≥5min: letter Ink loads + slots bind + overlay opens. Verified by structural code review; the e2e exercises the <5min path implicitly (the spec's reload happens in <1s wall-clock so the overlay does NOT fire on returning-player reload — fragment persistence is what we assert there)."
|
||||||
|
- "Compost-beat compostBeatTick is a monotonic counter (vs. boolean) so consecutive composts re-fire the toast without dedup. Boolean would have required a manual reset after the timeout; the counter pattern is simpler + matches React's useEffect dep-array semantics for re-firing on every change."
|
||||||
|
- "Silent-mode auto-harvest reuses the standard harvest() pipeline (vs. duplicating the selector + unlock logic). The cycle (auto-harvest.ts imports harvest from commands.ts; commands.ts imports autoHarvestReadyPlants from auto-harvest.ts) is benign in ESM — neither function references the other at module-init time. Verified empirically by all 312 tests passing."
|
||||||
|
- "gray-matter package.json entry left in place as a deferred-items cleanup task. The dep is no longer imported anywhere under src/ but removing it is a separate maintenance commit (out of Plan 02-05 scope, which only auto-fixed the runtime block)."
|
||||||
|
- "Playwright dev port pinned to 5273 (not the 5173 default) because the user's machine has another Vite project bound to 5173 (Apothecary). reuseExistingServer: false ensures the spec always launches a fresh Vite against this project's vite.config.ts. --strictPort makes a port collision fail loudly rather than silently latching onto another app."
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Boot path = the binding layer pattern. src/PhaserGame.tsx is the only place where save layer + scheduler + sim + store + Phaser all meet. It runs synchronously inside a useLayoutEffect (the async IIFE inside is for the await pattern only). Reusable for Phase 4+ Season-transition save-on-prestige logic."
|
||||||
|
- "Silent-mode simulate (D-10) — pure boolean flag on SimContext that flips behavior without changing function signatures. Reusable for Phase 4+ Memory Storms (Season 4) which may want a 'storm-tick' branch of similar shape."
|
||||||
|
- "Test-only window slots gated by import.meta.env.PROD. Reusable for Phase 8's visual-regression toolkit (which may want to expose render-tier internals to a test harness without polluting production builds)."
|
||||||
|
- "Thin transient toast pattern (CompostToast / PersistenceToast): tick counter or boolean in the session slice → component watches via useEffect → renders for a few seconds → fades. Reusable for any Phase 4+ Season-transition acknowledgement, Memory Storm warning, etc."
|
||||||
|
- "Frontmatter parsing without gray-matter (parseFrontmatter): 15-line regex handles strict YAML frontmatter under Vite's browser bundle. Reusable anywhere a project wants Markdown-with-frontmatter content without pulling in Node Buffer global."
|
||||||
|
|
||||||
|
requirements-completed: [UX-02, UX-10, CORE-03, CORE-11, PIPE-07, GARD-02, GARD-04]
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 20min
|
||||||
|
completed: 2026-05-09
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 2 Plan 05: Letter, Settings, Save Lifecycle, e2e Summary
|
||||||
|
|
||||||
|
## One-liner
|
||||||
|
|
||||||
|
Phase 2 closes — sim/offline + auto-harvest silent-mode branch (D-10), letter-from-the-garden Ink (UX-02 with the slot vocabulary plants_bloomed/fragment_titles/lura_was_here populated from offlineEvents), full-screen Letter overlay (D-20 with Pitfall 9 audio bootstrap on dismiss), Settings save-management UI (D-28 Export/Import/Restore with BLOCKER 2 unwrap→migrate pipeline), persistence-result toast (D-30) and a thin compost-beat toast (Plan 02-04 deferral), full PhaserGame.tsx boot path rewrite wiring clock selection (URL-flag FakeClock injection production-guarded by import.meta.env.PROD) + save lifecycle (UX-10) + offline catchup, and the Playwright PIPE-07 spec exercising the entire authored loop end-to-end (load → Begin → plant → fast-forward → harvest → reveal → journal → reload → persist). The Phase-2 vertical slice could plausibly ship as a free standalone Season-1 prologue.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~20 min (sequential executor)
|
||||||
|
- **Started:** 2026-05-09T14:44:16Z
|
||||||
|
- **Completed:** 2026-05-09T15:08:00Z (approximate; this commit fires)
|
||||||
|
- **Tasks:** 3 main + 1 deferral-fold-in (compost toast)
|
||||||
|
- **Files created:** 19 (incl. tests + .ink + barrel files)
|
||||||
|
- **Files modified:** 14
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: sim/offline + auto-harvest + letter Ink + letter-renderer** — `26eb77a` (feat)
|
||||||
|
2. **Task 2: Letter overlay + Settings UI + boot save lifecycle + clock injection** — `5d58d6c` (feat)
|
||||||
|
3. **Task 3: Playwright e2e for PIPE-07 — full Phase-2 loop** — `dd48696` (test)
|
||||||
|
4. **Compost beat toast wiring (Plan 02-04 deferral)** — `31f8ede` (feat)
|
||||||
|
|
||||||
|
**Plan metadata:** _(this commit)_ — `docs(02-05): complete letter-settings-e2e plan`
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- **Phase 2 vertical slice closed end-to-end on real authored content + real save round-trip + real offline catchup.** A player can launch, plant rosemary, watch it grow, harvest a Season-1 fragment authored in voice, see it filed in the Memory Journal, meet Lura at the gate (Plan 02-04), close the tab, return ≥5min later, see the letter from the garden in voice, dismiss to the live garden — and everything persists across reload.
|
||||||
|
- **Banner Concern 4 (system-clock cheating) defended at every layer.** The boot path's computeOfflineCatchup clamps elapsed ms at MAX_OFFLINE_MS (24h); drainTicks refuses negative deltas; STRY-10 narrative gating counts harvest events not wall time (Plan 02-04); the ESLint sim-purity rule (Plan 02-01 Block 3) prevents Date.now/setInterval inside src/sim/. Plan 02-05 inherits all of these and adds nothing that breaks them.
|
||||||
|
- **PIPE-07 PASSES.** Playwright spec runs in 1.5s test-runtime, 4s end-to-end including dev-server cold start, well under the <30s budget. The spec is the canonical proof that Phase 2 is shippable: it actually loads the dev build in Chromium, dispatches sim commands, exercises the full loop, and asserts persistence.
|
||||||
|
- **24/24 Phase-2 REQ-IDs structurally satisfied across the 5-plan set.** See the table at the end of this summary; every requirement has a plan that owned it and a SUMMARY documenting the satisfaction.
|
||||||
|
- **Bundle size DROPPED.** Removing gray-matter (Rule 3 auto-fix during the e2e) brought the entry chunk from 2.2MB → 1.9MB without changing any feature surface. The Markdown loader path now uses a 15-line parseFrontmatter regex helper.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
See frontmatter `key-files` for the full list (19 created + 14 modified).
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
See `key-decisions` in frontmatter (8 entries). Headlines:
|
||||||
|
|
||||||
|
1. URL-flag FakeClock injection landed cleanly first-try, production-guarded by `import.meta.env.PROD`.
|
||||||
|
2. Compost-beat UI wired as a thin transient toast (CompostToast) — minimum-viable; Ink runtime path stays available for Phase 4+ to swap in richer voice.
|
||||||
|
3. Save-payload helpers extracted to `src/save/payload.ts` (W2) — two-arg `(state, nowMs)` signature unifies Settings.tsx (passes `Date.now()`) and PhaserGame.tsx saveSync (passes `clock.now()`).
|
||||||
|
4. 5-minute absence threshold lives as `ABSENCE_LETTER_THRESHOLD_MS` constant (CONTEXT D-20).
|
||||||
|
5. `compostBeatTick` is a monotonic counter (not boolean) so consecutive composts re-fire the toast without dedup.
|
||||||
|
6. Silent-mode auto-harvest reuses the standard `harvest()` pipeline; the benign ESM circular import is verified by all 312 tests passing.
|
||||||
|
7. `gray-matter` package.json entry left in `package.json` for a separate cleanup commit (deferred-items.md tracks it).
|
||||||
|
8. Playwright dev port pinned to 5273 + `--strictPort` to avoid collisions with another Vite project on the user's machine.
|
||||||
|
|
||||||
|
## Compost-Beat UI Wiring Approach
|
||||||
|
|
||||||
|
**Chosen: thin transient CompostToast** (`src/ui/settings/compost-toast.tsx`) reading from `uiStrings[1].post_harvest_beat` (3 short authored lines that rotate per compost).
|
||||||
|
|
||||||
|
**Trade-off vs. Ink runtime path**: Phase 2 is closing tight; the user has been pushing back on ceremony. The Ink-authored richer voice in `content/dialogue/season1/compost-acknowledgements.ink` (6 short lines in the gardener-keeper voice, branched on `fragment_count`) IS:
|
||||||
|
- Compiled to JSON at every build (`npm run compile:ink` emits 5 .ink.json files now: 4 Lura + 1 letter; the compost compile output is also there).
|
||||||
|
- Runtime-loadable via `loadInkStory('compost-acknowledgements')` which Plan 02-04 wired.
|
||||||
|
- Sitting at the wiring point — `src/ui/settings/compost-toast.tsx` could be replaced wholesale with an Ink-driven component without touching the sim, store, or App.tsx mount.
|
||||||
|
|
||||||
|
The thin-toast surface satisfies D-07 (post-harvest acknowledgement beat) + GARD-04 (compost yields a tonal beat) for Phase 2's minimum-viable closeout. Phase 4+ may upgrade to the Ink runtime path if playtest demands richer voice.
|
||||||
|
|
||||||
|
## URL-Flag FakeClock Injection — Verification
|
||||||
|
|
||||||
|
**Landed cleanly first-try.** No iteration was needed on the production-guard or the slot-exposure mechanics. Verification:
|
||||||
|
|
||||||
|
- `window.__tlgFakeClock` and `window.__tlgStore` are written ONLY when `!isProd && devtime === 'fake'`. The production guard reads `import.meta.env.PROD` (Vite injects `true` for `vite build`, `false` for `vite dev`).
|
||||||
|
- Playwright spec uses `?devtime=fake` → both slots become available → spec dispatches `enqueueCommand` directly via `__tlgStore.getState().enqueueCommand({...})` and advances time via `__tlgFakeClock.advance(ms)`.
|
||||||
|
- Garden scene reads the clock via `readClockSlot()` which falls back to `wallClock` if no slot is set (covers the production code path + the unit-test path that instantiates the scene without going through `PhaserGame.tsx`).
|
||||||
|
|
||||||
|
## Playwright Run Time
|
||||||
|
|
||||||
|
- **Test runtime:** 1.5s (single spec, single test, single browser).
|
||||||
|
- **End-to-end including dev-server cold start:** ~4s.
|
||||||
|
- **Goal:** <30s per VALIDATION.md sampling rate row. Achieved with significant headroom.
|
||||||
|
|
||||||
|
## Manual Smoke Test Confirmation
|
||||||
|
|
||||||
|
**Not performed in this execution session** (sequential automated executor; user has not yet run `npm run dev`). Structural verification is comprehensive:
|
||||||
|
|
||||||
|
- 312/312 Vitest cases green (was 264 before this plan; +48 new — 14 sim/offline + 7 sim/garden auto-harvest + 10 letter-renderer + 7 Letter + 6 Settings + 4 CompostToast).
|
||||||
|
- `npm run lint` exits 0 (zero ESLint sim-purity violations; sim/offline + sim/garden/auto-harvest contain zero Date.now / setInterval).
|
||||||
|
- `npm run compile:ink` emits 5 .ink.json files (Plan 02-04's 4 + this plan's letter).
|
||||||
|
- `npm run build` exits 0; entry bundle 1.9MB (down from 2.2MB after gray-matter removal); Vite emits 5 lazy code-split chunks for the compiled Ink.
|
||||||
|
- `npm run check:bundle-split` exits 0 (PIPE-02 OK — Season-1 content reachable via build output).
|
||||||
|
- `npm run ci` exits 0 end-to-end with all six gates green.
|
||||||
|
- `npx playwright test tests/e2e/season1-loop.spec.ts` exits 0 in 4s.
|
||||||
|
|
||||||
|
The Plan 02-05 Playwright e2e IS the manual-smoke-equivalent for the active-play loop end-to-end. The user can run `npm run dev` to drive it interactively at any point.
|
||||||
|
|
||||||
|
## Final Tally — All 24 Phase-2 REQ-IDs
|
||||||
|
|
||||||
|
| REQ-ID | Plan | Status |
|
||||||
|
|--------|------|--------|
|
||||||
|
| CORE-02 | 02-01 (drainTicks fixed-timestep) + 02-02 (Garden update loop) | ✓ |
|
||||||
|
| CORE-03 | 02-01 (computeOfflineCatchup 24h cap) + 02-05 (boot path threads it) | ✓ |
|
||||||
|
| CORE-11 | 02-01 (drainTicks negative refusal) | ✓ |
|
||||||
|
| GARD-01 | 02-02 (plantSeed + SeedPicker) | ✓ |
|
||||||
|
| GARD-02 | 02-02 (growth state machine) + 02-05 (PIPE-07 verifies save round-trip) | ✓ |
|
||||||
|
| GARD-03 | 02-03 (harvest + reveal modal) | ✓ |
|
||||||
|
| GARD-04 | 02-03 (compost command) + 02-04 (compost.ink content) + 02-05 (CompostToast wired) | ✓ |
|
||||||
|
| MEMR-01 | 02-03 (selector returns exactly one fragment per harvest) | ✓ |
|
||||||
|
| MEMR-02 | 02-03 (17 fragments authored under /content/seasons/01-soil/) | ✓ |
|
||||||
|
| MEMR-03 | 02-03 (FragmentSchema regex enforces stable string ids) | ✓ |
|
||||||
|
| MEMR-04 | 02-03 (Memory Journal modal grouped by Season) | ✓ |
|
||||||
|
| MEMR-05 | 02-03 (DOM-rendered selectable text via `<pre>` + userSelect:'text') | ✓ |
|
||||||
|
| MEMR-06 | 02-03 (mulberry32-seeded selector + gating + no-dup + sentinel fallback) | ✓ |
|
||||||
|
| STRY-01 | 02-04 (3 Lura beats authored + LuraDialogue overlay) | ✓ |
|
||||||
|
| STRY-06 | 02-04 (compile-ink.mjs + 4 Lura beats) + 02-05 (letter Ink uses same pipeline) | ✓ |
|
||||||
|
| STRY-07 | 02-04 (vacuously satisfied — zero Keeper-spoken lines in Phase-2 .ink files) | ✓ |
|
||||||
|
| STRY-10 | 02-04 (lura-gate counts harvest events not wall time; FakeClock-24h-no-harvest test) | ✓ |
|
||||||
|
| AEST-07 | 02-02 (BeginScreen + bootstrapAudioContext synchronous-inside-click) | ✓ |
|
||||||
|
| UX-01 | 02-02 (Begin no-clutter overlay) + 02-03 (Journal reveals after first harvest) | ✓ |
|
||||||
|
| UX-02 | **02-05 (Letter overlay loads letter-from-the-garden.ink + binds slots from offlineEvents + Pitfall 9 audio bootstrap)** | ✓ |
|
||||||
|
| UX-10 | 02-01 (registerSaveLifecycleHooks + saveOnSeasonTransition) + 02-05 (PhaserGame.tsx boot wiring) | ✓ |
|
||||||
|
| UX-11 | 02-01 (formatHumanReadable / BigQty.format K/M/B/T/scientific) | ✓ |
|
||||||
|
| PIPE-02 | 02-02 (loadSeasonFragments lazy surface) + 02-03 (check-bundle-split.mjs structural verifier) | ✓ |
|
||||||
|
| PIPE-07 | **02-05 (Playwright e2e — full Phase-2 loop end-to-end in Chromium)** | ✓ |
|
||||||
|
|
||||||
|
**24 / 24 covered.**
|
||||||
|
|
||||||
|
## Total Test Count Across Phase 1 + Phase 2
|
||||||
|
|
||||||
|
- Phase 1 baseline: 53 tests
|
||||||
|
- Plan 02-01 (Wave 0): +75 (≈) → 128
|
||||||
|
- Plan 02-02 (Wave 1): +35 → 163
|
||||||
|
- Plan 02-03 (Wave 1): +54 → 217
|
||||||
|
- Plan 02-04 (Wave 2): +47 → 264
|
||||||
|
- **Plan 02-05 (Wave 2): +48 → 312**
|
||||||
|
|
||||||
|
312/312 tests green; 39 test files. `npm run ci` runs all of them in ~5s on this machine (Vitest only; Playwright is not in `ci` per minimum-viable doctrine — runs separately via `npm run test:e2e` before /gsd-verify-work and on release).
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 — Blocking] gray-matter pulls in Node Buffer global which is undefined under Vite's browser bundle**
|
||||||
|
|
||||||
|
- **Found during:** Task 3 — running the Playwright e2e for the first time. Vite dev mode surfaced `ReferenceError: Buffer is not defined` from `gray-matter/lib/utils.js`. The `vite build` step had been emitting a `Module "buffer" has been externalized for browser compatibility` warning since Plan 02-03 shipped; the warning masked a real runtime error that surfaces only in real browsers (Vitest + happy-dom never exercised the Markdown loader path because the existing tests use the test-only `loadFragmentsFromGlob` helper with mocked input).
|
||||||
|
- **Issue:** The Markdown fragment loader (lura-first-letter.md, winter-rose-night.md from Plan 02-03) was effectively broken in production browsers since its initial commit. Players running the dev or production build would have seen the React app crash at module-eval time when `loadMdFragments()` ran inside `src/content/loader.ts`.
|
||||||
|
- **Fix:** Replaced `gray-matter` with a 15-line `parseFrontmatter` regex helper in `src/content/loader.ts`. Handles the strict `---<yaml>---<body>` shape the .md files use; anything else falls through cleanly. No new dependencies; the existing `yaml` package already does the YAML parse.
|
||||||
|
- **Files modified:** src/content/loader.ts
|
||||||
|
- **Verification:** `npm run dev` no longer throws Buffer ReferenceError; Playwright e2e plant→harvest→reveal round-trip works end-to-end; bundle size dropped 2.2MB → 1.9MB as a tree-shake side effect; 13 content tests still green.
|
||||||
|
- **Committed in:** dd48696 (Task 3)
|
||||||
|
- **Deferred follow-up:** `gray-matter` package.json entry could be removed in a maintenance commit (no code references it). Tracked in `.planning/phases/02-season-1-vertical-slice-soil/deferred-items.md`.
|
||||||
|
|
||||||
|
### Tightenings (within plan author's discretion)
|
||||||
|
|
||||||
|
1. **Compost-beat UI wired as a CompostToast** with 4 dedicated tests (`src/ui/settings/compost-toast.test.tsx`). The plan said "implementation choice surfaced in SUMMARY"; chose the minimum-viable thin-toast surface to keep Phase 2 closing tight. Surface choice documented in this SUMMARY's Compost-Beat UI Wiring Approach section above.
|
||||||
|
2. **Playwright dev port + strictPort** — pinned to 5273 (not the default 5173) because the user's machine has another Vite project bound to 5173. Documented in playwright.config.ts comment block.
|
||||||
|
3. **Boot path's two-stage Phaser start** — start Phaser AFTER state hydration so the Garden scene's create() reads the correct initial tickCount + tiles. The plan's draft sketched this; the implementation formalized it as the canonical ordering (await save load → hydrate → start Phaser → register lifecycle hooks).
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
The gray-matter Buffer issue was the only substantive friction point. Beyond that, the plan was unusually well-specified — the 4 commits (3 main tasks + 1 compost-toast wiring) implemented as drafted with only minor cosmetic adjustments (e.g., `vi.hoisted` for the bootstrapSpy in Letter.test.tsx since Vitest hoists vi.mock factories above imports).
|
||||||
|
|
||||||
|
## TDD Gate Compliance
|
||||||
|
|
||||||
|
This plan is `type: execute`, not `type: tdd`. No RED → GREEN → REFACTOR commit-sequence gating applies. Tests landed alongside implementation in Tasks 1–3 + the compost-toast follow-up.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None — no external service configuration required. All work is in-tree TypeScript / authored content / a single Playwright spec.
|
||||||
|
|
||||||
|
## Phase 2 Readiness for Verification
|
||||||
|
|
||||||
|
- Phase 2's 5 plans are all complete:
|
||||||
|
- 02-01-foundations (Wave 0) — DONE
|
||||||
|
- 02-02-begin-plant-grow (Wave 1) — DONE
|
||||||
|
- 02-03-harvest-journal-fragments (Wave 1) — DONE
|
||||||
|
- 02-04-lura-gate-beats (Wave 2) — DONE
|
||||||
|
- **02-05-letter-settings-e2e (Wave 2) — DONE (this commit)**
|
||||||
|
- All 24 Phase-2 REQ-IDs satisfied across the 5-plan set; the table above maps each.
|
||||||
|
- `npm run ci` exits 0 (lint + compile:ink + 312/312 vitest + validate:assets + build + check:bundle-split).
|
||||||
|
- `npm run test:e2e` exits 0 (Playwright PIPE-07 spec; ~4s end-to-end).
|
||||||
|
- Phase 1's 53 tests + Phase 2's 259 new tests = 312 total green.
|
||||||
|
- The vertical slice could plausibly ship as a free standalone Season-1 prologue: a player can launch, plant, grow, harvest, meet Lura, leave, return to a letter, dismiss, and the save round-trip survives all of it. The 7-Season scope risk's defended-by-an-escape-hatch is realized.
|
||||||
|
|
||||||
|
**No blockers, no IOUs, no carried-over technical debt this plan produced** beyond the gray-matter dep cleanup tracked in deferred-items.md.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
Verification performed at SUMMARY-write time:
|
||||||
|
|
||||||
|
- src/sim/offline/events.ts: FOUND
|
||||||
|
- src/sim/offline/events.test.ts: FOUND
|
||||||
|
- src/sim/offline/index.ts: FOUND
|
||||||
|
- src/sim/garden/auto-harvest.ts: FOUND
|
||||||
|
- src/sim/garden/auto-harvest.test.ts: FOUND
|
||||||
|
- content/dialogue/season1/letter-from-the-garden.ink: FOUND
|
||||||
|
- src/save/payload.ts: FOUND
|
||||||
|
- src/ui/letter/Letter.tsx: FOUND
|
||||||
|
- src/ui/letter/Letter.test.tsx: FOUND
|
||||||
|
- src/ui/letter/letter-renderer.ts: FOUND
|
||||||
|
- src/ui/letter/letter-renderer.test.ts: FOUND
|
||||||
|
- src/ui/letter/index.ts: FOUND
|
||||||
|
- src/ui/settings/Settings.tsx: FOUND
|
||||||
|
- src/ui/settings/Settings.test.tsx: FOUND
|
||||||
|
- src/ui/settings/persistence-toast.tsx: FOUND
|
||||||
|
- src/ui/settings/compost-toast.tsx: FOUND
|
||||||
|
- src/ui/settings/compost-toast.test.tsx: FOUND
|
||||||
|
- src/ui/settings/index.ts: FOUND
|
||||||
|
- tests/e2e/season1-loop.spec.ts: FOUND
|
||||||
|
- .planning/phases/02-season-1-vertical-slice-soil/deferred-items.md: FOUND
|
||||||
|
- Commit 26eb77a (Task 1 — sim/offline + auto-harvest + letter Ink + letter-renderer): FOUND in `git log --oneline --all`
|
||||||
|
- Commit 5d58d6c (Task 2 — Letter overlay + Settings + boot save lifecycle + clock injection): FOUND in `git log --oneline --all`
|
||||||
|
- Commit dd48696 (Task 3 — Playwright e2e for PIPE-07): FOUND in `git log --oneline --all`
|
||||||
|
- Commit 31f8ede (compost-toast wiring — Plan 02-04 deferral): FOUND in `git log --oneline --all`
|
||||||
|
- `npm run ci` exits 0: VERIFIED
|
||||||
|
- 312/312 vitest tests pass: VERIFIED
|
||||||
|
- `npx playwright test tests/e2e/season1-loop.spec.ts` exits 0 (1.5s test runtime, ~4s end-to-end): VERIFIED
|
||||||
|
- ESLint sim-purity rule: zero violations (`npm run lint` exits 0)
|
||||||
|
- Build: `npm run build` exits 0; entry bundle 1.9MB (down from 2.2MB after gray-matter removal)
|
||||||
|
- 5 lazy code-split Ink chunks emitted: lura-arrival, lura-mid, lura-farewell, compost-acknowledgements, letter-from-the-garden
|
||||||
|
- All 24 Phase-2 REQ-IDs structurally satisfied across the 5-plan set
|
||||||
Reference in New Issue
Block a user