Files
buzz/mobile/ios
Tom Brow 5f59940544 fix(mobile): steady New DM picker rows and animate picks (#9249)
On iOS, the New DM (and Add Members) picker is a native UIKit sheet that
Flutter drives by pushing the picker's whole state on every rebuild.
Each push called `reloadData`, which rebuilt every visible row in a
fresh, unreused cell showing the initials fallback, then fetched each
avatar again over the platform channel. So whenever anything refreshed
(several refreshes land right after foregrounding), every avatar blinked
to its initial for a frame or two. Picks also jumped instantly from the
results to the chosen section.

Now the picker:

- ignores a pushed state identical to the one it shows;
- applies the rest as a diffable snapshot, so unchanged rows keep their
cells and changed rows are redrawn in place, in reused cells;
- keeps loaded avatars by avatar key for the life of the sheet, so a
redrawn row shows its avatar at once;
- animates a member's row moving between the chosen and result sections
when it's picked or removed (no animation with Reduce Motion).

Flutter's toggle handlers in both pickers now read the current selection
rather than the one captured at build time. A fast second tap could
otherwise add the same person twice, which the diffed table would
reject.

### Validation

- Native member picker test (`scripts/test-mobile-member-picker.sh`),
extended: after an avatar loads, a changed row is redrawn with that
avatar and requests nothing more. The avatar check fails on `main` and
passes here; the existing identity and accessibility checks still pass
with the diffable table.
- Added a New DM test: two toggles before Flutter rebuilds never list a
person twice. It fails without the toggle fix.
- Full mobile Flutter suite and analyzer pass in CI on the current head,
which includes `main`'s fix for the right-drag link tests.
- Simulator runs of the app (iPhone 17 Pro, iOS 27, signed in to a test
community): opened New DM, backgrounded and foregrounded the app,
searched, picked from the list and from a search, and removed a pick.
Without this change, every avatar flashed to its initial after a
foreground. With it, no avatar flashed at any point, and picks and
removals animate between sections. Recordings show real community
members, so they're shared in Buzz rather than here.

#### Full-app comparison, `main` vs this PR

The same scripted New DM sequence on a clone of the same signed-in
simulator (iPhone 17 Pro, iOS 27), first on `main` (65e864301d) and then
on this PR (330805cfc4): pick george, then glm; search "ju", pick judge,
clear the search; remove george; background and reopen the app. The list
shows only agents, no people.

Both builds end in the same state: each pick moves into the selected
section with its check mark, search narrows to one result and clearing
it restores the full list, the removed pick returns to the list, and
every avatar keeps rendering after the app reopens. Sampled at 6 frames
per second around the reopen, neither build fell back to initials in
this run. The visible difference is motion: on `main` a picked row jumps
into the selected section, and here it animates there.

Before (`main`):


https://github.com/user-attachments/assets/12d88f47-0c10-4e78-a12c-18235e068f88

After (this PR):


https://github.com/user-attachments/assets/b3d8edb1-94ad-44dd-b6b3-3db22d8311b0

### Before merge

- [x] Update onto `main` once main's link swipe tests are fixed (they
were fixed by [#9255 Solo chat
settings](https://github.com/block/buzz/pull/9255)), so Mobile Checks
can pass.

buzz-review-completed

🤖 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: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
2026-11-09 07:34:02 -08:00
..
…