ONC2-8555 · app PR to dev, api#1428 to main · updated 28 Oct, 10:55 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.

DoneThe code, every fix from the design review, and the before and after captures on iPhone, iPhone SE, iPad, Android and web.
DoneApp PR #2632 is open to dev and ready for review. Every CI check passes at the latest commit.
Doneapi #1428, which sends text a paragraph at a time, is green and approved, open to main.
NowWaiting on Pullfrog's review of the app PR.
NextMerge both when you say so. The ticket stays In Progress until both reach main.

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 each run of steps sits in a "Thinking" group, in order, between the text and cards around it. A step shows the moment it starts. Its detail fills in once it finishes.

Before, streaming
Before, mid-reply
After, streaming
After, mid-reply

The question stays pinned, and done groups fold

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. A group folds to "Thought for 4s" once it's done and the answer goes on below it, so a small phone shows the answer, not the steps. A tap opens it again, and the learner's choice sticks. A round "Jump to latest reply" button, 46 pt like the header's buttons, shows while part of the reply is below the screen.

Before, done
Before, done
After, done
After, done
After the jump
After the jump

Same chat on Android and iPad

Android folds the same way. Its jump button gets a thin ring, because Android draws the glass as flat white on the white reply. iPad keeps the same layout in landscape.

Android, done, with the ringed jump
Android, done, ringed jump
iPad landscape, done
iPad landscape, done

Smaller things

Speed

Measured on the iPhone 18 Pro simulator with a dev build, before the change:

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 found on the way, 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.

Android before the fix
Before the fix
Android after the fix
After the fix

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.

SE keyboard before the fix
Before the fix
SE keyboard after the fix
After the fix

The reply went blank as its text finished

When a block of text finished streaming, the app swapped it for a new view so highlighting could turn on. For a frame or a few, the reply was empty: one frame on desktop Chrome, where the card fell to an 11 px strip, and 5 frames (0.17 s) on the iPhone. Now the text keeps its views from first line to last, and highlighting turns on in place. A frame-by-frame check found no drop in 4,178 iPhone frames or 6,388 web frames.

On web the jump button hid while the reply ran past the screen

Up to 700 px of reply sat below the screen with no jump button. The web only measures a view if it was set up to report its size when it first appeared, and the reply's row appeared one step too early. Every message row now reports its size from the start.

A follow-up in the explanation chat ended up four screens down

The explanation chat loads its follow-up suggestions before the learner's earlier turns. A follow-up sent right away pinned, then the earlier turns landed above it and pushed it 2,833 px down. Now the question keeps its place, 22 px from the top in the same test, until the learner scrolls or sends again.

What the design review found, and what happened

A fresh reviewer checked the captures without seeing the code. Each finding was measured in the app before it was fixed or set aside.

Jump button 40 pt, under the 44 pt tap minimumFixed, now 46 pt
Android jump button white on white, no edgeFixed, 1 px ring
Thinking group stayed open after the answer, so a small phone showed no answerFixed, it folds once done
Reply blinked as it finished (web and iPhone)Fixed, see above
"Thinking" label too light on the blueFixed, 2.6:1 to 3.8:1
Jump button covers a word of the replyBy design. It floats over the list's fade, as in other chat apps, and hides at the end
Step details clamp at two linesBy design. A tap shows the rest, and screen readers hear all of it
No copy or like row under the newest replyBy design. The follow-ups take its place. Same on dev
Raw markdown flashes on web while text streamsapi#1428 fixes it once it reaches staging
Step details read like internal notesThe model's own summary. A prompt change on the api side, not this PR
Android steps all land at once, and a 4 s tap delayThe cloud device's network and remote taps, not the app
Privacy note peeks under the header for a few seconds on webSame on dev. Reported, not this PR

Where it was checked

iPhone 18 ProBefore and after captures at 3x, a walkthrough video
iPhone SEText fit, row alignment, keyboard
Largest text sizeHeader stays one line, steps wrap cleanly
Android (cloud Pixel 9)Captures and video after the fixes
iPad (cloud)Portrait and landscape
Web, desktop and phone widthBefore and after captures, videos, the explanation chat
AutomatedTypecheck, lint, jest, every CI check, and the end-to-end chat tests (13 of 13 on web)
Largest text size
Largest text size

For the next engineer

Noticed, not fixed here