Skip to main content

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

Business rule

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.

TypeWho sees it
INTERNALStaff only (OWNER, ADMIN, MANAGER, MEMBER)
EXTERNALStaff + 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:

ScopeapplicationDocumentDefinitionIdUse case
Application-levelnullGeneral discussion about the application
Document-levelSet to a slot IDDiscussion about a specific document slot

Comment categories

CategoryUsed for
APPLICATIONGeneral application-level comments
DOCUMENTSDocument slot comments
DECISIONComments in the decision section
APPLICANTSComments 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.

ActionactionTypeVisibility
ApproveAPPROVEDEXTERNAL
RejectREJECTEDEXTERNAL
Re-requestRE_REQUESTEDEXTERNAL
Not applicableNOT_APPLICABLEEXTERNAL
File uploadedUPLOADEDEXTERNAL

Action log comments have parentId linking them to the relevant reply thread.

Threading & unread tracking

  • Comments have a parentId for reply threading
  • ApplicationCommentRead tracks per-user read state per comment
  • needsReply flag — whether a comment requires a staff response
  • workspaceUnreadCommentCount — total unread count across all apps in workspace
  • markCategoryCommentsAsRead — 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.


See also