lilith-platform.live/codebase/@features/vip/docs/messages.md
2026-04-19 23:39:57 -07:00

58 lines
2.2 KiB
Markdown

# Messages Tab
**Nav id**: `messages` (default tab on load)
**Icon**: ✦
---
## What it shows
A full-screen chat thread between Quinn and the client. Quinn's messages appear on the right (gold-tinted bubbles), client messages on the left. Newest messages scroll into view automatically.
---
## Data flow
### Initial load
`fetchMessages(token)``GET /vip/messages/:token` — fetches the full message history on mount and on window focus.
After fetch, `markRead(token)` fires — `PUT /vip/messages/:token/read?side=client` — clears the unread badge server-side.
### Real-time updates
`useMessageStream` (`src/hooks/useMessageStream.ts`) opens a Server-Sent Events connection to `GET /vip/messages/:token/stream`. Incoming events are merged into local state, deduped by `msg.id`.
If SSE is unavailable or disconnects, the hook falls back to polling via `fetchMessages` on a timer.
### Sending
`sendMessage(token, text)``POST /vip/messages/:token` — appends the new message optimistically to local state on success.
Keyboard shortcut: Enter submits, Shift+Enter inserts a newline.
---
## Push notifications
When `Notification.permission === 'default'`, a soft prompt appears above the composer. On approval, `registerPushSubscription(token)` sends the browser's `PushSubscription` to `PUT /vip/push/:token/push-subscription`. The server stores it and sends a Web Push on new inbound Quinn messages.
Push state machine: `idle → requesting → granted | denied`. Once `granted` or `denied`, the prompt is hidden.
---
## Error states
| Condition | Behavior |
|-----------|---------|
| `invalid_token` on fetch | Sets `invalidToken = true` → portal renders "link no longer valid" |
| `invalid_token` on send | Same |
| Network error on fetch | Error card with "Try again" button |
| Network error on send | `error` state shown inline |
---
## Non-obvious details
- `bottomRef` div at the end of the message list is scrolled into view whenever messages change **and** the messages tab is active — prevents unwanted scroll when on other tabs.
- Messages tab is the only tab with a `<form>` composer and a push prompt — other tabs just render their content in the `flex: 1` area between header and nav.