mirror of
https://github.com/block/buzz.git
synced 2026-10-29 21:41:33 +00:00
fix(desktop): match people search on profile name
People search re-ranked relay results against the display name only, so a person whose display name differs from their name (e.g. "S" vs "Starfish Sol") was dropped even though the relay matched them. Score `name` alongside `display_name` in both the Rust and TS rankers. Co-authored-by: Otter-Opus <72c1a2fb33d700987f1e778749662d6fc95cf78e979f43e5039baa76dc2588aa@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Diem Nguyen <diem@squareup.com> Signed-off-by: Fizz <6be7be23ec5bba868f3e1db5ec86013d76bd1a293dce5b435a192158fec13641@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -66,6 +66,8 @@ pub struct UsersBatchResponse {
|
||||
pub struct UserSearchResultInfo {
|
||||
pub pubkey: String,
|
||||
pub display_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub nip05_handle: Option<String>,
|
||||
pub owner_pubkey: Option<String>,
|
||||
|
||||
@@ -18,6 +18,7 @@ pub fn user_search_result_from_event(ev: &Event) -> UserSearchResultInfo {
|
||||
.and_then(Value::as_str)
|
||||
.or_else(|| v.get("name").and_then(Value::as_str))
|
||||
.map(str::to_string),
|
||||
name: v.get("name").and_then(Value::as_str).map(str::to_string),
|
||||
avatar_url: v.get("picture").and_then(Value::as_str).map(str::to_string),
|
||||
nip05_handle: v.get("nip05").and_then(Value::as_str).map(str::to_string),
|
||||
is_agent: owner_pubkey.is_some(),
|
||||
@@ -126,10 +127,11 @@ pub fn rank_user_search_results(
|
||||
|
||||
let info = user_search_result_from_event(ev);
|
||||
let display = info.display_name.as_deref().unwrap_or("").to_lowercase();
|
||||
let name = info.name.as_deref().unwrap_or("").to_lowercase();
|
||||
let nip05 = info.nip05_handle.as_deref().unwrap_or("").to_lowercase();
|
||||
let pubkey = info.pubkey.to_lowercase();
|
||||
|
||||
let score = match_score(&q, &display, &nip05, &pubkey);
|
||||
let score = match_score(&q, &display, &name, &nip05, &pubkey);
|
||||
if score == 0 {
|
||||
continue;
|
||||
}
|
||||
@@ -156,7 +158,7 @@ pub fn rank_user_search_results(
|
||||
}
|
||||
}
|
||||
|
||||
fn match_score(q: &str, display_name: &str, nip05: &str, pubkey_hex: &str) -> u32 {
|
||||
fn match_score(q: &str, display_name: &str, name: &str, nip05: &str, pubkey_hex: &str) -> u32 {
|
||||
const DISPLAY_EXACT: u32 = 1000;
|
||||
const DISPLAY_PREFIX: u32 = 900;
|
||||
const DISPLAY_CONTAINS: u32 = 800;
|
||||
@@ -184,7 +186,13 @@ fn match_score(q: &str, display_name: &str, nip05: &str, pubkey_hex: &str) -> u3
|
||||
DISPLAY_EXACT,
|
||||
DISPLAY_PREFIX,
|
||||
DISPLAY_CONTAINS,
|
||||
);
|
||||
)
|
||||
.max(score_field(
|
||||
name,
|
||||
DISPLAY_EXACT,
|
||||
DISPLAY_PREFIX,
|
||||
DISPLAY_CONTAINS,
|
||||
));
|
||||
let nip05_score = score_field(nip05, NIP05_EXACT, NIP05_PREFIX, NIP05_CONTAINS);
|
||||
let pubkey_score = if !pubkey_hex.is_empty() && pubkey_hex.starts_with(q) {
|
||||
PUBKEY_PREFIX
|
||||
@@ -406,4 +414,13 @@ mod tests {
|
||||
assert_eq!(r.users.len(), 1);
|
||||
assert_eq!(r.users[0].display_name.as_deref(), Some("alice"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rank_matches_name_when_display_name_differs() {
|
||||
let e = ev(0, r#"{"name":"Starfish Sol","display_name":"S"}"#, vec![]);
|
||||
let r = rank_user_search_results(&[e], "starfish", 10);
|
||||
assert_eq!(r.users.len(), 1);
|
||||
assert_eq!(r.users[0].display_name.as_deref(), Some("S"));
|
||||
assert_eq!(r.users[0].name.as_deref(), Some("Starfish Sol"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +309,7 @@ export function MembersSidebar({
|
||||
: current.isAgent
|
||||
? currentName
|
||||
: (currentName ?? candidateName),
|
||||
name: current.name ?? candidate.name ?? null,
|
||||
nip05Handle: current.nip05Handle ?? candidate.nip05Handle ?? null,
|
||||
ownerPubkey: current.ownerPubkey ?? candidate.ownerPubkey ?? null,
|
||||
isAgent: current.isAgent || candidate.isAgent,
|
||||
|
||||
@@ -47,6 +47,13 @@ test("scoreUserCandidate ranks display labels before pubkeys", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("scoreUserCandidate matches a profile name that differs from the display name", () => {
|
||||
const user = makeUser({ displayName: "S", name: "Starfish Sol" });
|
||||
|
||||
assert.equal(scoreUserCandidate({ label: "S", query: "starfish", user }), 0);
|
||||
assert.equal(scoreUserCandidate({ label: "S", query: "sol", user }), 1);
|
||||
});
|
||||
|
||||
test("scoreUserCandidate supports agent labels and empty-query defaults", () => {
|
||||
const agent = makeUser({ isAgent: true });
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export function scoreUserCandidate({
|
||||
|
||||
const labels = [
|
||||
label,
|
||||
user.name?.trim() ?? "",
|
||||
user.nip05Handle?.trim() ?? "",
|
||||
user.isAgent ? "agent" : "",
|
||||
];
|
||||
|
||||
@@ -64,6 +64,7 @@ function fromRawUserSearchResult(user: RawUserSearchResult): UserSearchResult {
|
||||
return {
|
||||
pubkey: user.pubkey,
|
||||
displayName: user.display_name,
|
||||
name: user.name ?? null,
|
||||
avatarUrl: user.avatar_url,
|
||||
nip05Handle: user.nip05_handle,
|
||||
ownerPubkey: user.owner_pubkey,
|
||||
|
||||
@@ -126,6 +126,7 @@ export type UsersBatchResponse = {
|
||||
export type UserSearchResult = {
|
||||
pubkey: string;
|
||||
displayName: string | null;
|
||||
name?: string | null;
|
||||
avatarUrl: string | null;
|
||||
nip05Handle: string | null;
|
||||
ownerPubkey: string | null;
|
||||
|
||||
Reference in New Issue
Block a user