Local-First Workspace
Local-First Collaborative Workspace
LOCAL / FIRST
Two replicas. Concurrent edits. Deterministic convergence.
A plain-text editor whose synchronization engine is a custom operation-based CRDT. Each replica keeps its own identity, its own operation log, and its own IndexedDB database.
Live demo: two independent in-browser CRDT replicas; no server required.
React · TypeScript · Vite · Custom CRDT · IndexedDB
01 — Concurrent Editing
Concurrent Editing
Two people can insert and delete the same text without a shared clock they both trust. The editor has to keep every replica's history, deliver operations that arrive late, and still end on one structure.
The CRDT is custom on purpose. Using Yjs or Automerge would have handed the project's main problem to a library.
02 — Two Replicas
Two Replicas
The hosted lab opens two replica controllers in one browser. Each has its own client id and its own IndexedDB database. Operations move through an in-memory transport, not a socket.
Illustration
- Replica A
- Local operations
- In-browser transport
- Replica B
- Matching fingerprints
The row above is an illustration of that path. It is not a trace from the screenshot.

03 — Identity and Ordering
Identity and Ordering
An operation is an insert or a delete of one grapheme. There is no update operation, and there is no vector clock.
Operation id
clientId and a counter. The same id with the same payload is ignored. The same id with a different payload is a conflict.
Lamport clock
A plain Lamport clock, not a hybrid clock. Wall time is not an input.
Sibling order
Higher Lamport first, then client id, then counter.
Missing dependencies
An insert whose anchor has not arrived, or a delete whose target has not arrived, waits in a pending buffer.
Tombstones
A delete marks the node. The node stays in the structure. There is no tombstone garbage collection.
04 — IndexedDB
IndexedDB
Dexie stores the client clock, the operation log, the outbox, and local checkpoints. A local edit is written in one transaction before the in-memory replica changes.
- clientMeta
- documents
- operations
- outbox
- replicaSnapshots
- A checkpoint is written every 1,000 operations and is discarded if it does not validate.
- Lab databases are named apart from the workspace database, so a lab delivery is not recorded as a server write.
- Pausing one replica keeps its new operations local. The other replica does not move.

05 — Inspector
Inspector
Convergence is a structural check. The lab hashes a canonical snapshot with SHA-256 and shows a short prefix. Matching visible text is not enough.
Converged
Queues empty and fingerprints match.
Waiting
An operation is still pending or unresolved.
Partitioned
Delivery is paused, and the structures still match.
Diverged
Paused, with different structures or unfinished operations.
Error
The replica reported a failure.

06 — What Is Deployed
What Is Deployed
The Vercel page is the lab. The server-backed workspace is still in the repository, and it is a local-development path.
Live demo
Hosted lab
- Two in-browser replicas
- In-memory delivery, with a pause on each side
- Separate IndexedDB databases
- No WebSocket on this page
Not the hosted demo
Repository server
- Fastify, WebSocket, and SQLite
- A sequenced operation log and fan-out
- Used by the local workspace, not by the hosted lab
- Unauthenticated, and one database process
07 — Engineering Proof
Engineering Proof
208
Vitest cases
Declared in the repository. Not a run executed for this page.
21
Playwright cases
Chromium. Declared in the repository.
The CRDT package also includes property tests that replay the same operations in different orders and check the materialized text.
A single unpinned laptop note in the repository reports about 11.8 ms to apply 50,000 in-order operations, and about 59.1 ms when those operations arrive reordered. It is not a CI regression gate.
- The hosted demo is not cross-device multiplayer.
- Plain text only. No rich text, comments, or remote carets.
- Tombstones are not compacted.
- The local server has no authentication and no horizontal scaling.