Operation System
Every mutation to a NullCad document — drawing, moving, deleting, vertex edits, layer changes, comparisons, and more — goes through a single, strongly-typed operation system. This is what powers undo/redo, the audit trail, and (indirectly) the Lua plugin and MCP server integrations, since both reuse the same operation builders rather than mutating the document directly.
Key pieces
OperationType— a strongly-typed enum covering every mutation: create/delete/restore, move/rotate/scale, vertex edits, split/extend/trim, explode, layer changes, comparisons, and more.OperationHistory— an append-only log. Undo doesn’t remove the original operation — it appends a compensating (inverse) operation. Redo re-applies. This means the full history is always preserved as an audit trail, and a new operation clears the redo stack (no branching history).DocumentState— combines aDocument, anOperationHistory, and aVec<Snapshot>.apply_operation()applies the operation, records it, and auto-snapshots periodically (every 50 operations by default).undo()/redo()create and apply the compensating operations.- Phase tracking — every operation is tagged with the active survey phase at the time it was applied.
Example
#![allow(unused)]
fn main() {
let op = OperationType::MoveGeometry {
geometry_ids: vec![id],
delta: Vector2::new(10.0, 20.0),
};
doc_state.apply_operation(user_id, op)?;
doc_state.undo(user_id)?; // applies the compensating op
doc_state.redo(user_id)?;
}
Soft deletes
Deleted geometry is tombstoned rather than physically removed, preserving history — a delete is just another operation with a well-defined inverse (restore).
Persistence
Operation history is saved with the document (see The .nullcad Archive), so the full edit history travels with the project file.
Collaboration-ready by design
The operations system was designed to support future collaborative
editing with minimal changes: UUID geometry IDs, tombstone soft deletes,
operation metadata (user_id/timestamp/phase/description), an append-only
log, and a HashMap-based document are all CRDT-friendly properties. The
remaining path to multi-user editing is real authenticated user IDs, an
operation-sync transport, conflict resolution, and per-user undo — without
needing to break the existing model.
Adding a new operation
- Add a variant to
OperationTypeinsrc/operations/types.rs. - Implement
inverse(),summary(), andaffected_geometry_ids()for it. - Add execution logic in
apply_operation_type()insrc/operations/executor.rs. - Apply it via
DocumentState::apply_operation(). - Test the operation, its inverse, and its summary.