Rich text with Yjs
Character-level collaborative editing is the one place where last-write-wins or guarded patches are clearly the wrong tool: two people typing in the same paragraph should both win. For that, datadata embeds Yjs — a mature CRDT implementation — rather than reinventing text merging.
The hybrid model
Section titled “The hybrid model”A document’s structured data is JSON, changed by guarded patches. Where a field needs collaborative text, it holds a reference to an embedded Yjs document:
{ "title": "Roadmap", "body": { "$yjs": "yjs_a1b2c3" } }The Yjs document is created alongside its parent and travels with it: subscribing to the parent delivers the Yjs state too, and updates flow as binary Yjs deltas next to JSON patches in the same wire protocol events.
Clients work with real Y.Doc instances, so the whole Yjs editor ecosystem
(ProseMirror/Tiptap bindings, etc.) applies directly. Local Yjs edits are
batched and synced automatically.
Two merge semantics, on purpose
Section titled “Two merge semantics, on purpose”| Structured data | Text | |
|---|---|---|
| Format | JSON + RFC 6902 patches | Yjs binary updates |
| Concurrency | Server-ordered, guards detect conflicts | CRDT, always converges |
| Conflict surface | Explicit — rejected guards, staged conflict previews | None — merging is automatic |
This split is a deliberate design decision: structure benefits from explicit, reviewable conflicts; prose benefits from silent convergence.
The boundary extends to validation: the schema’s jurisdiction deliberately ends where Yjs begins. Validation governs the JSON structure (including that a field holds a Yjs reference); constraints on the collaborative text content — length, structure, formatting rules — are intentionally left to the application and its editor.