utilities_history_manager.js

/**
 * Command-pattern undo/redo history manager.
 * Each command is a plain object:
 *   { label: string, undo: () => void|Promise, redo: () => void|Promise, dirtyFlags: string[] }
 *
 * dirtyFlags values: 'transcript' | 'speakers' | 'annotations' | 'projectName' | 'projectFolder'
 */
export class HistoryManager {
    /**
     * @param {object} [options] - Optional configuration.
     * @param {number} [options.maxSize=100] - Maximum number of commands to retain in the undo stack.
     */
    constructor({ maxSize = 100 } = {}) {
        this._undoStack = [];
        this._redoStack = [];
        this._maxSize = maxSize;
    }

    /**
     * Pushes a command onto the undo stack and clears the redo stack.
     * @param {object} command - The command object with undo, redo, label, and dirtyFlags properties.
     */
    push(command) {
        this._undoStack.push(command);
        if (this._undoStack.length > this._maxSize) {
            this._undoStack.shift();
        }
        this._redoStack = [];
    }

    /**
     * Undoes the most recent command. Returns the command after awaiting undo().
     * @returns {Promise<object|null>}
     */
    async undo() {
        if (!this._undoStack.length) return null;
        const command = this._undoStack.pop();
        this._redoStack.push(command);
        await command.undo();
        return command;
    }

    /**
     * Redoes the most recently undone command. Returns the command after awaiting redo().
     * @returns {Promise<object|null>}
     */
    async redo() {
        if (!this._redoStack.length) return null;
        const command = this._redoStack.pop();
        this._undoStack.push(command);
        await command.redo();
        return command;
    }

    /** Clears both stacks. */
    clear() {
        this._undoStack = [];
        this._redoStack = [];
    }

    /** @returns {boolean} Whether there are commands available to undo. */
    get canUndo() { return this._undoStack.length > 0; }
    /** @returns {boolean} Whether there are commands available to redo. */
    get canRedo() { return this._redoStack.length > 0; }
}