ONC2-8555 · app branch to dev, api#1428 to main · updated 28 Oct, 4 am IST
The tutor chat, rebuilt
Rezzy, Savvy and Casey now show a reply in the order it happened, a step at a time. The chat stays still while the learner reads.
What learners see
Steps sit where they happened
Before, a reply's thinking steps were pulled above its text, so the answer jumped around as steps arrived. Now the steps sit in one "Thinking" group, in order. A step shows the moment it starts. Its detail fills in once it finishes.


The question stays pinned
Before, the list chased the stream to the bottom, so the learner couldn't read from the top. Now the question moves to the top once and the list stays put. When the reply is done the group reads "Thought for 15s". A round "Jump to latest reply" button shows while part of the reply is below the screen, and takes you to the follow-ups.



Smaller things
- Text arrives a paragraph or list item at a time, not word by word with a fading tail. The server side is api#1428.
- A stopped reply reads as stopped, with no spinner left running.
- A retried question shows its new reply once, with its own timer.
- The question screen's explanation chat and the drill's report card use the same reply and pin.
- Every course gets the same chat. Only the tutor's name and art change.
- Native pieces where the platform has them. iOS uses the SwiftUI spinner, Android the Compose spinner and shimmer, web our own spinner.
Speed
Measured on the iPhone 18 Pro simulator with a dev build, before the change:
- The composer, header, menu and voice stage each re-rendered about 111 times per reply.
- The hidden past-chats drawer refetched every 1.5 s while a reply ran. Its refetch at the end of a text-heavy reply froze the chat for 3.8 s.
Now each chat has its own store, and every part reads only the slice it needs. A streamed line redraws one reply, not the whole chat. The drawer fetches only while it's open.
Bugs QA found, now fixed
Android showed nothing while a reply ran
The question sat alone until the whole answer landed. Android's list detaches rows it thinks are off screen, and the native "Thinking" shimmer and spinner stayed at zero size inside a detached row. The chat list now keeps its rows attached.


On a small iPhone the jump button sat on the question
With the keyboard open on an iPhone SE, the button overlapped the pinned question by 5 pt. It now waits while the keyboard is up and comes back when it closes.


What the fresh review found
These are the reviewer's claims from the captures. I haven't confirmed them in the app yet.
Must fix
- The jump button is 40 pt across on iPhone. Tap targets need 44 pt.
Should fix
- The jump button covers a line of the reply. It also sits at a different height in the explanation chat.
- On Android the jump button is white on a white card, with no edge.
- On web, raw markdown (
**, table pipes) flashes for a moment while text streams. - On web, the reply blinks for about 0.1 s as it switches from streaming to done.
- On iPhone the Thinking group stays open after the answer, so on a small phone no answer text is on screen. Android collapses it.
- Step details can read like internal notes ("the user wants", tool names).
- Step details clamp at two lines, and the old per-step chevron to read the rest is gone.
- No like, share or retry row shows under a reply that just finished streaming. This one needs checking first.
- On web, the privacy note peeks out under the header for the first few seconds.
Some Android items (no steps while it runs, a 4 s tap delay) likely come from the cloud device's network and remote taps, not the app. I'll check those before treating them as bugs.
Where it was checked
| iPhone 18 Pro | Before and after captures, a 40 s video |
| iPhone SE | Text fit, row alignment, keyboard |
| Largest text size | Header stays one line, steps wrap cleanly |
| Android (cloud Pixel 9) | Captures and video after the fix |
| Web, desktop and phone width | Before and after captures, videos |
| iPad (cloud) | Videos done; landscape still to capture |
| Automated | Typecheck, lint, jest, and the end-to-end chat tests on web and iOS |

For the next engineer
projectReplyturns a message's parts into ordered pieces (thought groups, text, cards), andReplyBodydraws them.ThinkingGroupreplaces the old timeline and its step popup.- Each chat has a session store. Readers pick the slice they need. The old whole-object read is gone, and every caller moved in this PR.
useQuestionPinowns the pin and the jump for the tutor chat and the explanation chat. The old follow-to-bottom code is deleted.- The PR is large (141 files, about 7,000 lines added and 4,400 removed). The store, the ordered reply and the pin replace three things every chat screen reads, so splitting them would ship half-moved callers.
Noticed, not fixed here
- At the largest text size the learner's bubble breaks a word mid-word. Dev does it too.
- The streak celebration can open over the first chat between two answers.
- The cloud Android device's network hands a streamed reply over in one piece, so captures there can't show a reply partway.
- The end-to-end runner's iOS videos come out 1.2 s long.