Files
initiative/specs/007-json-import-export/plan.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

112 lines
6.0 KiB
Markdown

# Implementation Plan: JSON Import/Export
**Branch**: `007-json-import-export` | **Date**: 2026-03-27 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/007-json-import-export/spec.md`
## Summary
Add JSON import/export for the full application state (encounter, undo/redo history, player characters). Export creates a downloadable `.json` file; import reads a file, validates it using existing rehydration functions, and replaces the current state after user confirmation. UI is integrated via the action bar overflow menu.
## Technical Context
**Language/Version**: TypeScript 5.8 (strict mode, `verbatimModuleSyntax`)
**Primary Dependencies**: React 19, Vite 6, Tailwind CSS v4, Lucide React
**Storage**: localStorage (encounter, undo/redo, player characters)
**Testing**: Vitest (v8 coverage)
**Target Platform**: Browser (desktop + mobile)
**Project Type**: Web application (SPA)
**Performance Goals**: Export/import completes in under 1 second for typical encounters
**Constraints**: No server-side component; browser-only file operations
**Scale/Scope**: Encounters with up to ~50 combatants, undo stacks of up to 50 snapshots
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle | Status | Notes |
|-----------|--------|-------|
| I. Deterministic Domain Core | PASS | No new domain logic needed. ExportBundle type is pure data. Validation reuses existing pure functions. |
| II. Layered Architecture | PASS | Type in domain, validation in adapter layer (co-located with existing rehydration functions), file I/O and UI in adapter layer. No reverse dependencies. |
| II-A. Context-Based State Flow | PASS | Import reads/writes state via existing contexts. No new props beyond per-instance config. |
| III. Clarification-First | PASS | All decisions documented in research.md. No ambiguities remain. |
| IV. Escalation Gates | PASS | Feature scope matches spec exactly. No out-of-scope additions. |
| V. MVP Baseline Language | PASS | Format versioning and selective import noted as "not included in MVP baseline." |
| VI. No Gameplay Rules | PASS | No gameplay mechanics involved. |
**Post-Phase 1 re-check**: All gates still pass. The ExportBundle type is a pure data structure in the domain layer. The validation logic lives in the adapter layer alongside existing rehydration functions (it depends on adapter-layer `rehydrateEncounter` and `rehydrateCharacter`). File I/O and UI live in the adapter layer.
## Project Structure
### Documentation (this feature)
```text
specs/007-json-import-export/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
└── tasks.md # Phase 2 output (/speckit.tasks)
```
### Source Code (repository root)
```text
packages/domain/src/
├── export-bundle.ts # ExportBundle type definition
apps/web/src/
├── persistence/
│ └── export-import.ts # Export assembly + import validation + file handling
├── hooks/
│ └── use-encounter.ts # Existing — needs import/export state setters exposed
├── components/
│ ├── action-bar.tsx # Existing — add overflow menu items
│ └── import-confirm-prompt.tsx # Confirmation dialog for import
```
**Structure Decision**: Follows existing project structure. New files are minimal — one domain type, one persistence module (validation + export assembly + file handling), one UI component. Most work integrates into existing files.
## Implementation Phases
### Phase 1: ExportBundle Type + Validation Use Case
**Goal**: Define the export format and validation logic with full test coverage.
1. Create `ExportBundle` type in `packages/domain/src/export-bundle.ts`
2. Export from domain package index
3. Create `validateImportBundle()` in `apps/web/src/persistence/export-import.ts` — accepts `unknown`, returns validated `ExportBundle | DomainError`
4. Import `rehydrateEncounter()` from `./encounter-storage.ts` for encounter and undo/redo stack validation
5. Export `rehydrateCharacter()` from `player-character-storage.ts` and import it for player character validation
6. Write tests covering: valid bundles, missing fields, invalid encounter, invalid player characters, empty stacks, unknown version
### Phase 2: Export Functionality
**Goal**: Users can download the current state as a `.json` file.
1. Create `export-import.ts` in `apps/web/src/persistence/`
2. Implement `assembleExportBundle(encounter, undoRedoState, playerCharacters)` — pure function returning `ExportBundle`
3. Implement `triggerDownload(bundle: ExportBundle)` — creates JSON blob, generates filename with date, triggers browser download
4. Add "Export Encounter" item to action bar overflow menu
5. Wire button to read from contexts and call export functions
### Phase 3: Import Functionality + Confirmation
**Goal**: Users can import a `.json` file with confirmation and error handling.
1. Add "Import Encounter" item to action bar overflow menu
2. Implement file picker trigger (hidden `<input type="file">`)
3. On file selected: read text, parse JSON, validate via use case
4. If validation fails: show error toast
5. If encounter is non-empty: show confirmation dialog
6. On confirm (or if encounter is empty): replace encounter, undo/redo, and player characters via context setters
7. Write integration test: export → import round-trip produces identical state
## Notes
- Import `rehydrateEncounter()` from `apps/web/src/persistence/encounter-storage.ts` for encounter validation — do not duplicate
- Export `rehydrateCharacter()` from `apps/web/src/persistence/player-character-storage.ts` so it can be imported for player character validation
- 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