Your drawings live in your browser, sync directly between collaborators, and
— the moment you set a password — are end-to-end encrypted, so even
the relay that ferries them between people can't read a thing. Here's exactly
how that holds together, from the top down.
The big picture
What happens between your pen and everyone else's screen.
1
You draw
Your stroke becomes a shape in a document shared by everyone in the room.
2
The CRDT records it
Y.js turns the change into a small, conflict-free update — no central server has to referee.
3
Sign & encrypt
In an encrypted room, the update is signed and AES-256-GCM encrypted right in your browser.
4
The relay broadcasts
A tiny relay forwards the bytes to everyone else. In encrypted rooms it only ever sees ciphertext.
5
Peers verify
Each peer decrypts the frame and checks the signature before it's allowed to change anything.
6
Merge & render
Y.js merges the verified update and the canvas redraws — instantly, in order, on every screen.
7
Persist
Everything is saved locally to IndexedDB, so your work is there next time — even offline.
Under the hood
Each piece in plain language — open “Go deeper” for the real internals.
Real-time sync without a server
Two people can draw at the same instant and never clobber each other's work.
That's because the shared document is a CRDT (Conflict-free Replicated Data
Type): every change carries enough information to merge cleanly with every
other change, in any order, with no server deciding who wins.
Go deeper
The document is a Y.Doc from Y.js: a Y.Map of
boards, each holding a Y.Array of shapes. Concurrent edits to
the same field resolve last-writer-wins per property, while edits to
different properties (one person resizes, another recolors) both survive.
Because merging is purely local math, the same model powers offline editing
and reconnection. Cursors, names, and live drawing previews ride a separate
ephemeral “awareness” channel, so presence never pollutes the
saved document.
The relay is deliberately dumb
There is no application server and no database. The only thing in the middle
is a ~35-line relay whose entire job is to forward a message from one
collaborator to the others. It runs no app logic, stores nothing, and —
in encrypted rooms — can't read a single byte it carries.
Go deeper
Sync runs over a PartyKit WebSocket relay (this app does not
use WebRTC — a broadcast relay reaches reliably through firewalls and
NATs where peer-to-peer mesh stalls). In an encrypted room, every
frame is ciphertext before it leaves the browser: document updates,
full-state snapshots, cursor positions, names, even the live drawing
preview. The one thing advertised in the clear is a small “this room
is encrypted” marker, so someone who arrives without a key knows to
ask for an invite link instead of joining an unreadable room.
End-to-end encryption
Set a password and the room becomes end-to-end encrypted. The password never
travels to any server — it's turned into an encryption key inside your
browser, and only people who hold it can read what's drawn.
Go deeper
The password is stretched into a key with PBKDF2 (600,000 iterations of
SHA-256, per current OWASP guidance) against a random 16-byte salt minted
for that room, then used for AES-256-GCM with a fresh random IV per message.
Both the salt and the iteration count travel inside the invite link, so
every peer derives the identical key — and the count is clamped to a
safe range on decode, so a hand-edited link can neither weaken the
derivation below the legacy floor nor pin you to a multi-second one. No
key-exchange protocol is needed: the secret is shared out of band, embedded
in the link you send.
Capability links & roles
Sharing isn't a single “anyone with the URL can edit” switch. A
room can hand out owner, editor, and view-only invite links, and the link
itself carries what you're allowed to do.
Go deeper
The capability lives in the URL fragment (everything after the
#), which browsers never send to a server — so the relay
never sees it. A viewer link carries public keys only; an
editor link adds the editor signing key; an
owner link adds the owner key on top. The app never emits an
owner link — it stays in the creator's address bar — so editors
can collaborate but can't lock anyone out. A tampered or truncated link
fails closed rather than silently dropping you into an editable room.
View-only that actually holds
A view-only link can't be bypassed by editing the URL or opening dev tools.
Even though a viewer can decrypt and see the board, anything they try to draw
is rejected by everyone else — because they were never given the key
needed to author a change.
Go deeper
In an encrypted room, editors sign each update with a per-epoch ECDSA P-256
key, and every peer verifies that signature before applying the change.
Viewers simply hold no signing key, so any update they emit fails
verification at every honest peer and never lands in the shared document.
Hiding the editing UI for viewers is cosmetic — the real enforcement
is the missing key. Each signature also covers the update prefixed by
its epoch number, so a signature captured in one epoch can't be
replayed after the room rotates.
Owner root-of-trust & revocation
With no server to ask “is this link still allowed?”, revocation is
the hard part of a system like this. The answer is a per-room owner key that
acts as the root of trust: only the owner can grant editing, and only the
owner can take it away.
Go deeper
The owner's key signs an epoch certificate binding the current
editor public key to an epoch number; peers only trust an editor key that a
valid owner certificate vouches for, and fail closed otherwise. Changing the
password rotates the room: the owner mints epoch N+1 with a fresh editor key
and certificate and broadcasts an owner-signed rotate notice. Peers mark the
old epoch superseded and stop applying its updates, so anyone holding an old
link can no longer produce edits that anyone will accept — real
revocation, with no central authority.
Offline-first by default
Lose your connection and you can keep drawing. Your work is saved on your own
device the whole time, and quietly merges back in when you reconnect.
Go deeper
The Y.Doc is persisted to the browser's IndexedDB via y-indexeddb, which is
the source of truth for your local state. Edits made while offline are
ordinary CRDT updates; on reconnect they merge conflict-free with whatever
changed while you were away. A lightweight per-epoch snapshot cache also lets
a freshly joined or reconnecting peer bootstrap quickly without waiting on a
live editor to be online.
Hardened against the shared document
In a collaborative app the risky input isn't a form field — it's the
shared document itself, which arrives from other people. That untrusted data
is sanitized before it ever touches the page, with a strict security policy as
the backstop.
Go deeper
Peer-supplied shape fields, names, and presence are run through an
allow-list before any of them reach the DOM: colors must be plain hex,
numbers must be finite, and unknown tool names collapse to an inert literal.
On top of that, a strict Content-Security-Policy (script-src
'self', no inline scripts, base-uri 'none') means even a
missed sink can't execute injected markup. It's defense in depth: sanitize
the data, then make the browser refuse to run anything that slips past.
What's in the box
The whiteboard you get on top of all that machinery.
Drawing tools — freehand pen, highlighter, and inline text
Shapes & arrows — lines, arrows, rectangles, circles, ellipses, diamonds, triangles, with fills and stroke styles
Sticky notes — color-coded, with wrapped, scrollable text
Connectors — link shapes with arrows that follow them as they move
Laser pointer — an ephemeral trail for presenting, never saved