/** * Generic undo stack with clone-on-push semantics. * * Stores deep clones of state snapshots. Popped snapshots are returned * directly (no re-cloning) since they are already detached. */ export class UndoStack { private stack: S[] = []; private redoStack: S[] = []; /** Push a deep clone of the given state onto the undo stack. Clears redo history. */ push(state: S): void { this.stack.push(structuredClone(state)); this.redoStack.length = 0; } /** Pop and return the most recent undo snapshot, or undefined if empty. */ pop(): S | undefined { return this.stack.pop(); } /** * Push a deep clone of the given state onto the redo stack. * Called by the editor before applying an undo so the state can be redone. */ pushRedo(state: S): void { this.redoStack.push(structuredClone(state)); } /** * Push a deep clone of the given state onto the undo stack without clearing * the redo stack. Called by the editor before applying a redo so the state * remains undoable. */ pushUndo(state: S): void { this.stack.push(structuredClone(state)); } /** Pop and return the most recent redo snapshot, or undefined if empty. */ redo(): S | undefined { return this.redoStack.pop(); } canRedo(): boolean { return this.redoStack.length > 0; } /** Remove all snapshots from both undo and redo stacks. */ clear(): void { this.stack.length = 0; this.redoStack.length = 0; } get length(): number { return this.stack.length; } }