From b0a40145e9482ec0ba04a0786a9edd9cf61de04d Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Thu, 2 Jul 2026 09:35:57 -0700 Subject: [PATCH] Special-case 'me' values in search queries (#11043) --- src/lib/strings/helpers.ts | 34 +++++-------------- src/screens/Search/SearchResults.tsx | 10 +++--- .../__tests__/utils.test.ts | 6 ++++ .../DetectedLanguagesAdmonition.tsx | 7 +--- .../__tests__/search-posts-params.test.ts | 17 ++++++++++ src/state/queries/search-posts-params.ts | 10 ++++-- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/lib/strings/helpers.ts b/src/lib/strings/helpers.ts index b654721ea2..77ef18cb55 100644 --- a/src/lib/strings/helpers.ts +++ b/src/lib/strings/helpers.ts @@ -47,30 +47,12 @@ export function countLines(str: string | undefined): number { return str.match(/\n/g)?.length ?? 0 } -// Augments search query with additional syntax like `from:me` -export function augmentSearchQuery(query: string, {did}: {did?: string}) { - // Don't do anything if there's no DID - if (!did) { - return query - } - - // replace “smart quotes” with normal ones - // iOS keyboard will add fancy unicode quotes, but only normal ones work - query = query.replaceAll(/[“”]/g, '"') - - // We don't want to replace substrings that are being "quoted" because those - // are exact string matches, so what we'll do here is to split them apart - - // Even-indexed strings are unquoted, odd-indexed strings are quoted - const splits = query.split(/("(?:[^"\\]|\\.)*")/g) - - return splits - .map((str, idx) => { - if (idx % 2 === 0) { - return str.replaceAll(/(^|\s)from:me(\s|$)/g, `$1${did}$2`) - } - - return str - }) - .join('') +/** + * Normalizes a raw search query for the backend. The iOS keyboard inserts smart + * quotes, but only straight quotes work for exact-phrase matching. Operators + * like `from:me` are passed through untouched - the backend resolves `me` to + * the viewer. + */ +export function augmentSearchQuery(query: string) { + return query.replaceAll(/[“”]/g, '"') } diff --git a/src/screens/Search/SearchResults.tsx b/src/screens/Search/SearchResults.tsx index b6c8d56ae3..e212e91601 100644 --- a/src/screens/Search/SearchResults.tsx +++ b/src/screens/Search/SearchResults.tsx @@ -319,18 +319,18 @@ let SearchScreenPostResults = ({ }): React.ReactNode => { const ax = useAnalytics() const {t: l} = useLingui() - const {currentAccount, hasSession} = useSession() + const {hasSession} = useSession() const [isPTR, setIsPTR] = useState(false) const trackPostView = usePostViewTracking('SearchResults') const searchV2Enabled = ax.features.enabled(ax.features.SearchV2Enable) const augmentedV2Query = useMemo(() => { - return augmentSearchQuery(query || '', {did: currentAccount?.did}) - }, [query, currentAccount]) + return augmentSearchQuery(query || '') + }, [query]) const augmentedV1Query = useMemo(() => { - return augmentSearchQuery(queryWithParams || '', {did: currentAccount?.did}) - }, [queryWithParams, currentAccount]) + return augmentSearchQuery(queryWithParams || '') + }, [queryWithParams]) /* * Both hooks are called to keep hook order stable; `enabled` ensures only the diff --git a/src/screens/Search/components/AdvancedSearchDialog/__tests__/utils.test.ts b/src/screens/Search/components/AdvancedSearchDialog/__tests__/utils.test.ts index 8822c1337d..26b9212ca3 100644 --- a/src/screens/Search/components/AdvancedSearchDialog/__tests__/utils.test.ts +++ b/src/screens/Search/components/AdvancedSearchDialog/__tests__/utils.test.ts @@ -73,6 +73,12 @@ describe(`AdvancedSearchDialog serialize/parse`, () => { expect(state.until).toBe('2024-02-01') }) + it(`keeps from:me in the query box rather than lifting it into a row`, () => { + const state = parseAdvancedSearch('from:me', {}) + expect(state.query).toBe('from:me') + expect(state.filters.find(f => f.field === 'authors')).toBeUndefined() + }) + it(`merges a query-box operator with the matching filter param`, () => { const state = parseAdvancedSearch('hi from:bob', {author: 'alice'}) expect(state.query).toBe('hi') diff --git a/src/screens/Search/components/DetectedLanguagesAdmonition.tsx b/src/screens/Search/components/DetectedLanguagesAdmonition.tsx index be45e24e65..f0eb41649b 100644 --- a/src/screens/Search/components/DetectedLanguagesAdmonition.tsx +++ b/src/screens/Search/components/DetectedLanguagesAdmonition.tsx @@ -5,7 +5,6 @@ import {augmentSearchQuery} from '#/lib/strings/helpers' import {codeToLanguageName} from '#/locale/helpers' import {useLanguagePrefs} from '#/state/preferences/languages' import {useSearchPostsV2Query} from '#/state/queries/search-posts-v2' -import {useSession} from '#/state/session' import {type SearchFilters} from '#/screens/Search/searchParams' import {Admonition} from '#/components/Admonition' import {createStaticClick, InlineLinkText} from '#/components/Link' @@ -34,12 +33,8 @@ export function DetectedLanguagesAdmonition({ onPressLanguage: (code: string) => void }) { const {appLanguage} = useLanguagePrefs() - const {currentAccount} = useSession() - const augmentedQuery = useMemo( - () => augmentSearchQuery(query || '', {did: currentAccount?.did}), - [query, currentAccount], - ) + const augmentedQuery = useMemo(() => augmentSearchQuery(query || ''), [query]) const {data} = useSearchPostsV2Query({ query: augmentedQuery, diff --git a/src/state/queries/__tests__/search-posts-params.test.ts b/src/state/queries/__tests__/search-posts-params.test.ts index a35f319fea..9436507cc2 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`}, }, + // `me` is resolved by the backend, so the :me operators stay in q verbatim + // instead of being lifted into author/mentions. + { + name: `keeps from:me in q verbatim`, + input: `cats from:me`, + output: {q: `cats from:me`}, + }, + { + name: `keeps to:me in q verbatim`, + input: `cats to:me`, + output: {q: `cats to:me`}, + }, + { + name: `keeps mentions:me in q verbatim`, + input: `cats mentions:me`, + output: {q: `cats mentions:me`}, + }, { name: `accumulates multiple hashtags into tag[]`, input: `#cats #dogs`, diff --git a/src/state/queries/search-posts-params.ts b/src/state/queries/search-posts-params.ts index 420f814955..70dfe728ba 100644 --- a/src/state/queries/search-posts-params.ts +++ b/src/state/queries/search-posts-params.ts @@ -106,11 +106,17 @@ export function extractSearchPostsParams(query: string): ExtractedSearchParams { switch (op) { case 'from': - result.author ??= value + /* + * `me` is resolved to the viewer by the backend, so leave it in the + * query text verbatim rather than lifting it into a structured param. + */ + if (value === 'me') remaining.push(token) + else result.author ??= value break case 'mentions': case 'to': - result.mentions ??= value + if (value === 'me') remaining.push(token) + else result.mentions ??= value break case 'domain': result.domain ??= value