DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Design Real-Time Collaboration with Claude Code: CRDT (Yjs), OT, Conflict Resolution

Introduction

Google Docs-style co-editing — multiple users editing simultaneously without conflicts. Let Claude Code design Operational Transformation (OT) and CRDT patterns.

CLAUDE.md Rules

## Real-Time Collab Rules
- Simple text: OT (Operational Transformation)
- Distributed + offline: CRDT (use Yjs)
- Cursor sharing: Yjs Awareness
- Persistence: 10-second debounce to DB
- State cache: Redis binary (TTL: 1h)
Enter fullscreen mode Exit fullscreen mode

Generated Implementation

// src/collab/documentRoom.ts
import * as Y from 'yjs';
import { Awareness } from 'y-protocols/awareness';

class DocumentRoom {
  private ydoc: Y.Doc;
  private awareness: Awareness;
  private saveDebounce: NodeJS.Timeout | null = null;

  constructor(private documentId: string, initialState?: Uint8Array) {
    this.ydoc = new Y.Doc();
    this.awareness = new Awareness(this.ydoc);
    if (initialState) Y.applyUpdate(this.ydoc, initialState);

    this.ydoc.on('update', (update: Uint8Array) => {
      this.broadcastUpdate(update);
      this.scheduleSave();
    });
  }

  applyClientUpdate(update: Uint8Array): void {
    Y.applyUpdate(this.ydoc, update); // CRDT auto-resolves conflicts
  }

  private scheduleSave(): void {
    if (this.saveDebounce) clearTimeout(this.saveDebounce);
    this.saveDebounce = setTimeout(() => this.persistDocument(), 10_000);
  }

  private async persistDocument(): Promise<void> {
    const state = Y.encodeStateAsUpdate(this.ydoc);
    await prisma.document.update({
      where: { id: this.documentId },
      data: { yjsState: Buffer.from(state), lastSavedAt: new Date() },
    });
    await redis.set(`doc:state:${this.documentId}`, Buffer.from(state), { EX: 3600 });
  }
}
Enter fullscreen mode Exit fullscreen mode
// Frontend: React + Yjs
export function CollaborativeEditor({ documentId }: { documentId: string }) {
  useEffect(() => {
    const ydoc = new Y.Doc();
    const provider = new WebsocketProvider(WS_URL, documentId, ydoc);
    const quill = new Quill(editorRef.current!, { theme: 'snow' });
    const binding = new QuillBinding(ydoc.getText('content'), quill, provider.awareness);

    provider.awareness.setLocalStateField('user', {
      name: currentUser.name,
      color: '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16),
    });

    return () => { binding.destroy(); provider.destroy(); };
  }, [documentId]);
}
Enter fullscreen mode Exit fullscreen mode

Summary

  1. CRDT (Yjs): vector clock-based conflict auto-resolution
  2. Awareness: low-latency cursor/selection sharing
  3. 10-second debounce: avoid writing every keystroke to DB
  4. Redis binary cache: fast document state recovery on connection

Review with **Code Review Pack (¥980)* at prompt-works.jp*

myouga (@myougatheaxo) — Axolotl VTuber.

Top comments (0)