Real-time collaborative document editing for doh_chat using Yjs CRDTs.
collab_docs_server.js)collab_docs.documents - Document metadatacollab_docs.doc_state - Yjs CRDT state (base64 encoded)collab_doc_owner - Full control (read, write, delete, share)collab_doc_editor - Read and write accesscollab_doc_viewer - Read-only accesscollab_doc_room_member - Room members can read/write room docscollab_docs_client.js)Three main patterns:
On server startup, a "Lobby Welcome" document is automatically created if it doesn't exist:
lobby-welcome-doclobbysystemAll authenticated users can read and edit the lobby document.
Documents can be attached to chat rooms via room_id:
Room Member Resolution:
['*'] wildcard (all authenticated users)chat.room_members tableAccess Check:
Doh.permit() against permission groupsDocument Listing:
The chat module loads collab_docs package, which includes both server and client modules.
When a user enters a room:
chat_client.js calls docToolbar.loadDocs()collab:doc:list with room_idcollab:doc:list{ room_id: string | null }{ docs: Array<Document> }collab:doc:create - Create new documentcollab:doc:update_meta - Update title/languagecollab:doc:delete - Delete documentcollab:doc:join - Join document for editingcollab:doc:leave - Leave documentcollab:doc:close - Close documentcollab:doc:sync - Yjs sync messagescollab:doc:awareness - Cursor/presence updatescollab:doc:update - Broadcast document updatescollab:doc:client_count - Active client count updates⚠️ Always use socket.emit('collab:doc:list', ...) for listing documents, not Doh.ajaxPromise().
Why: ajaxPromise() with a URL attempts to route through socket.io, which can trigger the REST POST handler for document creation instead of the GET list handler. Using socket.emit directly ensures the correct event handler is called.
// ✅ CORRECT
Doh.socket.emit('collab:doc:list', { room_id: this.room_id }, (response) => {
if (response?.docs) {
this.renderTiles(response.docs);
}
});
// ❌ WRONG - May trigger document creation instead of listing
const result = await Doh.ajaxPromise('/api/collab/docs', { room_id: this.room_id });
{
id: string, // UUID
title: string,
room_id: string | null, // Chat room ID or null for standalone
owner: string, // Username
editors: string[], // Array of usernames
viewers: string[], // Array of usernames
type: string, // 'plaintext' | 'richtext'
language: string, // Monaco language ID
created_at: number, // Timestamp
updated_at: number // Timestamp
}
{
id: string, // Document ID
state: string // Base64-encoded Yjs state
}