> ## 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.

# Internal Real-Time Messaging – Integration

> Pattern 1: Platform Integration Layer

**Feature ID:** PF-67\
**Status:** ✅ Implemented (All 3 Phases Complete)\
**Last Updated:** 2026-04-09
**Spec:** [PF-67-internal-real-time-messaging.md](https://github.com/Encore-OS/encoreos/blob/development/specs/pf/specs/PF-67-internal-real-time-messaging.md)\
**Plan:** [PF-67-internal-real-time-messaging-PLAN.md](https://github.com/Encore-OS/encoreos/blob/development/specs/pf/plans/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

***

## Platform Foundation Dependencies (Upstream)

| 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

* **Spec:** [PF-67-internal-real-time-messaging.md](https://github.com/Encore-OS/encoreos/blob/development/specs/pf/specs/PF-67-internal-real-time-messaging.md)

* **Plan:** [PF-67-internal-real-time-messaging-PLAN.md](https://github.com/Encore-OS/encoreos/blob/development/specs/pf/plans/PF-67-internal-real-time-messaging-PLAN.md)

* **Tasks:** [PF-67-internal-real-time-messaging-TASKS.md](https://github.com/Encore-OS/encoreos/blob/development/specs/pf/tasks/PF-67-internal-real-time-messaging-TASKS.md)

* **Architecture:** [REAL\_TIME\_ARCHITECTURE.md](/architecture/REAL_TIME_ARCHITECTURE) §8

* **PLATFORM\_INTEGRATION\_LAYERS.md:** PF-67 entry added — `@/platform/messaging` exports 15 hooks, 14 components, and 7 utility functions
