From 7240aac2e0db64c869248ee934827c0911245737 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:26:33 +0100 Subject: [PATCH] fix(social): allow single-char search and prefer native federated profiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lower minimum query length from 2 to 1 character so single-letter searches return results. Fix dedup to prefer native profiles (homeUserId=null) over replicated ones. Previously the first-seen result won, which was usually the local replicated profile (no instance badge, namespaced username). Now when a native profile is found on the remote instance, it replaces the replicated copy — showing the clean username with the instance badge. --- packages/server/src/routes/social.ts | 2 +- packages/web/src/stores/socialStore.ts | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/server/src/routes/social.ts b/packages/server/src/routes/social.ts index fadd4006..f3dbf126 100644 --- a/packages/server/src/routes/social.ts +++ b/packages/server/src/routes/social.ts @@ -483,7 +483,7 @@ export async function socialRoutes(app: FastifyInstance): Promise { const { q } = request.query; const db = getDb(); - if (!q || q.length < 2) { + if (!q || q.length < 1) { return reply.code(200).send([]); } diff --git a/packages/web/src/stores/socialStore.ts b/packages/web/src/stores/socialStore.ts index b38c4075..491653d1 100644 --- a/packages/web/src/stores/socialStore.ts +++ b/packages/web/src/stores/socialStore.ts @@ -255,7 +255,8 @@ export const useSocialStore = create((set, get) => ({ const results = await Promise.allSettled(searches.map(s => s.promise)); const allUsers: TaggedUser[] = []; - const seen = new Set(); + // Map canonical ID → index in allUsers for dedup with replacement + const seen = new Map(); results.forEach((result, i) => { if (result.status !== 'fulfilled') return; @@ -263,10 +264,21 @@ export const useSocialStore = create((set, get) => ({ for (const user of result.value) { // Deduplicate by canonical identity: replicated profiles share // the same homeUserId as the native profile's id, so collapse them. - // Prefer the first seen (home instance is queried first → native wins). + // Prefer native profiles (homeUserId is null) over replicated ones. const canonicalId = user.homeUserId ?? user.id; - if (seen.has(canonicalId)) continue; - seen.add(canonicalId); + const isNative = !user.homeUserId; + const existingIdx = seen.get(canonicalId); + + if (existingIdx !== undefined) { + // Replace replicated with native when found + if (isNative) { + if (origin) normalizeUserAssets(user, origin); + allUsers[existingIdx] = { ...user, _instanceOrigin: origin }; + } + continue; + } + + seen.set(canonicalId, allUsers.length); if (origin) normalizeUserAssets(user, origin); allUsers.push({ ...user, _instanceOrigin: origin }); }