Launch OpenGPEX

State & Data Flow

How OpenGPEX stores, updates, and tracks all editor state — from layer properties to undo history — with native-refresh-rate performance and sub-2ms undo operations.


Design Overview

User Gesture (mouse/touch)
    │
    ▼
Interaction Dispatcher
    │
    ├─── rAF path ─────► Fast Track (volatile ref, no React re-render)
    │                         │
    │                         ▼
    │                    Canvas repaints immediately (rAF)
    │
    └─── Commit path ──► Reducer (Immer produce)
                              │
                              ├──► Undo Patch recorded (JSON Patch, ~100 bytes)
                              │
                              ▼
                         React re-render (selective via selectors)

Normalized State

All domain entities use a { byId, order } dictionary pattern:

  • byId — Hash map keyed by entity ID, providing O(1) lookups
  • order — String array defining rendering Z-order

This design enables:

  • O(1) updates — Direct property mutation without tree traversal
  • Efficient reordering — Only the order array changes; objects stay referentially stable
  • Minimal undo patches — Inserting a layer produces exactly 2 patches instead of shifting all indices

Dual-Track Architecture (Fast Track + Slow Track)

Slow Track (Persistent) Fast Track (Volatile)
Backing React useReducer + Immer Mutable useRef
Drives UI panels, layer tree Canvas paint, overlays
Triggers React re-render requestAnimationFrame only
Undo Produces patches on every dispatch Produces nothing until commit
Persist IndexedDB Never

During a drag interaction:

  1. Fast Track receives pointer-move updates every animation frame (60–120fps, no React involved)
  2. Canvas reads merged state (persistent + volatile) and repaints immediately
  3. On pointer-up, volatile state commits to Slow Track as one atomic write
  4. One undo patch is generated for the entire drag operation (not one per frame)

4-Layer Dispatch

All state mutations flow through one of four channels:

Layer Name Use Case Characteristics
1 Atomic Simple property changes (opacity, name) Direct reducer dispatch, auto undo
2 Fast Track High-frequency interactions (drag, resize, pan) Volatile ref, commit-on-end
3 Facade Multi-step operations (rotate, merge, crop) Groups into single undo step
4 Command Bus Cross-plugin messaging, keyboard shortcuts UID-addressed, async, extensible

Undo/Redo

Built on Immer JSON Patches — recording only the minimal property-level diff:

Metric Traditional (Full Snapshot) OpenGPEX (Patches)
Memory per step 1–10 MB 50–200 bytes
Undo latency 10–50ms (deep copy) <2ms (patch apply)
100 undo steps 100–1000 MB RAM ~20 KB RAM

Additional design choices:

  • Viewport stability — Camera state is excluded from undo to prevent jarring jumps
  • History limit — 50 steps max, oldest discarded automatically
  • Fast Track integration — Dragging 500px produces exactly 1 undo step, not 500

Last updated: 2026-07-30