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>
5.6 KiB
5.6 KiB
Data Model: JSON Import/Export
Feature: 007-json-import-export Date: 2026-03-27
Entities
ExportBundle
The top-level structure written to and read from .json files. Contains all exportable application state.
| Field | Type | Required | Description |
|---|---|---|---|
| version | number | Yes | Format version (starts at 1) |
| exportedAt | string (ISO 8601) | Yes | Timestamp of when the export was created |
| encounter | Encounter | Yes | Current encounter state (combatants + turn info) |
| undoStack | Encounter[] | Yes | Undo history (encounter snapshots, max 50) |
| redoStack | Encounter[] | Yes | Redo history (encounter snapshots) |
| playerCharacters | PlayerCharacter[] | Yes | Player character templates |
Encounter (existing)
Defined in packages/domain/src/types.ts. No changes needed — exported as-is via JSON serialization.
| Field | Type | Required | Description |
|---|---|---|---|
| combatants | Combatant[] | Yes | Ordered list of combatants |
| activeIndex | number | Yes | Index of current turn (0-based) |
| roundNumber | number | Yes | Current round (starts at 1) |
Combatant (existing)
Defined in packages/domain/src/types.ts. No changes needed.
| Field | Type | Required | Description |
|---|---|---|---|
| id | CombatantId | Yes | Unique identifier ("c-N") |
| name | string | Yes | Display name |
| initiative | number | No | Initiative roll result |
| maxHp | number | No | Maximum hit points (>= 1) |
| currentHp | number | No | Current HP (0 to maxHp) |
| tempHp | number | No | Temporary hit points |
| ac | number | No | Armor class (>= 0) |
| conditions | ConditionId[] | No | Active status conditions |
| isConcentrating | boolean | No | Concentration flag |
| creatureId | CreatureId | No | Link to bestiary creature |
| color | string | No | Visual color (from player char) |
| icon | string | No | Visual icon (from player char) |
| playerCharacterId | PlayerCharacterId | No | Link to player character template |
PlayerCharacter (existing)
Defined in packages/domain/src/player-character-types.ts. No changes needed.
| Field | Type | Required | Description |
|---|---|---|---|
| id | PlayerCharacterId | Yes | Unique identifier ("pc-N") |
| name | string | Yes | Character name |
| ac | number | Yes | Armor class (>= 0) |
| maxHp | number | Yes | Maximum hit points (>= 1) |
| color | PlayerColor | No | Visual color (10 options) |
| icon | PlayerIcon | No | Visual icon (15 options) |
Validation Rules
Import Validation
- Top-level structure: Must be a JSON object with
version,encounter,undoStack,redoStack, andplayerCharactersfields. - Version check:
versionmust be a number. Unknown versions are rejected. - Encounter validation: Delegated to existing
rehydrateEncounter()— validates combatant structure, HP ranges, condition IDs, player colors/icons. - Undo/redo stack validation: Each entry in both stacks is validated via
rehydrateEncounter(). Invalid entries are silently dropped. - Player character validation: Delegated to existing player character rehydration — validates types, ranges, color/icon enums.
- Graceful degradation: Invalid optional fields on combatants/characters are stripped (not rejected). Only structurally malformed data (missing required fields, wrong types) causes full rejection.
State Transitions
Export Flow
User triggers export
→ Read encounter from EncounterContext
→ Read undoRedoState from EncounterContext
→ Read playerCharacters from PlayerCharactersContext
→ Assemble ExportBundle { version: 1, exportedAt, encounter, undoStack, redoStack, playerCharacters }
→ Serialize to JSON
→ Trigger browser file download
Import Flow
User selects file
→ Read file as text
→ Parse JSON (reject on parse failure)
→ Validate top-level structure (reject on missing fields)
→ Validate encounter via rehydrateEncounter() (reject on null)
→ Validate undo/redo stacks via rehydrateEncounter() per entry (filter invalid)
→ Validate player characters via rehydration (filter invalid)
→ If current encounter is non-empty: show confirmation dialog
→ On confirm: replace encounter, undo/redo, and player characters in state
→ State changes trigger existing useEffect auto-saves to localStorage