Caret’s collaboration system lets multiple writers work on the same document simultaneously, with every keystroke reflected on every screen in real time. It is built on Y.js — a battle-tested CRDT library that guarantees conflict-free merging of concurrent edits without a central locking authority. The collaboration layer sits outside the API Gateway so that sync latency stays as low as possible: the browser connects directly to the collab service over a persistent WebSocket connection.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/arrozet/caret/llms.txt
Use this file to discover all available pages before exploring further.
Technology Stack
Y.js CRDTs
Conflict-free Replicated Data Types guarantee that concurrent edits from multiple users always converge to the same document, regardless of network ordering.
Tiptap Collaboration
@tiptap/extension-collaboration and @tiptap/y-tiptap bind the Y.js shared document (Y.Doc) directly to the Tiptap editor state, so all ProseMirror transactions propagate through the CRDT.y-websocket
Transports Y.js sync and awareness messages between clients and the collab service over a persistent WebSocket connection.
Supabase JWT Auth
The collab service validates the user’s Supabase JWT during the WebSocket handshake. Unauthenticated or expired connections are rejected before Y.js sync begins.
WebSocket Endpoint
The collab service exposes a single WebSocket endpoint per document. The frontend connects directly — not through the API Gateway.| Parameter | Description |
|---|---|
doc_id | UUID of the document being edited |
token | Supabase JWT from the authenticated session (session.access_token) |
Protocol Messages
The collab service implements two Y.js protocols over the WebSocket connection:| Message type | Protocol | Purpose |
|---|---|---|
0 | Y.js sync protocol | Exchange document state updates between clients and the server |
1 | Y.js awareness protocol | Broadcast ephemeral user presence data (cursor position, user name, color) |
Y.Doc state. The awareness protocol is lossy by design — brief connection drops may cause cursor positions to temporarily disappear, which is expected behavior.
Presence UI Components
Caret surfaces presence information through a set of coordinated UI components:CollaborationPresenceBar
A compact strip rendered in the editor status bar showing connection status (connected / connecting / disconnected) and avatar bubbles for all active collaborators. Displays up to 5 avatars; additional users are shown as a
+N overflow badge.LivePresenceIndicator
A pulsing dot indicator reflecting the current WebSocket connection status: emerald for connected, amber for connecting, and slate for disconnected.
RemoteCursor
Renders each remote collaborator’s cursor at their current document position, colored with their unique presence color.
CollaboratorsList
An expanded view of all active collaborators with names and their assigned presence colors.
useCollaborationSession and useCollaborationPresence hooks. Each user is assigned a deterministic color via deriveUserColor(user_id) so their cursor and avatar remain consistent across sessions.
Document Sharing and Roles
Collaboration access is controlled through a role-based permission model. Two sharing scopes exist:Workspace-level sharing
Invite a collaborator to the entire workspace via the Share dialog in the editor toolbar:Document-level sharing
Share a single document directly without granting workspace access:Workspace Roles
Workspace invites assign themember role by default. The full workspace role hierarchy is:
| Role | Permissions |
|---|---|
owner | Full control — manage members, rename, delete workspace |
admin | Manage members and workspace settings |
member | Create and edit documents in the workspace |
guest | Limited read-only access to the workspace |
Document Roles
Document-level sharing uses a separate role model:| Role | Permissions |
|---|---|
owner | Full control — edit, share, delete |
editor | Edit document content and invite collaborators |
commenter | Add comments; cannot modify document text |
viewer | Read-only access |
In-Memory Room State
Each document room is held in memory on the collab service for the duration of its active lifecycle.Why in-memory rooms?
Why in-memory rooms?
Y.js CRDT state is most efficient when held in a single in-memory
Y.Doc. The collab service keeps rooms alive in memory so that brief network disconnects (e.g., a user’s laptop going to sleep) do not cause the room to evict its state. When the user reconnects, they re-join the existing in-memory room and receive a sync step 1 message to catch up on any missed updates — without a round-trip to the database.Persistence
The collab service has two database tables for durable Y.js state:| Table | Contents |
|---|---|
document_collab_updates | Append-only log of Y.js binary update messages |
document_collab_snapshots | Periodic full-document snapshots for efficient startup |
DATABASE_URL is configured in the collab service environment.
Current limitation: When a collab room is started (either after a service restart or the first connection to a document), it begins from a fresh
Y.Doc rather than restoring the persisted Y.js state from document_collab_snapshots. This means that if the collab service restarts while a document is open, in-flight Y.js state that has not yet been reflected in the document content tables may be lost.The fix is to call CollabPersistenceService.loadDocument before sending the initial sync step 1 message. This is a known gap and will be addressed in a future release. As a mitigation, the document content is always persisted to the document table on every autosave, so the most recently saved version is always recoverable.Connection Lifecycle
JWT validation
The collab service validates the Supabase JWT from the
?token= query parameter during the WebSocket handshake. Invalid or expired tokens cause an immediate connection rejection.Room join
If a room for the document ID already exists in memory, the new client joins it. Otherwise, a fresh
Y.Doc and room are created.Sync step 1
The server sends a Y.js sync step 1 message containing its current document state vector. The client replies with any updates it has that the server is missing.
Awareness broadcast
The client sends its awareness state (cursor position, user name, color). The server broadcasts it to all other clients in the room.
Live collaboration
All subsequent document changes are encoded as Y.js binary updates and broadcast to every connected client in the room.
Development Harness
A dedicated route exists for manually testing collaboration behavior during development:CollabHarnessPage is rendered only in development mode (import.meta.env.DEV). It is excluded from production builds. Use it to simulate multi-user scenarios, inspect Y.js sync messages, and verify awareness propagation without needing two real browser sessions.
AI and Collaboration
When both collaboration and the AI assistant are active on the same document, accepted AI changes are written into the shared Y.Doc directly viareplace_collaboration_document_content, rather than through the local editor’s command API. This ensures all collaborators see the AI-proposed edit propagate through the CRDT like any other change — maintaining full Y.js consistency across the room.