Forty percent of a thought
A half-finished sentence is readable, a half-finished recommendation is dangerous — deciding which parts of an answer may be shown before they are complete is a product decision, not a rendering detail.
Ask the flight chatbot the Tokyo question and one of the sentences in the answer will be some version of ANA has award space on the 15th for 60,000 points each way. On the wire that sentence does not arrive as a sentence. It arrives as a run of fragments, and one of the intermediate states of the screen is:
ANA has award space on the 15th for 6For about a third of a second the interface asserts that the seat costs six points. Then sixty. Then six hundred. Then six thousand, sixty thousand, and finally the real number. Every one of those was rendered, legibly, in the same typeface as the true answer, with nothing marking it as provisional.
Nobody reads it that fast, and that is the usual defence. It is also not the point. The point is that a prefix of that sentence is not a shorter version of that sentence — it is a different claim, and the interface cannot tell the difference because it is rendering characters.
The mechanism: a prefix is a claim
Take four components out of a real answer and stream each one halfway.
| Complete | A prefix of it | What the prefix asserts |
|---|---|---|
| A paragraph of reasoning | Half the paragraph | The same thing, less of it |
| 60,000 points | 6 points | A different price, off by four orders of magnitude |
| You cannot transfer Amex points to ANA | You can transfer Amex points to ANA | The opposite of the answer |
| Book through Virgin Atlantic, not ANA directly | Book through Virgin Atlantic | An instruction missing its correction |
Only the first row is safe, and it is safe for a specific reason: prose degrades gracefully because a reader who sees half a paragraph knows they are seeing half a paragraph. The sentence is visibly cut off. Nothing about “6 points” or “you can transfer” looks cut off. A truncated number is a well-formed number. A negation that has not arrived yet is a grammatical sentence with the opposite meaning.
That is the whole mechanism and it has nothing to do with speed. A faster stream produces the same wrong intermediate states for less time. The decision you are being asked to make is not how fast should this arrive but which components are safe to render before they are finished, and that is a product decision with a defensible answer per component, not a rendering default you set once.
The forty percent is not a measurement
Neither is any other number you might reach for here. There is no published threshold for how much of an answer is enough, no measured point at which a partial recommendation starts misleading people, and this course is not going to invent one. Forty percent in the title is an arbitrary marker for “mid-arrival” and that is all it is.
What replaces the missing number is a test you can apply to a component without measuring anything:
Can a prefix of this component assert something the complete version would contradict?
If no, stream it. If yes, you have three honest options and no fourth one.
- Hold it. Render nothing where it will go until it is complete. Costs you the thing streaming bought — the user is back to waiting, for exactly the part they came for.
- Mark it. Render it, and render the fact that it is unfinished, in a way that survives a glance. Not a subtle opacity change. A price that is still arriving should not look like a price.
- Restructure the answer. Ask for the components in an order where the contradicting part arrives first. A negation that leads is not a negation that inverts.
The third one is underused and it is the strongest, because it removes the problem rather than dressing it. If the model is asked for a verdict field before a justification field, the verdict never streams through its own opposite — and that is a request-shape decision, made on the server, that the UI simply inherits.
The disclosure table
Do this once per surface, in writing, and the rest of the module becomes implementation. Every component the answer can contain gets a row and a decision:
component prefix-safe? decision why
narration line yes stream prose, visibly cut off
airline name no mark "Uni" is a plausible airline
points cost no hold 6 vs 60,000 reads as a price
cabin no hold "econ" prefixes "economy"
routing / stops no mark partial route reads as complete
"cannot transfer" no restructure ask for the verdict field first
booking link no hold a half-built URL is clickable
tool-call name yes stream naming work in progress is the pointTwo things fall out of writing it. The first is that the row you argue about longest is usually the one where the honest answer is restructure. The second is that a table of decisions with reasons is the artefact you hand a portfolio-company team who did not read this course — far more use than a component library, because it is the part they cannot derive from your code.
Where people get burned
The failure this prevents is not embarrassing, which is why it ships. Nobody files a bug that says “the price was briefly wrong.” What happens instead is that a user glances at a partial number, looks away to check their Amex balance, comes back, and now holds two numbers with no memory of which was which. On an advisory surface the damage is entirely to trust, it accumulates, and it is invisible in every metric you have.
It is also the failure most likely to survive review, because in a demo the reviewer watches the whole stream from the first token and therefore sees each wrong intermediate state get corrected. Users do not watch. They glance.
Check your recall
Answer from memory — no scrolling back.
Retrieval check
Someone argues the whole problem disappears if you buffer to sentence boundaries before rendering. Name what that fixes and what it does not.
Check your answer
It fixes the negation case and the mid-word case, which are real. A sentence that is only revealed once its terminal punctuation arrives never shows you its own opposite, and that is a genuine improvement over character-by-character rendering.
It does not fix the price. “ANA has award space on the 15th for 60,000 points each way.” is one sentence, and the model may correct itself two sentences later after a tool result comes back — the sentence was complete and still wrong. It also does not fix anything outside prose: a flight card, a table row, a booking link. And it costs you the property that made streaming worth doing, because a sentence-buffered stream on a slow model is a sequence of pauses. Sentence buffering is a treatment for one row of the disclosure table, not a substitute for the table.
Hands on
Write the disclosure table for the flight chatbot
Done when: ARTIFACT.md carries a row for every component the answer can contain, each with one of stream / mark / hold / restructure and a one-line reason — and at least one row is implemented in the running app so a held or marked component behaves differently from a streamed one.
- Add a
Module 2 — Rendering a partial answersection tolearning/streaming-ui/ARTIFACT.md, and under it a heading for this lesson. The file grows a field per lesson; do not pre-write the other three lessons’ fields. - Enumerate the components an answer can contain. Get this from real transcripts, not from memory — run five genuinely hard questions and list what actually came back. Expect at least one component you did not know the model was producing.
- For each one, apply the test: can a prefix of this assert something the complete version would contradict? Write the answer, not a feeling. Where you are unsure, stream it deliberately and read the intermediate states.
- Assign each row one of
stream,mark,hold,restructure, with a reason. A row with no reason is a row you will reverse under pressure from whoever thinks the UI feels slow. - Implement one
holdormarkrow in the real chatbot. The pass condition is behavioural: with the network throttled hard enough to watch, that component must be visibly distinguishable from a finished one, and a screenshot taken mid-stream must not contain a claim the finished answer contradicts. - Bring the table into the chat. I will push on any row marked
streamwhose prefix I can turn into a different claim, and on anymarkthat a glancing user would miss.
What this does not cover
This lesson gives you a decision per component and no way to express it. Deciding that the points cost is held until complete is worthless if the answer arrives as one undifferentiated string, because a string has no components — you would be pattern-matching a price out of prose and holding a substring. The lesson on streaming structure rather than text is where the decision becomes representable: a schema-shaped object whose fields arrive separately, which is what makes “hold this field” a thing you can write.
The prose half stays hard even then. An answer that streams markdown — a comparison table, a fenced block of transfer steps — is structurally invalid for most of its life, and that is the markdown-that-is-still-arriving lesson. What to draw in the space a held component will eventually occupy is the honest-skeleton lesson at the end of this module, and it is the direct cost of every hold you just wrote down.
What none of this covers is what the decisions cost to render. Re-rendering a card on every chunk, memoization boundaries, and whether any of it fits an interaction budget belong to Front-end performance under streaming load, which owns measurement in full. This course owns what may be shown. That course owns what showing it costs.
Read this next — primary source
From Judgment to Interference: Early Stopping LLM Harmful Outputs via Streaming Content MonitoringarXiv preprint 2506.09996, June 2026 — free; a preprint, not peer-reviewed. Fetched 2026-09-05
Read it for one reframing, and be clear about what it is not. It does not study interfaces, users, or trust, and it does not establish the argument this lesson makes — it is about safety filtering on a token stream. What it does carry, worked out rigorously in a domain where the stakes are legible, is the structural move this whole module turns on: streaming converts a decision over a finished object into a decision over a prefix, and a prefix is not a smaller version of the object. Read the problem setup and the framing; the detector architecture is not this course’s subject.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.