Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Comments

Threads, mentions and comments

Bijection source
git clone https://github.com/bijectionhq/comments.git
cd comments
git checkout 6ca6359f8a3746db5d2706fc7a7951738a42a2b7

Bijection 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.

Available to evaluate

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. 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.

agent-prompt.md
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:

  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 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 position field 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 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]

Keywords