Files
initiative/specs/007-json-import-export/tasks.md
Lukas fba83bebd6 Add JSON import/export for full encounter state
Export and import encounter, undo/redo history, and player characters
as a downloadable .json file. Export/import actions are in the action
bar overflow menu. Import validates using existing rehydration functions
and shows a confirmation dialog when replacing a non-empty encounter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:28:39 +01:00

9.0 KiB

Tasks: JSON Import/Export

Input: Design documents from /specs/007-json-import-export/ Prerequisites: plan.md, spec.md, research.md, data-model.md, quickstart.md

Tests: Domain tests included (pure function testing is standard for this project per CLAUDE.md).

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: Foundational (ExportBundle Type + Validation)

Purpose: Define the export format and validation logic that all stories depend on.

⚠️ CRITICAL: Export and import both depend on the ExportBundle type and validation.

  • T001 [P] Create ExportBundle type in packages/domain/src/export-bundle.ts with fields: version (number), exportedAt (string), encounter (Encounter), undoStack (Encounter[]), redoStack (Encounter[]), playerCharacters (PlayerCharacter[]). Export from packages/domain/src/index.ts.
  • T002 [P] Create validateImportBundle() in apps/web/src/persistence/export-import.ts — accepts unknown, validates top-level structure (version, encounter, undoStack, redoStack, playerCharacters), delegates encounter validation to rehydrateEncounter() (imported from ./encounter-storage.ts) and player character validation to rehydrateCharacter() (exported from ./player-character-storage.ts). Returns validated ExportBundle or DomainError.
  • T003 Write tests for validateImportBundle() in apps/web/src/__tests__/validate-import-bundle.test.ts — valid bundle, missing fields, invalid encounter, invalid player characters, empty stacks, unknown version, non-object input, invalid JSON types for each field.

Checkpoint: ExportBundle type and validation are tested and ready for use by export and import stories.


Phase 2: User Story 1 — Export Encounter to File (Priority: P1) 🎯 MVP

Goal: Users can download the current state as a .json file in one click.

Independent Test: Create an encounter with combatants, HP, conditions, and player characters. Click export. Verify the downloaded file contains all state and is valid JSON matching the ExportBundle schema.

Implementation for User Story 1

  • T004 [P] [US1] Create assembleExportBundle() function in apps/web/src/persistence/export-import.ts — takes encounter, undoRedoState, and playerCharacters, returns an ExportBundle with version 1 and current ISO timestamp.
  • T005 [P] [US1] Create triggerDownload(bundle: ExportBundle) function in apps/web/src/persistence/export-import.ts — serializes bundle to JSON, creates a Blob, generates filename initiative-export-YYYY-MM-DD.json, triggers download via anchor element with download attribute.
  • T006 [US1] Add "Export Encounter" item to the overflow menu in apps/web/src/components/action-bar.tsx — wire it to read encounter, undoRedoState, and playerCharacters from contexts, call assembleExportBundle(), then triggerDownload(). Use a Download icon from Lucide.
  • T007 [US1] Write test for assembleExportBundle() in apps/web/src/__tests__/export-import.test.ts — verify output shape, version field, timestamp format, and that encounter/stacks/characters are included.

Checkpoint: Export is fully functional. Users can download state as JSON.


Phase 3: User Story 2 — Import Encounter from File (Priority: P1)

Goal: Users can import a .json file and replace the current state.

Independent Test: Export a file, clear the encounter, import the file. Verify the encounter is restored with all combatants, HP, conditions, undo/redo history, and player characters.

Implementation for User Story 2

  • T008 [US2] Expose setEncounter and setUndoRedoState from useEncounter hook via EncounterContext in apps/web/src/hooks/use-encounter.ts and apps/web/src/contexts/encounter-context.tsx — these are needed for import to replace state directly (bypassing individual use cases). Also expose a replacePlayerCharacters setter from usePlayerCharacters hook via PlayerCharactersContext in apps/web/src/hooks/use-player-characters.ts and apps/web/src/contexts/player-characters-context.tsx.
  • T009 [US2] Create readImportFile(file: File) function in apps/web/src/persistence/export-import.ts — reads file as text, parses JSON, calls validateImportUseCase(), returns validated ExportBundle or error string.
  • T010 [US2] Create ImportConfirmPrompt component in apps/web/src/components/import-confirm-prompt.tsx — confirmation dialog (using native <dialog> element consistent with existing patterns) warning that the current encounter will be replaced. Props: open, onConfirm, onCancel.
  • T011 [US2] Add "Import Encounter" item to the overflow menu in apps/web/src/components/action-bar.tsx — renders a hidden <input type="file" accept=".json">, triggers it on menu item click. On file selected: validate via readImportFile(), show error toast on failure, show ImportConfirmPrompt if encounter is non-empty, replace state on confirm (or directly if encounter is empty). Use an Upload icon from Lucide.
  • T012 [US2] Write round-trip test in apps/web/src/__tests__/export-import.test.ts — assemble an export bundle, validate it via the import use case, verify the result matches the original state.

Checkpoint: Import is fully functional. Users can load exported files and restore state.


Phase 4: User Story 3 — Reject Invalid Import Files (Priority: P2)

Goal: Invalid files are rejected with clear error messages while preserving current state.

Independent Test: Attempt to import various invalid files (non-JSON, wrong structure, malformed combatants). Verify error messages appear and current state is unchanged.

Implementation for User Story 3

  • T013 [US3] Add user-facing error toast for import failures in apps/web/src/components/action-bar.tsx — use the existing toast/alert pattern in the app. Show specific messages: "Invalid file format" for non-JSON, "Invalid encounter data" for validation failures.
  • T014 [US3] Write validation edge case tests in apps/web/src/__tests__/validate-import-bundle.test.ts — non-JSON text file content, JSON array instead of object, missing version field, version 0 or negative, encounter that fails rehydration, undo stack with mix of valid and invalid entries (valid ones kept, invalid dropped), player characters with invalid color/icon (stripped but character kept). Include a state-preservation test: set up an encounter, attempt import of an invalid file, verify encounter is unchanged after error (FR-009).

Checkpoint: All three stories are complete. Invalid files are handled gracefully.


Phase 5: Polish & Cross-Cutting Concerns

Purpose: Final cleanup and documentation.

  • T015 Update CLAUDE.md spec listing to describe the feature in CLAUDE.md
  • T016 N/A — no project-level README.md exists

Dependencies & Execution Order

Phase Dependencies

  • Foundational (Phase 1): No dependencies — can start immediately
  • US1 Export (Phase 2): Depends on Phase 1 (needs ExportBundle type)
  • US2 Import (Phase 3): Depends on Phase 1 (needs validation use case) and Phase 2 (needs export for round-trip testing)
  • US3 Error Handling (Phase 4): Depends on Phase 3 (builds on import flow)
  • Polish (Phase 5): Depends on all stories being complete

Within Each Phase

  • Tasks marked [P] can run in parallel
  • T001 and T002 are parallel (different files)
  • T004 and T005 are parallel (different functions, same file but independent)
  • T008 must complete before T011 (setters must exist before import wiring)

Parallel Opportunities

  • Phase 1: T001 and T002 can run in parallel (type definition + validation use case)
  • Phase 2: T004 and T005 can run in parallel (assemble + download functions)
  • Phase 2: T007 can run in parallel with T006 (test + UI wiring)

Implementation Strategy

MVP First (Export Only)

  1. Complete Phase 1: ExportBundle type + validation
  2. Complete Phase 2: Export functionality
  3. STOP and VALIDATE: User can download encounter state as JSON
  4. Continue to Phase 3: Import functionality
  5. Continue to Phase 4: Error handling polish

Incremental Delivery

  1. Phase 1 → Foundation ready
  2. Phase 2 → Export works → Delivers backup value
  3. Phase 3 → Import works → Delivers full round-trip + sharing value
  4. Phase 4 → Error handling → Production-ready robustness
  5. Phase 5 → Documentation updated

Notes

  • Reuse rehydrateEncounter() from apps/web/src/persistence/encounter-storage.ts for all encounter validation — do not duplicate
  • Follow existing file picker pattern from apps/web/src/components/source-fetch-prompt.tsx
  • Follow existing overflow menu pattern in apps/web/src/components/action-bar.tsx
  • Follow existing <dialog> pattern from apps/web/src/components/settings-modal.tsx
  • Commit after each phase checkpoint