Comments
Threads, mentions and comments
git clone https://github.com/bijectionhq/comments.git
cd comments
git checkout 6ca6359f8a3746db5d2706fc7a7951738a42a2b7Bijection repository access is required. Request access.
Description
Threads, mentions and comments
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: bijectionhq/comments. Based on hamzasaleem2/convex-comments. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
A full-featured comments component for Convex with threads, mentions, reactions, and real-time typing indicators.
Explore the demo ↗Upstream overview
From the upstream package documentation.
A complete comments system component that adds threaded discussions with real-time features to any Convex application. It provides backend functions for managing comment threads organized into zones, messages with automatic mention parsing, emoji reactions, and typing indicators. The component includes optional React UI components and supports both simple flat comment lists and positioned comments for document annotation workflows.
Use cases
From the upstream package documentation.
- Document collaboration - Add comment threads to pages, posts, or documents where users need to discuss specific content with mentions and reactions
- Code review systems - Implement threaded discussions on pull requests or code changes with positioned comments anchored to specific line numbers
- Design tool feedback - Enable stakeholders to leave comments at specific x/y coordinates on designs, mockups, or canvas-based applications
- Content management workflows - Build approval processes where editors can comment on drafts, resolve discussion threads, and track conversation history
- Video or media annotation - Allow users to comment at specific timestamps or regions within media content with persistent positioning
How it works
From the upstream package documentation.
The component organizes comments into a three-tier hierarchy: zones (containers tied to your entities like documents or posts), threads (conversation groups within zones), and messages (individual comments). You integrate it by adding the component to your convex.config.ts, then create wrapper functions that call the provided Comments class methods like getOrCreateZone() and addComment().
The quickstart pattern uses getThreadForDocument() to handle the zero-to-one setup, automatically creating a zone for your entity ID and ensuring a default thread exists. Messages support automatic parsing of mentions and links, emoji reactions via toggleReaction(), and real-time typing indicators through setIsTyping() with automatic expiration.
For advanced use cases, threads accept an optional position field in addThread() to anchor comments to specific locations like canvas coordinates or document paragraphs. The component provides three integration approaches: the type-safe Comments class, direct component calls via ctx.runMutation(), or generated wrapper functions through exposeApi() with built-in authentication.
Upstream package FAQ
Does Convex Comments work with existing authentication systems?
Convex Comments integrates with Convex's built-in authentication system and can work with custom user schemas. The component uses Convex's user context to associate comments with authenticated users and handle permissions.
Can I customize the comment UI components?
Convex Comments provides optional React UI components that can be styled and customized. You can also use just the backend Convex functions and build your own frontend interface while leveraging the comment system logic.
How does the threading system handle deeply nested comments?
The threading system uses a parent-child relationship model in the Convex schema to create comment trees. Convex Comments handles the complexity of nested queries and provides functions to retrieve comment threads with proper ordering and hierarchy.
Are comment notifications included in the system?
Convex Comments includes the infrastructure for notifications when users are mentioned or when replies are added to threads. The notification delivery mechanism integrates with your existing notification system or can be implemented using Convex actions.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Comments with Bijection.
Read: https://bijection.com/components/comments/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from hamzasaleem2/convex-comments, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
Convex Comments Component
A comments system for Convex with threads, mentions, reactions, and typing indicators. Includes backend functions and optional React UI components.
Installation
npm install @hamzasaleemorg/convex-comments
Add the component to your Convex app:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import comments from "@hamzasaleemorg/convex-comments/convex.config.js";
const app = defineApp();
app.use(comments);
export default app;
Quick Start (5 minutes)
Get comments working in your Convex app:
1. Create backend functions:
// convex/comments.ts
import { v } from "convex/values";
import { Comments } from "@hamzasaleemorg/convex-comments";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
const comments = new Comments(components.comments);
/**
* Get or create the comment thread for a document.
* This is the "Zero-to-One" function that handles the initial setup.
*/
export const getThreadForDocument = mutation({
args: { documentId: v.string() },
handler: async (ctx, args) => {
// 1. Ensure the "Zone" (container) exists for this doc
const zoneId = await comments.getOrCreateZone(ctx, {
entityId: args.documentId
});
// 2. Ensure a "General" thread exists in that zone
const threadId = await comments.getOrCreateThread(ctx, { zoneId });
return threadId;
},
});
// Add a comment to a thread
export const addComment = mutation({
args: { threadId: v.string(), userId: v.string(), body: v.string() },
handler: async (ctx, args) => {
return await comments.addComment(ctx, {
threadId: args.threadId,
authorId: args.userId,
body: args.body,
});
},
});
// Get messages for the UI
export const getMessages = query({
args: { threadId: v.string() },
handler: async (ctx, args) => {
return await comments.getMessages(ctx, { threadId: args.threadId });
},
});
2. Use in React:
import { useMutation, useQuery } from "convex/react";
import { useEffect, useState } from "react";
import { api } from "../convex/_generated/api";
function CommentSection({ documentId, userId }) {
const [threadId, setThreadId] = useState<string | null>(null);
const setupThread = useMutation(api.comments.getThreadForDocument);
// 1. Initialize the thread for this document
useEffect(() => {
setupThread({ documentId }).then(setThreadId);
}, [documentId]);
// 2. Load the messages once we have a threadId
const messages = useQuery(
api.comments.getMessages,
threadId ? { threadId } : "skip"
);
const addComment = useMutation(api.comments.addComment);
if (!threadId || !messages) return <div>Loading...</div>;
return (
<div className="comment-section">
<h3>Comments ({messages.messages.length})</h3>
<div className="message-list">
{messages.messages.map((msg) => (
<div key={msg.message._id}>
<strong>{msg.message.authorId}:</strong> {msg.message.body}
</div>
))}
</div>
<button onClick={() =>
addComment({ threadId, userId, body: "Hello world!" })
}>
Post Comment
</button>
</div>
);
}
That's it! You now have a functional comment thread for any entity in your application.
🛠️ The "Standard" Setup
In most applications, you want to show a single list of comments for a specific page or resource. The pattern above is the fastest way to achieve this using our getOrCreate helpers:
- Entity ID: Use your existing
postId,docId, orpageId. getOrCreateZone: Maps your ID to the component's internal storage.getOrCreateThread: Ensures there's a conversation ready to receive messages.
If you are building something more complex, like positioned comments (annotations on a PDF or a canvas), see the Positioned Comments section.
See below for the complete API reference and React components.
Data Model
The component organizes comments into three levels:
- Zones - Containers for threads, tied to your entities (documents, tasks, etc.)
- Threads - Groups of messages within a zone
- Messages - Individual comments with mentions, reactions, and attachments
Each message can have:
- Body text with automatic mention and link parsing
- Attachments (URLs, files, images)
- Emoji reactions
- Resolved state
- Edit history
Backend Usage
Method 1: Comments Class
The recommended approach. Provides type-safe methods and optional callbacks.
import { Comments } from "@hamzasaleemorg/convex-comments";
import { components } from "./_generated/api";
import { mutation, query } from "./_generated/server";
const comments = new Comments(components.comments);
export const createZone = mutation({
args: { entityId: v.string() },
handler: async (ctx, args) => {
return await comments.getOrCreateZone(ctx, {
entityId: args.entityId,
});
},
});
export const addComment = mutation({
args: { threadId: v.string(), body: v.string() },
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
return await comments.addComment(ctx, {
threadId: args.threadId,
authorId: userId,
body: args.body,
});
},
});
Method 2: Direct Component Calls
Call component functions directly through ctx.runMutation or ctx.runQuery.
import { components } from "./_generated/api";
export const addComment = mutation({
args: { threadId: v.string(), body: v.string() },
handler: async (ctx, args) => {
return await ctx.runMutation(components.comments.lib.addComment, {
threadId: args.threadId,
authorId: await getAuthUserId(ctx),
body: args.body,
});
},
});
Method 3: Exposed API
Generate wrapper functions for frontend use.
import { exposeApi } from "@hamzasaleemorg/convex-comments";
import { components } from "./_generated/api";
export const {
getThreads,
addThread,
addComment,
toggleReaction,
setIsTyping,
getTypingUsers,
} = exposeApi(components.comments, {
auth: async (ctx, operation) => {
return await getAuthUserId(ctx);
},
});
API Reference
Zones
getOrCreateZone(ctx, args)
- Args:
{ entityId: string, metadata?: any } - Returns:
Id<"zones"> - Creates a zone if it doesn't exist, otherwise returns existing zone
getZone(ctx, args)
- Args:
{ entityId: string } - Returns: Zone or null
- Get zone by entity ID
deleteZone(ctx, args)
- Args:
{ zoneId: Id<"zones"> } - Deletes zone and all threads/messages within it
Threads
addThread(ctx, args)
- Args:
{ zoneId: Id<"zones">, position?: { x: number, y: number, anchor?: string }, metadata?: any } - Returns:
Id<"threads"> - Creates a new thread in the zone
- The
positionfield is optional - use it for positioned comments (document editors, design tools, video timestamps). See #positioned-comments-optional for examples.
getThreads(ctx, args)
- Args:
{ zoneId: Id<"zones">, limit?: number, cursor?: string, includeResolved?: boolean } - Returns:
{ threads: Thread[], nextCursor?: string, hasMore: boolean } - Lists threads with first message preview and pagination
resolveThread(ctx, args)
- Args:
{ threadId: Id<"threads">, userId: string } - Marks thread as resolved
unresolveThread(ctx, args)
- Args:
{ threadId: Id<"threads"> } - Reopens a resolved thread
deleteThread(ctx, args)
- Args:
{ threadId: Id<"threads"> } - Deletes thread and all messages
Messages
addComment(ctx, args)
- Args:
{ threadId: Id<"threads">, authorId: string, body: string, attachments?: Attachment[] } - Returns:
{ messageId: Id<"messages">, mentions: Mention[], links: Link[] } - Creates message and parses mentions/links automatically
getMessages(ctx, args)
- Args:
{ threadId: Id<"threads">, limit?: number, cursor?: string, currentUserId?: string } - Returns:
{ messages: Message[], nextCursor?: string, hasMore: boolean } - Lists messages with reactions, supports pagination
editMessage(ctx, args)
- Args:
{ messageId: Id<"messages">, body: string, authorId?: string } - Updates message body
deleteMessage(ctx, args)
- Args:
{ messageId: Id<"messages">, authorId?: string } - Soft deletes message (marks as deleted, preserves data)
Reactions
toggleReaction(ctx, args)
- Args:
{ messageId: Id<"messages">, userId: string, emoji: string } - Returns:
{ added: boolean } - Adds reaction if not present, removes if already exists
addReaction(ctx, args)
- Args:
{ messageId: Id<"messages">, userId: string, emoji: string } - Adds reaction (idempotent)
removeReaction(ctx, args)
- Args:
{ messageId: Id<"messages">, userId: string, emoji: string } - Removes reaction
getReactions(ctx, args)
- Args:
{ messageId: Id<"messages">, currentUserId?: string } - Returns grouped reactions with counts and user lists
Typing Indicators
setIsTyping(ctx, args)
- Args:
{ threadId: Id<"threads">, userId: string, isTyping: boolean } - Sets typing state, automatically expires after 3 seconds
getTypingUsers(ctx, args)
- Args:
{ threadId: Id<"threads">, excludeUserId?: string } - Returns list of users currently typing (filters expired)
clearUserTyping(ctx, args)
- Args:
{ userId: string } - Clears all typing indicators for user
React Components
Optional UI components for displaying comments.
import {
CommentsProvider,
Comments,
Thread,
Comment,
AddComment,
} from "@hamzasaleemorg/convex-comments/react";
function App() {
return (
<CommentsProvider
userId={currentUser.id}
resolveUser={(id) => ({ name: users[id].name })}
reactionChoices={["👍", "❤️", "😄", "🎉"]}
>
<Comments threads={threads} />
</CommentsProvider>
);
}
CommentsProvider
Required wrapper that provides configuration to child components.
Props:
userId: string | null- Current user IDresolveUser?: (userId: string) => Promise<{ name: string, avatar?: string }>- Function to fetch user detailsreactionChoices?: string[]- Available emoji reactionscanModerate?: boolean- Whether user can moderate commentsstyles?: CommentsStyles- Custom styling
Comments
Displays list of threads.
Props:
threads: Thread[]- Array of threads to displayhasMore?: boolean- Whether more threads existonThreadClick?: (threadId: string) => void- Thread click handleronNewThread?: () => void- New thread button handler
Thread
Displays single thread with messages.
Props:
thread: Thread- Thread datamessages: Message[]- Array of messagestypingUsers?: TypingUser[]- Users currently typingonSubmit?: (body: string) => void- Submit handleronToggleReaction?: (messageId: string, emoji: string) => voidonResolve?: () => void
Comment
Displays single message.
Props:
comment: Message- Message datamine?: boolean- Whether current user authored messageonToggleReaction?: (emoji: string) => voidonEdit?: (newBody: string) => voidonDelete?: () => void
AddComment
Message composer with mention autocomplete.
Props:
onSubmit?: (body: string, attachments?: Attachment[]) => voidonTypingChange?: (isTyping: boolean) => voidmentionableUsers?: MentionableUser[]- Users for autocompleteplaceholder?: stringallowEditing?: boolean
Positioned Comments (Optional)
The position field in addThread() is an optional feature for anchoring comments to specific locations. This is useful for:
- Document editors (comment on specific paragraphs)
- Design tools (comment at x/y coordinates on canvas)
- Video players (comment at specific timestamps)
- Code review (comment on specific line numbers)
Posit
[README truncated for prompt length]