mirror of
https://github.com/block/buzz.git
synced 2026-11-10 19:32:00 +00:00
Fixes two thread symptoms that come from the same state: after sending a reply, the composer clears but the new reply sits hidden under the composer (or out of view entirely) until you leave and return, and the **Latest** button stays visible even though the list looks like it is at the bottom. ### Cause 1. A sent reply grows into place over its 500 ms entrance transition (`LocalSendRow`). The thread's one-shot tail correction can finish while the row is still growing. Once it has finished, nothing moves the list as the reply keeps growing, so the list stops short of the real end (about 40 pt on an iPhone 17 Pro simulator) and the reply ends up behind the composer. Because the list isn't actually at its end, **Latest** shows. It is visible even with the composer focused when an earlier drag detached tail-follow. 2. A thread opened at a specific reply (from Inbox, a push notification or a message link) skipped the new-reply follow logic for its whole lifetime, not just for the replies it opened with. If the reader scrolled at all before replying, their own sent reply was never brought into view: it landed below the viewport, and a quick swipe couldn't reach it before the next layout. ### Fix - While the thread follows its tail, a `ScrollMetricsNotification` listener keeps a position that was at its previous end on the new end whenever the extent grows. It only acts when the last reply is laid out, the reader isn't dragging or opted out, and no navigation to the tail is in progress, so programmatic jumps away from the tail are left alone. This also covers other growth at the tail, such as media or typing rows. - A targeted entry now absorbs the replies it hydrates with until its target jump has revealed the list. Hydration can come in two steps (the route's snapshot of the target, then the relay's full thread). Absorbing only the first let the second, when it held the reader's own earlier replies, schedule a tail correction that cancelled the one-shot target jump and left the list hidden behind its loading placeholder. Later arrivals use the ordinary rules: the reader's own reply comes into view, and other people's replies still don't move a reader who is away from the tail. ### Before and after Same flow on an iPhone 17 Pro simulator, in a synthetic QA channel thread: scroll the list away from the bottom, focus the composer, send a long reply that wraps onto many lines. Before (main) the sent reply ends with its bottom edge under the composer; after (this branch) it ends clear above it. Before (main): https://github.com/user-attachments/assets/5101b63d-e2df-46d1-bfd1-49038330ddf5 After (this branch): https://github.com/user-attachments/assets/1df81ad5-bb6b-4df6-b4e3-5c37fd8aab67 ### Validation - Test `a sent reply stays above the composer as it grows in` now runs for a plain entry, a targeted entry (`initialMessageId`), and a targeted entry whose relay hydration is delayed and includes the reader's own replies. The targeted row opens at an older reply, checks it is visible, drags a little, focuses the composer with the keyboard up, sends a multi-line reply, and checks that the reply ends above the composer and **Latest** is hidden. Each row fails without its fix (growth: reply bottom at 504, composer top at 402; targeted: the reply is never followed; delayed hydration: the target never becomes hit-testable because the list stays hidden). - Full `flutter analyze` and mobile test suite pass. - Before/after capture on an iPhone 17 Pro simulator (iOS 27, debug builds of this branch head and of current main), in a synthetic QA channel thread with enough replies to scroll. Flow: scroll away from the bottom, focus the composer, send a long wrapping reply. Over 7 runs on main, the reply's bottom ended 3 to 25 pt under the composer in 5; over 6 runs on this branch, it ended 17 pt above the composer every time. Limitation: debug simulator builds only, and the shortfall on main varies run to run. - Automated full-app run on an iPhone 17 Pro simulator (iOS 27) against the Remote Agents Pilot community. In a test DM thread, I sent short and wrapping replies with no prior scroll, after dragging up and returning, and after the follow-up narrowing. Each reply ended fully above the composer, and **Latest** stayed hidden at the bottom. I also dragged up with the keyboard dismissed and confirmed the list stays at the tail with no stray **Latest**. For the targeted entry, I opened a thread from a message link, scrolled, and sent replies: without the targeted-entry fix they stayed out of view under the keyboard; with it hot-reloaded they were followed (the combined build is covered by the widget test rows above). I then reopened that link right after an app restart, which hydrates in two steps: before the hydration fix the thread showed only its root with the replies hidden, and with it the list revealed at the target and a sent reply came into view. Human testing is waived per the owner's standing exception. Limitation: this run did not cover physical devices or media replies. The simulator recording was taken in an internal community, so it is shared internally rather than embedded here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@localhost> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>