Documentation Index
Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
Feature ID: PF-67
Status: ✅ Implemented (All 3 Phases Complete)
Last Updated: 2026-04-09
Spec: PF-67-internal-real-time-messaging.md
Plan: PF-67-internal-real-time-messaging-PLAN.md
Purpose: Documents integration points for the Platform Messaging layer. All cores consume messaging via @/platform/messaging (Pattern 1: Platform Integration Layer).
Integration Type
Pattern 1: Platform Integration Layer
- Location:
src/platform/messaging/ (implemented)
- Public API: Hooks and components exported from
@/platform/messaging
- Consumers: All cores (RH, HR, FA, GR, FW, FM, LO, IT, CE) for record threads and internal chat
| Spec | Integration Type | Purpose |
|---|
| PF-66 (Realtime Layer) | Direct | useRealtimeSubscription, useRealtimeBroadcast, useRealtimePresence |
| PF-10 (Notifications) | Platform Layer | Offline/mention notifications via outbox + edge function |
| PF-02 (RBAC) | Direct | User authentication, sender identity |
| PF-06 (Profiles) | Direct | Display names, avatars for message bubbles and presence |
| PF-30 (Permissions) | Direct | Permission keys: pf.messaging.view, .create_group, .create_channel, .moderate, .manage_settings |
| PF-56 (File Upload) | Platform Layer | File/image sharing in messages |
| PF-42 (Rate Limiting) | Direct | 60 messages/min/user rate limiting |
Consumer Core Integration (Downstream)
| Core | Usage | Record Thread Entity Examples |
|---|
| RH | Incident discussions, bed coordination | rh_significant_events, rh_beds |
| HR | Candidate feedback, onboarding | hr_employees, hr_candidates |
| FW | Approval discussions | fw_approval_requests |
| FA | Invoice/expense discussions | fa_invoices, fa_bills |
| GR | Audit remediation, contracts | gr_audit_findings, gr_contracts |
| FM | Work order coordination | fm_work_orders |
| LO | Issue discussions (migration from LO discussions) | lo_issues, lo_todos |
| IT | Ticket discussions | it_tickets |
| CE | Lead follow-up (internal only) | ce_contacts, ce_leads |
Contract: Cores embed <ChatPanel recordType="…" recordId="…" /> from @/platform/messaging; no direct table access.
API Contract (Implemented)
Hooks
| Hook | Signature | Purpose |
|---|
useConversations | (options?) => { conversations, isLoading, error } | List user’s conversations |
useMessages | (options) => { messages, isLoading, hasMore, loadMore, isConnected, realtimeLastUpdated, realtimeError } | Paginated messages + PF-66 subscription |
useSendMessage | () => { sendMessage, isSending } | Send message mutation (invalidates active thread on success) |
useCreateConversation | () => { createConversation, isCreating } | Create DM/group/channel |
useRecordThread | (recordType, recordId) => { conversation, getOrCreate, isLoading } | Get or create record thread |
useUnreadCounts | () => { globalCount, perConversation } | Unread count tracking |
useTypingIndicator | (conversationId) => { typingUsers, sendTyping } | Typing indicator broadcast |
useOnlineStatus | () => { onlineUsers, trackPresence } | Org-wide presence |
useMessageReactions | (conversationId) => { toggleReaction, … } | Emoji toggle via pf_toggle_message_reaction |
useMarkConversationRead | (conversationId, messages) => void | Debounced pf_mark_conversation_read for read cursor |
useMessageAttachmentUrl | (fileUrl) => { displayUrl, … } | Signed URL for file_url storage paths |
useMessageSearch | (conversationId) => { results, isSearching, … } | Full-text message search |
useChannels | (options?) => { channels, isLoading } | Browse/discover channels |
useConversationMembers | (conversationId) => { members, isLoading } | Member management |
useMessageActions | (conversationId) => { editMessage, deleteMessage, togglePin } | Edit/delete + pf_toggle_message_pin |
useConversationSummarization | (conversationId) => { summarize, summary, isLoading } | AI conversation summary |
useMessageTranslation | (messageId) => { translate, translation, isLoading } | AI message translation |
Components
| Component | Props | Purpose |
|---|
ChatPanel | recordType?, recordId?, title? | Embeddable chat panel for record pages |
MessageBubble | message, currentUserId | Single message display |
MessageThread | conversationId | Scrollable message list |
MessageComposer | conversationId, onSend | Message input area |
OnlineStatusBadge | userId | Status dot (green/yellow/gray) |
ChatNotificationBadge | — | Global unread count badge |
ConversationList | onSelect, selectedId? | Sidebar conversation list |
ConversationCreateDialog | onCreated? | New DM/group dialog |
ChannelBrowserDialog | onJoined? | Channel discovery/join dialog |
MemberManageSheet | conversationId | Member add/remove sheet |
MessageSearchDialog | onResultClick? | Global message search |
EmojiReactionPicker | onSelect | Emoji reaction selector |
FileUploadButton | onFileReady | Upload to storage; persists path on send (not signed URL) |
FilePreview | file | Uploaded file preview |
Edge Functions
| Function | Trigger | Purpose |
|---|
messaging-notify-mentions | DB trigger on pf_messages INSERT | PF-10 notifications for @mentions |
messaging-retention-cleanup | Cron: daily 2 AM UTC | Delete expired messages per retention policy |
lo-issue-discussions-to-pf67 | Manual invocation | Migrate LO issue discussions to PF-67 conversations |
Event Contracts
| Event | Source | Payload | Consumers |
|---|
new_message | pf_messages INSERT (Realtime) | { conversation_id, sender_id, content_preview } | Messaging UI, notification outbox |
typing | PF-66 Broadcast | { userId, displayName } | Typing indicator UI |
presence | PF-66 Presence | { userId, displayName, avatarUrl, status } | Online status badges |
Database Tables
| Table | Purpose | RLS Pattern |
|---|
pf_conversations | Conversation containers (DM, group, channel, record_thread) | SECURITY DEFINER: pf_can_view_conversation() |
pf_conversation_members | Membership, roles, read state | SECURITY DEFINER: pf_can_view_conversation() |
pf_messages | Individual messages | SECURITY DEFINER: pf_is_conversation_member() |
pf_message_read_receipts | Per-user read tracking (append-only) | Org access check |
RPCs (SECURITY DEFINER)
| Function | Purpose |
|---|
pf_toggle_message_reaction(p_message_id, p_emoji) | Atomic reaction map update (any member) |
pf_toggle_message_pin(p_message_id) | Atomic pin toggle (sender or pf.messaging.moderate) |
pf_mark_conversation_read(p_conversation_id, p_last_read_message_id) | Per-user read cursor + unread reset |
Security Considerations
- Multi-tenant isolation: RLS with SECURITY DEFINER helpers on all tables
- Organization immutability: BEFORE UPDATE trigger prevents
organization_id changes
- PHI/PII: No PHI in Realtime broadcast/presence payloads; follow constitution §4 for AI and logging
- Broadcast/Presence payloads: Whitelisted fields only (no PHI/PII)
- Attachments: Store bucket-relative paths on
pf_messages.file_url; issue short-lived signed URLs in the client at read time
Integration Matrix
- CROSS_CORE_INTEGRATIONS.md: PF-67 is a Platform Integration Layer consumed by all cores via
@/platform/messaging.
Utilities
| Utility | Import | Purpose |
|---|
parseMentions | @/platform/messaging | Extract @mentions from message content |
hasMentions | @/platform/messaging | Check if content contains mentions |
sanitizeContent | @/platform/messaging | Sanitize message content for storage |
formatMessageTime | @/platform/messaging | Format message timestamps |
createMessagePreview | @/platform/messaging | Create preview text for conversation list |
formatConversationName | @/platform/messaging | Format conversation display name |
calculateGlobalUnreadCount | @/platform/messaging | Compute total unread across conversations |
isAttachmentStoragePath | @/platform/messaging | Detect path vs legacy https:// in file_url |
References