Best Practices

AI Rules for Realtime Collaboration

AI overwrites concurrent edits with last-write-wins. Rules for CRDTs, operational transforms, presence indicators, conflict resolution, cursor sharing, and optimistic local-first updates.

8 min read·March 15, 2025

Two users edit simultaneously, last save wins — one user work is silently overwritten

CRDTs with Yjs, presence cursors, delta sync, local-first editing, collaborative undo by author

AI Overwrites Concurrent Edits

AI generates collaborative features with: last-write-wins (two users edit the same document, the last save overwrites the other user changes completely), no presence awareness (users do not know who else is viewing or editing), no conflict resolution (simultaneous edits to the same field lose one user work), polling for updates (check for changes every 5 seconds instead of real-time push), and full-document replacement (save the entire document on every edit instead of sending the delta). Two users editing a document simultaneously is a recipe for lost work.

Modern realtime collaboration is: conflict-free (CRDTs or operational transforms merge concurrent edits without data loss), presence-aware (see who is online, where their cursor is, what they are selecting), delta-based (only changed operations are sent, not the full document), optimistic (local edits appear instantly, sync in the background), and undo-aware (undo reverses your operations without affecting collaborator edits). AI generates none of these.

These rules cover: CRDTs and operational transforms for conflict-free editing, presence indicators with shared cursors, delta-based synchronization, optimistic local-first updates, and collaborative undo/redo.

Rule 1: CRDTs or Operational Transforms for Conflict-Free Editing

The rule: 'Use CRDTs (Conflict-free Replicated Data Types) or OT (Operational Transform) to merge concurrent edits without conflicts. CRDTs: each edit is a mathematical operation that commutes and converges — regardless of the order operations are applied, all clients reach the same state. OT: operations are transformed against concurrent operations to preserve intent. Libraries: Yjs (CRDT, most popular for web), Automerge (CRDT, Rust-backed), ShareDB (OT, Node.js).'

For Yjs implementation: 'const ydoc = new Y.Doc(); const ytext = ydoc.getText("content"); const provider = new WebsocketProvider(wsUrl, roomId, ydoc). Yjs handles: concurrent text insertions (both users type at different positions — both insertions are preserved), concurrent deletions (both users delete different text — both deletions apply), and cross-deletion (user A deletes text that user B is editing — resolved deterministically). Bind Yjs to your editor: y-prosemirror for ProseMirror/Tiptap, y-codemirror for CodeMirror, y-monaco for Monaco.'

AI generates: on save, PUT the entire document content to the server. User A and User B both edit. User A saves (server has A version). User B saves (server has B version, A changes lost). With Yjs: both edits are operations that merge automatically. A types at line 5, B types at line 20 — both appear. A deletes line 10, B edits line 10 — the deletion wins (deterministic). No save button, no lost work, no conflicts.

  • CRDTs: mathematically guaranteed convergence — all clients reach the same state
  • Yjs: most popular web CRDT library, bindings for ProseMirror, CodeMirror, Monaco
  • Automerge: Rust-backed CRDT, efficient for large documents
  • OT (ShareDB): transform concurrent operations, used by Google Docs historically
  • No save button needed: changes sync continuously, all edits preserved
💡 No Save Button, No Lost Work

Yjs CRDT: User A types at line 5, User B types at line 20 — both insertions preserved automatically. No save button, no merge dialog, no conflicts. Mathematically guaranteed convergence: all clients reach the same state regardless of operation order.

Rule 2: Presence Indicators and Shared Cursors

The rule: 'Show who is currently viewing and editing the document. Presence data: { userId, name, color, cursor: { position, selection }, lastActive }. Display: colored avatar pills showing online collaborators, colored cursor indicators at each user position in the document, colored selection highlights showing what each user has selected, and a last-active timestamp for idle detection (dim the avatar after 5 minutes of inactivity).'

For Yjs presence: 'Yjs awareness protocol handles presence: const awareness = provider.awareness; awareness.setLocalState({ user: { name: "Alice", color: "#3b82f6" }, cursor: { anchor: 42, head: 42 } }); awareness.on("change", () => { const states = awareness.getStates(); renderCursors(states); }). Each client broadcasts its state (cursor, selection, user info). All other clients render it. Presence updates are lightweight (tiny JSON, not document data) and high-frequency (every cursor movement).'

AI generates: no presence indicators. Two users open the same document. Neither knows the other is there. Both edit the same paragraph. Both save. One user work is lost. With presence: Alice sees Bob cursor in paragraph 3, moves to paragraph 7 to avoid collision. Shared cursors prevent conflicts before they happen — social protocol supplements technical conflict resolution.

Rule 3: Delta-Based Synchronization

The rule: 'Send only the changed operations, not the full document. A keystroke produces one operation: { type: "insert", position: 42, content: "a" }. This operation: travels over the WebSocket (50 bytes), is applied by other clients, and is merged with concurrent operations. Sending the full 50KB document on every keystroke: 50KB x 5 keystrokes per second = 250KB/s per user. Sending deltas: 50 bytes x 5 keystrokes = 250 bytes/s. 1000x less bandwidth.'

For batching and compression: 'Batch rapid operations: collect keystrokes for 50ms, send as one batch operation. This reduces WebSocket messages from 5/second to 1/second while maintaining sub-100ms perceived latency. Compress the operation log: consecutive inserts at adjacent positions merge into a single insert operation. Yjs handles batching and compression internally — the developer interacts with the high-level API, the library optimizes the wire format.'

AI generates: onChange={() => saveDocument(entireContent)} — the full document sent on every keystroke. At 5 keystrokes per second on a 50KB document: 250KB/s upstream bandwidth per user. 10 concurrent users: 2.5MB/s. The server processes 50 full-document saves per second. With deltas: 10 users generate 2.5KB/s total. Same result, 1000x less bandwidth, 1000x less server load.

⚠️ 50KB vs 50 Bytes Per Keystroke

Sending the full 50KB document on every keystroke: 250KB/s per user, 2.5MB/s for 10 users. Delta operations: 50 bytes per keystroke, 250 bytes/s per user. 1000x less bandwidth, 1000x less server load. Same editing result.

Rule 4: Optimistic Local-First Updates

The rule: 'Apply edits locally immediately, then sync to the server. The user types: the character appears instantly (local CRDT update). The operation is sent to the server via WebSocket. Other clients receive and apply the operation. If the network is slow: the user sees their own edits immediately, other users see them with a slight delay (100-500ms). If the network drops: the user continues editing locally, changes sync when the connection returns. The editing experience is never blocked by network latency.'

For conflict merging: 'When the connection returns after offline editing, the CRDT merges local changes with remote changes automatically. Alice edited offline for 5 minutes (100 operations). Bob edited online during that time (80 operations). On reconnect: Yjs merges all 180 operations into a consistent document state. Both users see both sets of changes. No manual conflict resolution dialog, no merge conflicts, no lost work. The CRDT guarantees convergence.'

AI generates: await saveToServer(content) before showing the edit to the user. Network latency: 200ms delay on every keystroke. Network drop: editing is blocked entirely (the save fails, the UI shows an error). With local-first: the user is never blocked. Edits are instant. Sync is background. Network issues are invisible to the editing experience. The user types at full speed regardless of connection quality.

  • Local-first: edit appears instantly, syncs in background — never blocked by network
  • CRDT merges offline edits with remote edits automatically on reconnect
  • No manual conflict resolution: mathematical convergence guarantees consistency
  • 200ms network latency: invisible to the user (local edit is already displayed)
  • Network drops: user continues editing, changes sync when connection returns

Rule 5: Collaborative Undo/Redo

The rule: 'Undo must reverse only the current user operations, not other collaborators operations. If Alice types "hello" and Bob types "world", Alice pressing Ctrl+Z removes "hello" but preserves "world." This is undo by author, not undo by time. Yjs UndoManager: const undoManager = new Y.UndoManager(ytext, { trackedOrigins: new Set([ydoc.clientID]) }). The UndoManager tracks operations by origin (client ID) and reverses only operations from the local client.'

For undo scope: 'Define what constitutes one undoable action: a single keystroke is too granular (undo one character at a time is tedious), and a full editing session is too broad (undo everything since you opened the document). Group operations by: time gap (operations within 500ms are one group — typing a word), or explicit boundaries (pressing Enter, changing formatting, or moving the cursor to a different position starts a new group). Yjs UndoManager supports captureTimeout for time-based grouping.'

AI generates: an undo stack that reverses operations in chronological order regardless of author. Alice types, Bob types, Alice presses undo: Bob text is removed (it was most recent). Alice is confused, Bob is frustrated. Collaborative undo by author: Alice undo affects only Alice operations. Bob work is untouched. Each user has an independent undo history that coexists with the collaborative document.

ℹ️ Undo Your Work, Not Theirs

Chronological undo: Alice types, Bob types, Alice presses Ctrl+Z — Bob text disappears. Collaborative undo by author: Alice Ctrl+Z removes only Alice operations. Bob work is untouched. Each user has an independent undo history.

Complete Realtime Collaboration Rules Template

Consolidated rules for realtime collaboration.

  • CRDTs (Yjs, Automerge) or OT (ShareDB) for conflict-free concurrent editing
  • Presence: colored cursors, selection highlights, online avatars, idle detection
  • Delta sync: send operations (50 bytes), not full documents (50KB) — 1000x less bandwidth
  • Local-first: edits appear instantly, sync in background, never blocked by network
  • Offline resilience: continue editing offline, CRDT merges on reconnect automatically
  • Collaborative undo: reverse only your operations, not other users — undo by author
  • Undo grouping: 500ms time gap between groups, explicit boundaries on Enter/format change
  • Yjs ecosystem: y-prosemirror, y-codemirror, y-monaco — bindings for major editors