Reference
Realtime Protocol
Look up syntax, contracts, layouts, algorithms, and exact behavior.
WebSocket collaboration separates previews from durable commits. During drag, resize, and rotation, the frontend sends throttled preview operations so other users see motion without inflating the revision counter. Color picker movement is local-only preview state until the picker rests or blurs, so dragging through a spectrum does not create hundreds of saved revisions. On pointer up, blur, Enter, or another commit boundary, the frontend sends one durable operation with history enabled.
Opening a canvas uses both HTTP and WebSocket paths. The whiteboard first fetches canvas metadata and the current durable state through GET /api/canvases/{canvas_id}. It then opens /ws/canvases/{canvas_id}, authenticated by the same httpOnly session cookie. The socket returns a snapshot containing state, revision, active users, and undo/redo availability. From that point forward, ordinary resource management remains HTTP, while live editing, cursors, previews, undo, and redo travel over the socket.
This preview/commit split is what lets LiveBoard feel live without turning every pixel of movement into saved history. Other clients can see an object moving during a drag, but the durable record only contains the final resting position. The same idea applies to color selection: intermediate swatch values help the local user preview the result, while the saved canvas receives one final color change.
- cursor messages keep collaborators visible without changing canvas state.
- preview_op messages show remote drag, resize, and rotation motion without incrementing revision.
- op messages represent committed changes that must be validated, persisted, inverted, and broadcast with a new revision.
- undo and redo messages ask the server to apply shared history, so every connected editor sees the same result.
- presence_join and presence_leave messages maintain the active collaborator list.
- access_removed, session_expired, rate_limited, and preview_reset messages are recovery/control events that keep users and canvases in a correct state.
client A pointer move -> preview_op(batch update_shape...) -> room fanout -> client B applies transient preview client A pointer up -> op(batch update_shape..., undoable=true) -> backend validates + locks + persists -> op_applied(revision+1, history_status) -> all clients reconcile state
client current revision = 17 receives op_applied revision = 19 -> expected 18, observed 19 -> pause operation application -> GET /api/canvases/:canvasId -> replace local canvas state, revision, history -> resume from durable PostgreSQL snapshot
Revision-gap recovery is deliberately simple. The client does not try to replay a partial event stream or ask Redis for missed messages. It notices that the next durable revision is not the one it expected and replaces local canvas state with the authoritative HTTP snapshot. Pub/Sub can stay ephemeral because PostgreSQL remains the replay source for durable state.
Presence is a separate stream: cursor messages carry canvas-space coordinates, selected shape id, user id, username, and a deterministic user color. Remote cursors are inverse-scaled by the local zoom so the cursor glyph stays the same screen size as each editor pans or zooms.
Local optimism is intentionally bounded. A sender applies its own durable operation immediately so the UI does not wait on the network, but useCanvasSocket tracks operation ids so the sender does not double-apply the broadcast echo. If the server sends a snapshot, rate-limit event, or revision-gap refresh, the optimistic queue is cleared and local canvas state is replaced with PostgreSQL truth.