Skip to main content

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

  1. Edit comment-filter-bar.tsx ONLY (CommentFilterBar, passesCommentFilter, useShowActivity) — it feeds CommentTab (documents page), ThreadDetail and GlobalTimeline at once.
  2. Cross-pane filters (app, category) live lifted in index.tsx page 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

  • /messages route must stay in containedMessagesRoute (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