A real-time collaborative whiteboard application built with tldraw and Phoenix/Elixir.
- Real-time collaboration: Multiple users can draw and edit simultaneously
- Multi-tab sync: Changes sync across browser tabs and persist after refresh
- WebSocket-based: Uses tldraw's sync protocol v7 over WebSockets
- Persistent rooms: Room state is maintained via GenServers
- Backend: Elixir + Phoenix + Bandit (WebSocket server)
- Frontend: React + TypeScript + tldraw SDK
- Sync:
@tldraw/syncwith custom Phoenix WebSocket handler - Build: Vite
-
TldrawWeb.SyncHandler- WebSocket handler implementing tldraw sync protocol v7- Handles
connect,push,ping/pongmessages - Applies operational transformation via recursive diff patching
- Broadcasts changes to other clients via PubSub
- Handles
-
Tldraw.Sync.RoomServer- GenServer maintaining persistent room state- Stores document state and server clock per room
- Registered via
Registryfor easy lookup
-
Tldraw.Sync.RoomSupervisor- DynamicSupervisor managing room processes -
TldrawWeb.Plugs.SyncWebSocketPlug- Plug intercepting/sync/*requests for WebSocket upgrade
app.tsx- React app using tldraw'suseSynchook- Connects to WebSocket endpoint
- Handles loading/error states
- Renders tldraw editor
- Elixir 1.15+
- Node.js 18+
# Install Elixir dependencies
mix deps.get
# Install Node dependencies
cd assets && npm install && cd ..# Start Phoenix server
mix phx.serverVisit http://localhost:4000
Open multiple tabs or browsers to test real-time collaboration!
- Client connects via WebSocket to
/sync/{room_id} - Server sends full document state on connect
- Client sends
pushmessages with diffs when drawing - Server applies diffs using operational transformation
- Server broadcasts patches to all other clients in the room
- Room state persists in GenServer, survives page refreshes
Implements tldraw sync protocol v7:
connect: Initial handshake with full document hydrationpush: Client sends changes (diff format)push_result: Server acknowledges with new server clockpatch: Server broadcasts changes to other clientsping/pong: Keepalive messages
Diff operations: ["put", record], ["patch", changes], ["remove"]