Real-time multi-user whiteboard collaboration — ~100 ms over WebSockets
Every stroke, text node and image syncs across all participants in roughly 100 ms. A single Durable Object per board acts as the canonical state, so two people drawing at once never see divergent views. No accounts, no install — share a URL and you're collaborating live.
WebSocket per browser session. Each collaborator opens a long-lived WebSocket to the per-board Durable Object on Cloudflare's edge.
Single-writer Durable Object. One DO instance owns the canonical state of each board. Mutations are serialised through it, then broadcast to every connected client (including the originator, for confirm-on-receipt).
Latency: ~100 ms. A stroke you draw is visible to other participants in roughly 100 ms — the round-trip from your browser → nearest CF edge → DO → broadcast → all browsers.
Same validator on every transport. WebSocket mutations, REST mutations and MCP tool calls all go through the same applyAction path on the server. Quota limits and validation are identical.
Auto-reconnect. If your browser loses network, the WebSocket pauses; writes are disabled. When the online event fires the WebSocket reconnects and you re-sync.
What syncs live
Freehand strokes (drawn as SVG paths).
Text nodes — creation, edits, Markdown rendering, Mermaid re-renders.
Moves, resizes, deletes — through the same broadcast channel.
Eraser cascades — deleting a stroke locally erases it for everyone.
Sticky-note style toggles, colour changes.
Author attribution
Every mutation carries an author tag:
Humans: user:<uuid> — a stable per-device UUID generated on first visit and persisted in localStorage.
AI: ai:<label> — defaults to ai:claude for MCP, ai:rest for direct REST calls. AI-authored items are visually distinguished (purple border) in the SVG preview.
The author tag is immutable after creation — subsequent moves and edits by other collaborators never relabel the original creator.
Quotas (per board)
500 text nodes, up to 100 000 characters each.
50 images, up to ~900 kB each, total ≤ 10 MB per board.
By default, boards are open — the board ID is the access credential. Optionally a board can be PIN-locked with a 6-character key:
mode: write — anyone can view; only people with the key can edit.
mode: all — both reads and writes require the key.
Locked-board requests must include the key via the X-Board-Key header (REST + MCP), the access_key tool argument (MCP), or the WebSocket subprotocol (cnvs-key.<code>). There is no recovery — lose the key, lose the board.