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>
This commit is contained in:
79
specs/007-json-import-export/research.md
Normal file
79
specs/007-json-import-export/research.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Research: JSON Import/Export
|
||||
|
||||
**Feature**: 007-json-import-export
|
||||
**Date**: 2026-03-27
|
||||
|
||||
## Decision 1: Export Bundle Contents
|
||||
|
||||
**Decision**: Export includes encounter, undo/redo stacks, and player characters. Excludes bestiary cache, theme, and rules edition.
|
||||
|
||||
**Rationale**: The spec explicitly includes undo/redo history and player characters. Theme and rules edition are user preferences that should not transfer between DMs. Bestiary cache is large and can be rebuilt from sources.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Include theme/edition settings — rejected because these are personal preferences, not encounter data.
|
||||
- Exclude undo/redo — rejected because the spec explicitly requires it and it enables full session restore.
|
||||
- Include bestiary cache — rejected because it's large, device-specific, and reconstructable from source URLs.
|
||||
|
||||
## Decision 2: Import Strategy — Full Replacement vs Merge
|
||||
|
||||
**Decision**: Full state replacement. Import replaces encounter, undo/redo, and player characters entirely.
|
||||
|
||||
**Rationale**: The spec states "Import replaces all existing state." Merging would require conflict resolution (duplicate IDs, name collisions) which adds significant complexity for unclear benefit.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Merge player characters — rejected because ID conflicts between different sessions would be complex to resolve and the spec doesn't call for it.
|
||||
- Selective import (pick which parts to load) — rejected as out of MVP scope.
|
||||
|
||||
## Decision 3: Validation Approach
|
||||
|
||||
**Decision**: Reuse existing `rehydrateEncounter()` and player character validation from the persistence layer. These functions already handle all field validation, type checking, and graceful degradation for invalid fields.
|
||||
|
||||
**Rationale**: The spec explicitly states "validated using the same rules as localStorage loading." The existing `rehydrateEncounter()` function already validates every combatant field, filters invalid conditions, clamps HP values, and rejects structurally malformed data. Reusing it ensures consistency and avoids duplication.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Write separate import validation — rejected because it would duplicate existing validation logic and risk divergence.
|
||||
- Stricter validation (reject files with any invalid field) — rejected because the existing approach gracefully degrades (strips invalid optional fields) which is more user-friendly.
|
||||
|
||||
## Decision 4: File Download Mechanism
|
||||
|
||||
**Decision**: Use `URL.createObjectURL()` with an anchor element's `download` attribute for triggering the file download.
|
||||
|
||||
**Rationale**: This is the standard browser-native approach that works across all modern browsers without popup blockers interfering. No server-side component needed.
|
||||
|
||||
**Alternatives considered**:
|
||||
- `window.open()` with data URI — rejected because popup blockers can interfere.
|
||||
- FileSaver.js library — rejected because the native approach is sufficient and avoids an additional dependency.
|
||||
|
||||
## Decision 5: File Upload Mechanism
|
||||
|
||||
**Decision**: Use an `<input type="file" accept=".json">` element, consistent with the existing pattern in `source-fetch-prompt.tsx` which uses `file.text()` + `JSON.parse()`.
|
||||
|
||||
**Rationale**: The codebase already has this pattern for bestiary source uploads. Reusing the same approach keeps the UX consistent.
|
||||
|
||||
## Decision 6: UI Placement
|
||||
|
||||
**Decision**: Place export and import actions in the action bar's overflow menu, alongside existing items like "Players", "Manage Sources", and "Settings".
|
||||
|
||||
**Rationale**: The overflow menu already groups secondary actions. Import/export are infrequent operations that don't need primary button placement. The action bar's `buildOverflowItems()` function makes this straightforward to add.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Settings modal — rejected because import/export are actions, not settings.
|
||||
- Dedicated toolbar buttons — rejected because import/export are infrequent and would clutter the primary UI.
|
||||
|
||||
## Decision 7: Export File Naming
|
||||
|
||||
**Decision**: Use a filename pattern like `initiative-export-YYYY-MM-DD.json` with the current date.
|
||||
|
||||
**Rationale**: The date provides context for when the export was created. Including "initiative" in the name makes the file's purpose clear when browsing a downloads folder.
|
||||
|
||||
## Decision 8: State Restoration After Import
|
||||
|
||||
**Decision**: Import must update both React state and localStorage in one operation. The encounter hook's `setEncounter()` triggers a `useEffect` that auto-saves to localStorage, and `setUndoRedoState()` similarly auto-saves. For player characters, the same auto-save pattern applies.
|
||||
|
||||
**Rationale**: Following the existing state flow ensures consistency. Setting React state triggers the existing persistence effects, so no manual localStorage writes are needed for the import path.
|
||||
|
||||
## Decision 9: Export Format Versioning
|
||||
|
||||
**Decision**: Include a `version` field in the export format (e.g., `1`) but do not implement migration logic in MVP.
|
||||
|
||||
**Rationale**: The spec's assumptions state "Future format versioning is not included in MVP baseline." Including the version field costs nothing and enables future migration logic without breaking existing exports.
|
||||
Reference in New Issue
Block a user