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