Skip to content

Conversation

@jkomoros
Copy link
Contributor

@jkomoros jkomoros commented Dec 13, 2025

⚠️ PROTOTYPE - NOT FOR LANDING

This is an exploration/proof-of-concept, not a proposal to merge. The goal was to understand how real-time collaborative editing could integrate with the existing architecture.

What This Explores

Adds CRDT-based real-time collaborative editing to ct-code-editor and a new ct-richtext-editor component using Yjs.

Key Components

Server (toolshed):

  • Yjs WebSocket relay server at /api/collab/:roomId
  • Cryptographic auth using existing identity system (Signer/VerifierIdentity)
  • Generic design - no component-specific knowledge

Client (ui):

  • ct-code-editor - added collaborative prop with y-codemirror.next
  • ct-richtext-editor - new TipTap-based component with Yjs collaboration
  • Auth token signing using cell.runtime.storageManager.as

Design Decisions Explored

  1. Yjs over Automerge - Better ecosystem for text editing (y-codemirror, y-prosemirror)
  2. Room ID = Cell entity ID - Natural mapping, debuggable
  3. Auth extends existing identity - Components sign tokens with Cell's Signer
  4. Presence = consent model - Collaborative mode shows cursors; visual indicator serves as consent signal

What's Working

  • Real-time bidirectional sync between browser tabs
  • Cursor presence with user names/colors
  • Initial content sync from Cell value
  • Reconnection handling (y-websocket built-in)

Open Questions / Not Addressed

  • Persistence strategy (currently ephemeral - room state lost when all clients disconnect)
  • Integration with Cell reactivity (sync back to Cell on changes?)
  • Production scaling considerations
  • Whether this is even the right approach for Common Tools

Files Changed

See docs/SESSION-crdt-collaborative-editing.md for detailed implementation notes and decisions.


🤖 Generated with Claude Code


Summary by cubic

Adds Yjs-based real-time collaboration to code and rich text editors, backed by a simple WebSocket relay in toolshed with optional identity-based auth. Enables multi-user live sync, cursors, and initial content sync from Cells without touching Chronicle.

  • New Features

    • Server: Yjs WebSocket relay at /api/collab/:roomId with CORS and stats; verifies signed auth tokens via VerifierIdentity.
    • ct-code-editor: collaborative mode using y-codemirror.next; props for roomId, collabUrl, userName, userColor.
    • ct-richtext-editor: new TipTap editor with Collaboration and CollaborationCursor for live presence.
    • Client auth: helper to sign tokens using the Cell’s Signer and pass them to the server.
    • Working UX: real-time sync across tabs, cursor presence, initial content sync from Cell, reconnection handling.
  • Refactors

    • Server simplified to a generic Yjs relay (no editor-specific logic); clients handle document initialization from Cell values.

Written for commit a1d3342. Summary will update automatically on new commits.

jkomoros and others added 10 commits December 12, 2025 16:16
Planning document for adding real-time collaborative editing to
ct-code-editor and a new ct-richtext-editor component using Yjs.

Key decisions:
- Use Yjs (not Automerge) - complementary to existing Cell system
- Component-level integration (not framework-wide)
- Cell stores plain text/HTML (no CRDT persistence)
- Zero changes to Chronicle, Cell, or transaction system

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Decisions made:
- Embed y-websocket in toolshed (not separate process)
- Use direct Cell entity ID for room IDs
- Reuse toolshed auth for WebSocket
- Server initializes Y.Doc from Cell on room creation

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Phase 1 & 2 of CRDT collaborative editing implementation:

Server-side (toolshed):
- Add Yjs WebSocket server at /api/collab/:roomId
- Add yjs, y-protocols, lib0 dependencies
- Endpoints: GET /api/collab/stats, WebSocket upgrade, POST init

Client-side (ct-code-editor):
- Add collaborative, roomId, collabUrl, userName, userColor props
- Integrate y-codemirror.next for real-time sync
- Add yjs, y-codemirror.next, y-websocket, lib0 dependencies
- Update JSX type definitions for new props

Uses Yjs (not Automerge) as a thin sync layer that complements
existing Cell system. Cell handles persistence, Yjs handles
real-time sync.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Add a new TipTap-based rich text editor component with real-time
collaborative editing support via Yjs:

- ct-richtext-editor: TipTap editor with StarterKit extensions
- Collaboration extension for real-time sync via y-prosemirror
- CollaborationCursor extension for cursor presence
- Full rich text support (headings, bold, italic, lists, code, etc.)
- Same collaborative API as ct-code-editor (collaborative, roomId, etc.)

New dependencies:
- @tiptap/core, @tiptap/pm, @tiptap/starter-kit
- @tiptap/extension-collaboration, @tiptap/extension-collaboration-cursor
- y-prosemirror

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
- Add _initializeRoomOnServer method to ct-code-editor and ct-richtext-editor
- Add security documentation in collab.index.ts (auth TODO for production)
- Add bundle optimization notes in component imports
- Update session doc with Phase 4 progress

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Server changes:
- Rename initializeRoomContent -> initializeTextField (generic field name)
- Rename getRoomContent -> getTextField (generic field name)
- Remove component-specific knowledge (codemirror/prosemirror types)
- Server is now a pure Yjs sync relay

Client changes:
- Remove _initializeRoomOnServer method (not needed)
- Add sync-based initialization (clients init from Cell on first sync)
- All component-specific logic now lives in the components

This keeps the server simple and idiomatic while putting complexity
where it belongs - in the client components.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
- Mark all polish items as complete
- Update status to ready for review
- Document client-side initialization approach
- Note server simplification

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Implements identity-based authentication for collaborative WebSocket connections:

Server-side (toolshed):
- Add collab.auth.ts with CollabAuthToken verification using VerifierIdentity
- Update handler to extract and verify auth tokens from URL params
- Pass authenticated userIdentity to YjsServer for logging
- Tokens are signed payloads with roomId, timestamp, userDid

Client-side (ui):
- Add collab-auth.ts helper to sign auth tokens using Cell's Signer
- Update ct-code-editor and ct-richtext-editor to sign and pass tokens
- Auth tokens appended as URL query params (?payload=&sig=&did=)

Auth flow:
1. Component gets Signer from cell.runtime.storageManager.as
2. Signs payload with roomId, timestamp, userDid
3. Passes token in WebSocket URL
4. Server verifies signature using VerifierIdentity.fromDid()
5. Anonymous connections still allowed for now (TODO: enforce when ready)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Adds documentation explaining that collaborative mode implies full presence
sharing (cursor visibility = consent):

- Session log: Added "Presence/Privacy Model" decision section
- Component JSDoc: Added PRIVACY NOTE to collaborative prop documentation
  explaining that cursor position and userName are visible to all room members

The distinctive colored cursors serve as the visual consent indicator -
users who see collaborative cursors know they are in a shared session.
This follows the Google Docs model.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants