Files
buzz/mobile/lib
Tom Brow 13a6aa19f4 fix(mobile): list known people in recipient pickers before a name is typed (#9311)
## Problem

Add members (and New DM, which shares the picker) showed an empty list
until the first character was typed. An empty query runs no directory
search, so the only candidates were the viewer's own agents and agents
shared with them. Once those are members, or while the owned-agent list
is still loading, nothing is left.

`@` completion shows the channel's members before a name is typed. A
recipient picker has no channel to draw from, so this lists the people
the viewer already knows instead.

## Change

- With an empty query, `recipientCandidatesProvider` adds people from
the viewer's open channels and DMs (from the member lists the channel
list already loaded). Add members skips the target channel's own members
before choosing. The 50 people (`mentionSuggestionLimit`) are gathered
from DMs first, then channels, and shown in the usual label order.
- Empty-query rows list agents ahead of known people, so people never
push an agent off the 50-row list. When an agent the opening hasn't
offered yet arrives after rows were shown (the viewer's own agents
loading late, a picker opened mid-reload, or a reconnect that brings a
new agent), the people rows start over so it takes its place ahead of
them; agent rows already shown keep their places, including one whose
list is still reloading. A reload that brings no new agent moves
nothing, and typed rows never restart. Known people don't wait for the
agent lists, which never load offline.
- Member lists carry no profiles, so the unnamed people among the 50 are
loaded through the user cache's batched preload, each person once per
picker opening regardless of rebuilds.
- The roster scan stops at 50 people or 2,000 roster entries, whichever
comes first, and doesn't copy rosters.
- Typed queries and `@` completion are unchanged: same ranking,
directory search only, no known-people rows mixed in.

## Why nothing gets worse

- Network: at most 50 profile reads per opening, batched, only for
uncached people, and never repeated on rebuilds (new messages, presence,
profile updates).
- CPU: at most 2,000 roster entries per rebuild, only while a picker
shows an empty query.
- Agents: every agent the picker listed before still lists, ahead of the
new rows.
- Ranking, selection, typed search, the viewer and unshared-agent
filters, and "Add members hides existing members" are unchanged.

## Validation

- Added tests: `recipient_known_people_test.dart` (empty query lists
people from open channels and DMs, excludes archived and non-member
channels, loads a missing name; agents stay ahead of people and people
don't wait for an unloaded agent list; the real user cache reads each
unnamed person once across rebuilds; Add members skips the channel's own
members; at most 50 people are gathered; typed query adds nothing) and
`recipient_late_agents_test.dart` (Add members sheet with a controllable
own-agent list and 60 known people: agents arriving on first load, in a
picker opened mid-reload, and on a reconnect that adds a new agent all
take rows ahead of the people; reload, land, reload, land moves nothing;
typed rows keep their places; 55 agents fill the list and stay put
across reloads and a rename that changes their order; both agent lists
reloading after a reconnect and landing in either order, with a new
agent in the first, keep every earlier agent's row). Confirmed the
late-agent tests fail without the restart. Confirmed the ordering and
read-once tests fail without their fixes. Confirmed the first test fails
without the fix.
- Focused existing validation: `flutter test test/features/channels`
(2,308 passed), `flutter analyze` on the changed directory.
- Automated app run (on an earlier head; iOS 27 simulator, iPhone 17
Pro, a Builderlab test account): opened a private channel, Channel
details, Add members. Before: only the account's own agents. After:
agents, then people from its channels and DMs with names and avatars;
typing `ar` still narrows to directory matches. Captures show real
community members, so they were shared internally rather than embedded
here. Native iOS sheet only; Android and New DM share the provider but
were not run in the app.

🤖 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: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:18:36 +00:00
..