Real-time chat, collaboration, and AI services suite for Doh.js applications.
doh_chat is a full-featured communication platform built on Doh.js. It provides room-based chat with direct messages, peer-to-peer file sharing, real-time collaborative document editing (code and rich text), multi-provider AI integration, issue tracking, and embeddable chat widgets. All modules share a common Socket.IO transport layer and permission system.
┌─────────────────┐
│ chat │ Core chat system
│ rooms, DMs, │ (server + browser)
│ presence, bots │
└────────┬─────────┘
│ loads
┌───────────┬────────────┬────────┼──────────┬────────────┬────────────┐
│ │ │ │ │ │ │
┌──┴────┐ ┌───┴───┐ ┌──────┴──┐ ┌──┴──────┐ ┌──┴──────┐ ┌──┴──────┐ ┌──┴──────────┐
│chat_ │ │chat_ │ │chat_ │ │chat_ │ │chat_ │ │chat_ │ │ ai_services │
│files │ │url_ │ │push_ │ │web_push │ │calls │ │onboarding│ │ AI gateway │
│WebRTC │ │utils │ │server │ │(VAPID/ │ │WebRTC │ │spotlight │ │ (6 providers│
│file │ │ │ │(APNs/ │ │Web Push)│ │activities│ │tour │ │ + token │
│xfer │ │ │ │iOS push)│ │ │ │ │ │ │ │ tracking) │
└───────┘ └───────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └──────┬──────┘
│
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────┴──────┐
│ collab_docs │ │ collab_ │ │ collab_ │ │ ai_keys_ │
│ Monaco code │ │ richtext │ │ tracker │ │ admin │
│ editor + Yjs │ │ TinyMCE+Yjs │ │ task list+Yjs│ │ key mgmt UI │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └─────────────┘
│ │ │
└────────┬───────┴────────────────┘
┌──────┴──────┐
│collab_shared│
│ Yjs socket │
│ provider │
└─────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ chat_proxy │ │ dohtop │ │ games │ │chat_io_ │ │ chat_ │
│ URL proxy │ │ floating │ │ in-room │ │devices │ │ extensions │
│ for embeds │ │ window sys │ │ multiplayer │ │ mic/cam/ │ │ Chrome MV3 │
│ (htmlpog) │ │ tab groups │ │ game engine │ │ speaker mgmt│ │ + Safari │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ chat_embed │ │ chat_ │ │ issues │ │ user_avatar │
│ iframe chat │ │ messenger │ │ bug capture │ │ DiceBear │
│ for embeds │ │ floating bar│ │ + tracking │ │ avatars │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ dohpro_ │ │ dohpro_ │
│ admin │ │ sales │
│ tier/credit │ │ /pro pricing│
│ management │ │ /what-is- │
│ /admin/ │ │ dohchat │
│ dohpro │ │ pages │
└─────────────┘ └─────────────┘
chat.doh.js
├── chat_url_utils (shared)
├── ai_services (nodejs, async)
├── chat_server (nodejs) ← routes, socket events, permissions, DB
├── chat_files_server (nodejs)
├── chat_push_server (nodejs) ← APNs push (iOS/macOS)
├── chat_web_push_server (nodejs) ← VAPID Web Push (browsers)
├── chat_calls_server (nodejs) ← Activities (WebRTC signaling)
├── chat_proxy_server (nodejs) ← URL proxy for embeds (htmlpog)
├── games (shared, async)
├── collab_docs (shared, async)
├── collab_richtext (shared, async)
├── collab_tracker (shared, async) ← collaborative task list (Yjs CRDT)
├── dohtop (await) ← floating window system
├── user_avatar (browser)
├── chat_client (browser, await) ← full UI (ChatApp + 60+ patterns)
├── chat_files_client (browser, async)
├── chat_push_client (browser, async)
├── chat_web_push_client (browser, async)
├── chat_calls_client (browser, async) ← Activities UI
├── chat_onboarding_client (browser, async)
└── chat_io_devices_client (browser, async) ← I/O device management
Dependency flow: chat is the core — it loads chat_files, chat_push_server, chat_web_push_server, chat_calls, chat_proxy_server, chat_onboarding_client, chat_io_devices_client, ai_services, collab_docs, collab_richtext, collab_tracker, games, dohtop, and user_avatar. Standalone packages like chat_embed, issues, dohpro_admin, and dohpro_sales depend on chat. The chat_messenger is browser-only and used by chat_embed and issues for floating UI.
Depends on: express_router, user_host (auth), db_dataforge
Depended on by: chat_embed, issues
| Module | Description | Server | Browser |
|---|---|---|---|
chat |
Core room/DM chat, presence, bots, messaging | chat_server.js |
chat_client.js |
chat_files |
P2P file transfer (WebRTC + socket relay fallback) | chat_files_server.js |
chat_files_client.js |
chat_embed |
Embeddable iframe chat for extensions | chat_embed_server.js |
chat_embed_client.js |
chat_messenger |
OS-style dock bar with halfback glass strip, DockItem icons, popup windows | -- | chat_messenger_client.js |
chat_onboarding |
Spotlight tour for first-time users (desktop + mobile) | -- | chat_onboarding_client.js |
collab_docs |
Monaco code editor with Yjs CRDT sync | collab_docs_server.js |
collab_docs_client.js |
collab_richtext |
TinyMCE rich text editor on collab_docs backend | collab_richtext_server.js |
collab_richtext_client.js |
collab_shared |
Yjs socket provider and presence utilities | -- | collab_shared_client.js |
ai_services |
Multi-provider AI gateway and token tracking | ai_services.js |
-- |
ai_keys_admin |
Admin UI/API for AI provider API keys | ai_keys_admin.js |
ai_keys_admin.js |
dohpro_admin |
Subscription tier config and credit mapping (admin) | dohpro_admin_server.js |
dohpro_admin_client.js |
dohpro_sales |
Pricing page (/pro) and features page (/what-is-dohchat) |
dohpro_sales_server.js |
dohpro_sales_client.js |
issues |
Bug capture, issue tracking, analytics | issues_server.js |
issues_client.js |
chat_proxy |
URL proxy for embeds (htmlpog frame, Google Cache/Archive.org/Jina fallbacks) | chat_proxy_server.js |
-- |
dohtop |
Floating window system: drag, resize, tab groups, dock integration | -- | dohtop_client.js |
chat_io_devices |
I/O device management: mics, cameras, speakers, VU meters, routing | -- | chat_io_devices_client.js |
collab_tracker |
Real-time collaborative task list (Yjs CRDT) | -- | collab_tracker_client.js |
games |
In-room multiplayer games (Hangman, 20 Questions, Poker, WOPR) | game_engine.js |
game_client.js |
chat_extensions |
Browser extensions (Chrome MV3 + Safari) for URL tracking | -- | browser_chat_panel.js |
user_avatar |
DiceBear avatar generation | -- | user_avatar.js |
chat (or chat_embed, issues, etc.) to host_load in boot.pod.yamlchat.doh.js package declares the dependency graphchat_embed, issues, dohpro_admin, and dohpro_sales list chat as a dependency and add their own server/client modules# boot.pod.yaml
host_load:
- express_router
- users
- chat # Loads the full chat stack
- chat_embed # Optional: adds embed endpoints
- issues # Optional: adds issue tracking
- ai_keys_admin # Optional: adds key management UI
- dohpro_admin # Optional: adds /admin/dohpro tier management
- dohpro_sales # Optional: adds /pro and /what-is-dohchat pages
users module (provides authentication, Users.requireAuth, Doh.permit())express_router module (provides Router.AddRoute, Router.SendJSON, Router.SendHTML)With the local server running:
| URL | What it loads |
|---|---|
/chat |
Full chat application |
/doh-chat-embed?room=<name> |
Embeddable chat widget |
/docs |
Collaborative document list (Monaco) |
/collab/<doc-id> |
Specific collaborative document |
/richtext |
Rich text document list (TinyMCE) |
/richtext/<doc-id> |
Specific rich text document |
/admin/ai-keys |
AI provider key management (admin only) |
/admin/dohpro |
DohPro subscription tier and credit management (admin only) |
/pro |
DohPro pricing page |
/what-is-dohchat |
DohChat features and capabilities showcase |
BROWSER_TOOLS.md — Browser extension tool registry for bot interactionsPUSH_NOTIFICATIONS.md — APNs + Web Push notification systemsTURN_SERVER_SETUP.md — COTURN server setup for WebRTCSTATUS_LOCATIONS.md — Presence status display across 9 UI locationsAI_SERVICES_DOCUMENTATION.md — Detailed AI provider/model reference with pricingchat-url-tile-layout.md — URL tile DOM structure and resize mechanics| File | Purpose |
|---|---|
chat.doh.js |
Package definition (dependency graph) |
chat_server.js |
Server routes, socket events, permissions, DB setup (~8,700 lines) |
chat_client.js |
Full client UI — 70+ Doh Patterns (~24,700 lines) |
chat_calls_server.js |
Activities signaling server (~740 lines) |
chat_calls_client.js |
Activities manager and UI patterns (~2,600 lines) |
chat_proxy_server.js |
URL proxy for embed frames (htmlpog, Google Cache, Archive.org, Jina fallbacks) (~900 lines) |
chat_push_server.js |
APNs push notification registration and sending (~385 lines) |
chat_push_client.js |
Capacitor push notification setup (~200 lines) |
chat_web_push_server.js |
VAPID Web Push subscription and sending |
chat_web_push_client.js |
Browser Web Push API registration |
chat-notification-sw.js |
Service worker for Web Push notification display |
chat_url_utils.js |
URL parsing utilities (~100 lines) |
chat.css |
Main chat styles |
chat_calls.css |
Activities UI styles |
BROWSER_TOOLS.md |
Browser extension tool registry docs |
# pod.yaml
chat:
lobby_name: "General Chat"
max_message_length: 5000
attachment_storage_enabled: true
user_key_secret: "your-secret-key" # AES key for encrypting user AI keys
models: {} # AI model configuration overrides
providers: {} # Provider key map overrides
All tables are in the chat database (SQLite via AsyncDataforge). All are Idea tables (id TEXT, data TEXT JSON blob) unless noted.
| Table | Purpose |
|---|---|
chat.rooms |
Room metadata (name, type, members, settings, created_by, owners) |
chat.messages |
Message history (sender, content, timestamps, provenance) |
chat.room_members |
Membership records (username, role, joined_at, unread_count) |
chat.contacts |
User contact lists |
chat.connections |
Established user-to-user connections |
chat.connection_requests |
Pending connection requests |
chat.blocked_users |
Blocked user records |
chat.email_invites |
Email-based room invitations |
chat.sidebar_layouts |
Saved sidebar panel layouts per user |
chat.trace |
Browser navigation trace/history entries |
chat.bots |
Bot configurations and metadata |
chat.user_ai_keys |
Encrypted user API keys (AES-256-GCM) |
chat.pro_interest |
Doh Pro subscription interest registrations |
chat.token_usage |
AI token usage tracking per user/room/model |
chat.approval_requests |
Pending AI bot action approval requests |
chat.scheduled_tasks |
Recurring and one-shot AI bot scheduled tasks |
chat.notifications |
In-app notification records |
chat.notification_prefs |
Per-user notification scope preferences |
chat.room_invitations |
Room invitation tracking |
chat.settings_interactions |
Feature usage tracking for analytics |
Constants: LOBBY_ID = 'lobby'
Room ownership: Rooms have an owners array (string[]) that controls who can delete or clear the room. Regular rooms are created with owners: [creator]. DM rooms are created with owners: [userA, userB] (both participants). Old rooms without an owners field fall back to created_by for permission checks. DM rooms cannot be deleted or cleared regardless of ownership.
| Context | Condition |
|---|---|
chat_room |
ctx?.roomId exists |
server_logs |
Always true |
chat_maintenance |
Always true |
dohpro |
Always true |
| Group | Type | Permissions | Use Case |
|---|---|---|---|
chat_room_creator |
Assignable | create:chat_room |
Users who can create rooms |
log_viewer |
Assignable | view:server_logs |
Users who can stream server logs |
chat_admin |
Assignable | admin:chat_maintenance |
Admin maintenance (delete rooms, view all token usage) |
dohpro_subscriber |
Assignable | is:dohpro |
Doh Pro subscribers (access managed AI keys) |
doh users add-group user@example.com chat_room_creator
doh users add-group user@example.com chat_admin
Core send/edit/delete with emoji reactions. Messages carry provenance tracking (who created, edited, or deleted, with timestamps). Sticky notes via chat:notey_edit for the notey room format. Message reordering via chat:reorder. Unread counts tracked per-user per-room.
Message object shape:
{
id: string, // UUID
room_id: string,
sender: string, // username (or 'system' for system messages, 'bot:<id>' for bots)
sender_name: string, // chat_name (system messages only; regular messages resolve from ChatUserStore)
content: string,
created_at: number, // timestamp
edited_at: number | null,
deleted: boolean,
is_guest: boolean,
original_sequence_number: number,
adhoc_sequence_number: number | null,
provenance: [{ action: 'created'|'edited'|'deleted', user, name, at }]
}
Public lobby room (always available, auto-join). Permission-gated room creation. Room types: public, private, dm, bot, issue. Room format switching between:
Room export/import via YAML. Activity rollup folds consecutive system messages into collapsible summaries.
Create/get DM rooms via POST /api/chat/dm/:username or POST /api/chat/dm/bot/:bot_id. DM rooms have both participants as co-owners and cannot be deleted or cleared. Bot DMs auto-create a bot room with the specified bot.
Rich presence tracking: Online / Away / Busy / Offline with per-user persistence. Status saved to user record and broadcast to all lobby users via chat:presence. Typing indicators broadcast per-room excluding the sender.
onlineUsers Map: username → { sockets: Set<socket.id>, chat_name, avatar_salt, avatar_style, status }
socketToUser Map: socket.id → username
userRooms Map: socket.id → Set<room_id>
status is loaded from Users.getUserByUsername() on connect (field chat_status, defaults to 'online'). Updated in-memory on POST /api/chat/status and re-saved via Users.updateUser(). On socket disconnect the broadcast uses status: 'offline' regardless of saved value.
Multi-provider AI support (Anthropic, OpenAI, Google, xAI, Moonshot, HuggingFace) via ai_services. Users create custom bots with system prompts and model selection. Bot DM rooms for private conversations. User API keys encrypted with AES-256-GCM, stored as "iv:tag:encrypted" (hex, colon-separated). Masked display shows first 7 + last 4 characters.
Bot tool indicators (chat:bot_tool_use / chat:bot_tool_done) show real-time tool usage. Bot chaining (chat:bot_chain) allows bots to hand off to other bots. Browser extension tool integration lets bots interact with the user's browser — see BROWSER_TOOLS.md.
Bots execute via a graph-based workflow engine (agent_executor) instead of flat generateBotResponse. When a bot has a valid workflow document (a Yjs surface canvas with at least one input node and one output node), the graph executes; otherwise the bot falls back to flat mode (direct generateBotResponse).
agent_executor — walks the workflow graph: finds the input node, resolves edges to downstream nodes, executes each node type via agent_node_types, and routes results along edges until reaching the output node.handleBotResponseOutput — shared post-response handler used by both the workflow path and the flat path. Saves the bot's message, broadcasts it to the room, fires notifications, and handles bot-to-bot delegation (chaining).ChatBotContext — SPR source declaration (= {}) that shares helpers (DB access, bot lookup, message formatting) between chat_server, agent_executor, and agent_node_types without requiring load chain dependencies between them.workflowFromLegacy — validates and rebuilds workflow documents that are missing or incomplete. Called by the ensure-workflow endpoint. Checks for input/output nodes and re-creates them if absent.| Event | Direction | Description |
|---|---|---|
agent:join_builder |
C → S | Join a bot's workflow builder room for live execution updates |
agent:leave_builder |
C → S | Leave a bot's workflow builder room |
agent:resume |
C → S | Resume a paused workflow execution (e.g. after human review) |
agent:finish_tools |
C → S | Signal that tool execution is complete and workflow can continue |
Bot response processing sets a processing flag to prevent duplicate concurrent executions. A 2-minute auto-reset timer clears the flag if the bot becomes stuck (crash, timeout, unhandled error), allowing the next queued message to proceed.
AI bots can request approval before executing high-impact actions.
Flow:
[APPROVAL] message with a JSON card describing the proposed actionchat:approval_respondscheduled_task, the server creates a task record with schedule type and next-run timestampSCHEDULER_INTERVAL_MS) polls for due tasks and queues bot responseschat:approval_resolved broadcast updates the card UI to approved or deniedScheduled tasks support once and recurring types with configurable intervals. Tasks can be cancelled or reactivated via REST API.
Bot room conversations can be compressed or fully cleared to manage AI context window size.
POST /api/chat/rooms/:id/clear-context) — hard deletes all messages, wipes summary, sets last_cleared_at. Owner only.POST /api/chat/rooms/:id/compress) — uses the room's AI model to summarize the conversation, replaces all messages with a summary system message. Owner only.POST /api/chat/admin/clear-room) — hard deletes all messages in any room. Requires admin:chat_maintenance.Connections are bidirectional friendship links. Send/accept/decline requests. If mutual pending requests exist, auto-accepts. On connection, the server auto-creates a DM room and sends chat:navigate_to_room to switch to it. Connection records use compound ID "user1:user2" (alphabetically sorted) for deduplication.
Contacts are a lightweight one-way list for quick access. Add/remove via REST.
Blocking prevents message visibility and connection requests. Block records checked server-side in filterBlockedMessages before returning message history. Blocking cancels any pending connection requests between the users. The onlineUsers lobby list also filters blocked users per requesting user.
In-app notification bell with toast popups, unread badges, and per-user scope preferences. Paginated notification list with mark-read and clear-all. Socket events chat:notification and chat:notification_updated for real-time delivery.
Push notifications via two channels:
chat_push_server — see PUSH_NOTIFICATIONS.mdchat_web_push_server using VAPID keys. Service worker chat-notification-sw.js handles display and click-to-open navigation.Activities (WebRTC audio/video/screen sharing) are managed by chat_calls_server.js. Fully mesh topology — every participant establishes a direct peer connection to every other. The server acts as a signaling relay only.
Participant A ──── direct RTCPeerConnection ──── Participant B
│ │
└────── direct RTCPeerConnection ──── Participant C
Multi-device support: When a user joins from a second socket, the join mode controls behavior: 'move' (default) tears down old sockets and emits chat:call:replaced; 'add' keeps existing sockets and adds the new one, enabling multi-device participation (e.g. phone mic + laptop screen). chat:call:check_existing lets the client detect an active session before choosing a mode.
TURN credentials: Generated per-request using HMAC-SHA1. The /api/chat/calls/ice endpoint returns a fresh ice_servers array with time-limited TURN entries.
Pod configuration:
| Key | Default | Description |
|---|---|---|
chat_calls.turn_secret |
null |
HMAC secret for TURN credential generation |
chat_calls.turn_host |
null |
TURN server address, e.g. "1.2.3.4:3478" |
chat_calls.turn_ttl |
86400 |
TURN credential TTL in seconds |
chat_calls.max_participants |
8 |
Maximum simultaneous participants |
max_participants is sent to the browser via browser_pod. turn_secret and turn_host remain server-side only. See TURN_SERVER_SETUP.md for COTURN setup.
Messages in jelly format have persistent x, y, width, height positions stored under _jelly. Position updates via chat:jelly_positions, broadcast to room members.
Room merge (chat:room_merge) groups selected messages into a new private sub-room with parent_room_id pointing back. Posts a system message in the parent as a room-tile marker. Returns an undo descriptor for reversal.
Undo (chat:room_merge_undo) restores deleted messages, removes the system tile, and deletes the sub-room.
Add message (chat:room_add_message) moves or copies a single message into an existing sub-room with optional jelly positioning.
Connections (chat:jelly_connections) allow add/remove of visual connections between jelly messages.
Embedded collaborative documents (Monaco code editor, TinyMCE rich text) and collaborative task tracking via Yjs CRDT sync. Document tile size and open state synced across room members via chat:update_doc_size and chat:update_doc_open_state.
Shared browser panels (chat:browser_panel:*) for collaborative URL browsing within a room — open, navigate, move/resize, and close panels synced to all members.
The chat_embed browser extension reports page visits to a chat room. Each visit creates or updates a trace entry keyed by room_id:urlKey (hostname + pathname). A system message is posted with trace: true flag. Trace entries include URL, title, favicon, visitor info, scroll anchor, and user-assigned nickname.
Scroll sync relays scroll position within a shared browsing context via chat:scroll_sync. Used by chat_embed to synchronize the host page scroll position with room participants.
YouTube sync relays player state (play/pause/seek with timestamp) across room members via chat:yt_sync so everyone watches in sync.
Users with the log_viewer group can subscribe to real-time server log entries via chat:logs:subscribe. Entries stream as chat:logs:entry with timestamp, level, message, and source. Subscription is per-socket, auto-cleaned on disconnect.
In-room multiplayer games via the gamey room format. Game engine handles start/end/move/state lifecycle. Available games: Hangman, Twenty Questions, Poker, WOPR, Exquisite Corpse. See ../games/README.md for game definitions and engine API.
All routes prefixed /api/chat/ unless noted. Doh routes infer GET (no data) or POST (data sent) from the request. Auth = [Users.requireAuth]. Nickname = user must have set chat_name.
| Path | Auth | Description |
|---|---|---|
/api/chat/nickname |
Yes | POST: Set chat display name (2-32 chars) |
/api/chat/avatar |
Yes | GET: current settings; POST: update avatar_salt/avatar_style |
/api/chat/status |
Yes | POST: Set presence status (online/away/busy/offline) |
/api/chat/me |
Yes | GET: Current user profile |
| Path | Auth | Description |
|---|---|---|
/api/chat/lobby |
Yes + Nick | GET: Lobby room and members |
/api/chat/rooms/public |
Yes + Nick | GET: All public rooms |
/api/chat/rooms |
Yes + Nick | GET: Rooms user is a member of |
/api/chat/rooms/create |
Yes + Nick | POST: Create a room (create:chat_room) |
/api/chat/rooms/:id |
Yes + Nick | GET: Room details + members |
/api/chat/rooms/:id/preview |
Yes | GET: Room preview (non-members) |
/api/chat/rooms/:id/messages |
Optional | GET: Message history (paginated, limit/skip) |
/api/chat/rooms/:id/settings |
Yes | POST: Edit room settings (room admin) |
/api/chat/rooms/:id/delete |
Yes | POST: Delete room (room owner; DMs cannot be deleted) |
/api/chat/rooms/:id/clear |
Yes | POST: Clear all messages in room (room owner) |
/api/chat/rooms/:id/join |
Yes + Nick | POST: Join public/auto_membership room |
/api/chat/rooms/:id/join-me |
Yes + Nick | POST: Self-join room by ID |
/api/chat/rooms/:id/leave |
Yes | POST: Leave room (can't leave lobby) |
/api/chat/rooms/:id/ping-members |
Yes | POST: Ping room members |
/api/chat/rooms/:id/members |
Yes | POST: Add/invite member to room |
/api/chat/rooms/:id/members/:username |
Yes | DELETE: Remove member (room admin) |
/api/chat/rooms/:id/invitations/accept |
Yes | POST: Accept a room invitation |
/api/chat/rooms/:id/bot |
Yes | POST: Add bot to room |
/api/chat/rooms/:id/export |
Yes | POST: Export room as YAML |
/api/chat/rooms/:id/import |
Yes | POST: Import YAML into room (room admin) |
/api/chat/share-window |
Yes | POST: Share a window/tab with a room |
| Path | Auth | Description |
|---|---|---|
/api/chat/dm/:username |
Yes + Nick | POST: Create/get DM room |
/api/chat/dm/bot/:bot_id |
Yes | POST: Create/get bot DM room |
| Path | Auth | Description |
|---|---|---|
/api/chat/ai/models |
Yes | GET: List available AI models |
/api/chat/ai/keys |
Yes | GET: Key status per provider (masked) |
/api/chat/ai/keys/save |
Yes | POST: Save encrypted AI key |
/api/chat/ai/keys/delete |
Yes | POST: Delete AI key |
/api/chat/ai/keys/reveal |
Yes | POST: Decrypt and return full key |
/api/chat/bots |
Yes | GET: List user's bots |
/api/chat/bots/create |
Yes | POST: Create a bot |
/api/chat/bots/:id |
Yes | GET: Bot details |
/api/chat/bots/:id/update |
Yes | POST: Update bot config |
/api/chat/bots/:id/delete |
Yes | POST: Delete bot |
/api/chat/bots/:id/ensure-workflow |
Yes | POST: Validate/rebuild bot workflow doc (creates input+output nodes if missing) |
/api/chat/bots/:id/sync-workflow |
Yes | POST: Sync bot workflow doc from canvas state |
/api/chat/bots/:id/editors |
Yes | GET: List users currently editing a bot's workflow |
/api/chat/bots/tool-registry |
Yes | GET: Available tools for workflow node configuration |
/api/chat/rooms/:id/clear-context |
Yes | POST: Clear bot conversation context |
/api/chat/rooms/:id/compress |
Yes | POST: Summarize and compress context |
/api/chat/rooms/:id/clone |
Yes | POST: Clone bot room with empty history |
/api/chat/rooms/:id/model |
Yes | GET: Get bot's current AI model |
| Path | Auth | Description |
|---|---|---|
/api/chat/token-usage |
Yes | GET: User's aggregated token usage |
/api/chat/rooms/:id/token-usage |
Yes | GET: Per-room token usage |
/api/chat/admin/token-usage |
Admin | GET: All users' token usage |
| Path | Auth | Description |
|---|---|---|
/api/chat/contacts |
Yes | GET: Contact list with online status |
/api/chat/contacts/:username |
Yes | GET/POST: Get or add contact |
/api/chat/contacts/:username/remove |
Yes | DELETE: Remove from contacts |
/api/chat/connections/request/:username |
Yes | POST: Send connection request |
/api/chat/connections/requests/pending |
Yes | GET: Incoming requests |
/api/chat/connections/requests/sent |
Yes | GET: Outgoing requests |
/api/chat/connections/accept/:requester |
Yes | POST: Accept request |
/api/chat/connections/decline/:requester |
Yes | POST: Decline request |
/api/chat/connections |
Yes | GET: All connections |
/api/chat/connections/status/:username |
Yes | GET: Check connection status |
/api/chat/connections/remove/:username |
Yes | DELETE: Remove connection |
/api/chat/block/:username |
Yes | POST: Block user |
/api/chat/unblock/:username |
Yes | POST: Unblock user |
/api/chat/blocked |
Yes | GET: List blocked users |
| Path | Auth | Description |
|---|---|---|
/api/chat/notifications |
Yes | GET: List notifications (paginated) |
/api/chat/notifications/unread-count |
Yes | GET: Unread notification count |
/api/chat/notifications/read |
Yes | POST: Mark notifications read |
/api/chat/notifications/clear |
Yes | POST: Clear all notifications |
/api/chat/notifications/prefs |
Yes | GET/POST: Notification scope preferences |
/api/chat/notifications/channel |
Yes | POST: Update notification channel delivery |
/api/chat/invite-by-email |
Yes | POST: Send room invite via email |
/api/chat/invitations |
Yes | GET: Pending room invitations |
| Path | Auth | Description |
|---|---|---|
/api/chat/approvals |
Yes | GET: List pending approvals |
/api/chat/schedules |
Yes | GET: List scheduled tasks |
/api/chat/schedules/:id/cancel |
Yes | POST: Cancel scheduled task |
/api/chat/schedules/:id/reactivate |
Yes | POST: Reactivate recurring task |
/api/chat/rooms/:id/pending-approvals |
Yes | GET: Count pending approvals in room |
| Path | Auth | Description |
|---|---|---|
/api/chat/presence |
Yes + Nick | GET: Online users list |
/api/chat/users |
Yes + Nick | GET: Paginated user discovery |
/api/chat/users/by-email |
Yes | GET: Find user by email |
| Path | Auth | Description |
|---|---|---|
/api/chat/sidebar-layout |
Yes | GET: Saved sidebar layout |
/api/chat/sidebar-layout/save |
Yes | POST: Save sidebar layout |
/api/chat/sidebar-layout/reset |
Yes | POST: Reset to default |
/api/chat/links/user |
Yes + Nick | GET: Browser trace links |
/api/chat/collabs/user |
Yes + Nick | GET: User's collab documents |
| Path | Auth | Description |
|---|---|---|
/api/chat/ai/pro-interest |
Yes | POST: Register for Doh Pro |
/api/chat/ai/pro-interest/check |
Yes | POST: Check Pro registration |
/api/chat/backgrounds |
-- | GET: List available chat backgrounds |
/api/chat/unfurl |
-- | POST: URL metadata unfurl |
/api/chat/settings-interaction |
Yes | POST: Track feature usage for analytics |
| Path | Auth | Description |
|---|---|---|
/api/chat/calls/:roomId |
Yes | GET: Current activity state for a room |
/api/chat/calls/ice |
Yes | POST: Fresh ICE server list (with TURN credentials) |
| Path | Auth | Description |
|---|---|---|
/api/chat/push/register |
Yes | POST: Register APNs push token |
/api/chat/push/unregister |
Yes | POST: Unregister push token |
/api/chat/push/test |
Yes | POST: Send test push notification |
/api/chat/web-push/subscribe |
Yes | POST: Register browser push subscription |
/api/chat/web-push/unsubscribe |
Yes | POST: Remove push subscription |
/api/chat/web-push/vapid-key |
-- | GET: Public VAPID key |
/api/chat/web-push/test |
Yes | POST: Send test web push notification |
/chat-notification-sw.js |
-- | GET: Service worker file |
| Path | Auth | Description |
|---|---|---|
/api/chat/admin/delete-all-rooms |
Admin | POST: Delete all rooms except lobby |
/api/chat/admin/delete-empty-rooms |
Admin | POST: Delete rooms with < 3 messages |
/api/chat/admin/clear-room |
Admin | POST: Hard delete all messages in a room |
/api/chat/admin/clear-lobby |
Admin | POST: Clear lobby messages |
/api/chat/admin/test-models |
Admin | POST: Test AI provider connectivity |
/api/chat/admin/feature-stats |
Admin | POST: Feature adoption statistics |
| Path | Auth | Description |
|---|---|---|
/chat |
Yes | Full chat application page |
/chatop |
Yes | Chat application (chatop layout) |
/os |
Yes | Chat application (os layout) |
All events use wrapSocketHandler for consistent error handling. Auth comes from socket.user (authenticated) or socket.guestUser (guest with nickname).
| Event | Direction | Description |
|---|---|---|
chat:set_guest_identity |
C → S | Set guest name and ID |
window:join |
C → S | Join shared window room |
window:leave |
C → S | Leave shared window room |
window:presence |
S → Room | Window presence update |
| Event | Direction | Description |
|---|---|---|
chat:join |
C → S | Join a room socket room |
chat:leave |
C → S | Leave a room socket room |
chat:embed_joined |
C → S | Register embed client for a room |
chat:user_joined |
S → Room | User joined lobby |
chat:room_peer_joined |
S → Room | Peer joined any room |
chat:room_created |
S → Users | Room created |
chat:room_updated |
S → Room | Room metadata changed |
chat:room_deleted |
S → Room | Room deleted |
chat:member_added |
S → Room | Member added |
chat:member_removed |
S → Room | Member removed |
chat:room_invite |
S → User | Received room invite |
chat:room_cleared |
S → Room | Admin hard-cleared room messages |
| Event | Direction | Description |
|---|---|---|
chat:message |
C → S | Send a message |
chat:edit |
C → S | Edit a message |
chat:delete |
C → S | Delete a message |
chat:react |
C → S | Add a reaction |
chat:unreact |
C → S | Remove a reaction |
chat:notey_edit |
C → S | Edit a sticky note message |
chat:reorder |
C → S | Reorder messages in a room |
chat:read |
C → S | Mark messages as read |
chat:message_received |
S → Room | New message |
chat:message_edited |
S → Room | Message edited |
chat:message_deleted |
S → Room | Message deleted |
chat:messages_reordered |
S → Room | Message order changed |
chat:reaction_updated |
S → Room | Reactions updated |
chat:unread_update |
S → User | Unread count for a room |
| Event | Direction | Description |
|---|---|---|
chat:typing |
C → S | Broadcast typing state |
chat:user_typing |
S → Room | Typing indicator |
chat:presence |
S → Lobby | User presence state |
| Event | Direction | Description |
|---|---|---|
chat:jelly_positions |
C → S | Update canvas positions for messages |
chat:jelly_connections |
C → S | Add/remove connections between jelly messages |
chat:room_format |
C → S | Change room format (chatty/notey/jelly/gamey) |
chat:room_merge |
C → S | Group messages into a new sub-room |
chat:room_merge_undo |
C → S | Reverse a room merge |
chat:room_add_message |
C → S | Move/copy a message into an existing sub-room |
chat:update_tile_state |
C → S | Update generic tile position/size |
chat:jelly_positions_updated |
S → Room | Canvas positions updated |
chat:jelly_connections_updated |
S → Room | Jelly connections updated |
chat:room_format_updated |
S → Room | Room format changed |
chat:tile_state_updated |
S → Room | Generic tile state updated |
| Event | Direction | Description |
|---|---|---|
chat:update_doc_size |
C → S | Update collab doc tile size |
chat:update_doc_open_state |
C → S | Update collab doc open/closed |
chat:scroll_sync |
C → S | Relay scroll position to room |
chat:yt_sync |
C → S | Relay YouTube player state |
chat:doc_size_updated |
S → Room | Collab doc tile size updated |
chat:doc_open_state_updated |
S → Room | Collab doc open state updated |
chat:file_preview_size_updated |
S → Room | File preview size changed |
| Event | Direction | Description |
|---|---|---|
chat:browser_panel:open |
C → S | Open a shared browser panel |
chat:browser_panel:url |
C → S | Update browser panel URL |
chat:browser_panel:state |
C → S | Update browser panel position/size |
chat:browser_panel:close |
C → S | Close a browser panel |
chat:browser_tool_result |
C → S | Return browser tool result to server |
chat:browser_panel:opened |
S → Room | Browser panel opened |
chat:browser_panel:url_update |
S → Room | Browser panel URL changed |
chat:browser_panel:state_update |
S → Room | Browser panel moved/resized |
chat:browser_panel:close |
S → Room | Browser panel closed |
chat:browser_tool_request |
S → Room | Bot requesting browser tool |
| Event | Direction | Description |
|---|---|---|
chat:trace_update |
C → S | Report a page visit |
chat:trace_sync |
C → S | Request all trace entries for a room |
chat:trace_delete |
C → S | Delete a trace entry |
chat:trace_rename |
C → S | Set a nickname on a trace entry |
chat:trace_updated |
S → Room | Trace entry upserted |
chat:trace_deleted |
S → Room | Trace entry deleted |
chat:trace_renamed |
S → Room | Trace entry renamed |
| Event | Direction | Description |
|---|---|---|
chat:approval_respond |
C → S | Approve or deny a bot approval request |
chat:bot_tool_use |
S → Room | Bot started using a tool |
chat:bot_tool_done |
S → Room | Bot finished using a tool |
chat:bot_chain |
S → Room | Bot chaining to another bot |
chat:approval_pending |
S → Room | New approval request pending |
chat:approval_resolved |
S → Room | Approval approved or denied |
chat:context_cleared |
S → Room | Bot room context cleared/compressed |
agent:join_builder |
C → S | Join bot workflow builder room for live updates |
agent:leave_builder |
C → S | Leave bot workflow builder room |
agent:resume |
C → S | Resume paused workflow execution |
agent:finish_tools |
C → S | Signal tool execution complete |
| Event | Direction | Description |
|---|---|---|
chat:call:check_existing |
C → S | Check for existing activity session |
chat:call:join |
C → S | Join/start room activities |
chat:call:leave |
C → S | Leave activities |
chat:call:toggle_audio |
C → S | Toggle own audio |
chat:call:toggle_video |
C → S | Toggle own video |
chat:call:toggle_screen |
C → S | Toggle screen share |
chat:call:rtc_offer |
C → S / relay | Relay SDP offer to peer |
chat:call:rtc_answer |
C → S / relay | Relay SDP answer to peer |
chat:call:rtc_ice |
C → S / relay | Relay ICE candidate to peer |
chat:call:get_state |
C → S | Query current activity state |
chat:activity:stream_meta |
C → S / S → Room | Stream metadata update (audio/video/screen info) |
chat:activity:stream_meta_remove |
C → S / S → Room | Remove stream metadata |
chat:activity:stream_route |
C → S | Route audio/video stream to device |
chat:activity:get_streams |
C → S | Get current active streams |
chat:call:state |
S → Room | Full participant list |
chat:activity:stream_state |
S → Room | Stream state broadcast |
chat:call:peer_joined |
S → Room | Participant joined |
chat:call:peer_left |
S → Room | Participant left |
chat:call:media_state |
S → Room | Participant media state changed |
chat:call:resend_meta |
S → Client | Request meta resend after reconnect |
| Event | Direction | Description |
|---|---|---|
chat:notification |
S → User | New notification |
chat:notification_updated |
S → User | Notification updated |
chat:push:register |
C → S | Register push token |
chat:push:unregister |
C → S | Unregister push token |
| Event | Direction | Description |
|---|---|---|
chat:game_start |
C → S | Start a game in a room |
chat:game_end |
C → S | End a game |
chat:game_move |
C → S | Submit a game move |
chat:game_request_state |
C → S | Request current game state |
chat:game_state |
S → Room | Game state update |
chat:game_ended |
S → Room | Game finished |
| Event | Direction | Description |
|---|---|---|
chat:logs:subscribe |
C → S | Start streaming server logs (requires view:server_logs) |
chat:logs:unsubscribe |
C → S | Stop streaming server logs |
chat:logs:entry |
S → Client | Server log entry |
| Event | Direction | Description |
|---|---|---|
chat:connection_request |
S → User | Incoming connection request |
chat:connection_accepted |
S → Both | Connection accepted |
chat:navigate_to_room |
S → User | Navigate to newly created DM room |
chat:connection_removed |
S → Both | Connection removed |
chat:sidebar_layout_updated |
S → User | Sidebar layout saved |
chat_server exports a Chat object for use by other modules:
Doh.Module('my_module', ['chat_server'], function(Chat) {
// Create a system room
const room = await Chat.createSystemRoom({
name: 'Issue #42',
type: 'issue',
members: ['alice', 'bob'],
adminUser: 'alice'
});
// Manage membership
await Chat.addMemberToRoom(room.id, 'charlie', 'member');
await Chat.removeMemberFromRoom(room.id, 'bob');
// Query state
const online = Chat.isUserOnline('alice'); // boolean
const rooms = Chat.getUserRooms('alice'); // Room[]
const msgs = Chat.getRecentMessages(room.id, 50); // Message[]
const isMember = Chat.isRoomMember(room.id, 'alice'); // boolean
await Chat.deleteRoom(room.id);
});
| Function | Params | Returns | Description |
|---|---|---|---|
createSystemRoom(config) |
{ name, type?, members?, created_by?, is_public?, allow_guests?, adminUser? } |
Room |
Create room programmatically |
addMemberToRoom(roomId, username, role?) |
string, string, 'member'|'admin' |
MemberRecord | null |
Add member |
removeMemberFromRoom(roomId, username) |
string, string | void | Remove member |
deleteRoom(roomId) |
string | boolean | Delete room and all data |
getChatName(user) |
user object | string | Get chat display name for a user |
isUserOnline(username) |
string | boolean | Check presence |
trackUserOnline(socket, user) |
socket, user | void | Register presence |
trackUserOffline(socket) |
socket | void | Unregister presence |
getOnlineUsers() |
-- | [{ username, chat_name, status }] |
All online users |
broadcastUserPresence(user, online) |
user, boolean | void | Emit chat:presence to lobby |
getRoomById(roomId) |
string | Room | null |
Fetch room |
getUserRooms(username) |
string | Room[] |
User's rooms |
getPublicRooms() |
-- | Room[] |
All public rooms |
getRecentMessages(roomId, limit?) |
string, number | Message[] |
Fetch messages |
isRoomMember(roomId, username) |
string, string | boolean | Check membership |
canParticipate(user) |
user | boolean | Has chat_name set |
| Pattern | Parent | Purpose |
|---|---|---|
ChatApp |
['ChatConversation', 'ChatAppSocket'] |
Top-level app: sidebar, room state, nickname setup; shows partner status in DM room headers |
ChatAppSocket |
(mixin, no parent) | Unified socket event wiring for app-level concerns: presence, room CRUD, unread, notifications, activity state; used by both ChatApp and ChatMessengerBar |
ChatConversation |
html |
Shared base for ChatApp (full page) and ChatMessengerPopup (floating window): per-conversation messages, input, controls, socket wiring |
ChatSidebar |
['html', 'DohtopWindowBehavior'] |
Left nav: rooms, DMs, folders, issues section, user panel with clickable status picker. Pin/unpin toggle (default unpinned): unpinned = auto-sizing menu anchored above dock; pinned = persistent draggable/resizable window |
ChatStatusPicker |
html |
Floating or inline status picker (Online/Away/Busy/Offline); used by sidebar and settings |
ChatMessageList |
html |
Message container with layout modes (linear/thread/grouped) |
ChatMessage |
html |
Single message: avatar, content, actions, attachments |
ChatInput |
html |
Textarea with emoji picker, file attach, link preview, auto-resize |
ChatTypingIndicator |
html |
"User is typing..." display |
ChatNicknameSetup |
html |
First-login nickname prompt |
| Pattern | Parent | Purpose |
|---|---|---|
ChatCollabTile |
html |
Embedded collab document tile with mini editor |
ChatCollabCreatePopover |
html |
Popover for creating new collab documents |
ChatLogTile |
html |
Log/trace content display (URLs, stack traces) |
ChatUrlTile |
html |
Unfurled URL preview (title, description, thumbnail) |
ChatUrlOverlay |
html |
Full-page URL preview expansion |
ChatDropPopover |
html |
File drag-and-drop preview popover |
| Pattern | Parent | Purpose |
|---|---|---|
ChatAddMenu |
html |
Dropdown: create room, contact, folder, doc, bot |
ChatModal |
html |
Base modal dialog container |
ChatEmojiPickerModal |
html |
Emoji picker modal |
ChatAddContactModal |
html |
Add contact by username |
ChatUserPickerModal |
html |
Multi-select user picker |
ChatFolderContextMenu |
html |
Folder right-click: rename, delete |
ChatRoomContextMenu |
html |
Room right-click: rename, delete, archive |
| Pattern | Parent | Purpose |
|---|---|---|
ChatSettings |
html |
Root settings container with tab nav |
ChatSettingsAccount |
html |
Profile, password, preferences, inline status picker |
ChatSettingsAppearance |
html |
Theme, font size, message layout |
ChatSettingsExtensions |
html |
Plugins management |
ChatSettingsAIKeys |
html |
AI provider API key management |
ChatSettingsUsage |
html |
Token usage analytics |
ChatSettingsAIBots |
html |
Bot definitions and config |
ChatSettingsNotifications |
html |
Sound, desktop, mute rules |
ChatSettingsAttachments |
html |
File upload limits |
ChatSettingsStorage |
html |
Local cache, export/import |
ChatSettingsSchedules |
html |
Scheduled task management |
ChatSettingsConnections |
html |
Integrations management |
ChatSettingsFiles |
html |
File management and room files |
ChatSettingsDashboard |
html |
Admin dashboard |
ChatSettingsMaintenance |
html |
Admin tools (delete rooms, diagnostics) |
ChatSettingsTesting |
html |
Admin AI provider testing |
ChatSettingsFeatureStats |
html |
Admin feature adoption analytics |
| Pattern | Parent | Purpose |
|---|---|---|
ChatSidebarNode |
html |
Base tree node (draggable, selectable) |
ChatRoomItem |
html |
Room in sidebar with unread badge |
ChatFolderItem |
html |
Folder with expand/collapse |
ChatOnlineAvatars |
html |
Online user avatar row |
ChatMiniProfile |
html |
User hover card |
| Pattern | Parent | Purpose |
|---|---|---|
ChatToast |
html |
Temporary notification |
NotificationBell |
html |
Header notification icon with unread count badge |
NotificationCenter |
html |
Dropdown notification list with mark-read and clear-all |
NotificationItem |
html |
Single notification row in the dropdown |
NotificationToast |
html |
Notification popup with action buttons (view, mark read) |
ChatEmojiPicker |
html |
Floating emoji picker |
ChatApprovalCard |
html |
Bot approval request card (accept/reject/dismiss) |
ChatExpandableTile |
html |
Base collapsible tile for embedded content |
ChatTextTile |
ChatExpandableTile |
Inline text/note tile |
ChatRoomTile |
ChatExpandableTile |
Embedded sub-room tile (from jelly merge) |
ChatCreateGroupModal |
html |
Group room creation dialog |
ChatInvitePickerModal |
html |
Room invite user picker |
ChatSwarmPickerModal |
html |
Multi-bot swarm picker |
ChatContextFlyout |
html |
Context menu flyout |
ChatActivityRollup |
html |
Folds consecutive system messages into a collapsible summary |
ChatUsersPanel |
html |
User list panel for room members and online users |
ChatUrlBrowserPanel |
html |
Moved to url_browser module. URL browser panel with address bar, navigation, proxy toggle — see doh_chat/url_browser/. |
ChatRoomFileViewer |
html |
Room file viewer for browsing room attachments. Supports _storagePrefix for per-window localStorage isolation and onFiltersChanged callback. loadRoom() accepts a single ID, array of IDs, or null. |
FilesDohtopContent |
html |
Files DohtopWindow content: room sidebar + ChatRoomFileViewer. Supports multi-instance via _windowId, dynamic title sync, room context menus, and getContextMenuItems() for dock menu extension. |
ChatEmptySpaceContextMenu |
html |
Right-click on empty sidebar space |
ChatMessageContextMenu |
html |
Message right-click menu |
ChatFileContextMenu |
html |
File attachment context menu |
| Pattern/Object | Parent | Purpose |
|---|---|---|
ChatCallManager |
object | WebRTC peer connections, media tracks, activity state |
ChatCallIndicator |
html |
Room header badge: active activity, join buttons |
ChatActivityIndicator |
html |
Streaming activity indicator (typing, tool use, multi-device) |
ChatCallBar |
html |
Controls during activity: mute, video, screen share, leave, grid |
ChatCallRoomBadge |
html |
Room sidebar badge showing participant count |
All real-time features share a single Doh.socket connection. The client uses two composable patterns:
ChatAppSocket — a mixin pattern (no parent, non-html) that handles app-level socket events shared across all chat surfaces. Defines melded Method hooks (onPresenceChanged, onRoomCreated, onRoomUpdated, onRoomInvite, onRoomDeleted, onMemberRemoved, onUnreadUpdate, onSidebarLayout, onNotification, onNotificationUpdated) with base implementations that update centralized stores. Wired via wireAppSocket().
ChatConversation — a shared html base for per-conversation UI (messages, input, typing, controls). Both ChatApp (full page) and ChatMessengerPopup (floating window) extend it. Provides setupCoreSocketListeners() for message CRUD, layout sync, reactions, games, and format changes. Also provides setupCoreTypingListeners() for typing indicators.
Composition: ChatApp inherits both — Pattern('ChatApp', ['ChatConversation', 'ChatAppSocket'], {...}). ChatMessengerBar separately calls wireAppSocket() for app-level events, while each ChatMessengerPopup extends ChatConversation for its own conversation scope.
Four singleton stores provide reactive data across all chat surfaces:
ChatUserStore — user display data cache (chat_name, avatar_salt, avatar_style). Messages only carry sender (username); display data resolves from this store at render time. Methods: update(), get(), subscribe(), renderAvatar(), seedFromRooms(), seedFromSenders().
ChatPresenceStore — online/away/busy/offline status per user. On reconnect, all cached users reset to offline before re-seeding. Methods: update(), get() (defaults to 'offline'), subscribe().
RoomActivityStore — live activities per room (activities, games, open windows). Tracks participant counts, voice/video/screen flags, active games, and local activity state. Methods: update(), get(), seed(), setLocalCall(), setOpen(), subscribe(), _fromCallSummary().
ChatRoomStore — centralized room object cache. Lazy-fetches from /api/chat/rooms on first ensure(), dedupes concurrent calls, and auto-seeds ChatUserStore for DM partner data. Kept live by socket events (chat:room_created → update(), chat:room_updated → update(), chat:room_deleted → remove()). Bulk-seeded by ChatApp.loadRooms() and ChatSidebar messenger mode via seed() to avoid redundant fetches. Methods: ensure(), get(roomId), getAll(), update(room), remove(roomId), seed(rooms), subscribe(fn).
CollabDocStore — centralized collab document metadata cache (id, title, type, owner, language, updated_at). SPR source declaration (= {}) on Doh.Globals.CollabDocStore for cross-module access. Lazy-fetches from /api/collab/docs/:id on ensure(docId) (deduped). Kept live by collab:doc:meta_updated socket events wired in wireAppSocket(). Surfaces and trackers emit collab:doc:update_meta when their Yjs name changes, which triggers the server broadcast and store update. ChatCollabTile subscribes for live title updates and calls ensure() on mount to refresh stale message-time titles. Methods: ensure(docId), get(docId), update(doc), subscribe(fn).
The Files browser runs as a DohtopWindow (FilesDohtopContent pattern) with a room-navigation sidebar and a ChatRoomFileViewer panel.
Multi-instance: Multiple Files windows can be open simultaneously. Default click on the Files button opens/restores the singleton 'files' window. Right-click opens the dock context menu of the last-interacted Files window (which includes "New Files Window"). Each window has independent filter state, room selection, view mode, and sidebar width — persisted via per-window localStorage keys (chat-files-<windowId>-*).
Dynamic title: The window title and dock item label reflect the active type filters — e.g. "Files + Docs + Links", "Docs", "Files + Links". Updates on every filter change.
Room sidebar grouping: Rooms are partitioned into three sections:
ChatUserStore, bot gear icons, lobby #Filter toggle behavior: Type filters (Files/Docs/Links) and storage filters use click-to-solo, Ctrl/Shift-click-to-toggle. Tooltips show "Hold Ctrl to select multiple". Same toggle pattern for room selection in the sidebar.
Room context menu: Right-click a room in the files sidebar for "Open Room" (navigates in ChatApp) and "Open in New Window".
Orphaned files: The server (/api/chat/files/user) returns files uploaded by the current user whose room was deleted, enriched with room_type: 'deleted' and room_deleted: true. The /api/chat/links/user and /api/chat/collabs/user endpoints similarly return orphaned links/collabs.
Socket handlers resolve user via: socket.user (authenticated) → socket.guestUser (guest) → null
Every message tracks its edit/delete history:
provenance: [
{ action: 'created', user: 'alice', name: 'Alice', at: 1700000000 },
{ action: 'edited', user: 'alice', name: 'Alice', at: 1700000060 }
]
/chatchat_admin group)