7.5 KiB
Tasks: Persist Encounter
Input: Design documents from /specs/008-persist-encounter/
Prerequisites: plan.md, spec.md, research.md, data-model.md
Tests: Included -- persistence logic warrants unit tests to cover serialization, validation, and error handling.
Organization: Tasks are grouped by user story to enable independent implementation and testing of each story.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
Phase 1: Setup
Purpose: Create the persistence module structure
- T001 Create persistence module directory at
apps/web/src/persistence/
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Core storage adapter that all user stories depend on
- T002 Implement
saveEncounter(encounter: Encounter): voidfunction inapps/web/src/persistence/encounter-storage.tsthat serializes encounter state to localStorage under key"initiative:encounter"usingJSON.stringify, wrapped in try/catch that silently swallows errors (quota exceeded, storage unavailable) - T003 Implement
loadEncounter(): Encounter | nullfunction inapps/web/src/persistence/encounter-storage.tsthat reads from localStorage key"initiative:encounter", parses JSON, performs structural shape checks (object withcombatantsarray,activeIndexnumber,roundNumbernumber; each combatant hasidstring andnamestring), rehydratesCombatantIdvalues viacombatantId(), validates throughcreateEncounter, and returnsnullon any failure (parse error, shape mismatch, domain validation failure) - T004 Write unit tests for
saveEncounterandloadEncounterinapps/web/src/persistence/__tests__/encounter-storage.test.tscovering: round-trip save/load preserves encounter state,loadEncounterreturnsnullwhen localStorage is empty, returnsnullfor non-JSON strings, returnsnullfor JSON missing required fields, returnsnullfor invalid encounter data (e.g. empty combatants array, out-of-bounds activeIndex)
Checkpoint: Storage adapter is complete and tested -- user story implementation can now begin
Phase 3: User Story 1 - Encounter Survives Page Reload (Priority: P1) MVP
Goal: Encounter state persists across page reloads with zero data loss
Independent Test: Set up an encounter with combatants, initiative values, and advanced turns. Call saveEncounter, then loadEncounter and verify all state matches. In the hook, verify useEffect triggers saveEncounter on state changes.
Tests for User Story 1
- T005 [US1] Write tests in
apps/web/src/persistence/__tests__/encounter-storage.test.tsfor: round-trip preserves combatant IDs, names, and initiative values; round-trip preserves activeIndex and roundNumber; saving after modifications (add/remove combatant, change initiative) persists the latest state
Implementation for User Story 1
- T006 [US1] Modify
useEncounterhook inapps/web/src/hooks/use-encounter.tsto initialize state fromloadEncounter()-- if it returns a valid encounter, use it instead ofcreateDemoEncounter(); derivenextIdcounter from highest numeric suffix in existing combatant IDs (parsec-{N}pattern) - T007 [US1] Add a
useEffectinapps/web/src/hooks/use-encounter.tsthat callssaveEncounter(encounter)whenever the encounter state changes
Checkpoint: User Story 1 is fully functional -- encounter survives page reload
Phase 4: User Story 2 - Fresh Start with No Saved Data (Priority: P2)
Goal: First-time users see the default demo encounter
Independent Test: With no localStorage data, load the app and verify the demo encounter (Aria, Brak, Cael) is displayed.
Implementation for User Story 2
- T008 [US2] Verify and document in tests at
apps/web/src/persistence/__tests__/encounter-storage.test.tsthatloadEncounter()returnsnullwhen localStorage has no"initiative:encounter"key, confirming theuseEncounterhook falls back tocreateDemoEncounter()
Checkpoint: User Story 2 confirmed -- no saved data results in demo encounter
Phase 5: User Story 3 - Graceful Handling of Corrupt Data (Priority: P3)
Goal: Corrupt or invalid saved data never crashes the app; falls back to demo encounter
Independent Test: Write various malformed values to the "initiative:encounter" localStorage key and verify loadEncounter() returns null for each.
Tests for User Story 3
- T009 [US3] Add tests in
apps/web/src/persistence/__tests__/encounter-storage.test.tsfor corrupt data scenarios: non-object JSON (string, number, array, null), object with wrong types for fields (combatants as string, activeIndex as string), combatant entries missingidorname, valid JSON structure but domain-invalid data (zero combatants, negative roundNumber)
Implementation for User Story 3
- T010 [US3] Verify
loadEncounter()structural checks inapps/web/src/persistence/encounter-storage.tscover all corrupt data scenarios from T009 -- adjust shape validation if any test cases reveal gaps
Checkpoint: All corrupt data scenarios handled gracefully
Phase 6: Polish & Cross-Cutting Concerns
- T011 Run
pnpm checkto verify formatting, linting, type checking, and all tests pass - T012 Run quickstart.md manual verification steps to validate end-to-end behavior
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies
- Foundational (Phase 2): Depends on Phase 1 -- BLOCKS all user stories
- User Story 1 (Phase 3): Depends on Phase 2
- User Story 2 (Phase 4): Depends on Phase 2 (independent of US1)
- User Story 3 (Phase 5): Depends on Phase 2 (independent of US1, US2)
- Polish (Phase 6): Depends on all user stories complete
User Story Dependencies
- User Story 1 (P1): Depends on Foundational only. Core save/load wiring in the hook.
- User Story 2 (P2): Depends on Foundational only. Verifies the null-fallback path.
- User Story 3 (P3): Depends on Foundational only. Hardens validation against corrupt data.
Parallel Opportunities
- T002 and T003 are sequential (same file, T003 depends on T002's storage key constant)
- T005 and T006 can run in parallel (test file vs hook file)
- T008 and T009 target the same test file as T004/T005 (
encounter-storage.test.ts); sequence them after T005 to avoid merge conflicts. T006/T007 (hook file) can still run in parallel with test tasks. - US2 and US3 can proceed in parallel after Foundational phase
Implementation Strategy
MVP First (User Story 1 Only)
- Complete Phase 1: Setup (T001)
- Complete Phase 2: Foundational (T002-T004)
- Complete Phase 3: User Story 1 (T005-T007)
- STOP and VALIDATE: Run
pnpm check, manually test reload behavior - This alone delivers the core value of the feature
Incremental Delivery
- Setup + Foundational -> Storage adapter ready
- Add User Story 1 -> Reload persistence works (MVP!)
- Add User Story 2 -> First-time experience confirmed
- Add User Story 3 -> Corrupt data resilience hardened
- Polish -> Final validation
Notes
- All new code is in the adapter layer (
apps/web/); domain and application packages are unchanged - Tests use a localStorage mock (Vitest's jsdom environment or manual mock)
- The
nextIdcounter derivation (T006) is critical to avoid ID collisions after reload - Commit after each phase or logical group of tasks