From 81bf60d79266bc40def5f074e33bd13220205fda Mon Sep 17 00:00:00 2001 From: xan.lol Date: Thu, 23 Apr 2026 23:46:14 +0000 Subject: [PATCH] fix: preferences regression also revert 7753fdafc171ed2f4d28023df24205c698b186d6 and 5d662082605f109391bebeb649a2f958aeefb120 --- src/state/session/index.tsx | 11 ----------- src/state/queries/preferences/index.ts | 321 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 2 file(s) changed, 72 insertion(s)(+), 260 deletion(s)(-) diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -481,14 +481,3 @@ } return agent } - -export function useBlankPrefAuthedAgent(): Agent { - const agent = useContext(AgentContext) - if (!agent) { - throw Error('useAgent() must be below .') - } - - return useMemo(() => { - return (agent as BskyAppAgent | OauthBskyAppAgent).cloneWithoutProxy() - }, [agent]) -} diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -1,11 +1,9 @@ -import {useEffect, useMemo} from 'react' +import {useCallback} from 'react' import { type AppBskyActorDefs, type BskyFeedViewPreference, - type BskyPreferences, type LabelPreference, } from '@atproto/api' -import {TID} from '@atproto/common-web' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {PROD_DEFAULT_FEED} from '#/lib/constants' @@ -22,7 +20,7 @@ type UsePreferencesQueryResponse, } from '#/state/queries/preferences/types' import {createQueryKey} from '#/state/queries/util' -import {useBlankPrefAuthedAgent as useAgent} from '#/state/session' +import {useAgent} from '#/state/session' import {pdsAgent} from '#/state/session/agent' import {saveLabelers} from '#/state/session/agent-config' import {useAgeAssurance} from '#/ageAssurance' @@ -38,103 +36,6 @@ {}, {persistedVersion: 1}, ) - -/** - * Some screens interpret missing prefs as "use defaults", which causes a - * visible flicker when the preferences query briefly has no data. Retain the - * last successful snapshot per account so those consumers stay stable. - */ -const lastKnownPreferencesByDid = new Map() - -function normalizePreferences( - res: BskyPreferences, -): UsePreferencesQueryResponse { - return { - ...res, - savedFeeds: res.savedFeeds.filter(f => f.type !== 'unknown'), - /** - * Special preference, only used for following feed, previously - * called `home` - */ - feedViewPrefs: { - ...DEFAULT_HOME_FEED_PREFS, - ...(res.feedViewPrefs.home || {}), - }, - threadViewPrefs: { - ...DEFAULT_THREAD_VIEW_PREFS, - ...(res.threadViewPrefs ?? {}), - }, - userAge: res.birthDate ? getAge(res.birthDate) : undefined, - } -} - -function ensureBirthDate( - preferences: UsePreferencesQueryResponse, -): UsePreferencesQueryResponse { - if (!preferences.birthDate || preferences.birthDate instanceof Date) { - return preferences - } - return { - ...preferences, - birthDate: new Date(preferences.birthDate), - } -} - -function applyAgeAssurancePreferences( - data: UsePreferencesQueryResponse, - aa: ReturnType, -) { - /** - * Prefs are all downstream of age assurance now. For logged-out - * users, we override moderation prefs based on AA state. - */ - if (aa.state.access !== aa.Access.Full) { - return { - ...data, - moderationPrefs: makeAgeRestrictedModerationPrefs(data.moderationPrefs), - } - } - return data -} - -type PreferencesMutationContext = { - previousPreferences: UsePreferencesQueryResponse | undefined -} - -function updateCachedPreferences( - queryClient: ReturnType, - updater: (data: UsePreferencesQueryResponse) => UsePreferencesQueryResponse, -) { - queryClient.setQueryData( - preferencesQueryKey, - previous => (previous ? updater(previous) : previous), - ) -} - -async function mutateCachedPreferences( - queryClient: ReturnType, - updater: (data: UsePreferencesQueryResponse) => UsePreferencesQueryResponse, -): Promise { - await queryClient.cancelQueries({queryKey: preferencesQueryKey}) - const previousPreferences = - queryClient.getQueryData(preferencesQueryKey) - updateCachedPreferences(queryClient, updater) - return {previousPreferences} -} - -function restoreCachedPreferences( - queryClient: ReturnType, - context: PreferencesMutationContext | undefined, -) { - if (!context) return - queryClient.setQueryData(preferencesQueryKey, context.previousPreferences) -} - -function refetchPreferences(queryClient: ReturnType) { - void queryClient.invalidateQueries({ - queryKey: preferencesQueryKey, - }) -} export function usePreferencesQuery() { const agent = useAgent() @@ -153,46 +54,59 @@ const res = await pdsAgent(agent).getPreferences() // save to local storage to ensure there are labels on initial requests - void saveLabelers( + saveLabelers( agent.did, - res.moderationPrefs.labelers.map((l: {did: string}) => l.did), + res.moderationPrefs.labelers.map(l => l.did), ) - return normalizePreferences(res) + const preferences: UsePreferencesQueryResponse = { + ...res, + savedFeeds: res.savedFeeds.filter(f => f.type !== 'unknown'), + /** + * Special preference, only used for following feed, previously + * called `home` + */ + feedViewPrefs: { + ...DEFAULT_HOME_FEED_PREFS, + ...(res.feedViewPrefs.home || {}), + }, + threadViewPrefs: { + ...DEFAULT_THREAD_VIEW_PREFS, + ...(res.threadViewPrefs ?? {}), + }, + userAge: res.birthDate ? getAge(res.birthDate) : undefined, + } + return preferences } }, + select: useCallback( + (data: UsePreferencesQueryResponse) => { + /** + * Prefs are all downstream of age assurance now. For logged-out + * users, we override moderation prefs based on AA state. + */ + if (aa.state.access !== aa.Access.Full) { + data = { + ...data, + moderationPrefs: makeAgeRestrictedModerationPrefs( + data.moderationPrefs, + ), + } + } + return data + }, + [aa], + ), }) - useEffect(() => { - if (agent.did && query.data) { - lastKnownPreferencesByDid.set(agent.did, ensureBirthDate(query.data)) - } - }, [agent.did, query.data]) - - const stableData = useMemo(() => { - const data = - query.data ?? - (agent.did ? lastKnownPreferencesByDid.get(agent.did) : undefined) - if (!data) { - return data - } - return applyAgeAssurancePreferences(ensureBirthDate(data), aa) - }, [aa, agent.did, query.data]) - - if (!stableData) { - return query + if (query.data?.birthDate) { + /** + * The persisted query cache stores dates as strings, but our code expects a `Date`. + */ + query.data.birthDate = new Date(query.data.birthDate) } - return { - ...query, - data: stableData, - error: null, - isError: false, - isLoading: false, - isPending: false, - isSuccess: true, - status: 'success', - } + return query } export function useClearPreferencesMutation() { @@ -315,25 +229,13 @@ const queryClient = useQueryClient() const agent = useAgent() - return useMutation< - void, - unknown, - AppBskyActorDefs.SavedFeed[], - PreferencesMutationContext - >({ - onMutate: savedFeeds => - mutateCachedPreferences(queryClient, data => ({ - ...data, - savedFeeds, - })), - onError: (_error, _savedFeeds, context) => { - restoreCachedPreferences(queryClient, context) - }, - onSettled: () => { - refetchPreferences(queryClient) - }, + return useMutation({ mutationFn: async savedFeeds => { await agent.overwriteSavedFeeds(savedFeeds) + // triggers a refetch + await queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) }, }) } @@ -345,27 +247,14 @@ return useMutation< void, unknown, - Pick[], - PreferencesMutationContext + Pick[] >({ - onMutate: savedFeeds => - mutateCachedPreferences(queryClient, data => ({ - ...data, - savedFeeds: data.savedFeeds.concat( - savedFeeds.map(savedFeed => ({ - ...savedFeed, - id: TID.nextStr(), - })), - ), - })), - onError: (_error, _savedFeeds, context) => { - restoreCachedPreferences(queryClient, context) - }, - onSettled: () => { - refetchPreferences(queryClient) - }, mutationFn: async savedFeeds => { await agent.addSavedFeeds(savedFeeds) + // triggers a refetch + await queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) }, }) } @@ -374,25 +263,13 @@ const queryClient = useQueryClient() const agent = useAgent() - return useMutation< - void, - unknown, - Pick, - PreferencesMutationContext - >({ - onMutate: savedFeed => - mutateCachedPreferences(queryClient, data => ({ - ...data, - savedFeeds: data.savedFeeds.filter(feed => feed.id !== savedFeed.id), - })), - onError: (_error, _savedFeed, context) => { - restoreCachedPreferences(queryClient, context) - }, - onSettled: () => { - refetchPreferences(queryClient) - }, + return useMutation>({ mutationFn: async savedFeed => { await agent.removeSavedFeeds([savedFeed.id]) + // triggers a refetch + await queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) }, }) } @@ -401,49 +278,7 @@ const queryClient = useQueryClient() const agent = useAgent() - return useMutation< - void, - unknown, - { - forYouFeedConfig: AppBskyActorDefs.SavedFeed | undefined - discoverFeedConfig: AppBskyActorDefs.SavedFeed | undefined - }, - PreferencesMutationContext - >({ - onMutate: ({forYouFeedConfig, discoverFeedConfig}) => - mutateCachedPreferences(queryClient, data => { - let savedFeeds = data.savedFeeds - - if (forYouFeedConfig) { - savedFeeds = savedFeeds.filter( - feed => feed.id !== forYouFeedConfig.id, - ) - } - - if (!discoverFeedConfig) { - savedFeeds = savedFeeds.concat({ - type: 'feed', - value: PROD_DEFAULT_FEED('whats-hot'), - pinned: true, - id: TID.nextStr(), - }) - } else { - savedFeeds = savedFeeds.map(feed => - feed.id === discoverFeedConfig.id ? {...feed, pinned: true} : feed, - ) - } - - return { - ...data, - savedFeeds, - } - }), - onError: (_error, _variables, context) => { - restoreCachedPreferences(queryClient, context) - }, - onSettled: () => { - refetchPreferences(queryClient) - }, + return useMutation({ mutationFn: async ({ forYouFeedConfig, discoverFeedConfig, @@ -470,6 +305,10 @@ }, ]) } + // triggers a refetch + await queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) }, }) } @@ -478,30 +317,14 @@ const queryClient = useQueryClient() const agent = useAgent() - return useMutation< - void, - unknown, - AppBskyActorDefs.SavedFeed[], - PreferencesMutationContext - >({ - onMutate: feeds => - mutateCachedPreferences(queryClient, data => { - const nextById = new Map(feeds.map(feed => [feed.id, feed])) - return { - ...data, - savedFeeds: data.savedFeeds.map( - feed => nextById.get(feed.id) ?? feed, - ), - } - }), - onError: (_error, _feeds, context) => { - restoreCachedPreferences(queryClient, context) - }, - onSettled: () => { - refetchPreferences(queryClient) - }, + return useMutation({ mutationFn: async feeds => { await agent.updateSavedFeeds(feeds) + + // triggers a refetch + await queryClient.invalidateQueries({ + queryKey: preferencesQueryKey, + }) }, }) } -- tangled.sh