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

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

  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