JotDaddy
A real-time collaborative editor built around one idea: collaborating should take zero setup. Go to the site, enter a 4-digit code or share a link, and you're editing the same document together. No accounts, no Drive, no permissions to manage. Under the hood it uses CRDTs so edits never conflict, and a WebSocket layer I scaled across servers with Redis.

Collaborating shouldn't need setup
This came out of a real annoyance. In lectures and group note-taking sessions, getting everyone onto one Google Doc is more work than it should be: someone has to create it in their account, it takes up their Drive space, and then they're managing permissions, sharing the link, and turning on edit access for each person. By the time that's sorted, half the moment has passed.
I wanted the opposite. With JotDaddy you go to the site, enter a 4-digit code or share a link, and you're editing together. No login, no Drive, nothing to manage. That simplicity is the whole point. Everything below is what it took to make it work.
Two people typing at once
For collaboration to work, I first had to solve the problem underneath it: what happens when two people edit the same sentence at the same time? Say the text is "The cat sat on the mat" and you and I both edit at once:
- You insert
"big"→"The big cat sat on the mat" - I delete the first word →
"cat sat on the mat"
If we apply your change then mine, your "big" disappears. The other way around, my delete gets ignored. Neither order is more correct than the other, and I can't just pick one and drop someone's edit. The editor needs to merge edits so everyone ends up with the same result, no matter what order they arrive in.
CRDTs, so no server has to pick a winner
The solution is a CRDT (Conflict-free Replicated Data Type). I used a library called Yjs. With a CRDT, merges are commutative and idempotent: the order edits merge in doesn't change the result, and applying the same edit twice has no extra effect. So there's no central server deciding who wins. Every client can merge on its own and still end up identical.
Yjs does this with an algorithm called YATA. Every character you type gets a unique ID (the client's ID plus a counter). When two people type in the same place, those IDs let every client settle on the same order independently, without checking with a server first.
Two things made it practical to ship:
- Binary updates. Typing sends a small binary diff, not the whole document. The server keeps a state vector (a summary of what it already has) so clients only exchange the parts that are missing.
- Presence in memory. Cursor positions and who's online change constantly and don't need to be saved, so they go over a separate awareness channel and stay in memory instead of the document.
A dumb relay, by design
Since the CRDT handles the merging, the server doesn't need to understand the document, it just passes data between clients. The browser talks to it two ways: HTTP for the simple things (create a doc, save the title), and a WebSocket that stays open for everything live, which carries every keystroke and cursor move.
From one server to many
With one server this was simple, one process holds every connection. Once I ran more than one server behind a load balancer, there was a problem: someone connected to server A couldn't see edits from someone on server B.
Redis Pub/Sub solves it. Every edit is published to a Redis channel, and each server listens and forwards it to its own connected clients. That means I can add more servers and they stay in sync. With this in place it held a p95 connection time of 94ms and 0% connection errors at 400 concurrent users.
Making sure it actually works
I wanted to know it was correct and that it could handle load, not just assume it.
- Fuzz testing for correctness. I wrote a harness that applies random concurrent edits in different orders and checks that every client still ends up with the same document. That's what catches the merge bugs that only appear with specific timing.
- k6 for load. A 7-minute ramp pushed it to 1,600+ live WebSocket sessions at 332 edits per second, with Postgres handling versioning and sessions, and the connection numbers above stayed steady throughout.
Key decisions
CRDTs (Yjs) over Operational Transform
OT needs a central server to reorder every edit. A CRDT merges correctly on its own, so the server can stay simple and it keeps working when I run more than one of them.
Redis Pub/Sub for the WebSocket fan-out
Once there's more than one server, someone on server A can't see edits from server B. Publishing every edit to Redis and letting each server forward it to its clients is what makes scaling out work.
PostgreSQL over SQLite
SQLite only allows one writer at a time, which doesn't work across multiple servers. Postgres handles concurrent writes and gave me a clean place to store document versions and sessions.
Cursors and presence kept in memory
Cursor positions and who's online change constantly and don't matter after you leave. Keeping them in memory instead of the saved document avoids storing data that doesn't need to persist.
Where I'd take it
- Auth on the WebSocket itself, sending a token when you connect and checking it before you can join a doc.
- Version history, built on the edit log I already store, so you could name snapshots and roll back to them.
- Offline mode using Yjs's IndexedDB support, so edits queue up locally and merge cleanly once you're back online.
- Exporting to Markdown, HTML, and PDF.