# 2.2. The Chat Experience The Chat area is the reason 3F exists: a complete, streaming, multi-modal conversation UI that shows its work. Component root: `src/components/chat/`. ## Layout ``` ┌─ conversation-header ─ model, session, conversation controls (rename/delete/pin) ─┐ │ │ │ exchange-list ┌─ right-panel ────────────┐│ │ ├ chat-exchange (user) │ session metadata, ││ │ ├ chat-exchange (assistant) │ attachments, tools, ││ │ │ └ exchange-content │ playlist ││ │ │ ├ reasoning-area └──────────────────────────┘│ │ │ └ assistant-response (structured) │ │ └ pending-indicator (streaming state) │ ├─ prompt-action-bar ─ input, images, submit, cancel, think ────┤ └─ toolbar · floating-player · floating-playlist (media) ────────┘ ``` ## Exchanges An _exchange_ is one user request + the assistant's full response cycle, keyed by `requestId` and persisted per session/conversation (`HarnessConversation.content`). The `exchange-list` renders the history; each `chat-exchange` shows: - the user prompt with its attached images; - the **assistant-response**, rendered from the harness's _structured_ schemas — comparisons, describe/OCR blocks, summaries, articles, product/news cards, image/video galleries, and stock-market answers render as UI elements, not raw prose; foreign-language articles/videos appear in an _international coverage_ aside so they augment rather than displace primary-language content. ## Custom scroll experience The exchange list supports **two scroll modes**, switchable per conversation from the SysCtl chat-navigation section and persisted in localStorage (`app` store): - **Carousel** (`mode: 'carousel'`) — a **vertical carousel** of full-height sections (`vertical-carousel/`). Each exchange section is exactly one viewport tall; the container snaps between them and scrolling **crossfades** the outgoing section into the incoming one (`useCarouselBlend` computes per-slide opacity from scroll position, so the blend reads as a seamless cut rather than a hard jump). `useVerticalCarousel` owns the auto-scroll/pin behavior: a new prompt snaps to the newest section, the bottom pin releases once the first response content arrives so the user can read from the top, and a manual scroll-away is preserved until the response finishes. - **Native** (`mode: 'native'`) — a continuous scroll container with variable-height sections (`useNativeScroll`): auto-scroll-to-bottom while the user is at the bottom, active-section highlight, and `scrollToSection` for history-click navigation. Both modes share `ScrollableExchangeList` (the container that renders either variant) and `CarouselSection` (one section = user exchange + its assistant exchanges). Switching modes preserves the section the user was reading instead of jumping to the bottom. The chat column is composed by `ChatMainColumn`; empty states render `ExchangeEmptyState` / `NoConversationPanel`. ## Stock-market answers `stockmarketitem` / `stockmarketlist` responses render with a **D3 chart suite** (`templates/stockmarket-response/d3-charts/`): `D3UnifiedStockChart` (OHLC/heatmap/HLC-area with crosshair tooltips, zoom, and range controls) and `D3StackedAreaChart`. Charts fetch cached end-of-day history from `GET /api/v1/stock-data/history` (see **1.1**), paginate via `use-stock-history-pager`, and size their range controls from the coverage endpoint. The previous `lightweight-charts` implementation was removed. ## Gallery carousel Image galleries with more than a couple of items render as a **carousel** (`sections/gallery-section/carousel/`): a scroll-snap track with prev/next buttons, active-index tracking from scroll position, and a header. `AssistantCarousel` orchestrates the track; `CarouselContent`/`CarouselHeader` render it. ## Reasoning areas Models with thinking support stream their chain-of-thought as a distinct phase. The `reasoning-area` renders it collapsed-but-inspectable inside the exchange — you can watch the model think without letting it dominate the answer. `use-chat-think` wires the `think` parameter so thinking is a per-conversation choice. ## Conversation controls The conversation header manages the thread's lifecycle directly: - **Rename** — inline title editing (`ConversationTitleEditor`). - **Delete** — remove the conversation and its uploaded objects. - **Pin / Unpin** — toggle a conversation between **temporary** and **persistent** (`ConversationHeaderActions`), so important threads survive session cleanup while throwaway ones stay ephemeral. ## Streaming UX - Tokens land on the Socket.IO room as `text`/`stream` payloads (see **1.4**) and append live; `pending-indicator` reflects the harness phase rather than a spinner-of-doom. - `use-frozen-read-snapshot` + `use-read-tracker` freeze the reading viewport while content grows, and release it precisely — no scroll fights mid-stream. - **Cancel** posts `/api/v1/harness/cancel` and waits for `cancel_result` — in-flight generation actually stops (see **1.2**). ## Input The prompt action bar handles multimodal input: text plus one or more images (validated before upload), `numCtx` awareness (`use-chat-context-size` shows how much of the window the current thread consumes — the feature many proprietary chats hide), model picker fed by the live catalogue, and submit/cancel that maps 1:1 to the harness lifecycle. ## SysCtl, DLQ, PProc interplay Chat is not isolated: model + preprocessing + provider overrides configured in **SysCtl** apply to the next request; failed jobs surface in **DLQ** where they can be repaired and re-instated; **PProc** previews exactly which image variants the server will compute from your uploads. ## Debuggability `components/debug` and the `debug` store mirror the raw socket/API traffic. Every exchange can be traced client-side from submission through room events to the persisted record — the dashboard treats observability as a user feature.