Correspondence
Project-linked conversations, dissolved into the places where you already work.
The Design Principle
Every conversation in the portal is linked to a project. No exceptions. General questions that don’t belong to a project belong on a phone call or a text message — not in the system.
This constraint keeps the conversation log useful: when you open a project six months from now, you see every discussion that happened about it, in the same place as the certs, photos, and invoices.
There is no standalone Messages page. A separate Messages page gathers information in a place where context doesn’t exist. The project detail page already has the address, the permit, the SOW, the timeline, the files. Threading conversations alongside that context eliminates the need to cross-reference.
Where Conversations Live
| Surface | What You See |
|---|---|
| Project Detail | Full conversation thread with compose area, friday-ai access, file upload |
| Projects table | Red dot on the project ID when there are unread messages |
| Calendar | Red dot on the inspection event when the linked project has unread messages |
| Morning Briefing | Unread counts in the Messages stat row |
| Notification Bell | Unread message count in the bell dropdown |
| Sidebar Search | Conversations appear in quick-search results |
Red Dot System
Red is the only accent color. A 6px red circle on a project row or calendar event means: someone said something you haven’t read yet. It disappears when you open the conversation. No numbered badges, no colored pills — just presence or absence.
On calendar events, RFI dots take precedence over message dots.
The Thread
The Correspondence section on a project detail page shows one thread per conversation. Multiple conversations (general thread + RFI) show as tabs.
Visual Language
| Element | Treatment |
|---|---|
| Sender + timestamp | Monospace, uppercase, light — metadata recedes |
| Message body | Serif, normal weight — prose stands forward |
| friday-ai responses | Cream background, gold left border, dot-matrix attribution |
| Date breaks | 1px dashed rule with centered date |
| Actions | Lowercase monospace text links in a bottom bar |
| RFI conversations | Red left border |
Conversation Types
| Type | When to Use | Visual Cue |
|---|---|---|
| Project | General discussion about the job | Neutral left border |
| RFI | Request for information — something unclear or missing | Red left border, RFI badge |
| Review | Cert or invoice review discussion | Neutral, linked to OwnerReviewQueue |
No direct messages allowed. The backend rejects project_id: null. If it doesn’t belong to a project, it doesn’t belong in the portal.
Composing & Sending
| Action | How |
|---|---|
| Send a message | Enter or click the send arrow |
| New line | Shift + Enter |
| Ask friday-ai | Click ask friday in the action bar |
| Attach a file | Click attach in the action bar |
| Flag as RFI | Click flag rfi — converts the conversation type |
Messages are sent via REST and broadcast to all viewers via WebSocket (optimistic render).
Real-Time Features
| Feature | Behavior |
|---|---|
| Typing indicators | ”Jacob is typing…” — auto-clears after 4 seconds |
| Viewing presence | Thread header shows who else is viewing |
| Read receipts | Opening a conversation marks all messages as read |
| Live delivery | New messages appear for all viewers without refresh |
| Reconnection | Auto-reconnects with 3-second backoff |
Ask Friday (In-Thread)
The ask friday action invokes friday-ai directly inside the conversation thread. Friday reads the entire message history, understands the linked project, and responds as a visible participant.
| Ask Friday (in thread) | Friday Bubble (floating icon) | |
|---|---|---|
| Where | Inside project Correspondence | Floating eye icon, every page |
| Audience | Team-visible | Private |
| Storage | Permanent (SQLite) | Session only |
| Protocol | WebSocket (live broadcast) | REST (request/response) |
| Context | Full conversation + project data | Current page context |
Notification Routing
| Channel | When |
|---|---|
| Portal | Always — in-app notification + red dot |
| If recipient offline, delayed 2 minutes (cancellable) | |
| Field team (Darius) — critical/time-sensitive only | |
| SMS | If enabled in preferences — critical only |
DND hours respected. Consolidation batches multiple notifications within a 2-minute window.
Storage & Endpoints
| What | Where |
|---|---|
| Conversations & messages | /data/portal-auth/chat.db |
| WebSocket endpoint | /portal/chat/ws?token={jwt} |
| REST base | /portal/chat/* |
| Unread by project | /portal/chat/unread-by-project |
| friday-ai context | /portal/friday/chat-context |
| Project timeline | /portal/chat/project-timeline/{id} |