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.