From 17c17b4543d9ccc1a6bb91bf0257d7548f8885ef Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 4 Aug 2026 01:50:05 +0300 Subject: [PATCH] migrate the preference read-modify-writes to sdk actions The preferences hooks, nuxs, labeler subscriptions, list mute/block, post interaction settings, interests and live event preferences move onto @bsky.app/sdk actions. Preference reads and writes route through the pds client because they live on the account's actor store, not behind the appview proxy; the list mute writes stay on the appview client, which is where that graph endpoint lives. BskyAgent.getPreferences configured the agent's labelers as a side effect, so a labeler subscription took effect on the next appview read. The sdk action does not, so the query now calls configureLabelers explicitly. Exported hook signatures, query keys and the usePreferencesQuery response shape are unchanged: the sdk's BskyPreferences is field-for-field identical to the legacy one apart from branded strings, so the response is cast at that single seam and the muted-word and nux mutation inputs stay legacy-typed with a cast into the action. --- src/features/liveEvents/preferences.ts | 35 ++-- src/screens/Settings/InterestsSettings.tsx | 9 +- src/screens/Settings/Settings.tsx | 7 +- src/state/queries/labeler.ts | 21 ++- src/state/queries/list.ts | 26 ++- src/state/queries/nuxs/index.ts | 23 ++- .../queries/post-interaction-settings.ts | 11 +- src/state/queries/preferences/index.ts | 168 ++++++++++++------ 8 files changed, 192 insertions(+), 108 deletions(-) diff --git a/src/features/liveEvents/preferences.ts b/src/features/liveEvents/preferences.ts index 6fb2ac6d11..bd8cbbd645 100644 --- a/src/features/liveEvents/preferences.ts +++ b/src/features/liveEvents/preferences.ts @@ -1,12 +1,12 @@ import {useEffect} from 'react' -import {type Agent, AppBskyActorDefs, asPredicate} from '@atproto/api' +import {getPreferences, updateLiveEventPreferences} from '@bsky.app/sdk' import {useMutation, useQueryClient} from '@tanstack/react-query' import { preferencesQueryKey, usePreferencesQuery, } from '#/state/queries/preferences' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' import {useAnalytics} from '#/analytics' import * as env from '#/env' import {IS_WEB} from '#/env' @@ -14,10 +14,11 @@ import { type LiveEventFeed, type LiveEventFeedMetricContext, } from '#/features/liveEvents/types' +import {type app} from '#/lexicons' export type LiveEventPreferencesAction = Parameters< - Agent['updateLiveEventPreferences'] ->[0] & { + typeof updateLiveEventPreferences +>[1] & { /** * Flag that is internal to this hook, do not set when updating prefs */ @@ -38,7 +39,7 @@ export function useLiveEventPreferences() { function useWebOnlyDebugLiveEventPreferences() { const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() useEffect(() => { if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') { @@ -46,14 +47,14 @@ function useWebOnlyDebugLiveEventPreferences() { window.__updateLiveEventPreferences = async ( action: LiveEventPreferencesAction, ) => { - await agent.updateLiveEventPreferences(action) + await pdsClient.call(updateLiveEventPreferences, action) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) } } - }, [agent, queryClient]) + }, [pdsClient, queryClient]) } export function useUpdateLiveEventPreferences(props: { @@ -65,10 +66,10 @@ export function useUpdateLiveEventPreferences(props: { }) { const ax = useAnalytics() const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() return useMutation< - AppBskyActorDefs.LiveEventPreferences, + app.bsky.actor.defs.LiveEventPreferences, Error, LiveEventPreferencesAction, {undoAction: LiveEventPreferencesAction | null} @@ -108,10 +109,14 @@ export function useUpdateLiveEventPreferences(props: { } }, mutationFn: async action => { - const updated = await agent.updateLiveEventPreferences(action) - const prefs = updated.find(p => - asPredicate(AppBskyActorDefs.validateLiveEventPreferences)(p), - ) + /* + * The SDK action returns void, so after applying the update we read the + * fresh, interpreted preferences back to obtain the updated + * `liveEventPreferences` (the SDK extracts it from the raw prefs array for + * us, replacing the old `asPredicate(...).find(...)` lookup). + */ + await pdsClient.call(updateLiveEventPreferences, action) + const {liveEventPreferences: prefs} = await pdsClient.call(getPreferences) switch (action.type) { case 'hideFeed': @@ -138,7 +143,7 @@ export function useUpdateLiveEventPreferences(props: { break } case 'toggleHideAllFeeds': { - if (prefs!.hideAllFeeds) { + if (prefs.hideAllFeeds) { ax.metric('liveEvents:hideAllFeedBanners', { context: props.metricContext, }) @@ -156,7 +161,7 @@ export function useUpdateLiveEventPreferences(props: { queryKey: preferencesQueryKey, }) - return prefs! + return prefs }, }) } diff --git a/src/screens/Settings/InterestsSettings.tsx b/src/screens/Settings/InterestsSettings.tsx index ced7148669..351ee60ffc 100644 --- a/src/screens/Settings/InterestsSettings.tsx +++ b/src/screens/Settings/InterestsSettings.tsx @@ -1,5 +1,6 @@ import {useMemo, useState} from 'react' import {type TextStyle, View, type ViewStyle} from 'react-native' +import {setInterestsPref} from '@bsky.app/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -23,7 +24,7 @@ import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trendi import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' import {atoms as a, useGutters, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' import {Divider} from '#/components/Divider' @@ -88,7 +89,7 @@ function Inner({ setIsSaving: (isSaving: boolean) => void }) { const {_} = useLingui() - const agent = useAgent() + const pdsClient = usePdsClient() const qc = useQueryClient() const interestsDisplayNames = useInterestsDisplayNames() const preselectedInterests = useMemo( @@ -110,7 +111,7 @@ function Inner({ setIsSaving(true) try { - await agent.setInterestsPref({tags: interests}) + await pdsClient.call(setInterestsPref, {tags: interests}) qc.setQueriesData( {queryKey: preferencesQueryKey}, (old?: UsePreferencesQueryResponse) => { @@ -157,7 +158,7 @@ function Inner({ setIsSaving(false) } }, 1500) - }, [_, agent, setIsSaving, qc, preselectedInterests]) + }, [_, pdsClient, setIsSaving, qc, preselectedInterests]) const onChangeInterests = async (interests: string[]) => { setInterests(interests) diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 37d0e9b5fa..0d5b2bcc73 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -2,6 +2,7 @@ import {useState} from 'react' import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' +import {removeNuxs} from '@bsky.app/sdk' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -21,7 +22,7 @@ import {clearStorage} from '#/state/persisted' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' import {type SessionAccount, useSession, useSessionApi} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import {useLoggedOutViewControls} from '#/state/shell/logged-out' @@ -385,7 +386,7 @@ function ProfilePreview({ function DevOptions() { const {t: l} = useLingui() - const agent = useAgent() + const pdsClient = usePdsClient() const [override, setOverride] = useStorage(device, [ 'policyUpdateDebugOverride', ]) @@ -560,7 +561,7 @@ function DevOptions() {