The constraint
Every frame in the editor has to stay under 16ms, even when the customer is dragging a 4K image over a warped mockup. That means the history engine can't checkpoint the full scene graph — it has to diff.
Our approach
We store operations, not snapshots. Each op carries:
- An
apply(state)function. - An
invert(state)function that returns the reverse op. - A merge predicate so adjacent character-level edits collapse.
| Technique | Memory | Speed | Chosen |
|---|---|---|---|
| Full snapshots | huge | fast | — |
| Patches | medium | medium | — |
| Operation log | tiny | fast | yes |
The merge predicate is the part that makes typing feel native — without it, every keystroke would be its own undo step.
Gotchas
- Don't merge across selection changes.
- Don't merge across time gaps larger than 400ms.
- Don't merge image ops, ever.
Read the PixiJS v8 release notes if you want to understand how we avoid the old Graphics allocation penalty.