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

  1. Replica A
  2. Local operations
  3. In-browser transport
  4. Replica B
  5. Matching fingerprints

The row above is an illustration of that path. It is not a trace from the screenshot.

CRDT Collaboration Lab with two empty replicas, matching structure fingerprints, and a Replicas converged status.
The live lab after open. Both replicas are linked, both queues are empty, and the structure fingerprints match. Identifiers belong to that browser session.

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.
CRDT lab with Replica B paused, Replica A holding 41 pending operations, and a Diverged status with different structure fingerprints.
Replica B is paused. Replica A's edits stay pending, and the structure fingerprints diverge until delivery resumes.

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.

CRDT inspector showing a Diverged status, two different structure fingerprints, and an operation log of local inserts waiting for delivery.
The inspector during that pause. Each row is a real insert from the edit, with its Lamport clock, still pending delivery.

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.