A terminal-based collaborative text editor where multiple nodes type into the same document over direct TCP. There is no server so every node talks directly to every other node. If two people type at the same time, both edits land on all nodes and the text ends up identical everywhere.
graph LR
A[node A] <-->|TCP| B[node B]
B <-->|TCP| C[node C]
A <-->|TCP| C
The CRDT that makes this work is RGA (Replicated Growable Array). Each character gets a unique ID when inserted, so concurrent inserts at the same position always resolve in the same order on every node. All CRDT types are written from scratch with no external CRDT libraries.
Crates: crdt-lib has the CRDT types and document model, crdt-transport has the networking. crdt-client is the TUI that uses both.
graph TD
crdt-client --> crdt-transport
crdt-client --> crdt-lib
crdt-transport --> crdt-lib
Dev team: Tri Tac Le, Shakti Om Sharma, Yazan Samer Zarka
- RGA - the text document. Characters have stable IDs. Concurrent inserts converge to the same order using Lamport tiebreaking. Deletes are tombstones.
- Lamport clock - ticks on every local op, jumps to max on receive.
- Version vector - tracks which ops each replica has seen. Used to compute exactly what a peer is missing.
- LWW register and LWW map - last-write-wins using Lamport timestamps.
- OR-Set - add-wins set. Concurrent add and remove: add wins.
- Op log - append-only log per document, used for delta sync and snapshot restore.
- TCP transport - length-prefixed bincode frames over tokio TcpStream. Local ops fan out to all peers immediately.
- Causal buffer - ops that arrive out of order wait until the gap fills before being applied.
- Anti entropy - every 500ms each node sends its version vector to one peer. The peer sends back anything the node is missing. Catches any broadcast that got dropped.
sequenceDiagram
participant A as node A
participant B as node B
A->>B: Hello(version_vec)
B->>A: StateSync(missing ops)
- Atomic snapshot - op log saved to disk via write-to-tmp then rename. Node restarts replay the log and rejoin without a full resync.
- mDNS discovery - nodes advertise themselves on the local network and dial peers they find automatically. No
--peerflags needed on a LAN. Use--no-mdnsto turn it off. - Multiple documents - each session can hold multiple named documents. A document picker lets you switch between them.
- TUI - ratatui editor with a status line showing replica ID, peer count, and document length.
- We don't have WebSocket transport. Adding a WebSocket gateway would let browser clients connect to a node and edit from a web page without installing anything. The node would accept both raw TCP (from other nodes) and WebSocket (from browsers) on separate ports.
- No TLS. All traffic is plaintext. Adding TLS to the TCP connections would stop passive sniffing and give basic peer authentication via certificates.
- No NAT traversal. Peers behind different routers cannot find each other even with mDNS, since mDNS is link-local only. A STUN/TURN relay or a shared server would fix this.
| Crate | Used for |
|---|---|
tokio |
Async runtime, TCP, timers, mutexes |
serde |
Serialization derives |
bincode |
Binary wire format |
uuid |
UUIDv7 replica identifiers |
ratatui |
Terminal UI |
crossterm |
Terminal input and output |
clap |
CLI argument parsing |
tracing + tracing-subscriber |
Structured logging |
thiserror |
Typed error enums |
anyhow |
Error propagation in app code |
bytes + tokio-util |
Length-delimited codec |
futures |
Stream combinators in the message router |
mdns-sd |
mDNS peer discovery |
All CRDT types are written from scratch. The banned crate check at scripts/check-banned-crates.sh runs in CI and fails the build if any banned package appears in Cargo.toml or Cargo.lock.
Requires Rust 1.85+.
git clone https://github.com/yazanzarka1/crdt-collab
cd crdt-collab
cargo build --release -p clientBinary lands at target/release/crdt-client.
Nodes on the same machine or LAN find each other via mDNS automatically:
# terminal 1
./target/release/crdt-client --listen 0.0.0.0:4001 --journal state1
# terminal 2
./target/release/crdt-client --listen 0.0.0.0:4002 --journal state2
# terminal 3
./target/release/crdt-client --listen 0.0.0.0:4003 --journal state3They connect within a few seconds. Type in any window and the text appears in all of them.
To connect two machines on the same network, find each machine's IP (ip addr) and use --peer:
# machine A
./target/release/crdt-client --listen 0.0.0.0:4000 --peer <machine-B-ip>:4000
# machine B
./target/release/crdt-client --listen 0.0.0.0:4000 --peer <machine-A-ip>:4000mDNS auto-discovery works on LANs. On networks with client isolation like eduroam, mDNS is blocked - use --peer with the explicit IP instead.
To disable mDNS: --no-mdns.
Or use the tmux demo (requires tmux):
just demoQuit with Ctrl-C. Restart with the same --journal directory and the node restores from disk and rejoins.
just # fmt + clippy + all tests
just ci # fmt + clippy + tests + doc + preflight check
cargo test # unit tests only
cargo test -p transport -- --ignored three_peers_converge # slow convergence testThe convergence test spins up three nodes, fires concurrent inserts from all three, and checks the final text is identical on all nodes.
yazanzarka1.github.io/crdt-collab
Or generate locally:
cargo doc --no-deps --open