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>
143 lines
9.0 KiB
Markdown
143 lines
9.0 KiB
Markdown
# 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 `<dialog>` 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 `<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.
|
|
- [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 `<dialog>` pattern from `apps/web/src/components/settings-modal.tsx`
|
|
- Commit after each phase checkpoint
|