mirror of
https://github.com/block/buzz.git
synced 2026-10-30 22:11:36 +00:00
The HTTP bridge has read a per-filter `search_mode` extension since prefix typeahead landed, but the WebSocket REQ path hard-coded `SearchMode::FullText`, so a REQ carrying `search_mode: "prefix"` was silently answered with whole-lexeme semantics: `pro` could never match `project`. Parse the extension in `ClientMessage::parse` the way `before_id` already is, carrying it as `search_modes: Vec<buzz_search::SearchMode>` aligned with `filters` by index, thread it through `connection.rs` into `handle_req` and `handle_search_req`, and use it in place of the constant. `prefix` selects prefix mode; absence or any other value is full text, and a missing entry falls back to full text so the existing test callers are unchanged in behaviour. The bridge's private `extract_search_mode` moves to `protocol.rs` as the single shared definition, so the two transports cannot drift on the vocabulary; its two bridge unit tests pass unchanged. `page` is deliberately not honoured on this path, and a comment at the scan loop says why: the WebSocket handler pages internally as a scan cursor over raw FTS candidates while `limit` counts accepted rows after post-filtering, so a client offset would count a different thing than the limit it pairs with and cannot compose with the scan budget pinned by `search_scan_capacity_covers_advertised_nip11_max_limit`. Offset paging stays a bridge-only contract. The mobile doc comment on `searchUsers` still calls `search_mode` bridge-only; that remains the right client guidance until relays with this change are deployed, so it is left for the client change that starts sending search over WebSocket. Tests: `parse_req_extracts_prefix_search_mode_per_filter` asserts `[FullText, Prefix]` for a two-filter REQ, and `ws_search_prefix_mode_matches_partial_token` in `e2e_nostr_interop.rs` sends the same REQ twice over a live WebSocket, once with the extension and once without, asserting `pro` matches `project` only in prefix mode. The e2e test was verified to fail by mutation: with the handler pinned back to full text it fails at the prefix assertion with an empty result set. The two pre-existing P-256 PKCS#8 test fixtures in the bridge's NIP-FI tests are rewritten as `concat!` of per-line literals so the BEGIN line can carry the `sadscan:disable kingfisher.privkey.2` marker on the same line; the pre-commit secrets scan blocks any commit touching `bridge.rs` on those lines, honours only same-line markers, and the original `\`-continuation literal could not take one. Both values are byte-identical to before (checked by decoding both encodings), and the 82 NIP-FI tests that consume them pass. Verified with the lld linker workaround for sqlx proc-macros on this machine: `cargo test -p buzz-relay --lib` for `protocol::tests`, `handlers::req::tests`, the bridge `search_mode` tests, and the `nip_fi` tests (71 and 82 passed; Postgres-only tests ignored); `cargo test -p buzz-test-client --test e2e_nostr_interop -- --ignored` for the new test plus the five existing NIP-50 search tests against a locally launched relay built from this code (6 passed); `cargo fmt --all --check`; and `cargo clippy -p buzz-relay -p buzz-test-client --all-targets -- -D warnings`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>