Local-first software / Personal project
SyncLabKeep editing. Reconnect. Converge.
An offline-capable collaborative editor with a TypeScript CRDT, an encrypted persistent outbox, and an ordered ASP.NET Core WebSocket relay.
01 / The problem
Why I built it
Real-time editors cannot assume an always-on, ordered connection. A child insert may arrive before its parent, a delete before its target, and a reconnect may replay an operation the peer already applied.
02 / My contribution
What I engineered
I built the collaborative editor and its operation-based CRDT integration, offline replay path, and encrypted transport. I repaired out-of-order and duplicate handling, then added checks for convergence and relay ordering.
03 / Interactive demonstration
See the behavior, not just the claim.
Two independent CRDT replicas. Disconnect delivery, make edits, and replay the pending operations.
Replica A
Build together.
Replica B
Build together.
Disconnect delivery, append a different edit in each replica, then reconnect. Documents match.
Runs the repository’s RGA-style CRDT. Reconnect delivers pending operations out of order and twice; it does not copy one pane over the other. No server, encryption, or persistence in this isolated demo. Limited to 600 edit operations; reset to start again.
04 / Architecture
Follow the data.
- 01React editor
- 02RGA-style CRDT
- 03AES-GCM + persisted outbox
- 04Ordered WebSocket relay
- 05Peer decrypts + applies
Operation identity instead of arrival-order assumptions
Each operation carries a unique ID. Applied and pending IDs prevent duplicate delivery from duplicating edits. Missing dependencies are queued and replayed when their prerequisite arrives.
An offline outbox instead of disabling editing
Local operations apply optimistically while the encrypted outbox retains unconfirmed work for reconnect. The full app can replay events after refresh; the embedded demo isolates the CRDT mechanism.
Ciphertext relay instead of server-readable documents
The room key is carried in the URL fragment, which is not sent in HTTP or WebSocket requests. The relay handles ciphertext and ordering, while clients decrypt. Room IDs, timing, and sender metadata are still visible to the relay.
Explicit ordering on the relay
Bounded fragmented-message assembly, per-socket send serialization, per-room broadcast ordering, and monotonic sequence numbers address failures that a simple broadcast loop can miss.
05 / Verification
Results you can inspect.
The Python stress model uses 200 trials, 250 operations per trial, five replicas, 20% temporary loss, and 10% duplicate delivery. A shuffled anti-entropy pass then delivers the operations and checks identical materialized state. TypeScript regression tests separately exercise implementation edge cases.
Measurement context
250,000 means 200 × 250 × 5 replica-operation observations. It is not 250,000 independent trials or a benchmark of network latency. The stress model and the TypeScript implementation are complementary checks, not the same executable.
The browser demonstration below uses the repository’s TypeScript CRDT with in-memory simulated delivery. It does not run the ASP.NET relay, encryption, or persistent outbox, and it is not a full-stack integration test.
- Architecture, privacy & test scope
- CRDT implementation
- TypeScript regression cases
- Deterministic stress model
- WebSocket integration harness
Evidence pinned to repository revision 0b657aff3f07. Recorded results are from the linked project evidence, not new benchmark runs for this website.
06 / Engineering boundaries
What I would harden next
The relay keeps room history in memory, membership is unauthenticated, and the client retains only its latest 5,000 events. Production use would need durable room storage, authorization, key rotation, and compaction.
07 / Next iteration
Where it goes from here
A useful next stage is authenticated membership and durable room history, followed by a small collaboration pilot. The priority is preserving the offline and convergence properties as those boundaries change.