LiveBoard / docsProject documentation

Reference

Realtime Protocol

Look up syntax, contracts, layouts, algorithms, and exact behavior.

Canvas editor. The editor sends durable operations for committed changes and preview operations during high-frequency drag, resize, and rotation gestures.

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.
preview(opt)⇒statelocal∗,revision′=revisionpreview(op_t) \Rightarrow state_{local}^{*},\quad revision' = revision
Preview stream. Preview operations mutate transient room state for connected clients but do not persist history or increment revision.
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
WebSocket message flow. The same operation shape is used for previews and commits, but only committed operations enter PostgreSQL history.
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. Redis Pub/Sub is best-effort. Durable correctness comes from revision numbers plus snapshot refresh when a client detects that it missed an operation.

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.

Rejected writerate_limitedWriter snapshotpreview_resetPeer refreshDurable canvassender replaces local optimismpeers discard transient preview
Rate-limit recovery. Rejected writes are never persisted or fanned out. The sender and peers both return to the last durable canvas so transient previews cannot become visible drift.

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.