Delta and Checkpoint History
Overview
History entries are stored as a mix of:
- Checkpoint entries: full JSON-safe snapshots of objects and canvas size.
- Delta entries: forward/reverse patches between adjacent states.
This keeps undo/redo deterministic while reducing repeated full snapshots.
Behavior
- The first saved entry is always a checkpoint.
- Subsequent saves record deltas when possible.
- A checkpoint is forced when:
- the configured delta interval is reached, or
- cumulative delta bytes exceed the configured threshold.
- Undo applies reverse patches.
- Redo applies forward patches.
jumpToState(index)reconstructs state from the nearest prior checkpoint.- During restore, the editor applies incremental node sync (add/remove/change) instead of always rebuilding every canvas node.
- Undo/redo restores object data, canvas size, connections, and animation data from the same timeline.
- Editor selection is preserved after undo/redo when selected object IDs still exist in the restored state.
Export and Import
exportHistory()now outputs version2.0payloads withentries.importHistory()supports:- version
2.0entries, - legacy snapshot payloads (
version 1.0) and migrates them to the new model.
- version
- Imported patches are validated. Invalid path semantics (for example empty
replacepaths) or dangerous keys are rejected. - Entry order is validated during export/import. If a tampered sequence cannot replay forward/reverse consistently, import fails safely.
Persistence Rows (Database-Friendly)
History entries can be flattened into row records with:
iddocIdentryIndexdata(JSON.stringify(entry))schemaVersion(currently2.0)createdAt
Recommended index/constraint strategy:
- Primary key:
id - Unique:
(docId, entryIndex) - Query index:
(docId, entryIndex DESC)for pagination/recent reads
Runtime helpers:
toPersistedHistoryRowsandfromPersistedHistoryRowsselectReplayRows(latest checkpoint + trailing deltas)truncateRowsKeepingRecoverableBaseline(keep recent rows without losing replay baseline)pruneObsoletePersistedRows(drop unsupported schema rows and orphan doc rows)- Typed errors with codes:
ERR_INVALID_HISTORY_PAYLOADERR_UNSUPPORTED_HISTORY_SCHEMA
JSON-Safe Constraints
Persisted entries are JSON-safe:
- Dates are stored as ISO strings.
undefined, functions, class instances, and cyclic references are not allowed.- Dangerous keys such as
__proto__,constructor, andprototypeare blocked.
Notes
- Runtime states still expose
Dateobjects for timestamps and objectcreated/modifiedfields. - Compression can drop no-op delta entries and merge adjacent small deltas within a short time window.
Troubleshooting
Common Error Codes
ERR_INVALID_HISTORY_PAYLOAD- Typical causes: invalid JSON row data, index gaps, missing baseline checkpoint, unsafe patch path.
- Recovery: reload latest valid checkpoint window (
selectReplayRows) and drop invalid rows.
ERR_UNSUPPORTED_HISTORY_SCHEMA- Typical causes: old/unknown schema version rows.
- Recovery: run migration path to
2.0, or skip unsupported rows usingpruneObsoletePersistedRows.
Minimal Recovery Playbook for Corrupted History
- Load rows for one
docIdand sort byentryIndex. - Run
pruneObsoletePersistedRows(filter schema + orphan docs). - Run
selectReplayRowsfor target index to keepcheckpoint + deltas. - Parse with
fromPersistedHistoryRows; if this fails, fallback to the last known valid checkpoint row only. - Rebuild active history payload and continue editing from that baseline.
Migration and Cross-Platform Notes
importHistorysupports legacy snapshot payload (v1) and migrates tov2entries.- Persisted row format is plain JSON text and index-based ordering; row order can be reconstructed on any platform by sorting
entryIndex.