Rename spec 037-undo-redo to 006-undo-redo for sequential numbering
Delete merged feature branches (005–037) that inflated the auto-increment counter in create-new-feature.sh, and renumber the undo-redo spec to follow the existing 001–005 sequence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
specs/006-undo-redo/data-model.md
Normal file
83
specs/006-undo-redo/data-model.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Data Model: Undo/Redo
|
||||
|
||||
**Feature**: 006-undo-redo
|
||||
**Date**: 2026-03-26
|
||||
|
||||
## Entities
|
||||
|
||||
### UndoRedoState
|
||||
|
||||
Represents the complete undo/redo history for an encounter session.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| undoStack | Encounter[] | Ordered list of encounter snapshots, most recent last. Max 50 entries. |
|
||||
| redoStack | Encounter[] | Ordered list of encounter snapshots accumulated by undo operations. Cleared on any new action. |
|
||||
|
||||
### Encounter (existing, unchanged)
|
||||
|
||||
Each stack entry is a full `Encounter` snapshot as defined in `packages/domain/src/types.ts`. No schema changes to the encounter type.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| combatants | Combatant[] | Ordered list of combatants |
|
||||
| activeIndex | number | Index of the active combatant |
|
||||
| roundNumber | number | Current round number |
|
||||
|
||||
## State Transitions
|
||||
|
||||
### pushUndo(state, snapshot) -> UndoRedoState
|
||||
|
||||
Push a snapshot onto the undo stack. If the stack exceeds 50 entries, drop the oldest (index 0). Clear the redo stack.
|
||||
|
||||
**Precondition**: snapshot is a valid Encounter
|
||||
**Postcondition**: undoStack length <= 50, redoStack is empty
|
||||
|
||||
### undo(state, currentEncounter) -> { state: UndoRedoState, encounter: Encounter } | DomainError
|
||||
|
||||
Pop the most recent snapshot from the undo stack. Push the current encounter onto the redo stack. Return the popped snapshot as the new current encounter.
|
||||
|
||||
**Precondition**: undoStack is non-empty
|
||||
**Postcondition**: undoStack length decremented by 1, redoStack length incremented by 1
|
||||
**Error**: "nothing-to-undo" if undoStack is empty
|
||||
|
||||
### redo(state, currentEncounter) -> { state: UndoRedoState, encounter: Encounter } | DomainError
|
||||
|
||||
Pop the most recent snapshot from the redo stack. Push the current encounter onto the undo stack. Return the popped snapshot as the new current encounter.
|
||||
|
||||
**Precondition**: redoStack is non-empty
|
||||
**Postcondition**: redoStack length decremented by 1, undoStack length incremented by 1
|
||||
**Error**: "nothing-to-redo" if redoStack is empty
|
||||
|
||||
### clearHistory() -> UndoRedoState
|
||||
|
||||
Reset both stacks to empty. Used when the encounter is cleared.
|
||||
|
||||
**Postcondition**: undoStack and redoStack are both empty
|
||||
|
||||
## Persistence
|
||||
|
||||
### Storage Keys
|
||||
|
||||
| Key | Content | Format |
|
||||
|-----|---------|--------|
|
||||
| `initiative:encounter:undo` | Undo stack | JSON array of serialized Encounter objects |
|
||||
| `initiative:encounter:redo` | Redo stack | JSON array of serialized Encounter objects |
|
||||
|
||||
### Serialization
|
||||
|
||||
Stacks are serialized as JSON arrays of `Encounter` objects, identical to the existing encounter serialization format. On load, each entry is validated using the same rehydration logic as `loadEncounter()`.
|
||||
|
||||
### Failure Modes
|
||||
|
||||
- **localStorage quota exceeded**: Stacks continue in-memory; persistence is best-effort. Silently swallow write errors (matching existing encounter persistence pattern).
|
||||
- **Corrupt data on load**: Start with empty stacks. Log no error (matching existing pattern).
|
||||
- **Schema mismatch after upgrade**: Invalid entries are dropped during rehydration; stacks may be shorter than persisted but never contain invalid data.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. `undoStack.length <= 50` at all times
|
||||
2. `redoStack` is empty after any non-undo/redo action
|
||||
3. `undoStack.length + redoStack.length` represents the total history depth (not capped as a whole — redo can grow up to 50 if all actions are undone)
|
||||
4. Each stack entry is a valid, complete `Encounter` snapshot
|
||||
5. Undo followed by redo returns the encounter to the exact same state
|
||||
Reference in New Issue
Block a user