Model - Edit history
API reference for src/core/editHistory, src/schema/editHistory on @huukhanhnguyen/model, reachable from ..
See the Guide for the ModelJSON / evaluate pipeline.
API reference
Signatures are generated from the live package .d.ts - not hand-written.
createEditHistory
function
createEditHistory(target: (Model | ModelScope)): EditHistoryEDIT_HISTORY_MAX_STEPS
const
Max past steps retained per model.
MEASURED (2026-08-06, packages/parametric): JSON size of a typical inverse entry after JSON.stringify:
- laneSetField / settingsSet / laneMove: ~80–200 bytes
- laneInsert / laneRemove of a small sheet/view: ~150–600 bytes
- laneSetOps with a short chain (≤5 ops): ~0.5–2 KB Stated memory budget: keep history JSON under ~2.5 MB per live model. Cap 500 steps × worst-case ~5 KB (large setOps body) ≈ 2.5 MB. Simple editing sessions stay well under that (500 × ~300 B ≈ 150 KB).
const EDIT_HISTORY_MAX_STEPS = 500EditHistory
type
type EditHistory
// = {
/** Completed undo steps (oldest first). */
readonly past: readonly HistoryStep[];
/** Steps undone and available to redo (oldest first among future). */
readonly future: readonly HistoryStep[];
/**
* Door helpers call this after a successful mutation. No-op when
* `inverse` is null (blocked remove, no-op move) or while undo/redo is
* replaying through the door.
*/
note(call: LaneEditCall, inverse: LaneEditCall | null | undefined): void;
/** Push a single entry (clears redo). Used by tests and `apply`. */
record(entry: HistoryEntry): void;
/** Apply a call through the door and record the inverse when present. */
apply(call: LaneEditCall): LaneEditCall | null;
undo(): boolean;
redo(): boolean;
readonly canUndo: boolean;
readonly canRedo: boolean;
beginGroup(): void;
endGroup(): void;
withGroup<T>(fn: () => T): T;
/**
* Atomic multi-edit: run `fn` inside a group. On success the whole body is
* one undo step (same as `withGroup`). On throw, every edit captured so
* far is inverted in reverse order so the document is byte-identical to
* the pre-transaction state, then the error is rethrown — no undo step is
* left on the stack. Nested calls flatten into the outer transaction
* (inner begin only deepens the group; only the outermost rolls back or
* commits). Dirty / re-eval still fires per door edit during the body
* (preserves the current group contract — batching evaluation is a
* separate concern).
*/
withTransaction<T>(fn: () => T): T;
/** Drop past/future (model load / switch). Open groups are discarded. */
clear(): void;
/** Subscribe to stack changes (canUndo/canRedo UI). Returns unsubscribe. */
onChange(listener: () => void): () => void;
}HistoryEntry
type
One door edit and the inverse that restores the pre-edit document.
type HistoryEntry
// = {
call: LaneEditCall;
inverse: LaneEditCall;
}HistoryStep
type
One undo step. Ungrouped edits have a single entry; a user gesture that emits several door calls is one step with multiple entries (undone in reverse order, redone in original order).
type HistoryStep
// = {
entries: HistoryEntry[];
}