TL;DR
A production React AI chat UI has five parts that share one message type: a thread, the messages, a composer, a reasoning row and a model picker. The work is in their behavior: follow the stream only while the reader is at the bottom, re-render only the message that changed, repair half-written markdown, ignore Enter during IME composition and keep the composer above the phone keyboard. Stream each delta into a new object for that one message, and abort with an AbortSignal so Stop keeps the partial answer.
A React AI chat UI looks finished after an afternoon: a list of bubbles, a textarea and a fetch that streams text. Real use finds the gaps. The thread drags the reader back down while they reread an earlier answer, a half-written code fence turns the rest of the reply into code, someone typing with a Japanese IME sends half a sentence with Enter, and on an iPhone the composer slides behind the keyboard.…