Undo/Redo
Snapshot history, HistoryStack, compression, and integration patterns
Timelinx history is snapshot-based. A committed edit stores the accepted TimelineState plus the transaction that produced it. Undo and redo move a cursor through those accepted snapshots.
Recommended API: HistoryStack
HistoryStack is the newer history implementation. It supports limits, transaction-aware compression, checkpoints, serialization, and restore.
import {
DEFAULT_COMPRESSION_POLICY,
HistoryStack,
dispatch,
} from '@timelinx/core';
const history = new HistoryStack(100, DEFAULT_COMPRESSION_POLICY);
history.push({
state: initialState,
transaction: {
id: 'initial',
label: 'Initial state',
timestamp: 0,
operations: [],
},
});
const result = dispatch(initialState, transaction);
if (result.accepted) {
history.pushWithCompression(
{ state: result.nextState, transaction },
transaction,
);
}Compression is useful for repeated one-operation edits such as dragging a clip, resizing an edge, or moving a keyframe. Instead of filling history with dozens of near-identical snapshots, compressible transactions inside the configured window can replace the last entry.
Undo And Redo With HistoryStack
const previous = history.undo();
if (previous !== null) {
state = previous;
}
const next = history.redo();
if (next !== null) {
state = next;
}undo() returns null when there is no earlier state. redo() returns null when there is no later state.
Pure Helper API
The older pure helper API is still exported for compatibility and simple integrations:
import {
canUndo,
createHistory,
getCurrentState,
pushHistory,
redo,
undo,
} from '@timelinx/core';
let history = createHistory(initialState);
if (result.accepted) {
history = pushHistory(history, result.nextState);
}
if (canUndo(history)) {
history = undo(history);
state = getCurrentState(history);
}Use this path when you specifically want immutable history containers in your own state manager. For new editor integrations, prefer HistoryStack or TimelineEngine from @timelinx/react.
React Engine Behavior
The React TimelineEngine owns a HistoryStack. When engine.dispatch(transaction) accepts:
- Core
dispatch()validates and returnsnextState. - The engine computes
diffStates(previous, nextState). - The engine pushes the accepted entry with compression.
- Track and snap indexes are rebuilt or scheduled.
- Playback receives the new state.
- A stable snapshot is rebuilt and subscribers are notified.
That means UI components generally call engine.undo() and engine.redo() rather than manually manipulating history.
History stores accepted states only. Never push a rejected transaction into history. Rejected transactions are useful for UI feedback and logs, but they did not change the document.
Guarantees And Expectations
- Undo moves from the present accepted state to the previous accepted state.
- Redo moves forward only after an undo.
- Pushing a new state after undo clears the redo path in the pure helper API.
- History limits evict older entries when capacity is exceeded.
- Checkpoints let advanced integrations name a specific history cursor and restore it later.
- Serialization stores state snapshots using the timeline serializer plus the transaction metadata.
What History Does Not Do
History does not merge conflicting remote edits, persist projects to your backend, or decide whether a user should be allowed to undo a particular business action. Those are application or collaboration-layer responsibilities.