Files
initiative/specs/008-persist-encounter/data-model.md

1.8 KiB

Data Model: Persist Encounter

Existing Entities (unchanged)

Combatant

Field Type Notes
id CombatantId (branded string) Unique identifier
name string Display name
initiative number or undefined Initiative value, optional

ID Format Convention (existing code)

  • Demo combatants use plain numeric IDs: "1", "2", "3"
  • User-added combatants use the pattern c-{N} (e.g., "c-1", "c-2")
  • On reload, nextId counter is derived by scanning existing IDs matching c-{N} and starting from max(N) + 1
  • IDs not matching c-{N} (e.g., demo IDs) are ignored during counter derivation

Encounter

Field Type Notes
combatants readonly Combatant[] Ordered list of combatants
activeIndex number Index of the combatant whose turn it is
roundNumber number Current round (positive integer)

Persisted State

Storage Key

"initiative:encounter"

Serialized Format

The Encounter object is serialized as-is via JSON.stringify. The branded CombatantId serializes as a plain string. On deserialization, IDs are rehydrated with combatantId().

Validation on Load

  1. Parse JSON string into unknown value
  2. Structural check: verify it is an object with combatants (array), activeIndex (number), roundNumber (number)
  3. Verify each combatant has id (string) and name (string)
  4. Pass through createEncounter to enforce domain invariants
  5. On any failure: discard and return null (caller falls back to demo encounter)

State Transitions

App Load
  ├─ localStorage has valid data → restore Encounter
  └─ localStorage empty/invalid  → create demo Encounter

State Change (any use case)
  └─ new Encounter saved to React state
     └─ useEffect triggers → serialize to localStorage