# Comments Threads, mentions and comments - Package: @bijectionhq/comments - Category: Collaboration - Status: Preview - License: Apache-2.0 - Bijection source (repository access required): https://github.com/bijectionhq/comments/tree/6ca6359f8a3746db5d2706fc7a7951738a42a2b7 - Upstream: https://github.com/hamzasaleem2/convex-comments/tree/5015cc7c651857abca5c7d0a6df683b9e4968865 - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/comments ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/comments.git cd comments git checkout 6ca6359f8a3746db5d2706fc7a7951738a42a2b7 ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @hamzasaleemorg/convex-comments; check the Bijection adaptation separately. 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 • **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 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 README # Convex Comments Component A comments system for Convex with threads, mentions, reactions, and typing indicators. Includes backend functions and optional React UI components. ## Installation ```bash npm install @hamzasaleemorg/convex-comments ``` Add the component to your Convex app: ```typescript // 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:** ```typescript // 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:** ```tsx 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(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
Loading...
; return (

Comments ({messages.messages.length})

{messages.messages.map((msg) => (
{msg.message.authorId}: {msg.message.body}
))}
); } ``` **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: 1. **Entity ID**: Use your existing `postId`, `docId`, or `pageId`. 2. **`getOrCreateZone`**: Maps your ID to the component's internal storage. 3. **`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](#positioned-comments-optional) 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. ```typescript 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`. ```typescript 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. ```typescript 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 `position` field is **optional** - use it for positioned comments (document editors, design tools, video timestamps). See [#positioned-comments-optional](#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. ```typescript import { CommentsProvider, Comments, Thread, Comment, AddComment, } from "@hamzasaleemorg/convex-comments/react"; function App() { return ( ({ name: users[id].name })} reactionChoices={["👍", "❤️", "😄", "🎉"]} > ); } ``` ### CommentsProvider Required wrapper that provides configuration to child components. Props: - `userId: string | null` - Current user ID - `resolveUser?: (userId: string) => Promise<{ name: string, avatar?: string }>` - Function to fetch user details - `reactionChoices?: string[]` - Available emoji reactions - `canModerate?: boolean` - Whether user can moderate comments - `styles?: CommentsStyles` - Custom styling ### Comments Displays list of threads. Props: - `threads: Thread[]` - Array of threads to display - `hasMore?: boolean` - Whether more threads exist - `onThreadClick?: (threadId: string) => void` - Thread click handler - `onNewThread?: () => void` - New thread button handler ### Thread Displays single thread with messages. Props: - `thread: Thread` - Thread data - `messages: Message[]` - Array of messages - `typingUsers?: TypingUser[]` - Users currently typing - `onSubmit?: (body: string) => void` - Submit handler - `onToggleReaction?: (messageId: string, emoji: string) => void` - `onResolve?: () => void` ### Comment Displays single message. Props: - `comment: Message` - Message data - `mine?: boolean` - Whether current user authored message - `onToggleReaction?: (emoji: string) => void` - `onEdit?: (newBody: string) => void` - `onDelete?: () => void` ### AddComment Message composer with mention autocomplete. Props: - `onSubmit?: (body: string, attachments?: Attachment[]) => void` - `onTypingChange?: (isTyping: boolean) => void` - `mentionableUsers?: MentionableUser[]` - Users for autocomplete - `placeholder?: string` - `allowEditing?: 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]