From 5b9967b17d51c7485cf6bf5ca10a9807632fa721 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:23:11 -0700 Subject: [PATCH] Strip leading @ in from: queries (#11158) --- .../__tests__/search-posts-params.test.ts | 17 +++++++++++++++++ src/state/queries/search-posts-params.ts | 15 +++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/state/queries/__tests__/search-posts-params.test.ts b/src/state/queries/__tests__/search-posts-params.test.ts index 9436507cc2..9b0c374642 100644 --- a/src/state/queries/__tests__/search-posts-params.test.ts +++ b/src/state/queries/__tests__/search-posts-params.test.ts @@ -26,6 +26,23 @@ describe(`extractSearchPostsParams`, () => { input: `cats to:alice`, output: {q: `cats`, mentions: `alice`}, }, + // Handles may be typed with a leading @ (e.g. from:@alice.bsky.social), + // which the appview rejects. Strip it to match the advanced-search dialog. + { + name: `strips a leading @ from from:`, + input: `cats from:@alice.bsky.social`, + output: {q: `cats`, author: `alice.bsky.social`}, + }, + { + name: `strips a leading @ from mentions:`, + input: `cats mentions:@alice.bsky.social`, + output: {q: `cats`, mentions: `alice.bsky.social`}, + }, + { + name: `strips a leading @ from to: (mentions alias)`, + input: `cats to:@alice.bsky.social`, + output: {q: `cats`, mentions: `alice.bsky.social`}, + }, // `me` is resolved by the backend, so the :me operators stay in q verbatim // instead of being lifted into author/mentions. { diff --git a/src/state/queries/search-posts-params.ts b/src/state/queries/search-posts-params.ts index 70dfe728ba..dcfd9aa4b5 100644 --- a/src/state/queries/search-posts-params.ts +++ b/src/state/queries/search-posts-params.ts @@ -13,6 +13,17 @@ import { const DATE_RE = /^\d{4}-\d{2}-\d{2}/ +/** + * Strips a leading `@` from a handle so `from:@alice.bsky.social` and + * `from:alice.bsky.social` both resolve to the same author. Mirrors the marker + * stripping the advanced-search dialog applies to handles entered in its + * filter fields (see `serializeAdvancedSearch`), which otherwise 400s the + * appview. + */ +function stripHandleMarker(value: string): string { + return value.startsWith('@') ? value.slice(1) : value +} + export type ExtractedSearchParams = { q: string author?: string @@ -111,12 +122,12 @@ export function extractSearchPostsParams(query: string): ExtractedSearchParams { * query text verbatim rather than lifting it into a structured param. */ if (value === 'me') remaining.push(token) - else result.author ??= value + else result.author ??= stripHandleMarker(value) break case 'mentions': case 'to': if (value === 'me') remaining.push(token) - else result.mentions ??= value + else result.mentions ??= stripHandleMarker(value) break case 'domain': result.domain ??= value