fix: user search returns zero results for partially typed names#1603
Conversation
Every caller of the search_users Tauri command is a typeahead surface — member picker, @mention popup, DM recipient search, topbar people results. But the command sent a plain NIP-50 search, which the relay bridge runs through websearch_to_tsquery whole-word matching: "tyl" returned zero results for "Tyler", reading as "user doesn't exist" in the picker. Opt into the relay's existing bridge-only prefix search_mode (the same extension the topbar message search already uses), extract the filter into build_user_search_filter, and pin the mode with a unit test so it can't drift. Reproduced live against staging: "bax" 0 -> 3 results, "tyl" 0 -> 1. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
wesbillman
left a comment
There was a problem hiding this comment.
Approve. Traced the claims against the code rather than re-running CI (all required checks are green, including the new unit test in Desktop Core).
Root cause verified: the relay bridge defaults NIP-50 search to whole-word FTS — extract_search_mode (crates/buzz-relay/src/api/bridge.rs:221) returns SearchMode::FullText unless the filter carries search_mode: "prefix", and search_users never sent it. SearchMode::Prefix (crates/buzz-search/src/query.rs:147) prefix-matches only the trailing token, and topbar message search already opts in the same way (messages.rs:113). Reproduced the symptom live through the current path: name lookup for tyl → 0 results, Tyler → 1.
Fix verified: one filter change through the single search_users backend that feeds every typeahead surface (member picker, @mention popup, DM recipient dialog, topbar people). Downstream client re-rank (rank_user_search_results) scores DISPLAY_PREFIX for partial-name hits, so prefix results survive the local filter instead of being dropped again. Extracted builder + pinned test mirrors the messages.rs convention. Minimal, correct, well-scoped.
Two non-blocking observations, both pre-existing and out of scope:
pagein the filter is ignored server-side —handle_bridge_searchhardcodespage: 1("no pagination for bridge MVP"), so thenext_cursorthe command emits pages nothing. Unchanged by this PR.ForumComposercallsuseMentionswithoutchannelType, so its mention popup never does a global user search at all — this fix reaches it only via the chatMessageComposer(which passeschannelTypefor stream/forum/dm). Worth a follow-up if forum-post mentions should also find non-members.
Problem
Wes reported a user missing from the add-member picker, and separately from the @mention popup (buzz-bugs thread). It looked like a result-count limit — it isn't. Every hop was traced: GUI asks for 25–50, the Tauri command clamps at 500, the relay bridge and FTS layer clamp at 500. Nothing truncates below what the UI displays.
The real bug: the
search_usersTauri command — the single backend for all user typeahead surfaces (member picker, @mention popup, DM recipient search, topbar people results) — sends a plain NIP-50 search. The relay bridge runs that throughwebsearch_to_tsquerywhole-word matching, so a partially typed name matches nothing:Live repro against staging (
sprout-oss.stage):search_mode: prefix)tylbaxWeZero results reads as "user doesn't exist" — exactly what Wes saw mid-typing.
Fix
Opt user search into the relay's existing bridge-only
search_mode: "prefix"extension — the same one the topbar message search already uses (build_search_messages_filter, messages.rs:134). Prefix mode keeps completed tokens exact and prefix-matches only the trailing token, server-side in Postgres, with all access checks unchanged.Filter construction is extracted into
build_user_search_filterand pinned by a unit test, mirroring the messages.rs convention, so the mode can't silently drift.Not fixed here (by design)
The specific "Brad" case has a second, non-code cause: his kind:0
display_nameisbaxen— no profile on the relay contains "Brad" in its name (verified by paging all 3,443 kind:0 events). No search mode can fix that; it's a profile-naming question.Testing
just desktop-tauri-test: 1006 passed, 0 failed (includes newuser_search_filter_requests_prefix_mode_for_typeahead)POST /querywith and withoutsearch_mode