
When you’re building out a GRC platform, you eventually hit that moment where “leaving notes” isn’t enough. Records, requirements, risks—everything ends up needing proper conversation around it. And that conversation has to live with the object, not in someone’s inbox or a random Slack thread. After ignoring this part for way too long, I finally decided to build a proper comments module that feels native to the way the rest of the platform works.
One thing I knew right away: this couldn’t be some generic free-floating comment widget. In a multi-tenant environment, comments are tied to context and permissions just as much as the underlying data is. Comments on a record need to behave differently than comments on a requirement. Some tenants will barely use it; others will shove half their workflow into it. So the design had to be flexible, tenant-safe, and reusable across every model I throw at it.
Another challenge was making the experience actually usable. If you ever worked with messy comment chains in legacy systems, you know how quickly things turn into chaos—no structure, no filters, no sense of what’s open or closed. I didn’t want that. If a team uses comments to track open questions or internal reviews, they need a clean way to sort, filter, and follow activity without scrolling through a wall of noise. That pushed me to design something closer to threaded discussions than basic “leave a note” functionality.
And because this platform is built on relationships—records connected to requirements, requirements connected to libraries—I wanted users to be able to see the full picture without losing context. If a record has twenty requirements under it, the comments on those requirements shouldn’t be hidden away in some other tab. They should be accessible, filterable, and tied directly into the conversation happening on the record. With all that in mind, I ended up writing a full specification for the comments module before touching a line of code.
Here are specifications that I outlined:
Comments Module Specification
1. Scope & Context
- Add a Comments module that:
- Exists only in relation to other models (no standalone comments).
- Is shown inside a “Comments” tab on detail pages.
- Is tenant-scoped: each comment has
tenant_idand can only be accessed within the current tenant’s scope.
- Initial integration targets:
records.showrequirements.show
- The module must be:
- Polymorphic (
commentable_type,commentable_id). - Implemented as a reusable component that can plug into different models.
- Polymorphic (
2. Data & Model Requirements
- Comments must include at least:
tenant_idcommentable_typecommentable_idparent_id(nullable;nullfor primary comments)body(plain text with line breaks)statusfor primary comments only (open/closed)created_byupdated_by(nullable)closed_by(nullable, for primaries)closed_at(nullable, for primaries)- Timestamps (
created_at,updated_at)
- Formatting:
- Comments are plain text with line breaks.
- No rich text / markdown is required.
- No delete functionality:
- No requirement to delete comments (no UI or behavior for delete).
3. Permissions & Tenant Scope
- View:
- User must be authenticated within the tenant and able to view the parent model.
- Create:
- Authenticated users within tenant scope can create comments on models they can view.
- Edit:
- Only the author of a comment can edit it.
- Open/Close/Reopen:
- Only applicable to primary comments.
- Only users authorized by business rules (e.g., author and/or admins) can change status.
- All operations must enforce:
tenant_idmatching current tenant.- Access rules for the parent
Record/Requirement.
4. Filters, Sorting & Pagination
4.1 Filters
- Comments section has three filters:
- All
- Open (default)
- Closed
- Filter semantics:
- “Open” / “Closed” filters are based on the status of the primary comment (thread).
- All child comments are displayed under their parent when the parent passes the filter.
- Filters must interact correctly with pagination (see below).
4.2 Sorting
- Sorting modes:
- Recent Activity (thread-level):
- Each thread’s “activity timestamp” is the maximum of:
- Parent’s
created_at - Parent’s
updated_at - All child comments’
created_atandupdated_at
- Parent’s
- “Newest” = threads ordered by latest activity timestamp descending.
- “Oldest” = threads ordered by activity timestamp ascending.
- Each thread’s “activity timestamp” is the maximum of:
- (Optional extension later: simple “Created” sort; current spec focuses on Recent Activity.)
- Recent Activity (thread-level):
- Within a thread:
- Replies are sorted by
created_atascending.
- Replies are sorted by
4.3 Pagination
- Pagination options:
All15(default)50100
- Pagination applies to primary comments only (i.e., threads):
- Children count does not affect pagination.
- All child comments for a given parent are shown with that parent when the parent is included.
- Filters (All/Open/Closed) and sort (Recent Activity) operate over the set of primary comments being paginated.
5. Threading Rules
- Only primary comments can have a status (
open/closed). - Child comments:
- Have no independent open/closed status.
- Are always displayed under their parent.
- Display:
- Comments are always shown as a threaded tree, not as separate flat entries.
- Depth:
- Allow unlimited threading depth at the data level.
- Visually:
- Indent threads to fit within typical screen resolution.
- After a certain depth, visually flatten indentation while still indicating parent/child relationships.
6. Record-specific Behavior (Requirements Overlay)
- Applies only to Records (
records.show), not Requirements. - Add a checkbox:
- Label: “Show all comments from requirements”
- When the checkbox is checked:
- Include comments from all sub-requirements linked to this Record.
- For each requirement-originated primary comment shown in the Record context:
- Display a button with the requirement’s
[custom_id]on the right side of the primary comment header. - Clicking the
[custom_id]button:- Opens a popup (same behavior as mappings tree popup):
- Resizable.
- Draggable.
- The popup shows:
- Requirement breadcrumb.
- Requirement title (with hyperlink to
requirements.show). - Requirement description.
- Reuse the top section layout from
requirements.show.
- Opens a popup (same behavior as mappings tree popup):
- Display a button with the requirement’s
- Requirement comments pulled into the Record’s Comments tab must:
- Respect the same filters (All/Open/Closed).
- Respect the same sorting (Recent Activity).
- Respect the same pagination rules.
- Respect user permissions on the Requirements.
7. UI/UX for Comment Cards
Each comment card (primary or child) must show:
- User identity:
- Display user’s name.
- Avatar:
- Round shape.
- 2-letter initials, generated from first and last name.
- For single-word names, use the first two letters or a defined rule.
- Background color is deterministic, e.g., based on user ID hash.
- Timestamps:
- “x minutes ago” / “x hours ago” style relative timestamp.
- Hover tooltip showing full datetime.
- If edited:
- Show an “edited” indicator and an edited timestamp, also with tooltip.
- Actions:
- Reply button.
- Action menu (“…”):
- Option to edit the comment (author only).
- For primary comments only:
- Show either:
- “Close thread” (if currently open), or
- “Reopen thread” (if currently closed).
- Only one relevant toggle is visible at a time.
- Show either:
7.1 Closed Thread Styling
- For primary comments with status
closed:- Grey out the thread visually (e.g., reduced opacity / muted colors).
- Show a small “Closed” badge near the header.
- Display closed timestamp:
- “Closed x minutes ago” with tooltip showing full datetime.
- Behavior:
- Do not allow new comments (replies) to be added to closed threads.
- Closed threads can be reopened via the toggle.
8. Reply Behavior & New Comment Box
8.1 Replies
- Every comment (primary or child) can receive replies (thread continues downward).
- When the user clicks Reply on a comment:
- Show an inline reply editor (text field/area) under that comment.
- Change the “Reply” button text to “Replying”.
- If the user clicks Reply on another comment:
- Hide the previously shown reply editor.
- Change the previous comment’s “Replying” text back to “Reply”.
- Keyboard behavior:
- Pressing Enter must not submit the comment.
- Comments are submitted only by clicking an explicit “Post” button.
- Cancel reply options:
- Clicking “Replying” again hides the reply editor and restores the button text to “Reply”, or
- Provide a small “Cancel” link next to the reply textarea that hides the reply editor and restores the button text.
8.2 New Primary Comment Box
- At the bottom of all comments:
- Show a new comment textbox, using the same style as the reply editor.
- When a reply editor is active (user is replying to a comment):
- Hide the bottom new-comment box.
- When no reply editor is active:
- Show the bottom new-comment box again.
- Empty state:
- If there are no comments:
- Display text like “No comments yet”.
- Still show the new comment box so users can add the first comment.
- If there are no comments:
9. Validation
- Comment body is required:
- Non-empty.
- On validation failure:
- Show a clear validation message near the relevant input.
- Do not clear the user’s input.
10. Activity Log Requirements
- Use Activity Log to track comment-related events in relation to the parent model (e.g.,
Record,Requirement). - Events to track:
comment_createdcomment_editedcomment_opened(thread opened)comment_closed(thread closed)comment_reopened
- For each event, log:
- Who performed the action (user).
- When it occurred (timestamp).
- Parent model (
commentable_type,commentable_id). - For status changes:
- Previous status.
- New status.
Leave a Reply