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>
2.5 KiB
Quickstart: Undo/Redo
Feature: 006-undo-redo Date: 2026-03-26
Overview
This feature adds undo/redo to all encounter state changes using the memento (snapshot) pattern. Each action captures the pre-action encounter state onto an undo stack. Undo restores the previous state; redo re-applies an undone state.
Implementation Layers
Domain (packages/domain/src/undo-redo.ts)
Pure functions for stack management:
pushUndo(state, snapshot)— push snapshot, cap at 50, clear redoundo(state, currentEncounter)— pop undo, push current to redoredo(state, currentEncounter)— pop redo, push current to undoclearHistory()— reset both stacks
All functions take and return immutable data. No I/O.
Application (packages/application/src/)
Use cases that orchestrate domain calls via store ports:
undoUseCase(encounterStore, undoRedoStore)— execute undoredoUseCase(encounterStore, undoRedoStore)— execute redo
New port interface UndoRedoStore in ports.ts:
get(): UndoRedoStatesave(state: UndoRedoState): void
Web Adapter (apps/web/src/)
Hook (use-encounter.ts): Wraps every action callback to capture pre-action snapshot. Exposes undo(), redo(), canUndo, canRedo.
Persistence (persistence/undo-redo-storage.ts): Save/load undo/redo stacks to localStorage keys "initiative:encounter:undo" and "initiative:encounter:redo".
UI (components/turn-navigation.tsx): Undo/Redo buttons in the top bar, inboard of the turn step buttons, disabled when stack is empty.
Keyboard (hooks/use-undo-redo-shortcuts.ts): Global keydown listener for Ctrl+Z / Ctrl+Shift+Z (Cmd on Mac). Suppressed when text input has focus.
Key Design Decisions
- Memento over Command: Full encounter snapshots, not inverse events. Simpler at encounter scale (~2-5 KB per snapshot).
- Capture in hook, not domain: Snapshot capture happens in the adapter layer. Domain and application layers are unaware of undo/redo.
- React state for stacks: Enables reactive button disabled states without manual re-render triggers.
- Clear is not undoable: Both stacks reset on encounter clear (per spec).
Testing Strategy
- Domain tests: Pure function tests for stack operations (push, pop, cap, clear, undo/redo roundtrip).
- Application tests: Use case tests with mock stores.
- Integration: Spec acceptance scenarios mapped to test cases (undo restores state, redo reapplies, new action clears redo, keyboard suppression during input focus).