Vize

Undo/Redo History

Headless Undo/Redo History; covers ProseMirror history, CodeMirror history, Redux undo.

Package @vizejs/ui/history
Maturity stable
Own the source vize lib pull history
Requires —
Aliases undo/redo, undo stack, history timeline, editable transaction
Covers ProseMirror history, CodeMirror history, Redux undo

API

createHistory

Create an undo/redo timeline with coalescing, transactions, and snapshots. Entries describe changes that are already applied; the controller only replays them. It owns no DOM or timers, so it is safe to create during server rendering. Pushes that happen while the controller is restoring state are discarded so reactive mirrors cannot corrupt the timeline. Call HistoryController.dispose when using this factory outside a Vue effect scope.

function createHistory(options: HistoryOptions = {}): HistoryController

useHistory

Create a history controller disposed with the current Vue effect scope.

function useHistory(options: HistoryOptions = {}): HistoryController

createEditableTransaction

Create an in-place editing session that records one history step per edit. begin captures the pre-edit value, update writes live values without touching history, and commit pushes a single snapshot step covering the whole session; cancel restores the captured value instead. This keeps per-keystroke writes out of the timeline so field edits undo as one unit.

function createEditableTransaction<Value>( options: EditableTransactionOptions<Value>, ): EditableTransaction<Value>

resolveHistoryOptions

function resolveHistoryOptions(options: HistoryOptions): ResolvedHistoryOptions

toRecord

function toRecord(entry: HistoryEntry, at: number): HistoryRecord

toSnapshotEntry

function toSnapshotEntry<Value>( options: HistorySnapshotOptions<Value>, ): HistoryEntry | null

tryCoalesce

Merge record into top when both carry the same coalescing key and the push arrived inside the coalescing window. The merged step keeps the first undo, adopts the latest redo, and restarts the window.

function tryCoalesce( top: HistoryRecord | undefined, record: HistoryRecord, coalesceWindow: number, ): boolean

foldRecords

Fold staged transaction records into one composite step.

function foldRecords( records: readonly HistoryRecord[], label: string | null, at: number, ): HistoryRecord

Behavior

Normative state × input → outcome table for @vizejs/ui/history. Every row is exercised by src/families/interaction/history/history*.test.ts; compile-only assertions live in src/families/interaction/history/history.types.test-d.ts.

# State Input Outcome Proven by
H1 applied change push then undo and redo entries replay in reverse and forward order with reactive depths timeline test
H2 undone steps pending redo new push the redo timeline clears timeline test
H3 same coalescing key pushes within the coalescing window steps merge keeping the first undo and the latest redo coalescing test
H4 same coalescing key pushes beyond the window or after undo steps stay separate coalescing test
H5 snapshot push equal before and after values no-op snapshots are dropped snapshot test
H6 open transaction staged pushes then commit everything folds into one labeled step that undoes and redoes atomically transaction test
H7 open transaction rollback or a throwing transaction callback staged entries undo in reverse and the failure surfaces transaction test
H8 nested transactions inner settle then outer settle inner frames fold into the outer step; LIFO misuse throws transaction test
H9 open transaction undo, redo, or clear stable transaction diagnostic rejects the call transaction test
H10 restoring state push from a reactive mirror the push is discarded so replay cannot corrupt the timeline restoring test
H11 timeline at limit new step the oldest step drops limit test
H12 throwing entry undo or redo the failure surfaces, the entry drops, and the timeline stays usable failure test
H13 editable session begin/update then commit live writes bypass history and one snapshot step covers the session editable test
H14 editable session cancel or an unchanged commit the pre-edit value restores, or no step is pushed editable test
H15 invalid options malformed entries, snapshots, or options stable runtime diagnostics reject the misuse diagnostics test
H16 active controller dispose or Vue scope stop both timelines clear and imperative calls become terminal lifecycle test
H17 concurrent SSR requests identical consumers byte-identical markup with no request-global timeline state SSR test
H18 SSR followed by hydration undoable interaction host identity remains and undo/redo state renders without warnings hydration test
H19 DOM, SSR, and Vapor lanes authored consumer compiles every renderer accepts the same controller surface renderer gate
H20 root and subpath consumer only history is retained equal CSS-free bundles exclude unrelated component families tree-shaking gate
H21 public TypeScript API mutation or invalid options compile-only assertions reject misuse type declaration test

Accessibility obligation

Undo is an error-recovery affordance: keep Undo and Redo reachable through visible, focusable controls with disabled state bound to canUndo and canRedo, not only through keyboard accelerators. Use undoLabel and redoLabel to give those controls specific accessible names (for example "Undo Typing"), and announce destructive coalesced steps before dropping them. Field editing should flow through the editable transaction so one keystroke-level mistake never needs dozens of undo activations.