Messages is the portal's built-in chat system. Persistent, project-linked conversations replace email threads and scattered WhatsApp messages. All roles have access.
Open Messages in the portal sidebar. A gold badge appears when you have unread messages.
| URL | What It Opens |
|---|---|
| /messages | Conversation list + thread |
| /messages?conv={id} | Specific conversation |
| /messages?conv=new | New conversation dialog |
| /messages?conv=new&project_id={id} | New conversation pre-linked to a project |
Split panel. Conversation list on the left (320px), message thread on the right. On mobile (<768px), these become full-width single views with a back arrow to toggle between them.
Each entry shows:
Sorted by most recent activity. New messages push conversations to the top.
Click + New in the list header.
| Field | Required | Description |
|---|---|---|
| Subject | Yes | Title shown in the conversation list |
| Type | Yes | Direct, Project, RFI, or Review |
| Project ID | No | Links conversation to a project (Project and RFI types) |
| First Message | No | Sends an initial message on creation |
/messages?conv=new&project_id={id}, pre-filling the project link.
| Type | When to Use |
|---|---|
| Direct | General team discussion, not tied to a project |
| Project | Discussion about a specific project — adds "View Project" link in header |
| RFI | Request for Information — clarifying project requirements |
| Review | Cert or invoice review discussion |
Status: open (default, no badge), resolved, archived.
Priority: urgent, high, normal, low. Urgent shows a red indicator in the list.
| Action | How |
|---|---|
| Send | Enter or click Send |
| New line | Shift + Enter |
| Ask Friday | Gold Ask Friday button (next to Send) |
Messages are sent via REST for reliability, then broadcast to all viewers via WebSocket. You see an optimistic local copy immediately while the server confirms.
The page maintains a persistent WebSocket connection. Status indicator next to the page title: Connected or Reconnecting... (auto-retries with 3-second backoff).
| Feature | How It Works |
|---|---|
| Typing indicators | Shows "Jacob is typing..." below the thread. Auto-clears after 4 seconds. |
| Viewing presence | Thread header shows who else is viewing: Jacob viewing |
| Read receipts | Opening a conversation marks all messages as read. Badges clear. |
| Unread badges | Gold badge on sidebar + per-conversation counts. Header shows total unread. |
| Live delivery | New messages appear instantly for all viewers without refresh. |
The gold Ask Friday button in the compose area invokes Friday AI within the conversation. Friday reads the full message history, understands the linked project, and responds as a visible participant.
Friday's reply is saved to the database and broadcast via WebSocket — everyone viewing the conversation sees it. Friday messages have a gold left border and an "AI" label.
The portal has two chat interfaces. They are separate systems with one bridge.
| Messages | Friday Bubble | |
|---|---|---|
| Where | Full page at /messages | Floating eye icon, every page |
| Purpose | Team collaboration | Personal AI assistant |
| Storage | Permanent (SQLite) | Session only (lost on tab close) |
| Multi-user | Yes — broadcasts to all | Single user only |
| Protocol | WebSocket (live) | REST (request/response) |
| Typing | Shows who's typing | N/A |
| Read receipts | Yes | N/A |
The Ask Friday button in Messages is the one bridge. When clicked, Friday reads from the Messages database, responds, and saves its reply back as a chat message visible to all. The floating Friday bubble never touches the Messages database — its conversations are completely independent.
| What | Where |
|---|---|
| Conversations & messages | /data/portal-auth/chat.db |
| WebSocket endpoint | /portal/chat/ws?token={jwt} |
| REST endpoints | /portal/chat/* |
| Friday context endpoint | /portal/friday/chat-context |
All conversations and messages are stored permanently in SQLite. The WebSocket connection requires a JWT token (re-issued via /portal/chat/ws-token to work around httpOnly cookie constraints).