# 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. - [x] 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`. - [x] 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`. - [x] 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 - [x] 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. - [x] 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. - [x] 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. - [x] 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 - [x] 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`. - [x] 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. - [x] T010 [US2] Create `ImportConfirmPrompt` component in `apps/web/src/components/import-confirm-prompt.tsx` — confirmation dialog (using native `` element consistent with existing patterns) warning that the current encounter will be replaced. Props: `open`, `onConfirm`, `onCancel`. - [x] T011 [US2] Add "Import Encounter" item to the overflow menu in `apps/web/src/components/action-bar.tsx` — renders a hidden ``, 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. - [x] 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 - [x] 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. - [x] 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. - [x] T015 Update CLAUDE.md spec listing to describe the feature in `CLAUDE.md` - [x] 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 `` pattern from `apps/web/src/components/settings-modal.tsx` - Commit after each phase checkpoint