Streaming chat checklist
Each row is a failure that keeps showing up in real chat UIs, what this app does about it, and the automated test that proves it. Copy the tests into your own app and point them at your selectors.
| Failure | What this app does | Test |
|---|---|---|
| Screen reader announces every token | Streaming text is not in a live region. Whole sentences go to a separate polite region, at most one announcement per 1.2 s. Code blocks are summarised. | e2e/a11y.spec.ts › no token flood |
| Auto-scroll yanks the reader back to the bottom | Follow mode detaches on any upward user gesture and only re-attaches when the user returns to the bottom or presses “Jump to latest”. | e2e/scroll.spec.ts |
| Dropped stream shown as a finished answer | A stream with no finish_reason and no [DONE] is marked interrupted. The partial text is kept and Retry is offered. | e2e/faults.spec.ts › disconnect |
| Hung stream spins forever | An idle watchdog shows “still waiting” after 2 s and gives up after 8 s, cancelling the body. | e2e/faults.spec.ts › stall, hang |
| 429 retried immediately or not at all | Honours retry-after-ms / retry-after, shows a countdown, retries once. | e2e/faults.spec.ts › rate limit |
| Error frame mid-stream loses the partial reply | Partial text stays, the message is marked failed, the error is announced assertively. | e2e/faults.spec.ts › server error |
| One bad frame kills the whole reply | Malformed JSON frames are skipped and logged; the reply finishes. | src/lib/chat-controller.test.ts |
| Split multi-byte characters render as � | Bytes are decoded with a streaming TextDecoder before SSE parsing. | e2e/faults.spec.ts › utf-8 |
| Tool-call arguments parsed before they finish | Fragments are joined by index; the tool runs only after finish_reason=tool_calls and the JSON parses. | e2e/faults.spec.ts › tool call |
| Stop button hides the UI but the request keeps running | Stop aborts the fetch and cancels the body; the route handler passes request.signal upstream. | src/lib/chat-controller.test.ts › stop |
| Re-rendering on every token | Frames update a draft; one commit per animation frame. The inspector shows frames vs commits. | e2e/perf.spec.ts |
| Open code fence flashes raw backticks | An unclosed fence renders as an open code block until it closes. | src/lib/blocks.test.ts |