Real-time collaboration
Multiple editors in the same doc see each other's cursors and edits live. Built on Yjs CRDT, broadcast through Supabase Realtime. No conflicts, no save-the-merge dialog.
When more than one person is editing a document, you see each other in real time: colored cursors with name labels, selections that move as the other person highlights, and a small stack of presence avatars in the toolbar. Edits merge automatically — there’s no save-the-merge dialog, no overwriting each other’s work.
How it works
Each document has a Yjs CRDT session — Y.Doc for the content, Awareness for cursor positions and selections. Both ride over a per-document Supabase Realtime channel:
- Doc updates are encoded as Yjs binary deltas, base64-ed for the channel, broadcast to every other client
- Awareness updates carry your cursor position, your user info (name, color, avatar), and your selection
- A new client joining the doc sends a sync request; one of the existing peers responds with the full Y.Doc state, then incremental updates flow normally
Yjs guarantees that all clients converge on the same content regardless of the order updates arrive — this is the formal CRDT property. Two people typing into the same paragraph at the same time both land, with a deterministic merge.
Who counts as a collaborator
Anyone with Write or Manage access on the doc — added via Invite by email or folder sharing cascade. Public link viewers don’t collaborate because:
- They open the doc in read-only mode (no editor instance)
- They don’t authenticate, so there’s no identity to label
The presence stack only shows signed-in users with Write access who currently have the doc open in Edit mode.
Cursors
Each peer’s cursor renders as a vertical color bar at their selection start with a small name tag above it. The Tiptap CollaborationCaret extension handles the rendering.
| Aspect | Behavior |
|---|---|
| Color | Stable per user — derived from a hash of their user ID so the same person gets the same color across sessions |
| Name | Their display name from profiles (or “Anonymous” if blank) |
| Selection | When someone has text selected, the same color tints the selection background lightly on your screen |
| Idle | Cursors stop updating when the other person hasn’t moved in a while, but stay visible at their last position |
| Departure | When someone leaves (closes the tab, navigates away), their cursor disappears within a couple of seconds |
The presence stack
In the doc toolbar (top-right area), a small overlapping stack of circular avatars shows who else is currently in the doc:
| Element | Details |
|---|---|
| Avatar circle | The peer’s profile picture (or initials on a colored background matching their cursor) |
| Up to 4 visible | Beyond that, a “+N” overflow chip appears |
| Hover | Tooltip shows the person’s display name |
| Deduplication | The same person in two tabs counts once |
| You | Your own presence isn’t in the stack — only others |
The stack updates instantly as people join and leave.
Saving in a collab session
When multiple people are editing, every save flushes the full Y.Doc state to documents.yjs_state alongside the content text. This means:
- New clients joining later can resume from the latest persisted state if no live peer is around to sync from
- The OCC conflict path is bypassed for CRDT-backed docs — server divergence gets merged into your Y.Doc rather than surfacing as a conflict (see Autosave)
Offline collaboration
Yjs is a local-first CRDT — if you lose your network mid-edit:
- The editor keeps working; your edits are recorded in your local Y.Doc
- Awareness pauses (peers see your cursor freeze at its last position)
- When you reconnect, your accumulated updates rebroadcast and merge with whatever the others wrote while you were offline
- Everyone converges on the same final state
This is the killer property — no merge UI, no conflict resolution, no “you have lost your changes” prompt.
Limits and edge cases
| Aspect | Detail |
|---|---|
| Concurrent editors per doc | Practical: tens of editors. Yjs scales well; the bottleneck is usually screen-share of who’s where, not the CRDT. |
| Latency | Edits typically arrive within ~150 ms on a healthy connection |
| Doc size | Yjs encodes incrementally; the persisted yjs_state grows with edit history. Periodic compaction is on the roadmap. |
| Comments | Have their own timeline and don’t live in the Y.Doc; they sync via Postgres realtime separately |
| Image uploads | Don’t go through Yjs — they upload to R2, then the image node (with its URL) is inserted into the doc, which then syncs via Yjs |
What “Write access” actually grants
Real-time collaboration is unlocked by Write or Manage on the document — see Permissions for the full level grid. Read-only collaborators can comment but cannot type into the body.