Memento-based undo/redo with full encounter snapshots. Undo stack capped at 50 entries, persisted to localStorage. Triggered via buttons in the top bar (inboard of turn navigation) and keyboard shortcuts (Ctrl+Z / Ctrl+Shift+Z, Cmd on Mac, case-insensitive key matching). Clear encounter resets both stacks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
54 lines
2.5 KiB
Markdown
54 lines
2.5 KiB
Markdown
# Quickstart: Undo/Redo
|
|
|
|
**Feature**: 037-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 redo
|
|
- `undo(state, currentEncounter)` — pop undo, push current to redo
|
|
- `redo(state, currentEncounter)` — pop redo, push current to undo
|
|
- `clearHistory()` — 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 undo
|
|
- `redoUseCase(encounterStore, undoRedoStore)` — execute redo
|
|
|
|
New port interface `UndoRedoStore` in `ports.ts`:
|
|
- `get(): UndoRedoState`
|
|
- `save(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
|
|
|
|
1. **Memento over Command**: Full encounter snapshots, not inverse events. Simpler at encounter scale (~2-5 KB per snapshot).
|
|
2. **Capture in hook, not domain**: Snapshot capture happens in the adapter layer. Domain and application layers are unaware of undo/redo.
|
|
3. **React state for stacks**: Enables reactive button disabled states without manual re-render triggers.
|
|
4. **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).
|