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)
Messages pages (redesigned 2026-07)
Both messages surfaces use the same model: a conversation inbox on the left and a unified timeline on the right. A conversation is one place comments live (an application's general thread, one document slot, the decision, AI notes, …). The page never opens empty — the pinned All messages view is the landing state.
Workspace Messages page (Communication → Messages), two columns:
- Left — one flat inbox across all applications. Each row: application (colored dot + title), where the conversation lives ("Documents › PCT-Antrag"), and the last-message preview; sorted by latest activity; red badge = unread count. Search, All/Unread/External chips, and a funnel menu for the category filter. The ⋮ row menu offers Show only this application and Open in Application.
- Right — All messages feed or one conversation. The feed shows every comment across all applications with clickable context chips (application ⌄ › scope › ↗ open page), date separators, newest/oldest sort, and "Load older" paging. Selecting a conversation opens its thread with the composer (replies always target that conversation explicitly).
- Filters compose and stay visible as removable chips (application, category) in both panes. "All" counts real messages; the action log is behind a sticky Activity checkbox.
Application Messages tab — the identical two-pane experience scoped to one application (no application column/line; otherwise same rows, same thread header, same filters).
Thread header (both pages): context path (application › category, each opening a small menu — Show only … / Open in …), conversation title with an Open in {page} shortcut (Documents links land on the slot's Comments tab), and the applicants chip (primary + "+n"; the popover lists every applicant with type, name and email incl. copy button).
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.