Messages Feature
Overview
Messages = one comment system surfaced as conversation inboxes + unified timelines. A "conversation" is (application × category × leaf). Both the global page and the per-app tab land on All messages — the page never opens empty. Replying always happens inside a conversation whose target is explicit.
Key files
Frontend
src/features/messages/index.tsx # MessagesPagePerApp / MessagesPageGlobal, lifted filters
src/features/messages/components/global-inbox.tsx # cross-app inbox (appDotClass, CATEGORY_ICON)
src/features/messages/components/global-timeline.tsx # workspace feed, Load older, sort, filter chips
src/features/messages/components/conversation-inbox.tsx # per-app inbox (also DM rail — optional-controlled typeFilter)
src/features/messages/components/thread-detail.tsx # thread view + ThreadHeader + ApplicantsChip + ScopePageLink/MenuItem
src/features/messages/hooks/use-workspace-comments.ts # summary / stream / connection / mark-read / subscription
src/features/applications/detail/messages/components/comment-filter-bar.tsx # THE shared filter bar
Backend (ams-core)
document/WorkspaceCommentService.java # summary + workspaceApplicationComments (LIKE pattern built here)
document/domain/ApplicationCommentRepository.java
document/web/ApplicationCommentController.java
Architecture
workspaceCommentSummary ─┐ ┌─ GlobalConversationInbox (rail)
workspaceApplicationComments (window) ┼─ GlobalTimeline (All messages feed)
applicationCommentsConnection ────────┴─ ThreadDetail (one conversation / per-app all-mode)
markCategoryCommentsAsRead ← 1.5s linger in ThreadDetail
Common tasks
Add a filter / change filter behavior
- Edit
comment-filter-bar.tsxONLY (CommentFilterBar,passesCommentFilter,useShowActivity) — it feeds CommentTab (documents page), ThreadDetail and GlobalTimeline at once. - Cross-pane filters (app, category) live lifted in
index.tsxpage components.
Add an action to the application / category menus
One paradigm everywhere: two-action dropdown ("Show only …" / "Open in …"),
align="end". Touch: inbox row ⋮ (global-inbox), timeline app chip
(global-timeline), header app title + category crumb (thread-detail).
Add i18n keys
messages.* namespace in src/lib/i18n/translations/{en,de,ja}/messages.ts —
always all three; verify with a key-diff grep.
Known pitfalls
/messagesroute must stay incontainedMessagesRoute(authenticated-layout) — viewport lock at ALL sizes.- Feed bottom-anchoring needs the ResizeObserver; asc anchors bottom, desc top.
- Never
LOWER(CONCAT('%', :nullableParam, '%'))in JPQL (bytea plan error) — pass a ready LIKE pattern. - Exclude notebook-header comments (
parseTarget().type === 'notebook') from every list/count. - Global All-feed does NOT auto-mark read (by design); conversations do.
Full reference
See docu/docs/ai/claude/rules/messages.md
See docu/docs/domain/comments-messaging.md