# Build the "Token streaming" pattern: `StreamingMessage`

## Stack (edit this block to match your project)
- React 18+ with TypeScript: one function component, typed props, no `any`.
- Tailwind CSS, using your design system's tokens if you have them.
- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.
- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.

## What the pattern is for
Progressive text rendering that makes latency legible and lets users bail early.

Use it when: Every LLM text surface — its absence now reads as broken.

## What to build
Displays an AI response as it streams in, so people see progress immediately and can stop it early. It keeps the layout stable while text arrives, keeps the partial answer if stopped, and can show timing.

### Props
- `text`: string, the content received so far. The parent appends chunks; the component never fetches.
- `status`: "waiting" | "streaming" | "stopped" | "done" | "error".
- `format`: "plain" | "markdown". In markdown mode, buffer incomplete syntax so half-written links, code fences or tables never flash.
- `onStop()`, `onContinue()` and `onRegenerate()`: callbacks for the controls.
- `timing?`: { firstTokenMs?: number; totalMs?: number }, shown quietly when present.
- `reserveLines?`: number, the minimum height to reserve before the first token (default 3).

### States it must render
- Waiting: reserved space with a quiet "Thinking" indicator; Stop is already available.
- Streaming: text grows with a caret at the end and a prominent Stop. The view follows new text only if the reader is already at the bottom.
- Stopped: the partial text stays, with a note like "Stopped at 42 of about 120 words" and Continue and Regenerate actions.
- Done: the caret is removed, controls are replaced by actions (copy, regenerate), with optional timing such as "first words 0.5 s, full answer 6.2 s".
- Error: keep what arrived, say the stream broke, and offer Retry from where it stopped.

### Interactions
- Stop (the button, or Escape while focus is in the conversation) calls `onStop` and shows the stopped state at once, without waiting for the server.
- Continue resumes from the existing text instead of starting over; Regenerate starts a fresh answer.
- If the reader scrolls up while streaming, stop auto-scrolling and show a "Jump to latest" button.

### Edge cases
- Chunks that split a word, emoji or markdown token: render only complete characters and tokens.
- Very fast streams: batch DOM updates per animation frame so the text never stutters.
- Very long answers: don't re-render the whole message on every chunk.
- An empty response with status done: show "No answer returned" with Retry.

## Must avoid
Streaming into a layout that reflows on every chunk, so reading is impossible until it stops.

## Engineering constraint
Reserve layout space up front; buffer markdown so partial syntax does not flash.

## Accessibility
- Announce status changes (started, stopped, done, error) in a polite live region, not every chunk; expose the full text once done.
- Stop is reachable by keyboard while streaming and keeps focus when pressed.

## House rules
- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live="polite"`; respect `prefers-reduced-motion`.
- All user-facing text comes from props (or a `labels` prop) so it can be translated.
- One consistent accent marks anything the AI made or is doing; amber means "needs a person", red means risky, green means done. Never rely on colour alone.
- Fits a 360 px wide container without horizontal scrolling.

## Acceptance checks
- [ ] Every state above can be rendered by passing props, with no internal fake data.
- [ ] Every interaction and edge case above behaves as described.
- [ ] Nothing in "Must avoid" can happen with this component.

## Deliver
1. The component file with its exported prop types.
2. A short usage example that renders each state.
