Documentation source
Comments
Threaded comments on entity records with one level of nesting, inline editing, emoji reactions, @mention autocomplete, activity logging, and real-time updates.
# Comments
The Comments module adds threaded discussion to any entity record. Comments support one level of reply nesting, inline editing, emoji reactions, @mention autocomplete with tenant-scoped notification delivery, activity logging, and real-time updates via the Realtime module.
## Overview
Comments are scoped to a single entity and tenant. The module lives in `features/comments/` and provides server actions for CRUD (including edit), React Query hooks for client-side state, and pre-built UI components for rendering threaded conversations with reactions and mentions. Comments appear on entity detail pages, typically in a dedicated section below the entity content.
## Key Concepts
**CommentRecord** -- The core data type representing a comment:
- `id`, `tenant_id`, `entity_id` -- identity and scoping
- `parent_id` -- null for top-level comments, set for replies (one level deep only)
- `user_id`, `body`, `created_at`, `updated_at` -- content and metadata
- `author` -- joined from `profiles` table (display_name, email, handle)
- `replies` -- child comments assembled during thread building
- `reactions` -- emoji reactions keyed by emoji + user list
**One-Level Threading** -- Replies can only be added to top-level comments, not to other replies. The reply button is only shown on comments at depth 0.
**Inline Editing** -- Authors can edit their own comments. `<SingleComment>` displays a pencil icon on hover for own comments; clicking it toggles an edit field pre-filled with the current body. `updateComment` validates the new body (≤10 000 chars) via Zod and enforces ownership server-side.
**Emoji Reactions** -- Users can react to any comment with an emoji. Reactions are stored per comment and returned alongside the thread by `useComments`. Multiple users can use the same emoji; the reaction UI groups identical emojis with a count.
**@Mention Autocomplete** -- The comment input resolves `@handle` patterns using `mention.ts`. Handle parsing uses the regex `/@([a-zA-Z0-9_]{3,20})\b/`, which aligns with the profile handle constraint. Profile lookup is always tenant-scoped via `user_tenants` membership — same-handle users in other tenants are not matched. Each resolved mention fires an in-app notification to the mentioned user.
**Activity Logging** -- Every new comment creates an activity record via `logActivity()` with action `comment_added`. The activity description is the comment body truncated to 100 characters.
**Ownership Enforcement** -- Users can only edit or delete their own comments. Both `updateComment` and `deleteComment` verify `user_id` matches the current user.
## How It Works
### Thread Building
The `listComments()` server action fetches all comments for an entity, then builds the threaded structure in memory:
1. Fetch all comments ordered by `created_at` ascending
2. Join with `profiles` table to get author display names and handles
3. Attach reactions to each comment
4. Build a lookup map by comment ID
5. Iterate: if a comment has a `parent_id` and that parent exists, push it into the parent's `replies` array. Otherwise, add it to the top-level list.
This returns a flat list of top-level comments, each with a `replies` array and `reactions` map.
### Mention Notification Flow
1. `createComment` validates and saves the comment body
2. `extractMentions(body)` from `mention.ts` parses all `@handle` tokens
3. Tenant-scoped `profiles` lookup via `user_tenants` join resolves handles to user IDs
4. `createNotification({ type: 'mention', userId, tenantId, link })` fires for each resolved user (server-only, not a `"use server"` action)
### Client-Side Data Management
React Query hooks manage comment state:
- `useComments(entityId)` -- fetches threaded comments (with reactions) via `/api/comments?entityId=...`
- `useCreateComment(entityId)` -- mutation that POSTs to `/api/comments` and invalidates the comment query
- `useUpdateComment(entityId)` -- mutation that PATCHes `/api/comments/\{id\}` and invalidates
- `useDeleteComment(entityId)` -- mutation that DELETEs via `/api/comments/\{id\}` and invalidates
The `commentsQueryKey(entityId)` function returns `["comments", entityId]`, which aligns with Realtime entity listeners for automatic cache invalidation when comments change remotely.
### UI Components
**CommentThread** is the top-level component. It displays the full comment list with loading and empty states, plus a `CommentInput` at the bottom for new comments.
**SingleComment** renders each comment with: author avatar, name, handle, timestamp, body (with @mention highlighting), reaction bar, and action buttons (Reply for top-level; Edit/Delete for own comments). Edit mode replaces the body with an inline textarea pre-filled with the current content.
**CommentInput** includes @mention autocomplete — as the user types `@`, a dropdown resolves tenant-scoped handles.
**CommentCount** is a lightweight component that displays just the count, suitable for use in entity cards or list rows.
## API Reference
### Server Actions
| Function | Location | Purpose |
|---|---|---|
| `listComments(entityId)` | `features/comments/server/actions.ts` | Fetch and thread all comments for an entity (includes reactions) |
| `createComment(entityId, body, parentId?)` | Same | Create a comment; resolves @mentions and fires notifications |
| `updateComment(id, body)` | Same | Edit own comment; ownership enforced; body ≤10 000 chars |
| `deleteComment(id)` | Same | Delete own comment (ownership verified) |
| `getCommentCount(entityId)` | Same | Count of comments on an entity |
### Hooks
| Hook | Purpose |
|---|---|
| `useComments(entityId)` | Fetch threaded comments with reactions |
| `useCreateComment(entityId)` | Create comment mutation |
| `useUpdateComment(entityId)` | Edit comment mutation |
| `useDeleteComment(entityId)` | Delete comment mutation |
| `commentsQueryKey(entityId)` | Stable query key for cache sharing |
### Utilities
| Function | Location | Purpose |
|---|---|---|
| `extractMentions(body)` | `features/comments/utils/mention.ts` | Parse @handle tokens from comment text |
### Components
| Component | Purpose |
|---|---|
| `CommentThread` | Full threaded comment UI with input |
| `CommentInput` | Text input with @mention autocomplete |
| `SingleComment` | Single comment with edit mode, reactions, action buttons |
| `CommentCount` | Comment count badge |
## For Agents
Agents do not currently have a dedicated tool for posting comments. Comments are a human-to-human collaboration feature. Agents can observe comment activity through the `searchEntities` tool (activity records include `comment_added` events) and the entity's activity timeline.
If comment functionality is needed for agents in the future, a `postComment` tool could be added to the entity tool group.
## Related Modules
- **Social Features** (`features/tenant/`, `features/notifications/`) -- @mention notifications fire through the social notification layer; see [Social Features](/docs/features/social-features)
- **Entity System** (`features/entities/`) -- comments are scoped to entities
- **Realtime** (`features/realtime/`) -- entity realtime listeners include `comments` table changes
- **Activity** -- comment creation logs an activity record for the timeline