Files
initiative/packages/domain/src/undo-redo.ts
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

71 lines
1.6 KiB
TypeScript

import type { DomainError, Encounter } from "./types.js";
export interface UndoRedoState {
readonly undoStack: readonly Encounter[];
readonly redoStack: readonly Encounter[];
}
const MAX_UNDO_STACK = 50;
export const EMPTY_UNDO_REDO_STATE: UndoRedoState = {
undoStack: [],
redoStack: [],
};
export function pushUndo(
state: UndoRedoState,
snapshot: Encounter,
): UndoRedoState {
const newStack = [...state.undoStack, snapshot];
if (newStack.length > MAX_UNDO_STACK) {
newStack.shift();
}
return { undoStack: newStack, redoStack: [] };
}
export function undo(
state: UndoRedoState,
currentEncounter: Encounter,
): { state: UndoRedoState; encounter: Encounter } | DomainError {
if (state.undoStack.length === 0) {
return {
kind: "domain-error",
code: "nothing-to-undo",
message: "Nothing to undo",
};
}
const restored = state.undoStack.at(-1) as Encounter;
return {
state: {
undoStack: state.undoStack.slice(0, -1),
redoStack: [...state.redoStack, currentEncounter],
},
encounter: restored,
};
}
export function redo(
state: UndoRedoState,
currentEncounter: Encounter,
): { state: UndoRedoState; encounter: Encounter } | DomainError {
if (state.redoStack.length === 0) {
return {
kind: "domain-error",
code: "nothing-to-redo",
message: "Nothing to redo",
};
}
const restored = state.redoStack.at(-1) as Encounter;
return {
state: {
undoStack: [...state.undoStack, currentEncounter],
redoStack: state.redoStack.slice(0, -1),
},
encounter: restored,
};
}
export function clearHistory(): UndoRedoState {
return EMPTY_UNDO_REDO_STATE;
}