On this page
Undo and redo that actually revert authoritative pixel and role buffers by restoring the before and after snapshots recorded on each covered command entry.
#server.src.pixelweaver.undo_history
#server.src.pixelweaver.undo_history
Undo/redo revert for authoritative state.
Defect 3: undo/redo popped/pushed the command history and redo stack but never reverted the authoritative pixel/role buffers -- pixel_data was byte-identical across an undo.
Mechanism: when a covered pixel/role command is applied (on either the WebSocket or MCP path) it records a small _undo snapshot on the command entry capturing the affected layer's target-frame buffer BEFORE and AFTER the mutation. Undo restores the BEFORE image; redo restores the AFTER image. Because the whole record (base64-encoded buffers) rides the command entry, it survives the JSON round-trip between the collab server and the MCP process, so both undo paths revert identically without re-executing the command (which would be fragile across the differing client/MCP command schemas).
The record is intentionally targeted (one layer buffer, not the whole project) to bound the size of the persisted history. Structural commands (layer/frame/ resize) and uncovered command types carry no _undo record, so undo pops them from history without a buffer revert -- reverting those is future work.
#make_pixel_undo_record
def make_pixel_undo_record(canvas_name: str, kind: str, layer_id: str, frame_index: int, before: bytes | None, after: bytes | None) -> dict[str, Any]Build a JSON-serializable before/after buffer snapshot record.
kind is "rgba" (pixel_data) or "role" (role_data). before/after are the target-frame layer buffers (None = the layer had no buffer in that plane, which undo/redo restores by deleting the key).
#snapshot_canvas_buffers
def snapshot_canvas_buffers(canvas) -> dict[str, Any]Capture a canvas's full dimension + buffer state for a dimension undo.
Dimension ops (rotate/scale/crop/resize) rewrite every frame's buffers and the canvas size, so a targeted per-layer record cannot revert them. This stores the whole state by necessity (bounded to one canvas).
#make_dimension_undo_record
def make_dimension_undo_record(canvas_name: str, before: dict[str, Any], after: dict[str, Any]) -> dict[str, Any]Build a full before/after dimension snapshot record for one canvas.
before/after come from :func:snapshot_canvas_buffers.
#serialize_canvas
def serialize_canvas(canvas) -> dict[str, Any]Capture a WHOLE canvas (metadata + layer tree + every frame buffer).
Unlike :func:snapshot_canvas_buffers (dims + buffers only) this is a full, self-contained description from which :func:_deserialize_canvas can rebuild an identical CanvasState -- used by the project-level canvases_added record so a redo re-creates the exact canvases an undo removed.
#_deserialize_canvas
def _deserialize_canvas(content: dict[str, Any])Rebuild a CanvasState from a :func:serialize_canvas snapshot.
#make_canvases_added_record
def make_canvases_added_record(canvases: list[dict[str, Any]]) -> dict[str, Any]Build a project-level canvases_added record for a multi-canvas add.
canvases is a list of serialize_canvas snapshots (in creation order). Undo removes each named canvas from the project; redo re-adds them with the exact captured content. The first project-scoped structural undo record -- every other record family is canvas-scoped.
#apply_undo_record
def apply_undo_record(project: ProjectState, record: dict[str, Any], *, redo: bool) -> boolRestore the BEFORE (undo) or AFTER (redo) state from a record.
Two record families share this entry point: - pixel/role buffer snapshots (kind == "rgba"/"role"), the original targeted before/after image revert; - structural records (an op key), which revert/re-apply a layer or frame structure change produced by structural_ops.
Returns True if state was restored, False if the target canvas/frame no longer resolves (a best-effort no-op rather than a crash).
#_apply_structural_record
def _apply_structural_record(project: ProjectState, record: dict[str, Any], *, redo: bool) -> boolRevert (undo) or re-apply (redo) a structural op record.
#_apply_compound
def _apply_compound(project: ProjectState, record: dict[str, Any], *, redo: bool) -> boolRevert (undo) or re-apply (redo) a compound record.
A compound wraps an ordered list of sub-records (each a normal record type) applied together as one undoable step. Redo re-applies them in forward order; undo reverts them in REVERSE order (so the state unwinds exactly as it was built). Each sub-record is dispatched through apply_undo_record, so any record family (meta_snapshot, pixel/role, dimension, ...) can nest. This is the reusable multi-part record: future combined ops get undo for free.
#_apply_canvases_added
def _apply_canvases_added(project: ProjectState, record: dict[str, Any], *, redo: bool) -> boolRevert (undo) or re-apply (redo) a multi-canvas add.
Undo removes each named canvas from the project; redo rebuilds each canvas from its captured content and re-inserts it. Project-scoped (the record has no single canvas target), so it is dispatched before the canvas lookup.
#_apply_dimension_record
def _apply_dimension_record(project: ProjectState, record: dict[str, Any], *, redo: bool) -> boolRevert (undo) or re-apply (redo) a whole-canvas dimension snapshot.
Restores the canvas dimensions and rebuilds every frame's pixel/role buffers from the captured base64 images. Frames are matched by id; a frame whose id is absent from the snapshot is left untouched (best-effort).
#_apply_meta_snapshot
def _apply_meta_snapshot(project: ProjectState, record: dict[str, Any], *, redo: bool) -> boolRevert (undo) or re-apply (redo) a project/canvas metadata snapshot.
Restores each captured attribute by raw setattr (the value was already validated when it was first written, so no re-validation is needed). The sentinel target "__project__" addresses project-level attributes; any other target is a canvas name. A missing canvas target is a best-effort no-op for that change rather than a crash.