iOS: mention autocomplete list grows under the finger — late relay results shift rows up and steal the tap
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
On iOS, the mention autocomplete popover appears with the channel-member matches first, and then ~0.3–1s later grows as debounced relay search results land. Because the popover is **bottom-anchored above the composer**, appending rows pushes the existing rows *upward* — so a row that the user is already reaching for moves out from under their finger, and a newly arrived candidate takes over the screen position it just vacated.
Reported case: typing `@bu` in a channel shows **Buzzpert** (a channel member) alone. While tapping it, **Bumble** and other `bu*` agents arrive, Buzzpert shifts up, and the tap lands on Bumble — the wrong agent gets mentioned and the message goes to the wrong recipient.
This is not a ranking bug — the ranking is stable and correct. It is a layout + timing interaction: a tappable surface mutates under the user's finger with no settle guard.
## Steps to reproduce
1. Open a channel that has an agent as a member whose name shares a prefix with agents outside the channel (e.g. `Buzzpert` in-channel, `Bumble` on the relay).
2. Type `@bu` in the composer.
3. Immediately tap the first suggestion.
Expected: the tap selects what was under the finger when the touch began.
Actual: after ~0.3–1s the list grows, the rows shift up, and the tap selects a different candidate.
## Mechanism
Two independent pieces combine:
**1. The candidate list arrives in two waves.**
`mobile/lib/features/channels/mentions/mention_candidates_provider.dart`
- `mentionUserSearchProvider` (`:22-53`) is debounced by `_mentionSearchDebounce = 250ms` (`:15`, `:28`) and then does a relay round trip (`session.queryRelay`, `:35`).
- `mentionCandidatesProvider` (`:70-113`) folds those late results in at `:97-99` (`.asData?.value ?? const []`), so the first frame renders members only, and a later frame renders members + global search results.
**2. The popover is bottom-anchored, so growth moves existing rows.**
`mobile/lib/features/channels/compose_bar/dock.dart:131` positions the overlay panel with `bottom: layoutInfo.overlaySize.height - overlayAnchorY` — the bottom edge is pinned just above the composer and the panel expands upward. `mobile/lib/features/channels/compose_bar/suggestions.dart:29-33` is a `shrinkWrap` `ListView` inside a `maxHeight: 240` box, so height tracks item count directly.
Ranking (`mention_ranking.dart:36-41`) puts channel members in group 0 and non-member agents in group 3, i.e. late-arriving outsiders append at the **bottom** of the list — which, with a fixed bottom edge, is precisely the screen position the previously-last row occupied.
There is no hit-test guard: nothing suppresses or re-targets a tap that begins before the list changes.
## Notes on scope
Reporting the problem, not prescribing the fix. Some shapes that would address it, for whatever the mobile composer owners think fits:
- Reserve the layout: keep the panel's height stable while a search is in flight (skeleton rows or a fixed minimum), so arrival does not move anything.
- Ignore taps for a short window after the item list changes, or resolve a tap against the candidate identity captured at touch-down rather than the index at touch-up.
- Anchor growth downward, or otherwise keep the first row's screen position invariant.
The general principle: a surface the user can tap should not reflow under them, and if it must, the tap should resolve to what they aimed at.
## Environment
- iOS, App Store build **0.10.0** (released 2026-08-13).
- Code path unchanged in `main` @ `93114c9c` and in `mobile-v0.12.0-rc.1`.
## Related but different
- #1660 (closed) — desktop, stale suggestion committed on Tab. Same family (async list vs. commit), different surface and long fixed.
- #5542, #3840 — *which* candidates appear. This issue is about *when* they appear and what that does to the layout; the candidate set here is correct.
Contributor guide
Research direction
Start with mobile/lib/features/channels/mentions/mention_candidates_provider.dart, then inspect compose_bar/dock.dart and suggestions.dart to reproduce how late relay results change the popover geometry. Check mention_ranking.dart for ordering and run the @bu reproduction on iOS. Done means a suggestion does not move under an active touch, and the tap selects the intended candidate after late results arrive.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart, flutter, ios
- Domain
- frontend, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100