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

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

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