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>
103 lines
5.6 KiB
Markdown
103 lines
5.6 KiB
Markdown
# 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
|
|
|
|
1. **Top-level structure**: Must be a JSON object with `version`, `encounter`, `undoStack`, `redoStack`, and `playerCharacters` fields.
|
|
2. **Version check**: `version` must be a number. Unknown versions are rejected.
|
|
3. **Encounter validation**: Delegated to existing `rehydrateEncounter()` — validates combatant structure, HP ranges, condition IDs, player colors/icons.
|
|
4. **Undo/redo stack validation**: Each entry in both stacks is validated via `rehydrateEncounter()`. Invalid entries are silently dropped.
|
|
5. **Player character validation**: Delegated to existing player character rehydration — validates types, ranges, color/icon enums.
|
|
6. **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
|
|
```
|