Comments System
Comments System
The platform includes a generic, polymorphic comments system that lets users add notes and discussion threads to any supported entity. Comments are attributed to the authoring user and written to the audit log.
Supported Entity Types
| Entity Type | Description |
|---|---|
offer | Comments on a property offer record |
tenancyTerm | Comments on a tenancy term record |
todo | Comments on a to-do item |
compliance | Comments on a compliance document |
Using the CommentThread Component
The CommentThread component can be embedded in any page that corresponds to a supported entity type.
import { CommentThread } from "@/components/comment-thread";
<CommentThread
entityType="offer"
entityId={offerId}
placeholder="Add a comment about this offer..."
title="Offer Comments"
/>
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
entityType | "offer" | "tenancyTerm" | "todo" | "compliance" | ✅ | — | The type of entity being commented on |
entityId | string | ✅ | — | The ID of the entity |
placeholder | string | ❌ | "Type a comment or note..." | Placeholder text for the comment input |
title | string | ❌ | "Comments" | Section header label for the comments list |
Behaviour
- Comments are scoped to the active organisation (
orgId) automatically. - The textarea enforces a 2,000 character limit with a live counter.
- On successful post, the input clears and the comment list and count badge refresh automatically.
- If posting fails, an inline error message is displayed beneath the form.
- The list shows a loading skeleton while fetching and an empty-state message when no comments exist.
- Each comment displays the author's name initial as an avatar, their full name, the comment body, and a relative timestamp (e.g. "3 minutes ago").
Displaying a Comment Count Badge
To show a comment count outside the CommentThread component (e.g. in a page header), use the comment.countByEntity tRPC query:
const { data: commentCount } = trpc.comment.countByEntity.useQuery(
{ entityType: "offer", entityId: offerId },
{ enabled: !!orgId }
);
{(commentCount?.count ?? 0) > 0 && (
<span>{commentCount?.count}</span>
)}
tRPC API Reference
comment.add
Creates a new comment on an entity. Verifies the entity exists and belongs to the caller's organisation, then writes to the audit log.
Input
{
entityType: "offer" | "tenancyTerm" | "todo" | "compliance";
entityId: string;
body: string; // max 2,000 characters
}
comment.listByEntity
Returns a list of comments for a given entity, ordered by creation date (newest first), with author names resolved.
Input
{
entityType: "offer" | "tenancyTerm" | "todo" | "compliance";
entityId: string;
limit?: number; // default 50
}
Response
{
items: Array<{
id: string;
body: string;
authorName: string;
createdAt: string; // ISO timestamp
}>;
}
comment.countByEntity
Returns the total number of comments for a given entity. Useful for rendering count badges without loading the full comment list.
Input
{
entityType: "offer" | "tenancyTerm" | "todo" | "compliance";
entityId: string;
}
Response
{ count: number }
Database Schema
The entity_comments table stores all comments:
CREATE TABLE entity_comments (
id TEXT PRIMARY KEY,
org_id TEXT NOT NULL,
entity_type TEXT NOT NULL, -- 'offer' | 'tenancyTerm' | 'todo' | 'compliance'
entity_id TEXT NOT NULL,
author_user_id TEXT NOT NULL,
body TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
);
Indexes:
(org_id)— organisation-level scoping(org_id, entity_type, entity_id)— primary lookup for per-entity comment lists(org_id, author_user_id)— per-author lookups(org_id, entity_type, entity_id, created_at)— ordered fetches
Notes
- The legacy
tenancyCommentstable andtenancy.addComment/tenancy.listCommentsprocedures are not affected by this release. They continue to function as before. The new generic system runs alongside the existing tenancy-specific comments. - Entity ownership is verified server-side before a comment is accepted — posting a comment to an entity that doesn't belong to your organisation will be rejected.