Skip to main content
Reference status

This page predates the current package split. Use the current package guides for checked installation, public imports, and onboarding examples. The detailed examples below have not all been revalidated.

History (Undo/Redo)

History manages the undo/redo stack for the editor. Every transaction is recorded as a history entry with both forward and inverse operations, enabling reliable undo and redo.

How It Works​

History Entry​

Each history entry stores everything needed to undo or redo:

interface HistoryEntry {
id: string;
timestamp: Date;
operations: TransactionOperation[];
inverseOperations: TransactionOperation[];
description?: string;
metadata?: {
selectionBefore?: ModelSelection | null;
selectionAfter?: ModelSelection | null;
};
}
  • operations — the forward operations that were executed
  • inverseOperations — the reverse operations (stored in reverse order for undo)
  • selectionBefore / selectionAfter — selection state snapshots for restoring cursor position

Basic Usage​

// Undo the last operation
await editor.undo();

// Redo the last undone operation
await editor.redo();

// Check availability
editor.canUndo(); // boolean
editor.canRedo(); // boolean

History Branch Pruning​

When the user makes a new edit after undoing, the redo stack is discarded:

This follows the standard linear undo model — once you branch off with a new edit, the old redo history is removed.

History Size Management​

// Default max size: 100 entries
const editor = new Editor({
history: { maxSize: 200 } // increase limit
});

// Dynamically resize at runtime
editor.historyManager.resize(50);

// Check memory usage (approximate bytes)
editor.historyManager.getMemoryUsage();

// Get statistics
editor.historyManager.getStats();
// { totalEntries: 42, currentIndex: 41, canUndo: true, canRedo: false }

When the limit is reached, the oldest entries are removed first.

History Compression​

Compression merges consecutive similar operations into a single entry. For example, typing "Hello" character by character normally creates 5 history entries — compression merges them into one:

editor.historyManager.compress();

Compression rules:

  • Only consecutive single-operation entries of the same type are merged
  • Currently targets setText operations on the same node
  • Inverse operations are combined in reverse order

History Validation​

Validate the integrity of the history stack:

const { isValid, errors } = editor.historyManager.validate();
if (!isValid) {
console.error('History corruption:', errors);
editor.historyManager.clear(); // reset if corrupted
}

Validation checks:

  • currentIndex is within valid bounds
  • Every entry has required fields (id, timestamp, operations)
  • operations and inverseOperations have matching lengths

Querying History​

// Get all entries
const entries = editor.historyManager.getHistory();

// Get a specific entry
const entry = editor.historyManager.getEntry(5);

// Search entries by predicate
const textOps = editor.historyManager.findEntries(
e => e.operations.some(op => op.type === 'setText')
);

// Get entries in a time range
const recentOps = editor.historyManager.getEntriesByTimeRange(
new Date('2026-01-01'),
new Date()
);

Selection Preservation​

By default, undo/redo restores the cursor to where it was before/after the operation. This behavior can be controlled per transaction:

await editor.executeTransaction(operations, {
preserveSelectionInHistory: false // don't store selection snapshots
});

When preserveSelectionInHistory is true (default), the HistoryEntry.metadata stores both selectionBefore and selectionAfter, and undo/redo restores the appropriate one.

Undo/Redo and Transactions​

Undo/redo operations are themselves executed as transactions, but they are not recorded in history (to prevent infinite loops). The TransactionManager tracks this via an _isUndoRedoOperation flag.

Next Steps​