mirror of
https://github.com/block/buzz.git
synced 2026-11-11 20:01:09 +00:00
## 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>