Add threaded comments and annotations to any Convex resource with author-gated edits, soft-delete, reactive pagination, and sandboxed tables in one component.
npm install @vllnt/convex-commentsComments is threaded commenting and annotation on any resource as a Convex component: `post()` attaches a comment to an opaque `resourceRef`, `parentId` threads it as a reply, and author-gated `edit`, `remove` (soft-delete), and `resolve` mutations enforce ownership — the component issues `NOT_AUTHOR` on mismatch, never trusting the caller. Because `list` runs inside a Convex query, every post and resolve streams reactively to connected clients over the same subscriptions as the rest of your app data; a typed `Comments<TBody>` generic and `bodyValidator` narrows plain text or rich blocks at the boundary. Comment rows live in sandboxed tables invisible to your schema, the host owns auth and moderation policy while the component enforces only authorship, and it runs identically on Convex Cloud and self-hosted convex-backend.
@vllnt/convex-comments provides a Comments class you mount via app.use in convex.config.ts. Call comments.post(ctx, resourceRef, authorRef, body, parentId) to create top-level comments or threaded replies by passing a parentId. Comments are listed reactively with comments.list(ctx, resourceRef, paginationOpts) inside a Convex query, so the client updates in real time without extra wiring.
Because @vllnt/convex-comments accepts an opaque resourceRef string, you can attach comment threads to any entity, including document regions or time-coded clips. The component supports multiple named mounts, so you can run app.use(comments, { name: 'annotations' }) alongside a separate comments mount, each with its own isolated table sandbox.
@vllnt/convex-comments implements soft-delete via comments.remove(ctx, commentId, authorRef), which marks the row as deleted and clears the body but keeps it in place so replies remain intact. A built-in daily cron prunes deleted rows past the configured retention period, and the list query excludes deleted comments by default unless includeDeleted is passed.
In @vllnt/convex-comments, the edit, remove, and resolve mutations each require the caller to pass the original authorRef. If the ref does not match the stored author, the component throws a NOT_AUTHOR error. The host application is responsible for resolving the identity from its own auth system and passing it in; the component enforces only authorship, not broader access control.
@vllnt/convex-comments is a backend-only Convex component and does not ship a React entry point. You build the UI by calling your own re-exported host query and mutation functions, for example using useQuery or usePaginatedQuery over your list and count wrappers.
@vllnt/convex-comments is auth-agnostic for access control. The host application resolves the user identity using its own auth system and passes an opaque authorRef string to each mutation. The component only enforces that edit, remove, and resolve are called by the original author; broader access rules such as who may post or moderate are entirely the host's responsibility.
Yes. Because @vllnt/convex-comments is mount-safe, you can register it twice under different names using app.use, for example once as comments and once as annotations. Each mount gets its own isolated table sandbox and a separate Comments instance, so the two contexts never share data.
@vllnt/convex-comments requires convex@^1.41.0 as a peer dependency. Install the package with npm install @vllnt/convex-comments or pnpm add @vllnt/convex-comments, then register the component in convex/convex.config.ts using app.use(comments).
The list method on @vllnt/convex-comments accepts a standard Convex paginationOptsValidator argument and returns a PaginationResult<CommentView> ordered oldest-first. You can scope pagination to direct replies of a specific parent by passing opts.parentId. The query is reactive, so clients subscribed via usePaginatedQuery receive live updates when new comments are posted.