Skip to main content

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.

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.

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.
# Local development
ws://localhost:3003/document/{doc_id}?token={jwt}

# Production
wss://ws.caret.page/document/{doc_id}?token={jwt}
ParameterDescription
doc_idUUID of the document being edited
tokenSupabase JWT from the authenticated session (session.access_token)
Collaboration WebSockets must not be routed through the API Gateway. The frontend is expected to connect directly to the collab service. Proxying Y.js sync through the gateway would add latency and break the in-memory room lifecycle.

Protocol Messages

The collab service implements two Y.js protocols over the WebSocket connection:
Message typeProtocolPurpose
0Y.js sync protocolExchange document state updates between clients and the server
1Y.js awareness protocolBroadcast ephemeral user presence data (cursor position, user name, color)
The sync protocol ensures all clients converge to the same 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.
Presence state is managed by the 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:
POST /api/v1/workspaces/{workspace_id}/invite
Content-Type: application/json

{ "email": "collaborator@example.com" }
Workspace members inherit access to all documents in the workspace.

Document-level sharing

Share a single document directly without granting workspace access:
POST /api/v1/documents/{document_id}/invite
Content-Type: application/json

{ "email": "collaborator@example.com" }

Workspace Roles

Workspace invites assign the member role by default. The full workspace role hierarchy is:
RolePermissions
ownerFull control — manage members, rename, delete workspace
adminManage members and workspace settings
memberCreate and edit documents in the workspace
guestLimited read-only access to the workspace

Document Roles

Document-level sharing uses a separate role model:
RolePermissions
ownerFull control — edit, share, delete
editorEdit document content and invite collaborators
commenterAdd comments; cannot modify document text
viewerRead-only access

In-Memory Room State

Each document room is held in memory on the collab service for the duration of its active lifecycle.
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:
TableContents
document_collab_updatesAppend-only log of Y.js binary update messages
document_collab_snapshotsPeriodic full-document snapshots for efficient startup
The write path for both tables is active when 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

1

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.
2

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.
3

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.
4

Awareness broadcast

The client sends its awareness state (cursor position, user name, color). The server broadcasts it to all other clients in the room.
5

Live collaboration

All subsequent document changes are encoded as Y.js binary updates and broadcast to every connected client in the room.
6

Disconnect

On disconnect, the server removes the user’s awareness state and notifies remaining clients. If the room becomes empty, it remains in memory briefly before being eligible for eviction.

Development Harness

A dedicated route exists for manually testing collaboration behavior during development:
/debug/collab-harness
The 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.
The /debug/collab-harness route is development-only. It is not rendered in production.

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 via replace_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.

Build docs developers (and LLMs) love