Synapse
Back to the canvas
About the project

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.

The why

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.

How it got built

Six months, one stubborn idea.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

What it does

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.

The interesting parts

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.

Tech stack

What it’s made of, and why.

Yjs (CRDT)battle-tested, compact binary encoding, merges that actually converge.
y-indexeddbreal offline durability — reload and the board is still there.
Custom Node + ws relaya server too dumb to betray you; it only forwards ciphertext.
WebCrypto · AES-256-GCMnative, authenticated encryption — no crypto library to trust.
Next.js + TypeScript (strict)fast DX and a compiler that helps on concurrency-heavy code.
Tailwindone set of design tokens; the whole app speaks one visual language.
Project links

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.