Implement the 008-persist-encounter feature that saves encounter state to localStorage so it survives page reloads
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
50
specs/008-persist-encounter/data-model.md
Normal file
50
specs/008-persist-encounter/data-model.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user