Comments & Messaging
Overview
AMS has a unified comment system used at multiple scopes and with configurable visibility. Understanding internal vs external is critical — it is a core business rule with security implications.
Visibility: Internal vs External
INTERNAL comments are visible only to org staff (OWNER, ADMIN, MANAGER, MEMBER). EXTERNAL comments are visible to both staff and clients.
Clients never see INTERNAL comments. This is enforced at the GraphQL query level.
| Type | Who sees it |
|---|---|
| INTERNAL | Staff only (OWNER, ADMIN, MANAGER, MEMBER) |
| EXTERNAL | Staff + clients |
When adding a comment, staff must explicitly choose the visibility. The default varies by context.
Comment scope
Comments can be scoped to two levels:
| Scope | applicationDocumentDefinitionId | Use case |
|---|---|---|
| Application-level | null | General discussion about the application |
| Document-level | Set to a slot ID | Discussion about a specific document slot |
Comment categories
| Category | Used for |
|---|---|
| APPLICATION | General application-level comments |
| DOCUMENTS | Document slot comments |
| DECISION | Comments in the decision section |
| APPLICANTS | Comments about applicants section |
The category is set automatically based on scope: document-level comments default to DOCUMENTS, others to APPLICATION.
Action log auto-comments
When staff review a document (approve, reject, re-request, mark not-applicable), the system automatically creates a comment with an actionType set. These are not user-written comments — they are system-generated audit entries.
| Action | actionType | Visibility |
|---|---|---|
| Approve | APPROVED | EXTERNAL |
| Reject | REJECTED | EXTERNAL |
| Re-request | RE_REQUESTED | EXTERNAL |
| Not applicable | NOT_APPLICABLE | EXTERNAL |
| File uploaded | UPLOADED | EXTERNAL |
Action log comments have parentId linking them to the relevant reply thread.
Threading & unread tracking
- Comments have a
parentIdfor reply threading ApplicationCommentReadtracks per-user read state per commentneedsReplyflag — whether a comment requires a staff responseworkspaceUnreadCommentCount— total unread count across all apps in workspacemarkCategoryCommentsAsRead— marks an entire category as read (triggered after 1.5s viewing)
Workspace messages inbox
The workspace-level Messages page shows a cross-application view of all comment threads:
Left panel: Tree of threads filtered by:
- All / Internal / External
- Unread
- Needs Reply
- Category
- Search
Right panel: Thread detail with full comment history
Application-level messages tab shows:
- Application-level comments (paginated, 20 per page)
- Document-level comment summaries per slot
Notification format
When a comment triggers a notification, the message uses this format:
"Domain · Application Title · Visibility"
Examples:
"Application · My Tax App · Internal"— app-level internal comment"Document · My Tax App · External"— doc-slot external comment
The parseContext() function in notification-panel.tsx splits on · to extract { label, title, visibility }.
GraphQL hooks (frontend)
Use useCreateApplicationComment(applicationId, applicationTitle?) from use-application-comments.ts:
// Build message for notification
const contextLabel = input.applicationDocumentDefinitionId ? 'Document' : 'Application'
const visibility = input.type === 'INTERNAL' ? 'Internal' : 'External'
const message = applicationTitle
? `${contextLabel} · ${applicationTitle} · ${visibility}`
: `${contextLabel} · ${visibility}`
applicationTitle must be passed by the caller — it is NOT fetched inside the hook.