Appearance
Whiteboard
Every room has a shared collaborative canvas (Excalidraw behind the engine adapter). The board lives in memory for the lifetime of the room: opening the panel reconnects you to the live board, and the board is dropped when the room ends or its last participant leaves. A server restart blanks boards mid-meeting; nothing is persisted.
Using the board
- Open: press the board button in the room's right controls. The canvas opens over the participant grid; your camera, microphone, and screen share are unaffected.
- Draw permission follows the host lock:
Host-only drawing(default) - only the room owner can draw; everyone else sees a live read-only canvas.Drawing open- every room participant (including approved guests) can draw.
- The owner toggles the lock from the board header; the change applies live to every open board.
- Undo/redo reverts your own actions and propagates to every participant. Nobody can undo someone else's action.
Architecture
- Engine adapter (
packages/whiteboard-core): the room UI, sync, and authorization know the canvas only through theWhiteboardEngineinterface. The engine owns its document model; the host injects an opaque byte transport. Replacing or adding an engine never touches the room view, the gateway, or the permissions. - Default engine (
packages/whiteboard-react): upstream Excalidraw (MIT) bound to a Yjs document -Y.Mapof elements by id (last-writer-wins by elementversion),Y.Arraypreserving z-order, multiplayer undo throughY.UndoManagerwith Excalidraw's built-in per-client history disabled. - Server (
src/whiteboard/): a Socket.io/whiteboardnamespace mirroring the chat gateway's identity model (registered user via access-token cookie or approved guest via the room-scoped guest cookie; registered users must be active SFU peers). The gateway holds the authoritative in-memoryY.Docper room: joins receive the full document state (whiteboard:state), updates are merged after admission and draw-permission checks and relayed to the room (whiteboard:update, binary Yjs updates, capped per message). Updates from locked-out participants are dropped server-side in addition to the read-only canvas. - Teardown: the room board is dropped through the SfuService room-closed tap (the same seam the egress pipelines use), covering both room-end paths.
- Room UI: the whiteboard registers in the room panel registry (
apps/client/src/features/room/room-panels.ts);active-room-viewrenders registered panels generically and imports no panel internals. The engine bundle (1 MB+) is lazy-loaded through thevendor-excalidrawchunk only when the panel opens.
Local smoke
With the dev stack running, open the same room in two browser profiles (one logged-in owner, one guest), open the board in both, and draw: strokes appear on both canvases, a third profile joining late sees existing content, and the owner's Lock drawing flips the guest canvas to read-only instantly. Undo on one canvas removes that participant's stroke on both.
Decision record
The engine adapter, the Yjs sync, and the removal of tldraw (license change of Sep 2025 removed free production use) are documented in docs/adr/0003-whiteboard-engine-adapter-yjs.md.