real-time-collab
$
npx mdskill add BuilderIO/agent-native/real-time-collabMerge edits seamlessly across multiple users and agents.
- Enables simultaneous editing by humans and agents without conflicts.
- Depends on Yjs CRDT, TipTap, and SQL persistence tables.
- Executes surgical search-replace operations on document nodes.
- Delivers live cursor positions and synchronized document states.
SKILL.md
.github/skills/real-time-collabView on GitHub ↗
---
name: real-time-collab
description: >-
Multi-user collaborative editing with Yjs CRDT and live cursors. Use when
adding real-time collaborative editing to a template, debugging sync issues,
or understanding how the agent and humans edit documents simultaneously.
---
# Real-Time Collaboration
## Rule
Collaborative editing uses Yjs CRDT via TipTap. The agent and human users are equal participants — both edit the same Y.Doc and changes merge cleanly without conflicts.
## How It Works
- **`Y.Doc`** stores the document as a `Y.XmlFragment` (ProseMirror node tree)
- **TipTap's Collaboration extension** binds the editor to the Y.XmlFragment via `ySyncPlugin`
- **CollaborationCaret extension** renders remote users' cursors with names and colors
- **Polling** (every 2s) syncs Y.Doc updates and awareness state between clients and server
- **SQL `_collab_docs` table** persists Yjs state as base64-encoded binary (works across SQLite/Postgres)
## Agent + Human Editing
1. **Human edits** → TipTap → ySyncPlugin → Y.XmlFragment → `POST /_agent-native/collab/:docId/update`
2. **Agent edits** → `edit-document` action → server search-replace → Y.XmlFragment mutation → poll update → all clients
Both produce minimal Yjs operations that merge cleanly. Agent edits appear without destroying cursor position, selection, or undo history.
The `edit-document` action uses surgical search-and-replace on Y.XmlText nodes — more efficient than regenerating the entire document.
## Enabling Collaboration
### 1. Install packages
```bash
pnpm add @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/y-tiptap
```
### 2. Add collab server plugin
```ts
// server/plugins/collab.ts
import { createCollabPlugin } from "@agent-native/core/collab";
export default createCollabPlugin({
table: "documents",
contentColumn: "content",
idColumn: "id",
});
```
### 3. Use the client hook
```ts
import { useCollaborativeDoc } from "@agent-native/core/client";
const { ydoc, provider } = useCollaborativeDoc(documentId);
```
### 4. Add TipTap extensions
```ts
import { Collaboration } from "@tiptap/extension-collaboration";
import { CollaborationCaret } from "@tiptap/extension-collaboration-caret";
const editor = useEditor({
extensions: [
Collaboration.configure({ document: ydoc }),
CollaborationCaret.configure({
provider,
user: { name: session.email, color: "#6366f1" },
}),
],
});
```
### 5. Add to vite.config.ts optimizeDeps
```ts
optimizeDeps: {
include: [
"@tiptap/extension-collaboration",
"@tiptap/extension-collaboration-caret",
"@tiptap/y-tiptap",
],
}
```
## Collab Routes (auto-mounted)
| Route | Purpose |
| ----- | ------- |
| `GET /_agent-native/collab/:docId/state` | Fetch full Y.Doc state |
| `POST /_agent-native/collab/:docId/update` | Apply client Yjs update |
| `POST /_agent-native/collab/:docId/text` | Apply full text (diff-based) |
| `POST /_agent-native/collab/:docId/search-replace` | Surgical find/replace in Y.XmlFragment |
| `POST /_agent-native/collab/:docId/awareness` | Sync cursor/presence state |
| `GET /_agent-native/collab/:docId/users` | List active users |
## Common Pitfalls
- **Don't pass `content` as a TipTap prop** when Collaboration is enabled — Yjs owns the content. Set initial content via the Y.Doc instead.
- **Don't use `editor.setContent()`** for agent edits — it bypasses Yjs and causes conflicts. Use the `search-replace` route or `edit-document` action.
- **Add packages to `optimizeDeps`** — Vite won't pre-bundle Yjs packages correctly otherwise, causing runtime errors in dev.
- **One `Y.Doc` per document** — Don't create multiple Y.Doc instances for the same document ID. Use the `useCollaborativeDoc` hook which caches by ID.
## Related Skills
- `real-time-sync` — Polling infrastructure that delivers Y.Doc updates
- `storing-data` — The `_collab_docs` table where Yjs state is persisted
- `self-modifying-code` — Agent edits to collaborative documents go through `edit-document`, not raw SQL
More from BuilderIO/agent-native