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

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
```