Synapse
A local-first, end-to-end-encrypted thinking canvas — real-time multiplayer, fully offline, conflict-free, with a time machine. Built so the server is the least-trusted thing in the system.
An itch about who your thoughts belong to.
Most collaborative tools make a quiet trade you never agreed to: your thoughts live on someone else’s server, in plaintext, and stop working the second your Wi-Fi does. I wanted the opposite — a canvas that’s yours first. Works on a plane. Merges without a fight when you land.
And a server that literally cannot read what you wrote, because it only ever sees ciphertext. Synapse started as a dare to myself: build real-time multiplayer where the server is the least-trusted component — and prove it, not just claim it.
Six months, one stubborn idea.
- Jan 2026
The itch
Read one too many “local-first software” essays, then watched a doc quietly drop my edits because it couldn’t reconnect. Decided to find out how hard the good version actually is.
- Feb 2026
Scoping & the big decision
CRDTs vs OT. OT needs a trusted, plaintext-aware server — exactly the thing I was trying to delete. Chose Yjs and a blind relay, and wrote the threat model before I wrote the code.
- Mar 2026
MVP — a canvas that’s offline on day one
A custom canvas (no whiteboard library), Yjs as the source of truth, IndexedDB for durability. Pan, zoom, nodes, edges. It worked on a plane the first afternoon, because that was the whole point.
- Apr 2026where it got hard
The part that broke everything
End-to-end encryption and the blind relay went in — and my own updates started echoing back through the network and re-applying. A feedback loop that looked like the canvas having a seizure. The fix was origin discipline: tag every network-applied transaction so the local handler can tell “I received this” from “I made this.” Two lines. Three days to find them.
- May 2026
Time-travel & presence
Realized the CRDT update log was already a complete history — so the time machine (scrub, play, restore, all conflict-free) came almost for free. Then smoothed the remote cursors so presence felt native instead of teleporting one packet at a time.
- Jun 2026
Launch
Wrote a headless smoke test that proves convergence end-to-end over the real encrypted wire, shipped the relay to Fly.io and the app to Vercel — and stopped polishing before I ruined it.
Key features.
Works fully offline
Every edit hits a local Yjs doc and IndexedDB first. Lose Wi-Fi, close the tab, reboot — your board is still there.
Conflict-free sync
Two people edit the same board offline, reconnect, and it merges with no conflicts — because the core is a CRDT, not a diff.
End-to-end encrypted
Updates are AES-256-GCM-sealed before they leave the device. The relay only ever holds ciphertext it can’t read.
A real time machine
Scrub the whole history, play it back keystroke by keystroke, and restore to any moment — conflict-free.
Live presence
Named, colored cursors that interpolate smoothly, plus live remote selection rings. Encrypted like everything else.
Convergence, provable
A ⚡ button fires a burst of concurrent edits; open a second tab and watch both converge to identical state.
Decisions & the problems that forced them.
↯A blind server that still has to sync state.
The room key lives only in the URL’s #fragment, which browsers never send anywhere. The relay routes by base64url(SHA-256(key)) — a room id it can derive but can’t reverse. Sync without ever shipping the secret.
↯Remote updates echoing into infinite loops.
Strict origin discipline: updates applied from the network are tagged with the provider as their transaction origin, so the doc’s own update handler never re-broadcasts data it just received. The bug that ate three days became the design that holds the whole thing together.
↯History without a separate undo stack.
Because the ordered CRDT update log is the history, “state at time T” is just replaying updates[0..T] into a throwaway doc. Restore diffs that snapshot against the live doc and applies the minimal ops, so every peer converges on the past state.
↯Cursors that look broken at packet rate.
Remote cursors are eased toward their latest position in a requestAnimationFrame loop that writes transforms straight to the DOM — bypassing React entirely. Raw packets teleport; interpolation looks intentional.
The full deep-dive — CRDT vs OT, the E2EE key model, and an honest threat model — lives in ARCHITECTURE.md.
What it’s made of, and why.
Want to build something — or break something interesting?
I’m always up for a good problem. Whether it’s LLMs in production, automation that runs itself, or the next weird idea that won’t leave me alone — let’s talk.