Files
initiative/specs/037-undo-redo/quickstart.md
Lukas 17cc6ed72c Add undo/redo for all encounter actions
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>
2026-03-26 23:30:33 +01:00

2.5 KiB

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).