> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cfosilvia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CHAT MAIN FLOWS

# Chat page main user flows

**Required reading before changing chat-page behavior**: switching chats,
loading a chat, the composer and Send, streaming and re-attaching, or the
history panel. Ticket: CFO-9379.

## Why this exists

CFO-9358 made switching chats instant. Its tests checked how fast a chat
appeared. None of them did what users do next: type and send. The change kept
Send disabled for about half a second after each switch, and it reached
production (fixed in CFO-9375, #4747 / #4750).

The lesson is that a change to the chat page is tested against **what users
actually do on it**, measured in PostHog, not only against the property the
change set out to improve.

## The flows (PostHog, 14 days to 2026-10-08)

What users do next, on `/chat`, after each of these events. "p25" is the
fastest quarter of users.

### After switching chats (`chat_session_selected`)

| Next action | Times | Median | p25 | Under 3 s |
| - | - | - | - | - |
| Switch to another chat | 1,741 | 11.3 s | 3.2 s | 423 |
| Start typing (`composer_draft_started`) | 1,210 | 20.3 s | 7.0 s | 40 |
| Toggle the history panel | 1,115 | 0.0 s | 0.0 s | 1,084 |
| Send a message | 646 | 76.4 s | 34.3 s | 2 |
| Leave via the sidebar | 505 | 33.7 s | 13.2 s | 13 |
| Upload a file | 122 | 31.3 s | 10.2 s | 3 |

### After sending (`chat_message_sent`)

| Next action | Times | Median |
| - | - | - |
| Answer completes | 27,786 | 124.0 s |
| Answer fails | 3,241 | 96.3 s |
| Start typing the next message | 3,012 | 46.3 s |
| Leave via the sidebar | 1,203 | 31.5 s |
| Queue another question | 594 | 65.7 s |
| Stop the answer | 487 | 18.0 s |
| Switch chats while answering | 425 | 17.5 s |

## What must always work, and the test that guards it

| Flow | Test |
| - | - |
| Switch back to a chat opened before, type at once: Send works before the server confirms | `app/(app)/chat/[[...sessionId]]/__tests__/chat-main-flows.test.tsx` |
| Hop quickly between chats: the screen ends on the last chat clicked, even if an earlier one answers last | same file |
| Switch to a chat never opened: it loads, then typing and sending work | same file |
| Switch away while an answer streams, come back after it finished: no stuck spinner or Stop button | `contexts/__tests__/reattach-settle.test.tsx` |
| Send while a cached chat is still being confirmed: the send wins, the late confirmation does not overwrite it | `contexts/__tests__/shared-chat-context-cached-load.test.tsx` |

The page-level tests render the real `ChatPage`, `SharedChatProvider` and
composer, remount the page per route like the App Router does, and hold the
server's answers back so each case controls what has loaded. Only the network
and the streaming hook are mocked. `chat-main-flows.test.tsx` was checked to
fail when the CFO-9358 regression is put back.

## The rule for a chat-page change

1. **Re-measure.** Run the two queries below for the components you touch. If
   a new next action shows up high in the list, or an existing one gets much
   faster, it is a flow to cover.
2. **Replay.** Each flow your change can affect needs a page-level test that
   does what the user does, at their speed (for example, type straight after
   switching), and asserts what the user can do, such as Send being enabled.
   Assert on behavior, not on internal state.
3. **Keep the suite green.** `chat-main-flows.test.tsx` must pass. If a change
   makes a case wrong, change the case in the same PR and say why.
4. **Say what was not tested live.** If a live check on staging is blocked
   (for example, the e2e account is out of allowance), say so in the PR. Do
   not treat a blocked check as a passed one.
5. **Watch after release.** `composer_send_blocked` with
   `reason = 'submit-disabled'` ran at about 0.35 an hour in the week before
   2026-10-08. A rise after a chat release means users are hitting a disabled
   Send.

## Re-measuring

Run these through the PostHog MCP (`execute-sql`). Change the anchor event in
the last `WHERE` to measure what follows another action.

```sql theme={null}
WITH ev AS (
  SELECT person_id, timestamp, event FROM events
  WHERE timestamp > now() - INTERVAL 14 DAY
    AND properties.$pathname LIKE '/chat%'
    AND event IN ('chat_session_selected', 'composer_draft_started',
      'chat_message_sent', 'composer_send_blocked', 'chat_history_panel_toggled',
      'sidebar_nav_clicked', 'scroll_to_bottom_clicked', 'chat_file_upload_started',
      'voice_input_started', 'composer_add_menu_opened', 'chat_question_queued',
      'artifact_downloaded', 'generative_ui_interacted')
), seq AS (
  SELECT person_id, timestamp, event,
    leadInFrame(event) OVER w AS next_event,
    leadInFrame(timestamp) OVER w AS next_ts
  FROM ev
  WINDOW w AS (PARTITION BY person_id ORDER BY timestamp
    ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
)
SELECT next_event, count() AS n,
  round(quantile(0.5)(dateDiff('millisecond', timestamp, next_ts)) / 1000, 1) AS p50_s,
  round(quantile(0.25)(dateDiff('millisecond', timestamp, next_ts)) / 1000, 1) AS p25_s,
  countIf(dateDiff('millisecond', timestamp, next_ts) < 3000) AS within_3s
FROM seq
WHERE event = 'chat_session_selected'
  AND next_ts > timestamp AND dateDiff('minute', timestamp, next_ts) < 10
GROUP BY next_event ORDER BY n DESC LIMIT 15
```

For after-send flows, use `'chat_message_sent'` as the anchor and add
`'chat_response_completed'`, `'chat_response_failed'` and
`'chat_generation_stopped'` to the event list.

Blocked sends by reason:

```sql theme={null}
SELECT properties.reason AS reason, count() AS n
FROM events
WHERE event = 'composer_send_blocked' AND timestamp > now() - INTERVAL 7 DAY
GROUP BY reason ORDER BY n DESC
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.