Skip to content

feat(chat): draw the typing dots as the newest transcript row - #1620

Merged
bmc08gt merged 2 commits into
code/cashfrom
feat/chat-typing-row
Sep 29, 2026
Merged

bmc08gt merged 2 commits into
code/cashfrom
feat/chat-typing-row

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

Ports the last piece of code-payments/code-ios-app#908: the typing dots leave the message bar and sit in the transcript as the newest row. When the only person typing sends, their bubble grows out of the dots bubble instead of appearing separately. Builds on #1617, which brought over the insertion springs, receipt handoff and 800 ms linger.

How the row is drawn. The dots render inside the newest message's lazy item, below its bubble, not as a lazy item of their own. MessageList's read tracking, focus pin and headroom helpers all treat a lazy index as a paging index, and a leading item would have offset every one of them. Growing the newest row by the dots' height has the same effect on layout: resting at the bottom, the rows above ride up and down on insertionPush. Scrolled up, the change goes into the scroll offset, so nothing on screen moves.

Handoff. TypingRowModel compares each paged snapshot with the last one. When messages land while the dots are showing, takesTypingDots decides whether the newest one takes their frame. It applies the shared rule merged in flipcash-client-orchestrator#31: the sender was typing, and nobody is still typing afterwards. If so, the new row starts at the dots' height and grows to its own on iOS's fromTyping spring. Its outline grows from the dots bubble's size, the content fades in after 58 ms, and the dots fade out over 80 ms. If not, the new row still opens from the dots' height, so the rows above don't jump, and the dots stay under it minus the sender's avatar. The starting height also accounts for the gap change on the row above: it had the tight newest-row gap and now has whatever rowGapBelow gives against the arrival.

Tracker. messageArrived used to drop the typist at once. The message reaches the paged list a beat later, so the dots vanished before there was anything to grow out of them. The typist is now kept for up to 1.5 s with arrived = true, and the transcript lets go as soon as the message lands. A trailing STOPPED doesn't extend the hold, and typing again starts a new session.

Grouping. The dots never join a run, matching iOS's interItemSpacing: a normal gap under the typist's own bubble, a wide one under anyone else's. A handed-off bubble starts from solo corners and settles into its run on the existing corner spring.

TypingHandoffTest covers each case in the shared table. TypingIndicatorTrackerTest covers the arrival hold.

The typing dots move out of the message bar and sit under the newest
message, as iOS draws them since code-ios-app#908. When the only typist's
message lands, its bubble grows out of the dots bubble instead of being
inserted beside it.

The dots are drawn inside the newest row rather than as a lazy item of
their own, so list indices keep matching paging indices for the helpers
that assume it. The newest row opens and closes by the dots' height on
the insertion push spring while the list rests on it, and scrolled up the
scroll offset absorbs the change so nothing on screen moves.

Which arrival takes the dots follows the shared rule in the orchestrator's
cross-platform-parity doc: the newest arrival, if its sender was typing and
nobody is still typing after it. The tracker now holds an arrived typist
for up to 1.5s so the dots are still there when the message reaches the
paged list.
@bmc08gt bmc08gt self-assigned this Sep 29, 2026
@github-actions github-actions Bot added type: feature New functionality area: ui Compose UI, theme, components, resources and removed type: feature New functionality labels Sep 29, 2026
…rows above

The arrival row read the handoff decision before the model had made it, so
the message appeared at full height and the dots vanished with nothing to
grow from. The decision is now made idempotently by whichever of update() or
the arrival row reaches it first. The first handoff frame also shifted the
rows above by the change in the gap above the row; the model now carries that
delta into the starting height.
@github-actions github-actions Bot added the type: feature New functionality label Sep 29, 2026
@bmc08gt
bmc08gt merged commit 38a2623 into code/cash Sep 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui Compose UI, theme, components, resources type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant