Engineering Journal
Schema Editor
Schema Editor

Snapshot Undo Is Underrated. The Whole 'Command Pattern Is Correct' Argument Misses the Point.

2026-06-04

TLDR

Traditional software architecture tutorials label the Command Pattern (execute() / undo()) as the only valid approach for building undo systems. For modern canvas and schematic editors, full-state snapshot undo ({ before, after }) is significantly faster to implement, immune to edge-case command drift, and far easier to maintain. Paired with focus-time state capture, snapshot undo eliminates per-feature command classes.
Undo ArchitectureImplementation OverheadMaintenance CostPrimary Failure Mode
Command PatternHigh (Custom do/undo per feature)High (Command classes for every tool)Logic drift in inverse commands
Full Snapshot UndoLow (Uniform JSON state pairs)Zero (Handled by serializer)Late before-state capture

Problem statement: the overhead of the command pattern

The Command Pattern requires developers to write bidirectional transformation logic for every editor feature:

As an editor grows to support dozens of tools (rotation, alignment, grouping, symbol insertion), maintaining inverse command logic for every feature becomes a significant codebase tax.


Technical failure mode: subtle inverse command drift

In complex diagramming engines, writing exact inverse commands is error-prone:

  1. Unbalanced Operations: A GroupCommand.undo() might restore elements, but fail to re-establish wire port connectivity listeners.
  2. Command Avalanche: Adding a single new tool requires creating dedicated command classes, test cases, and stack serializers.

The fix: timed snapshot state pairs

Snapshot undo replaces custom command objects with uniform state snapshot pairs:

// Uniform Snapshot History Manager
class SnapshotUndoEngine {
  constructor() {
    this.historyStack = [];
  }

push(label, beforeState, afterState) { // Ignore identical snapshots if (JSON.stringify(beforeState) === JSON.stringify(afterState)) return; this.historyStack.push({ label, before: beforeState, after: afterState }); }

undo() { const entry = this.historyStack.pop(); if (entry) restoreState(entry.before); } }

By hooking before-state capture into user interaction entry points (focus for forms, pointerdown for drags), snapshot undo handles every current and future feature without requiring custom command classes.

Rule of thumb: Prefer full-state snapshot undo over the Command Pattern for web-based schema editors. Timed state serialization eliminates per-feature inverse command overhead.
Read this post in the full Engineering Journal →