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:

  1. An apply(state) function.
  2. An invert(state) function that returns the reverse op.
  3. A merge predicate so adjacent character-level edits collapse.
TechniqueMemorySpeedChosen
Full snapshotshugefast
Patchesmediummedium
Operation logtinyfastyes

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.