From 12f2884bb719d305c187d86c6e84820921b742b3 Mon Sep 17 00:00:00 2001 From: Rishi Balakrishnan Date: Tue, 11 Aug 2026 17:36:05 -0400 Subject: [PATCH 1/2] fix(search): default appview routing to Bluesky --- src/lib/api/search-routing.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/api/search-routing.ts b/src/lib/api/search-routing.ts index 42d108daeda..4269f6c3314 100644 --- a/src/lib/api/search-routing.ts +++ b/src/lib/api/search-routing.ts @@ -4,12 +4,11 @@ import {Features, features} from '#/analytics/features' /** * Route search and feed-discovery reads to the Bluesky appview when the * `search_appview:route` flag is set to 'bluesky', to shed search load off the - * home appview. Defaults to 'home', which returns empty opts so the call - * follows the agent's current proxy header (home, or the global fallback - * target during an outage). Read at call time so a flag flip takes effect on - * the next query without a reload. + * home appview. Defaults to 'bluesky' because the home appview no longer serves + * search. Read at call time so a flag flip takes effect on the next query + * without a reload. */ export function searchAppviewOpts() { - const route = features.getFeatureValue(Features.SearchAppviewRoute, 'home') + const route = features.getFeatureValue(Features.SearchAppviewRoute, 'bluesky') return route === 'bluesky' ? BLUESKY_APPVIEW_PINNED_OPTS : {} } From 7630b3e92f249526ac9f4a5d75d40297ee50eaaf Mon Sep 17 00:00:00 2001 From: Rishi Balakrishnan Date: Wed, 12 Aug 2026 19:31:35 -0400 Subject: [PATCH 2/2] fix(search): route logged-out search to the Bluesky appview The atproto-proxy header is only honored by a PDS, so a logged-out agent talking to the appview directly kept its search reads on the home appview regardless of the routing flag. Use a Bluesky-hosted agent for those calls, and apply the routing opts to getSuggestedFeeds, which set its own headers and never picked them up. --- package.json | 2 +- .../Autocomplete/useAutocomplete/index.ts | 5 ++- src/lib/api/search-routing.ts | 31 ++++++++++++++++--- src/state/queries/actor-autocomplete.ts | 7 ++--- src/state/queries/actor-search.ts | 5 ++- src/state/queries/search-posts.ts | 5 ++- .../trending/useGetSuggestedFeedsQuery.ts | 2 ++ 7 files changed, 39 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index d13487e377a..1417dfc717e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blacksky.community", - "version": "1.127.1", + "version": "1.127.2", "private": true, "packageManager": "pnpm@11.7.0", "engines": { diff --git a/src/components/Autocomplete/useAutocomplete/index.ts b/src/components/Autocomplete/useAutocomplete/index.ts index ab917f70114..b49f12084c2 100644 --- a/src/components/Autocomplete/useAutocomplete/index.ts +++ b/src/components/Autocomplete/useAutocomplete/index.ts @@ -2,12 +2,11 @@ import {useCallback, useMemo} from 'react' import {moderateProfile, type ModerationOpts} from '@atproto/api' import {keepPreviousData, useQuery} from '@tanstack/react-query' -import {searchAppviewOpts} from '#/lib/api/search-routing' +import {searchAppviewOpts, useSearchAgent} from '#/lib/api/search-routing' import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {STALE} from '#/state/queries' import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences' -import {useAgent} from '#/state/session' import { type AutocompleteApi, type AutocompleteItem, @@ -32,7 +31,7 @@ export function useAutocomplete({ limit?: number showSearchFallback?: boolean }): AutocompleteApi { - const agent = useAgent() + const agent = useSearchAgent() const moderationOpts = useModerationOpts() const emojiSearch = useEmojiSearch() diff --git a/src/lib/api/search-routing.ts b/src/lib/api/search-routing.ts index 4269f6c3314..afc98f26cc2 100644 --- a/src/lib/api/search-routing.ts +++ b/src/lib/api/search-routing.ts @@ -1,6 +1,16 @@ -import {BLUESKY_APPVIEW_PINNED_OPTS} from '#/lib/constants' +import {AtpAgent} from '@atproto/api' + +import {BLUESKY_APPVIEW_PINNED_OPTS, PUBLIC_BSKY_API} from '#/lib/constants' +import {useAgent, useSession} from '#/state/session' import {Features, features} from '#/analytics/features' +function routeSearchToBluesky() { + return ( + features.getFeatureValue(Features.SearchAppviewRoute, 'bluesky') === + 'bluesky' + ) +} + /** * Route search and feed-discovery reads to the Bluesky appview when the * `search_appview:route` flag is set to 'bluesky', to shed search load off the @@ -8,7 +18,20 @@ import {Features, features} from '#/analytics/features' * search. Read at call time so a flag flip takes effect on the next query * without a reload. */ -export function searchAppviewOpts() { - const route = features.getFeatureValue(Features.SearchAppviewRoute, 'bluesky') - return route === 'bluesky' ? BLUESKY_APPVIEW_PINNED_OPTS : {} +export function searchAppviewOpts(): {headers?: Record} { + return routeSearchToBluesky() ? BLUESKY_APPVIEW_PINNED_OPTS : {} +} + +const blueskyPublicAgent = new AtpAgent({service: PUBLIC_BSKY_API}) + +/** + * The `atproto-proxy` header from {@link searchAppviewOpts} is only honored by a + * PDS. A logged-out agent talks to the appview directly, so the header is + * dropped and the read lands on the home appview regardless of the flag — use a + * Bluesky-hosted agent for those calls instead. + */ +export function useSearchAgent(): AtpAgent { + const agent = useAgent() + const {hasSession} = useSession() + return !hasSession && routeSearchToBluesky() ? blueskyPublicAgent : agent } diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 9b6ec1f8e7e..340e1be0948 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -6,11 +6,10 @@ import { } from '@atproto/api' import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query' -import {searchAppviewOpts} from '#/lib/api/search-routing' +import {searchAppviewOpts, useSearchAgent} from '#/lib/api/search-routing' import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation' import {logger} from '#/logger' import {STALE} from '#/state/queries' -import {useAgent} from '#/state/session' import {useModerationOpts} from '../preferences/moderation-opts' import {DEFAULT_LOGGED_OUT_PREFERENCES} from './preferences' @@ -28,7 +27,7 @@ export function useActorAutocompleteQuery( limit?: number, ) { const moderationOpts = useModerationOpts() - const agent = useAgent() + const agent = useSearchAgent() prefix = prefix.toLowerCase().trim() if (prefix.endsWith('.')) { @@ -69,7 +68,7 @@ export type ActorAutocompleteFn = ReturnType export function useActorAutocompleteFn() { const queryClient = useQueryClient() const moderationOpts = useModerationOpts() - const agent = useAgent() + const agent = useSearchAgent() return useCallback( async ({query, limit = 8}: {query: string; limit?: number}) => { diff --git a/src/state/queries/actor-search.ts b/src/state/queries/actor-search.ts index eff77ee8de8..4cec9d23d84 100644 --- a/src/state/queries/actor-search.ts +++ b/src/state/queries/actor-search.ts @@ -7,9 +7,8 @@ import { useInfiniteQuery, } from '@tanstack/react-query' -import {searchAppviewOpts} from '#/lib/api/search-routing' +import {searchAppviewOpts, useSearchAgent} from '#/lib/api/search-routing' import {STALE} from '#/state/queries' -import {useAgent} from '#/state/session' export const RQKEY_ROOT = 'actor-search' export const RQKEY = (query: string, limit?: number) => [ @@ -29,7 +28,7 @@ export function useActorSearch({ maintainData?: boolean limit?: number }) { - const agent = useAgent() + const agent = useSearchAgent() return useInfiniteQuery< AppBskyActorSearchActors.OutputSchema, Error, diff --git a/src/state/queries/search-posts.ts b/src/state/queries/search-posts.ts index 95cc5f24a8d..09c75efd5dc 100644 --- a/src/state/queries/search-posts.ts +++ b/src/state/queries/search-posts.ts @@ -13,9 +13,8 @@ import { useInfiniteQuery, } from '@tanstack/react-query' -import {searchAppviewOpts} from '#/lib/api/search-routing' +import {searchAppviewOpts, useSearchAgent} from '#/lib/api/search-routing' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useAgent} from '#/state/session' import { didOrHandleUriMatches, embedViewRecordToPostView, @@ -38,7 +37,7 @@ export function useSearchPostsQuery({ sort?: 'top' | 'latest' enabled?: boolean }) { - const agent = useAgent() + const agent = useSearchAgent() const moderationOpts = useModerationOpts() const selectArgs = useMemo( () => ({ diff --git a/src/state/queries/trending/useGetSuggestedFeedsQuery.ts b/src/state/queries/trending/useGetSuggestedFeedsQuery.ts index 42d27d9f397..9184008dc18 100644 --- a/src/state/queries/trending/useGetSuggestedFeedsQuery.ts +++ b/src/state/queries/trending/useGetSuggestedFeedsQuery.ts @@ -4,6 +4,7 @@ import { aggregateUserInterests, createBskyTopicsHeader, } from '#/lib/api/feed/utils' +import {searchAppviewOpts} from '#/lib/api/search-routing' import {getContentLanguages} from '#/state/preferences/languages' import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' @@ -30,6 +31,7 @@ export function useGetSuggestedFeedsQuery({enabled}: {enabled?: boolean}) { }, { headers: { + ...searchAppviewOpts().headers, ...createBskyTopicsHeader(aggregateUserInterests(preferences)), 'Accept-Language': contentLangs, },