On this page
WebSocket message handler for PixelWeaver.
#server.src.pixelweaver.websocket
#server.src.pixelweaver.websocket
WebSocket message handler for PixelWeaver.
Routes incoming messages to the appropriate handler based on type, validates them through the protocol models, and coordinates responses/broadcasts.
#handle_message
async def handle_message(websocket: WebSocket, raw: dict[str, Any], state: ServerState, manager: ConnectionManager, on_dirty: DirtyCallback=None) -> NoneDispatch a parsed JSON message from a client.
Never raises -- all errors are sent back to the client as error messages.
#_handle_command
async def _handle_command(websocket: WebSocket, msg: CommandMessage, state: ServerState, manager: ConnectionManager, on_dirty: DirtyCallback=None) -> NoneValidate, APPLY to authoritative state, store in history, ack, broadcast.
Every arriving command takes exactly ONE of two paths -- there is no silent fall-through and no escape hatch:
- Covered (
apply_ws_commandmaterialized it): the mutation is stored in
authoritative pixel/role/structure state, recorded in history (riding its undo record), acked, and a state_patch is broadcast so other clients repaint from authoritative state.
- Not covered: REJECTED with
command_rejectand NOT recorded in history.
An applier error (a covered command that fails to apply) is also a rejection and does NOT touch history.
#_handle_sync_request
async def _handle_sync_request(websocket: WebSocket, msg: Any, state: ServerState, manager: ConnectionManager) -> NoneRestore the active project on the REQUESTING socket only.
Repurposed handshake: the client sends sync_request on every socket open (initial connect AND reconnect -- one path). Under the mutation lock (so the snapshot is never a torn read against a concurrent MCP mutation) we build an authoritative-replace full_state snapshot and send it -- as the same binary state_patch frame broadcasts use -- to the requester alone.
A fresh server with no active project sends NOTHING: the client's canvas simply stays empty, which is the correct fresh-server state and needs no new message type.
#_handle_undo
async def _handle_undo(websocket: WebSocket, msg: UndoMessage, state: ServerState, manager: ConnectionManager, on_dirty: DirtyCallback=None) -> NonePop last command, REVERT its authoritative buffers, push onto redo.
When the client sends command_id (the id of the command it just undid locally), the server verifies it matches the id at the top of its command history BEFORE reverting. A mismatch means the histories diverged -- the undo is rejected loudly (command_reject) and nothing is reverted, so the client surfaces the divergence instead of silently reverting a stale edit.
#_handle_redo
async def _handle_redo(websocket: WebSocket, msg: RedoMessage, state: ServerState, manager: ConnectionManager, on_dirty: DirtyCallback=None) -> NonePop last undone command, REAPPLY its authoritative buffers, restore it.
Like undo, when the client sends command_id the server verifies it matches the top of its redo stack before reapplying; a mismatch is rejected loudly instead of reapplying a diverged command.
#broadcast_patches
async def broadcast_patches(connections: ConnectionManager, project_name: str, patches: list[dict[str, Any]], command_id: str | None=None, exclude_ws: WebSocket | None=None) -> NoneBroadcast data patches to all connected WebSocket clients.
After MCP modifies server state, call this with the resulting patches so every frontend can apply the changes. exclude_ws allows skipping the originating client (e.g. the one that sent a frontend command -- it already applied the mutation locally).
#broadcast_full_state
async def broadcast_full_state(state: ServerState, connections: ConnectionManager, project_name: str | None=None, command_id: str | None=None, exclude_ws: WebSocket | None=None, replace: bool=False) -> NoneBuild a full-state patch from the active project and broadcast it.
Convenience wrapper combining build_full_state_patch + broadcast_patches. Used by MCP tools after they mutate pixel data / layers / canvas.
replace threads the EXPLICIT authoritative-replace signal onto the emitted patch. It stays False for MCP mutations (merge) and is True only for a PROJECT SWITCH (the activate path), so switched-to clients rebuild their layer tree, drop stale canvases, and repoint the active canvas.