From 612a778361edc0314f92a0451abb98843e7bef9a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 3 Mar 2026 19:35:09 +0000 Subject: [PATCH 1/8] Set mute word dialog text input return key type to "done" (#9967) Co-authored-by: Claude --- src/components/dialogs/MutedWords.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index 9c7b7580a3..94c48dcc23 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -132,6 +132,7 @@ function MutedWordsInner() { autoCorrect={false} autoCapitalize="none" autoComplete="off" + returnKeyType="done" label={_(msg`Enter a word or tag`)} placeholder={_(msg`Enter a word or tag`)} value={field} From 37a82761f597d165b4e6262d31b21f938edda7c1 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 3 Mar 2026 19:50:22 +0000 Subject: [PATCH 2/8] Cache profile view before opening AfterReportDialog (#9962) --- src/components/dms/AfterReportDialog.tsx | 4 ++-- src/components/dms/ConvoMenu.tsx | 13 ++++++++++++- src/components/dms/MessageContextMenu.tsx | 7 ++++++- src/components/moderation/ReportDialog/action.ts | 2 +- src/screens/Messages/components/RequestButtons.tsx | 13 ++++++++++++- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/components/dms/AfterReportDialog.tsx b/src/components/dms/AfterReportDialog.tsx index ad375be4da..7897846154 100644 --- a/src/components/dms/AfterReportDialog.tsx +++ b/src/components/dms/AfterReportDialog.tsx @@ -69,13 +69,13 @@ function DialogInner({ const control = Dialog.useDialogContext() const { data: profile, - isLoading, + isPending, isError, } = useProfileQuery({ did: params.message.sender.did, }) - return isLoading ? ( + return isPending ? ( diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 6fcca20814..1ea2a90cd6 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -5,6 +5,7 @@ import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' +import {useQueryClient} from '@tanstack/react-query' import {type NavigationProp} from '#/lib/routes/types' import {type Shadow} from '#/state/cache/types' @@ -13,7 +14,10 @@ import { useMarkAsReadMutation, } from '#/state/queries/messages/conversation' import {useMuteConvo} from '#/state/queries/messages/mute-conversation' -import {useProfileBlockMutationQueue} from '#/state/queries/profile' +import { + unstableCacheProfileView, + useProfileBlockMutationQueue, +} from '#/state/queries/profile' import * as Toast from '#/view/com/util/Toast' import {type ViewStyleProp} from '#/alf' import {atoms as a} from '#/alf' @@ -63,6 +67,7 @@ let ConvoMenu = ({ style?: ViewStyleProp['style'] }): React.ReactNode => { const {_} = useLingui() + const queryClient = useQueryClient() const leaveConvoControl = Prompt.usePromptControl() const reportControl = Prompt.usePromptControl() @@ -125,6 +130,12 @@ let ConvoMenu = ({ }} control={reportControl} onAfterSubmit={() => { + const sender = convo.members.find( + member => member.did === latestReportableMessage.sender.did, + ) + if (sender) { + unstableCacheProfileView(queryClient, sender) + } blockOrDeleteControl.open() }} /> diff --git a/src/components/dms/MessageContextMenu.tsx b/src/components/dms/MessageContextMenu.tsx index 8bc7e018f1..b9eacf1ee4 100644 --- a/src/components/dms/MessageContextMenu.tsx +++ b/src/components/dms/MessageContextMenu.tsx @@ -4,11 +4,13 @@ import * as Clipboard from 'expo-clipboard' import {type ChatBskyConvoDefs, RichText} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import {useTranslate} from '#/lib/hooks/useTranslate' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {useConvoActive} from '#/state/messages/convo' import {useLanguagePrefs} from '#/state/preferences' +import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import * as ContextMenu from '#/components/ContextMenu' @@ -36,6 +38,7 @@ export let MessageContextMenu = ({ const {_} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() + const queryClient = useQueryClient() const convo = useConvoActive() const deleteControl = usePromptControl() const reportControl = usePromptControl() @@ -170,7 +173,6 @@ export let MessageContextMenu = ({ { + if (sender) { + unstableCacheProfileView(queryClient, sender) + } blockOrDeleteControl.open() }} /> diff --git a/src/components/moderation/ReportDialog/action.ts b/src/components/moderation/ReportDialog/action.ts index 7f29cd33f8..ee776fd623 100644 --- a/src/components/moderation/ReportDialog/action.ts +++ b/src/components/moderation/ReportDialog/action.ts @@ -102,7 +102,7 @@ export function useSubmitReportMutation() { } if (__DEV__) { - logger.info('Submitting report', { + logger.info('Submitting report (dry run)', { labeler: { handle: labeler.creator.handle, }, diff --git a/src/screens/Messages/components/RequestButtons.tsx b/src/screens/Messages/components/RequestButtons.tsx index 9f9d7236b1..82b5b553f7 100644 --- a/src/screens/Messages/components/RequestButtons.tsx +++ b/src/screens/Messages/components/RequestButtons.tsx @@ -12,7 +12,10 @@ import {useEmail} from '#/state/email-verification' import {useAcceptConversation} from '#/state/queries/messages/accept-conversation' import {precacheConvoQuery} from '#/state/queries/messages/conversation' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' -import {useProfileBlockMutationQueue} from '#/state/queries/profile' +import { + unstableCacheProfileView, + useProfileBlockMutationQueue, +} from '#/state/queries/profile' import * as Toast from '#/view/com/util/Toast' import {atoms as a} from '#/alf' import { @@ -53,6 +56,8 @@ export function RejectMenu({ const {_} = useLingui() const shadowedProfile = useProfileShadow(profile) const navigation = useNavigation() + const queryClient = useQueryClient() + const {mutate: leaveConvo} = useLeaveConvo(convo.id, { onMutate: () => { if (currentScreen === 'conversation') { @@ -174,6 +179,12 @@ export function RejectMenu({ }} control={reportControl} onAfterSubmit={() => { + const sender = convo.members.find( + member => member.did === lastMessage.sender.did, + ) + if (sender) { + unstableCacheProfileView(queryClient, sender) + } blockOrDeleteControl.open() }} /> From 7dc3b4fa8eda951fe6de91981d3293e14d237993 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Tue, 3 Mar 2026 12:49:38 -0800 Subject: [PATCH 3/8] Add search event analytics (#9949) Co-authored-by: Eric Bailey --- src/analytics/metrics/types.ts | 26 ++++ src/components/FeedCard.tsx | 48 +++--- src/components/ProfileCard.tsx | 88 +++++------ src/components/forms/SearchInput.tsx | 15 +- src/screens/Search/SearchResults.tsx | 147 ++++++++++++++++-- src/screens/Search/Shell.tsx | 18 ++- .../Search/components/AutocompleteResults.tsx | 8 +- .../Search/components/SearchHistory.tsx | 33 ++-- src/view/com/profile/ProfileCard.tsx | 5 +- src/view/shell/desktop/Search.tsx | 23 ++- 10 files changed, 291 insertions(+), 120 deletions(-) diff --git a/src/analytics/metrics/types.ts b/src/analytics/metrics/types.ts index efb002f40c..28c3150c26 100644 --- a/src/analytics/metrics/types.ts +++ b/src/analytics/metrics/types.ts @@ -649,6 +649,32 @@ export type Events = { tab: string } + 'search:query': { + source: 'typed' | 'history' | 'autocomplete' + } + + 'search:results:loaded': { + tab: 'top' | 'latest' | 'people' | 'feeds' + initialCount: number + } + + 'search:result:press': { + tab?: 'top' | 'latest' | 'people' | 'feeds' + resultType: 'post' | 'profile' | 'feed' + position: number + uri: string + } + + 'search:recent:press': { + profileDid: string + position: number + } + + 'search:autocomplete:press': { + profileDid: string + position: number + } + 'progressGuide:hide': {} 'progressGuide:followDialog:open': {} diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index bf2245599d..8c087d17e7 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react' +import {useCallback, useEffect, useMemo} from 'react' import {type GestureResponderEvent, View} from 'react-native' import { type AppBskyFeedDefs, @@ -6,9 +6,7 @@ import { AtUri, RichText as RichTextApi, } from '@atproto/api' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Plural, Trans} from '@lingui/react/macro' +import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {sanitizeHandle} from '#/lib/strings/handles' @@ -73,11 +71,11 @@ export function Link({ }: Props & Omit) { const queryClient = useQueryClient() - const href = React.useMemo(() => { + const href = useMemo(() => { return createProfileFeedHref({feed: view}) }, [view]) - React.useEffect(() => { + useEffect(() => { precacheFeedFromGeneratorView(queryClient, view) }, [view, queryClient]) @@ -212,7 +210,7 @@ export function Description({ description, ...rest }: {description?: string} & Partial) { - const rt = React.useMemo(() => { + const rt = useMemo(() => { if (!description) return const rt = new RichTextApi({text: description || ''}) rt.detectFacetsWithoutResolution() @@ -279,7 +277,7 @@ function SaveButtonInner({ pin?: boolean text?: boolean } & Partial) { - const {_} = useLingui() + const {t: l} = useLingui() const {data: preferences} = usePreferencesQuery() const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} = useAddSavedFeedsMutation() @@ -289,13 +287,13 @@ function SaveButtonInner({ const uri = view.uri const type = view.uri.includes('app.bsky.feed.generator') ? 'feed' : 'list' - const savedFeedConfig = React.useMemo(() => { + const savedFeedConfig = useMemo(() => { return preferences?.savedFeeds?.find(feed => feed.value === uri) }, [preferences?.savedFeeds, uri]) const removePromptControl = Prompt.usePromptControl() const isPending = isAddSavedFeedPending || isRemovePending - const toggleSave = React.useCallback( + const toggleSave = useCallback( async (e: GestureResponderEvent) => { e.preventDefault() e.stopPropagation() @@ -312,17 +310,17 @@ function SaveButtonInner({ }, ]) } - Toast.show(_(msg({message: 'Feeds updated!', context: 'toast'}))) + Toast.show(l({message: 'Feeds updated!', context: 'toast'})) } catch (err: any) { logger.error(err, {message: `FeedCard: failed to update feeds`, pin}) - Toast.show(_(msg`Failed to update feeds`), 'xmark') + Toast.show(l`Failed to update feeds`, 'xmark') } }, - [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig, type], + [l, pin, saveFeeds, removeFeed, uri, savedFeedConfig, type], ) - const onPrompRemoveFeed = React.useCallback( - async (e: GestureResponderEvent) => { + const onPromptRemoveFeed = useCallback( + (e: GestureResponderEvent) => { e.preventDefault() e.stopPropagation() @@ -335,11 +333,13 @@ function SaveButtonInner({ <>