Skip to content
  • i want an undo system
  • first, figure out "go back to" points
    • drag
    • stretch (gigantic hierarchy ???)
    • rename
    • edit a cell
    • delete
    • add/insert child
    • rotate
    • straighten
    • not?
      • select
      • fit
      • 2D/3D
      • solid/xray

Proposal: Snapshot-based Undo

Why snapshots over commands

Command pattern would require retrofitting every mutation site with execute/undo pairs — 12+ action types, each with its own reversal logic (delete is especially gnarly: subtree + formula cleanup). Snapshot approach: capture serialized scene state before each mutation, restore to undo.

Existing infrastructure

  • scenes.save() serializes the full scene to Portable_Scene (all SOs, constants, camera, selection)
  • Smart_Object.serialize() / deserialize() round-trips cleanly
  • Engine's setup/load path rebuilds the live scene from Portable_Scene

Design

New file: src/lib/ts/managers/History.ts

class History {
    stack: Portable_Scene[]          // past states (capped, eg 50)
    redo_stack: Portable_Scene[]

    snapshot(): void                 // capture current state, push to stack, clear redo
    undo(): Portable_Scene | null    // pop stack, push current to redo, return scene to restore
    redo(): Portable_Scene | null    // pop redo, push current to stack, return scene to restore
    clear(): void                    // reset both stacks (on file load / new scene)
}

Snapshot sites (before the mutation, not after)

  1. Drag startdrag.set_target()
  2. Rotate startrotate_object() first call per gesture
  3. Dimension commitdimensions.commit()
  4. Angular commitangulars.commit()
  5. Deleteengine.delete_selected_so()
  6. Add childengine.add_child_so()
  7. Insert from fileengine.insert_child_from_text()
  8. Rename — SO name or constant name change
  9. Reset angleP_Angles.svelte reset handler
  10. Formula edit — attribute cell commit in D_Attributes

Not undoable (view state, not model state)

select, fit, 2D/3D toggle, solid/xray

Trigger

Cmd+Z / Cmd+Shift+Z in Events.ts keydown handler.

Restore path

undo() returns a Portable_Scene. Caller (engine) runs the same path as file-load: clear scene, deserialize SOs, rewire hierarchy, rebuild constraints, restore selection + camera.

Tradeoffs

  • Memory: 50 snapshots of a typical scene (10 SOs) ≈ ~500KB. Fine.
  • Hierarchy concern ("stretch — gigantic hierarchy ???"): snapshot sidesteps this. No need to figure out which descendants changed. Restore the whole thing.
  • Continuous drags: only pre-drag state saved, so undo jumps to before the drag started. Standard CAD behavior.

Build plan

  1. History.ts — stack manager (~60 lines)
  2. history.snapshot() calls at each mutation site (~10 one-liners)
  3. engine.undo() / engine.redo() — call history then restore
  4. Cmd+Z / Cmd+Shift+Z handling in Events.ts