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>
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
ExportBundletype inpackages/domain/src/export-bundle.tswith fields:version(number),exportedAt(string),encounter(Encounter),undoStack(Encounter[]),redoStack(Encounter[]),playerCharacters(PlayerCharacter[]). Export frompackages/domain/src/index.ts. - T002 [P] Create
validateImportBundle()inapps/web/src/persistence/export-import.ts— acceptsunknown, validates top-level structure (version, encounter, undoStack, redoStack, playerCharacters), delegates encounter validation torehydrateEncounter()(imported from./encounter-storage.ts) and player character validation torehydrateCharacter()(exported from./player-character-storage.ts). Returns validatedExportBundleorDomainError. - T003 Write tests for
validateImportBundle()inapps/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 inapps/web/src/persistence/export-import.ts— takes encounter, undoRedoState, and playerCharacters, returns anExportBundlewith version 1 and current ISO timestamp. - T005 [P] [US1] Create
triggerDownload(bundle: ExportBundle)function inapps/web/src/persistence/export-import.ts— serializes bundle to JSON, creates a Blob, generates filenameinitiative-export-YYYY-MM-DD.json, triggers download via anchor element withdownloadattribute. - 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, callassembleExportBundle(), thentriggerDownload(). Use aDownloadicon from Lucide. - T007 [US1] Write test for
assembleExportBundle()inapps/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
setEncounterandsetUndoRedoStatefromuseEncounterhook viaEncounterContextinapps/web/src/hooks/use-encounter.tsandapps/web/src/contexts/encounter-context.tsx— these are needed for import to replace state directly (bypassing individual use cases). Also expose areplacePlayerCharacterssetter fromusePlayerCharactershook viaPlayerCharactersContextinapps/web/src/hooks/use-player-characters.tsandapps/web/src/contexts/player-characters-context.tsx. - T009 [US2] Create
readImportFile(file: File)function inapps/web/src/persistence/export-import.ts— reads file as text, parses JSON, callsvalidateImportUseCase(), returns validatedExportBundleor error string. - T010 [US2] Create
ImportConfirmPromptcomponent inapps/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 viareadImportFile(), show error toast on failure, showImportConfirmPromptif encounter is non-empty, replace state on confirm (or directly if encounter is empty). Use anUploadicon 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)
- Complete Phase 1: ExportBundle type + validation
- Complete Phase 2: Export functionality
- STOP and VALIDATE: User can download encounter state as JSON
- Continue to Phase 3: Import functionality
- Continue to Phase 4: Error handling polish
Incremental Delivery
- Phase 1 → Foundation ready
- Phase 2 → Export works → Delivers backup value
- Phase 3 → Import works → Delivers full round-trip + sharing value
- Phase 4 → Error handling → Production-ready robustness
- Phase 5 → Documentation updated
Notes
- Reuse
rehydrateEncounter()fromapps/web/src/persistence/encounter-storage.tsfor 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 fromapps/web/src/components/settings-modal.tsx - Commit after each phase checkpoint