Skip to content

Document editor architecture

Tiptap renders the editor. Yjs merges edits. One Cloudflare Durable Object per document orders writes. PostgreSQL stores Yjs state and validated Tiptap JSON together in one transaction.

flowchart LR
  Editor["Tiptap + Yjs"] <-->|"y-partyserver/provider"| Room["Document DO"]
  Actions["AI edits + history restore"] --> Room
  Room -->|"Domain RPC"| DB["PostgreSQL"]
  DB --> Readers["Knowledge + shared pages"]

The DO runs inside the Web Worker. Its SQLite journal retains pending updates until PostgreSQL commits them. Browser document persistence uses only y-indexeddb; there is no separate JSON draft or localStorage document cache.

  • Human and AI creation share createDocumentTx(). Existing documents load stored Yjs state; there is no JSON autosave or legacy fallback.
  • Validated edits sync immediately. Native onSave debounces PostgreSQL writes (2 s idle, 10 s maximum); alarms retry failures. Only a PG checkpoint receipt confirms saving.
  • New private-image references commit before broadcast. AI reads, edits, restores, and sharing checkpoint pending changes first; sharing stays inside the DO gate.
  • AI targets blocks by node ID and content fingerprint. Changed targets are rejected; edits preserve the existing CRDT. Action receipts commit with edits so retries cannot apply them twice.
  • Transactions enforce ownership, schema, size, and media rules. Restore loads an owned history snapshot. Writes cannot recreate deleted documents.
  • The title is Y.Text; the body comes from Yjs. TanStack DB holds metadata for the editor, lists, and chat. Public reads use committed JSON.

Under web-tanstack/src/:

Responsibility Entry point
Schema lib/editor/document-schema.ts
Browser connection and saved status components/editor/useDocumentCollaboration.ts
DO ordering and synchronization server/document-collaboration.ts
Persistence and history server/document-write.server.ts
Conversion and AI block edits server/document-content.server.ts

Migration tracking: issue #179.