2.2 KiB
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
bottomRefdiv 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 theflex: 1area between header and nav.