From a148b2477b93c60c9b5bd641b2fe1dd3b1e6ba7a Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Wed, 4 Feb 2026 15:06:42 -0500 Subject: [PATCH 01/20] update design for stacked avatars --- src/components/ProgressGuide/List.tsx | 138 ++++++++++++++------------ src/components/icons/Person.tsx | 5 + 2 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index 81abc586f..f86059a4c 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -1,4 +1,10 @@ -import {type StyleProp, View, type ViewStyle} from 'react-native' +import {useState} from 'react' +import { + type LayoutChangeEvent, + type StyleProp, + View, + type ViewStyle, +} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -11,7 +17,7 @@ import { import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useBreakpoints, useLayoutBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' -import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' +import {Person_Filled as PersonIcon} from '#/components/icons/Person' import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' @@ -51,7 +57,7 @@ export function ProgressGuideList({style}: {style?: StyleProp}) { a.flex_col, a.gap_md, a.rounded_md, - t.atoms.bg_contrast_25, + t.atoms.bg_contrast_50, a.p_lg, style, ]}> @@ -112,70 +118,80 @@ export function ProgressGuideList({style}: {style?: StyleProp}) { function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) { const t = useTheme() - const {centerColumnOffset} = useLayoutBreakpoints() + const [containerWidth, setContainerWidth] = useState(0) + + const onLayout = (e: LayoutChangeEvent) => { + setContainerWidth(e.nativeEvent.layout.width) + } - // Smaller avatars for narrower viewport - const avatarSize = centerColumnOffset ? 30 : 37 - const overlap = centerColumnOffset ? 9 : 11 - const iconSize = centerColumnOffset ? 14 : 18 + // Overlap ratio (22% of avatar size) + const overlapRatio = 0.22 + + // Calculate avatar size to fill container width + // Formula: containerWidth = avatarSize * count - overlap * (count - 1) + // Where overlap = avatarSize * overlapRatio + const visiblePortions = TOTAL_AVATARS - overlapRatio * (TOTAL_AVATARS - 1) + const avatarSize = containerWidth > 0 ? containerWidth / visiblePortions : 0 + const overlap = avatarSize * overlapRatio + const iconSize = avatarSize * 0.45 - // Use actual follows count, not the guide's event counter const followedAvatars = follows?.slice(0, TOTAL_AVATARS) ?? [] const remainingSlots = TOTAL_AVATARS - followedAvatars.length - // Total width calculation: first avatar + (remaining * visible portion) - const totalWidth = avatarSize + (TOTAL_AVATARS - 1) * (avatarSize - overlap) - return ( - - {/* Show followed user avatars */} - {followedAvatars.map((follow, i) => ( - - - - ))} - {/* Show placeholder avatars for remaining slots */} - {Array(remainingSlots) - .fill(0) - .map((_, i) => ( - - - - ))} + + {containerWidth > 0 && ( + <> + {/* Show followed user avatars */} + {followedAvatars.map((follow, i) => ( + + + + ))} + {/* Show placeholder avatars for remaining slots */} + {Array(remainingSlots) + .fill(0) + .map((_, i) => ( + + + + ))} + + )} ) } diff --git a/src/components/icons/Person.tsx b/src/components/icons/Person.tsx index 55324f10d..74a8ac97d 100644 --- a/src/components/icons/Person.tsx +++ b/src/components/icons/Person.tsx @@ -36,3 +36,8 @@ export const PersonPlus_Stroke2_Corner2_Rounded = createSinglePathSVG({ export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({ path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z', }) + +export const Person_Filled = createSinglePathSVG({ + viewBox: '0 0 14 16', + path: 'M6.5 0C4.567 0 3 1.567 3 3.5C3 5.433 4.567 7 6.5 7C8.43301 7 10 5.433 10 3.5C10 1.567 8.43301 0 6.5 0Z M6.50002 8C3.43062 8 1.08148 9.78593 0.127626 12.2902C-0.145062 13.0062 0.0416292 13.7118 0.475104 14.2134C0.897533 14.7023 1.54964 15 2.25212 15H10.7479C11.4503 15 12.1024 14.7023 12.5249 14.2134C12.9584 13.7118 13.145 13.0062 12.8724 12.2902C11.9185 9.78593 9.56941 8 6.50002 8Z', +}) -- 2.51.2 From a57faedbae1508f89df6c510fcab5d6f35d71c44 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 5 Feb 2026 08:51:53 -0500 Subject: [PATCH 02/20] update icon --- assets/icons/person_filled_corner2_rounded.svg | 1 + src/components/ProgressGuide/List.tsx | 4 ++-- src/components/icons/Person.tsx | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 assets/icons/person_filled_corner2_rounded.svg diff --git a/assets/icons/person_filled_corner2_rounded.svg b/assets/icons/person_filled_corner2_rounded.svg new file mode 100644 index 000000000..d22056593 --- /dev/null +++ b/assets/icons/person_filled_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index f86059a4c..868ee79a3 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -17,7 +17,7 @@ import { import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useBreakpoints, useLayoutBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' -import {Person_Filled as PersonIcon} from '#/components/icons/Person' +import {Person_Filled_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' @@ -133,7 +133,7 @@ function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) { const visiblePortions = TOTAL_AVATARS - overlapRatio * (TOTAL_AVATARS - 1) const avatarSize = containerWidth > 0 ? containerWidth / visiblePortions : 0 const overlap = avatarSize * overlapRatio - const iconSize = avatarSize * 0.45 + const iconSize = avatarSize * 0.5 const followedAvatars = follows?.slice(0, TOTAL_AVATARS) ?? [] const remainingSlots = TOTAL_AVATARS - followedAvatars.length diff --git a/src/components/icons/Person.tsx b/src/components/icons/Person.tsx index 74a8ac97d..83a6db1f2 100644 --- a/src/components/icons/Person.tsx +++ b/src/components/icons/Person.tsx @@ -37,7 +37,6 @@ export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({ path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z', }) -export const Person_Filled = createSinglePathSVG({ - viewBox: '0 0 14 16', - path: 'M6.5 0C4.567 0 3 1.567 3 3.5C3 5.433 4.567 7 6.5 7C8.43301 7 10 5.433 10 3.5C10 1.567 8.43301 0 6.5 0Z M6.50002 8C3.43062 8 1.08148 9.78593 0.127626 12.2902C-0.145062 13.0062 0.0416292 13.7118 0.475104 14.2134C0.897533 14.7023 1.54964 15 2.25212 15H10.7479C11.4503 15 12.1024 14.7023 12.5249 14.2134C12.9584 13.7118 13.145 13.0062 12.8724 12.2902C11.9185 9.78593 9.56941 8 6.50002 8Z', +export const Person_Filled_Corner2_Rounded = createSinglePathSVG({ + path: 'M12.233 2a4.433 4.433 0 1 0 0 8.867 4.433 4.433 0 0 0 0-8.867ZM12.233 12.133c-3.888 0-6.863 2.263-8.071 5.435-.346.906-.11 1.8.44 2.436.535.619 1.36.996 2.25.996h10.762c.89 0 1.716-.377 2.25-.996.55-.636.786-1.53.441-2.436-1.208-3.173-4.184-5.435-8.072-5.435Z', }) -- 2.51.2 From 5459857112a25d1b99342c6dba9124c6f0a59450 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:27:18 -0800 Subject: [PATCH 03/20] Use Tanstack Query status over isPending --- src/view/screens/Profile.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index d2f46a4da..2f0dc4cc8 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -79,14 +79,14 @@ function ProfileScreenInner({route}: Props) { data: resolvedDid, error: resolveError, refetch: refetchDid, - isLoading: isLoadingDid, + status: didStatus, } = useResolveDidQuery(name) const { data: profile, error: profileError, refetch: refetchProfile, - isLoading: isLoadingProfile, isPlaceholderData: isPlaceholderProfile, + status: profileStatus, } = useProfileQuery({ did: resolvedDid, }) @@ -117,7 +117,7 @@ function ProfileScreenInner({route}: Props) { }, [queryClient, profile?.viewer?.blockedBy, resolvedDid]) // Most pushes will happen here, since we will have only placeholder data - if (isLoadingDid || isLoadingProfile) { + if (didStatus === 'pending' || profileStatus === 'pending') { return ( -- 2.51.2 From 57997d6d164afface2ff3030f0931dda12e5df10 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Feb 2026 14:01:27 +0000 Subject: [PATCH 04/20] Add --what-to-test to iOS submissions (#9856) Sets the "What to test" field in TestFlight to distinguish between TestFlight and Production builds. Co-authored-by: Claude Opus 4.6 --- .github/workflows/build-submit-ios.yml | 7 ++++++- .github/workflows/bundle-deploy-eas-update.yml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-submit-ios.yml b/.github/workflows/build-submit-ios.yml index 2ebbd66ec..a2576e329 100644 --- a/.github/workflows/build-submit-ios.yml +++ b/.github/workflows/build-submit-ios.yml @@ -118,7 +118,12 @@ jobs: fi - name: 🚀 Deploy - run: eas submit -p ios --non-interactive --path ios-build/ios/build/Bluesky.ipa + env: + PROFILE: ${{ inputs.profile || 'testflight' }} + run: > + eas submit -p ios --non-interactive + --path ios-build/ios/build/Bluesky.ipa + --what-to-test "$([[ "$PROFILE" == "production" ]] && echo "Production build" || echo "TestFlight build")" - name: 🪲 Upload dSYM to Sentry run: > diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index ee59cad97..46c848b87 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -242,7 +242,7 @@ jobs: --local --output build.ipa --non-interactive - name: 🚀 Deploy - run: eas submit -p ios --non-interactive --path build.ipa + run: eas submit -p ios --non-interactive --path build.ipa --what-to-test "TestFlight build" - name: ⬇️ Restore Cache id: get-base-commit -- 2.51.2 From 8581285d34e635898fdd396a9a98cd3488a5cba1 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Feb 2026 16:20:48 +0200 Subject: [PATCH 05/20] simplify some conditionals --- src/components/ProgressGuide/List.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index 868ee79a3..d7cb4d0f0 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -87,8 +87,7 @@ export function ProgressGuideList({style}: {style?: StyleProp}) { a.justify_between, a.gap_sm, ] - : a.flex_col, - !inlineLayout && a.gap_md, + : [a.flex_col, a.gap_md], ]}> -- 2.51.2 From 8580e4a00e8875ae5910f2444f295d0353109e6e Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Feb 2026 15:22:13 +0000 Subject: [PATCH 06/20] absolutely position header, set a headerheight (#9858) --- src/screens/ProfileList/index.tsx | 33 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/screens/ProfileList/index.tsx b/src/screens/ProfileList/index.tsx index ef40706cd..bc802335b 100644 --- a/src/screens/ProfileList/index.tsx +++ b/src/screens/ProfileList/index.tsx @@ -1,4 +1,4 @@ -import {useCallback, useMemo, useRef} from 'react' +import {useCallback, useMemo, useRef, useState} from 'react' import {View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' import { @@ -35,12 +35,13 @@ import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader' import {FAB} from '#/view/com/util/fab/FAB' import {type ListRef} from '#/view/com/util/List' import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' -import {atoms as a, platform} from '#/alf' +import {atoms as a, native, platform, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Hider from '#/components/moderation/Hider' +import {IS_WEB} from '#/env' import {AboutSection} from './AboutSection' import {ErrorScreen} from './components/ErrorScreen' import {Header} from './components/Header' @@ -149,6 +150,7 @@ function ProfileListScreenLoaded({ moderationOpts: ModerationOpts preferences: UsePreferencesQueryResponse }) { + const t = useTheme() const {_} = useLingui() const queryClient = useQueryClient() const {openComposer} = useOpenComposer() @@ -164,6 +166,8 @@ function ProfileListScreenLoaded({ const scrollElRef = useAnimatedRef() const addUserDialogControl = useDialogControl() const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] + // modlist only + const [headerHeight, setHeaderHeight] = useState(null) const moderation = useMemo(() => { return moderateUserList(list, moderationOpts) @@ -263,13 +267,24 @@ function ProfileListScreenLoaded({ - {renderHeader()} - + setHeaderHeight(evt.nativeEvent.layout.height)} + style={[ + native([a.absolute, a.z_10, t.atoms.bg]), + + a.border_b, + t.atoms.border_contrast_low, + ]}> + {renderHeader()} + + {headerHeight !== null && ( + + )} openComposer({logContext: 'Fab'})} -- 2.51.2 From 4eca13941a867a18b8e281707b5e6cb6bbc7850f Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Feb 2026 16:51:23 +0000 Subject: [PATCH 07/20] Fix GIFs breaking mute state/pausing phone audio (#9859) * fix operator precedence mistake * ensure gifs can't change the global mute state --- .../VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx index 9cad63e9a..55ad79e50 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -58,7 +58,7 @@ export function VideoEmbedInnerNative({ { setIsActive(e.nativeEvent.isActive) @@ -67,7 +67,9 @@ export function VideoEmbedInnerNative({ setIsLoading(e.nativeEvent.isLoading) }} onMutedChange={e => { - setMuted(e.nativeEvent.isMuted) + if (!isGif) { + setMuted(e.nativeEvent.isMuted) + } }} onStatusChange={e => { setStatus(e.nativeEvent.status) -- 2.51.2 From 37e5a0f7a4f59e45cf88cc27d1f92c9f6ee9618e Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Feb 2026 17:46:28 +0000 Subject: [PATCH 08/20] Clean up starter packs dialog styles + add optimistic updates (#9469) --- src/components/dialogs/StarterPackDialog.tsx | 116 +++++++++--------- src/state/queries/actor-starter-packs.ts | 27 +---- src/state/queries/list-memberships.ts | 118 ++++++++++++++++++- src/view/com/profile/ProfileMenu.tsx | 20 ++-- 4 files changed, 187 insertions(+), 94 deletions(-) diff --git a/src/components/dialogs/StarterPackDialog.tsx b/src/components/dialogs/StarterPackDialog.tsx index 871b34da5..06aea1bec 100644 --- a/src/components/dialogs/StarterPackDialog.tsx +++ b/src/components/dialogs/StarterPackDialog.tsx @@ -1,4 +1,4 @@ -import {useCallback, useState} from 'react' +import {useCallback} from 'react' import {View} from 'react-native' import { type AppBskyGraphGetStarterPacksWithMembership, @@ -7,20 +7,18 @@ import { import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' -import {useQueryClient} from '@tanstack/react-query' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {type NavigationProp} from '#/lib/routes/types' -import { - invalidateActorStarterPacksWithMembershipQuery, - useActorStarterPacksWithMembershipsQuery, -} from '#/state/queries/actor-starter-packs' +import {isNetworkError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {useActorStarterPacksWithMembershipsQuery} from '#/state/queries/actor-starter-packs' import { useListMembershipAddMutation, useListMembershipRemoveMutation, } from '#/state/queries/list-memberships' -import * as Toast from '#/view/com/util/Toast' -import {atoms as a, useTheme} from '#/alf' +import {useProfileQuery} from '#/state/queries/profile' +import {atoms as a, native, platform, useTheme} from '#/alf' import {AvatarStack} from '#/components/AvatarStack' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -29,6 +27,7 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/ import {StarterPack} from '#/components/icons/StarterPack' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {Loader} from '#/components/Loader' +import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' @@ -131,6 +130,7 @@ function StarterPackList({ }) { const control = Dialog.useDialogContext() const {_} = useLingui() + const {data: subject} = useProfileQuery({did: targetDid}) const { data, @@ -155,9 +155,13 @@ function StarterPackList({ const renderItem = useCallback( ({item}: {item: StarterPackWithMembership}) => ( - + ), - [targetDid], + [targetDid, subject], ) const onClose = useCallback(() => { @@ -168,9 +172,11 @@ function StarterPackList({ <> Add to starter packs @@ -181,7 +187,8 @@ function StarterPackList({ variant="ghost" color="secondary" size="small" - shape="round"> + shape="round" + style={{margin: -8}}> @@ -232,7 +239,10 @@ function StarterPackList({ onEndReachedThreshold={0.1} ListHeaderComponent={listHeader} ListEmptyComponent={} - style={IS_WEB ? [a.px_md, {minHeight: 500}] : [a.px_2xl, a.pt_lg]} + style={platform({ + web: [a.px_2xl, {minHeight: 500}], + native: [a.px_2xl, a.pt_lg], + })} /> ) } @@ -240,66 +250,54 @@ function StarterPackList({ function StarterPackItem({ starterPackWithMembership, targetDid, + subject, }: { starterPackWithMembership: StarterPackWithMembership targetDid: string + subject?: bsky.profile.AnyProfileView }) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() - const queryClient = useQueryClient() const starterPack = starterPackWithMembership.starterPack const isInPack = !!starterPackWithMembership.listItem - const [isPendingRefresh, setIsPendingRefresh] = useState(false) - - const {mutate: addMembership} = useListMembershipAddMutation({ - onSuccess: () => { - Toast.show(_(msg`Added to starter pack`)) - // Use a timeout to wait for the appview to update, matching the pattern - // in list-memberships.ts - setTimeout(() => { - invalidateActorStarterPacksWithMembershipQuery({ - queryClient, - did: targetDid, - }) - setIsPendingRefresh(false) - }, 1e3) - }, - onError: () => { - Toast.show(_(msg`Failed to add to starter pack`), 'xmark') - setIsPendingRefresh(false) - }, - }) + const {mutate: addMembership, isPending: isPendingAdd} = + useListMembershipAddMutation({ + subject, + onSuccess: () => { + Toast.show(_(msg`Added to starter pack`)) + }, + onError: err => { + if (!isNetworkError(err)) { + logger.error('Failed to add to starter pack', {safeMessage: err}) + } + Toast.show(_(msg`Failed to add to starter pack`), {type: 'error'}) + }, + }) - const {mutate: removeMembership} = useListMembershipRemoveMutation({ - onSuccess: () => { - Toast.show(_(msg`Removed from starter pack`)) - // Use a timeout to wait for the appview to update, matching the pattern - // in list-memberships.ts - setTimeout(() => { - invalidateActorStarterPacksWithMembershipQuery({ - queryClient, - did: targetDid, - }) - setIsPendingRefresh(false) - }, 1e3) - }, - onError: () => { - Toast.show(_(msg`Failed to remove from starter pack`), 'xmark') - setIsPendingRefresh(false) - }, - }) + const {mutate: removeMembership, isPending: isPendingRemove} = + useListMembershipRemoveMutation({ + onSuccess: () => { + Toast.show(_(msg`Removed from starter pack`)) + }, + onError: err => { + if (!isNetworkError(err)) { + logger.error('Failed to remove from starter pack', {safeMessage: err}) + } + Toast.show(_(msg`Failed to remove from starter pack`), {type: 'error'}) + }, + }) + + const isPending = isPendingAdd || isPendingRemove const handleToggleMembership = () => { - if (!starterPack.list?.uri || isPendingRefresh) return + if (!starterPack.list?.uri || isPending) return const listUri = starterPack.list.uri const starterPackUri = starterPack.uri - setIsPendingRefresh(true) - if (!isInPack) { addMembership({ listUri: listUri, @@ -309,7 +307,6 @@ function StarterPackItem({ } else { if (!starterPackWithMembership.listItem?.uri) { console.error('Cannot remove: missing membership URI') - setIsPendingRefresh(false) return } removeMembership({ @@ -344,7 +341,7 @@ function StarterPackItem({ starterPack.listItemsSample.length > 0 && ( <> p.subject)} @@ -375,8 +372,9 @@ function StarterPackItem({ label={isInPack ? _(msg`Remove`) : _(msg`Add`)} color={isInPack ? 'secondary' : 'primary_subtle'} size="tiny" - disabled={isPendingRefresh} + disabled={isPending} onPress={handleToggleMembership}> + {isPending && } {isInPack ? Remove : Add} diff --git a/src/state/queries/actor-starter-packs.ts b/src/state/queries/actor-starter-packs.ts index d40e05453..97cc3b5e0 100644 --- a/src/state/queries/actor-starter-packs.ts +++ b/src/state/queries/actor-starter-packs.ts @@ -1,13 +1,4 @@ -import { - type AppBskyGraphGetActorStarterPacks, - type AppBskyGraphGetStarterPacksWithMembership, -} from '@atproto/api' -import { - type InfiniteData, - type QueryClient, - type QueryKey, - useInfiniteQuery, -} from '@tanstack/react-query' +import {type QueryClient, useInfiniteQuery} from '@tanstack/react-query' import {useAgent} from '#/state/session' @@ -28,13 +19,7 @@ export function useActorStarterPacksQuery({ }) { const agent = useAgent() - return useInfiniteQuery< - AppBskyGraphGetActorStarterPacks.OutputSchema, - Error, - InfiniteData, - QueryKey, - string | undefined - >({ + return useInfiniteQuery({ queryKey: RQKEY(did), queryFn: async ({pageParam}: {pageParam?: string}) => { const res = await agent.app.bsky.graph.getActorStarterPacks({ @@ -59,13 +44,7 @@ export function useActorStarterPacksWithMembershipsQuery({ }) { const agent = useAgent() - return useInfiniteQuery< - AppBskyGraphGetStarterPacksWithMembership.OutputSchema, - Error, - InfiniteData, - QueryKey, - string | undefined - >({ + return useInfiniteQuery({ queryKey: RQKEY_WITH_MEMBERSHIP(did), queryFn: async ({pageParam}: {pageParam?: string}) => { const res = await agent.app.bsky.graph.getStarterPacksWithMembership({ diff --git a/src/state/queries/list-memberships.ts b/src/state/queries/list-memberships.ts index 410a613ad..dd7fde248 100644 --- a/src/state/queries/list-memberships.ts +++ b/src/state/queries/list-memberships.ts @@ -14,12 +14,23 @@ * -prf */ -import {AtUri} from '@atproto/api' -import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' +import { + type AppBskyActorDefs, + type AppBskyGraphGetStarterPacksWithMembership, + AtUri, +} from '@atproto/api' +import { + type InfiniteData, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query' import {STALE} from '#/state/queries' import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members' import {useAgent, useSession} from '#/state/session' +import type * as bsky from '#/types/bsky' +import {RQKEY_WITH_MEMBERSHIP as STARTER_PACKS_WITH_MEMBERSHIPS_RKEY} from './actor-starter-packs' // sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records const SANITY_PAGE_LIMIT = 1000 @@ -91,9 +102,14 @@ export function getMembership( } export function useListMembershipAddMutation({ + subject, onSuccess, onError, }: { + /** + * Needed for optimistic update of starter pack query + */ + subject?: bsky.profile.AnyProfileView onSuccess?: (data: {uri: string; cid: string}) => void onError?: (error: Error) => void } = {}) { @@ -151,6 +167,60 @@ export function useListMembershipAddMutation({ queryKey: LIST_MEMBERS_RQKEY(variables.listUri), }) }, 1e3) + + // update WITH_MEMBERSHIPS query + + if (subject) { + queryClient.setQueryData< + InfiniteData + >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { + if (!old) return old + + return { + ...old, + pages: old.pages.map(page => ({ + ...page, + starterPacksWithMembership: page.starterPacksWithMembership.map( + spWithMembership => { + if ( + spWithMembership.starterPack.list && + spWithMembership.starterPack.list?.uri === variables.listUri + ) { + return { + ...spWithMembership, + starterPack: { + ...spWithMembership.starterPack, + listItemsSample: [ + { + uri: data.uri, + subject: subject as AppBskyActorDefs.ProfileView, + }, + ...(spWithMembership.starterPack.listItemsSample?.filter( + item => item.subject.did !== variables.actorDid, + ) ?? []), + ], + list: { + ...spWithMembership.starterPack.list, + listItemCount: + (spWithMembership.starterPack.list.listItemCount ?? + 0) + 1, + }, + }, + listItem: { + uri: data.uri, + subject: subject as AppBskyActorDefs.ProfileView, + }, + } + } + + return spWithMembership + }, + ), + })), + } + }) + } + onSuccess?.(data) }, onError, @@ -206,6 +276,50 @@ export function useListMembershipRemoveMutation({ queryKey: LIST_MEMBERS_RQKEY(variables.listUri), }) }, 1e3) + + queryClient.setQueryData< + InfiniteData + >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { + if (!old) return old + + return { + ...old, + pages: old.pages.map(page => ({ + ...page, + starterPacksWithMembership: page.starterPacksWithMembership.map( + spWithMembership => { + if ( + spWithMembership.starterPack.list && + spWithMembership.starterPack.list.uri === variables.listUri + ) { + return { + ...spWithMembership, + starterPack: { + ...spWithMembership.starterPack, + listItemsSample: + spWithMembership.starterPack.listItemsSample?.filter( + item => item.subject.did !== variables.actorDid, + ), + list: { + ...spWithMembership.starterPack.list, + listItemCount: Math.max( + 0, + (spWithMembership.starterPack.list.listItemCount ?? + 1) - 1, + ), + }, + }, + listItem: undefined, + } + } + + return spWithMembership + }, + ), + })), + } + }) + onSuccess?.(data) }, onError, diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 8d7846d81..684dcbf0f 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -329,15 +329,17 @@ let ProfileMenu = ({ )} )} - - - Add to starter packs - - - + {!isSelf && ( + + + Add to starter packs + + + + )} Date: Wed, 11 Feb 2026 09:46:47 -0800 Subject: [PATCH 09/20] Address feedback and lint warnings --- src/view/screens/Profile.tsx | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 2f0dc4cc8..dd1450c1c 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo} from 'react' +import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' import { @@ -79,45 +79,45 @@ function ProfileScreenInner({route}: Props) { data: resolvedDid, error: resolveError, refetch: refetchDid, - status: didStatus, + isPending: isDidPending, } = useResolveDidQuery(name) const { data: profile, error: profileError, refetch: refetchProfile, isPlaceholderData: isPlaceholderProfile, - status: profileStatus, + isPending: isProfilePending, } = useProfileQuery({ did: resolvedDid, }) - const onPressTryAgain = React.useCallback(() => { + const onPressTryAgain = useCallback(() => { if (resolveError) { - refetchDid() + void refetchDid() } else { - refetchProfile() + void refetchProfile() } }, [resolveError, refetchDid, refetchProfile]) // Apply hard-coded redirects as need - React.useEffect(() => { + useEffect(() => { if (resolveError) { if (name === 'lulaoficial.bsky.social') { console.log('Applying redirect to lula.com.br') - navigate('Profile', {name: 'lula.com.br'}) + void navigate('Profile', {name: 'lula.com.br'}) } } }, [name, resolveError]) // When we open the profile, we want to reset the posts query if we are blocked. - React.useEffect(() => { + useEffect(() => { if (resolvedDid && profile?.viewer?.blockedBy) { resetProfilePostsQueries(queryClient, resolvedDid) } }, [queryClient, profile?.viewer?.blockedBy, resolvedDid]) // Most pushes will happen here, since we will have only placeholder data - if (didStatus === 'pending' || profileStatus === 'pending') { + if (isDidPending || isProfilePending) { return ( @@ -186,20 +186,20 @@ function ProfileScreenLoaded({ did: profile.did, enabled: !!profile.associated?.labeler, }) - const [currentPage, setCurrentPage] = React.useState(0) + const [currentPage, setCurrentPage] = useState(0) const {_} = useLingui() - const [scrollViewTag, setScrollViewTag] = React.useState(null) + const [scrollViewTag, setScrollViewTag] = useState(null) - const postsSectionRef = React.useRef(null) - const repliesSectionRef = React.useRef(null) - const mediaSectionRef = React.useRef(null) - const videosSectionRef = React.useRef(null) - const likesSectionRef = React.useRef(null) - const feedsSectionRef = React.useRef(null) - const listsSectionRef = React.useRef(null) - const starterPacksSectionRef = React.useRef(null) - const labelsSectionRef = React.useRef(null) + const postsSectionRef = useRef(null) + const repliesSectionRef = useRef(null) + const mediaSectionRef = useRef(null) + const videosSectionRef = useRef(null) + const likesSectionRef = useRef(null) + const feedsSectionRef = useRef(null) + const listsSectionRef = useRef(null) + const starterPacksSectionRef = useRef(null) + const labelsSectionRef = useRef(null) useSetTitle(combinedDisplayName(profile)) @@ -315,7 +315,7 @@ function ProfileScreenLoaded({ ) useFocusEffect( - React.useCallback(() => { + useCallback(() => { setMinimalShellMode(false) return listenSoftReset(() => { scrollSectionToTop(currentPage) @@ -601,16 +601,16 @@ function ProfileScreenLoaded({ function useRichText(text: string): [RichTextAPI, boolean] { const agent = useAgent() - const [prevText, setPrevText] = React.useState(text) - const [rawRT, setRawRT] = React.useState(() => new RichTextAPI({text})) - const [resolvedRT, setResolvedRT] = React.useState(null) + const [prevText, setPrevText] = useState(text) + const [rawRT, setRawRT] = useState(() => new RichTextAPI({text})) + const [resolvedRT, setResolvedRT] = useState(null) if (text !== prevText) { setPrevText(text) setRawRT(new RichTextAPI({text})) setResolvedRT(null) // This will queue an immediate re-render } - React.useEffect(() => { + useEffect(() => { let ignore = false async function resolveRTFacets() { // new each time @@ -620,7 +620,7 @@ function useRichText(text: string): [RichTextAPI, boolean] { setResolvedRT(resolvedRT) } } - resolveRTFacets() + void resolveRTFacets() return () => { ignore = true } -- 2.51.2 From 63fbf577d994ae0fd0e0ea9be75dfdfff7b0ddfb Mon Sep 17 00:00:00 2001 From: Spence Pope Date: Wed, 11 Feb 2026 13:11:15 -0500 Subject: [PATCH 10/20] Update src/components/ProgressGuide/List.tsx Co-authored-by: Samuel Newman --- src/components/ProgressGuide/List.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index d7cb4d0f0..4a25e02e7 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -158,6 +158,7 @@ function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) { type="user" size={avatarSize - 2} avatar={follow.avatar} + noBorder /> ))} -- 2.51.2 From 1ec70420e60d5ceaba970624ae7cb9ba1a01693f Mon Sep 17 00:00:00 2001 From: Spence Pope Date: Wed, 11 Feb 2026 13:11:24 -0500 Subject: [PATCH 11/20] Update src/components/ProgressGuide/List.tsx Co-authored-by: Samuel Newman --- src/components/ProgressGuide/List.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index 4a25e02e7..8ce57a843 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -147,11 +147,11 @@ function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) { key={follow.did} style={[ a.rounded_full, + a.border, + t.atoms.border_contrast_low, { marginLeft: i === 0 ? 0 : -overlap, zIndex: TOTAL_AVATARS - i, - borderWidth: 1, - borderColor: t.atoms.bg_contrast_25.backgroundColor, }, ]}> Date: Wed, 11 Feb 2026 13:11:30 -0500 Subject: [PATCH 12/20] Update src/components/ProgressGuide/List.tsx Co-authored-by: Samuel Newman --- src/components/ProgressGuide/List.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index 8ce57a843..e07f3305f 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -173,14 +173,14 @@ function StackedAvatars({follows}: {follows?: bsky.profile.AnyProfileView[]}) { a.justify_center, a.rounded_full, t.atoms.bg_contrast_300, + a.border, + t.atoms.border_contrast_low, { width: avatarSize, height: avatarSize, marginLeft: followedAvatars.length === 0 && i === 0 ? 0 : -overlap, zIndex: TOTAL_AVATARS - followedAvatars.length - i, - borderWidth: 1, - borderColor: t.atoms.border_contrast_low.borderColor, }, ]}> Date: Wed, 11 Feb 2026 17:41:34 -0600 Subject: [PATCH 13/20] Bump API SDK (#9862) * Bump API SDK * Bump dev-env --- package.json | 4 +- yarn.lock | 275 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 162 insertions(+), 117 deletions(-) diff --git a/package.json b/package.json index ccf27c7f5..0c49dc24d 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.18.20", + "@atproto/api": "^0.18.21", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@bsky.app/alf": "^0.1.6", @@ -229,7 +229,7 @@ "zod": "^3.20.2" }, "devDependencies": { - "@atproto/dev-env": "^0.3.208", + "@atproto/dev-env": "^0.3.209", "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@babel/runtime": "^7.26.0", diff --git a/yarn.lock b/yarn.lock index f27ce92f8..4524ecd66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -82,12 +82,12 @@ "@atproto/xrpc" "^0.7.6" "@atproto/xrpc-server" "^0.10.0" -"@atproto/api@^0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.18.tgz#0c02585ea1b0f4a37444b0d8dfbe347c99ea5f64" - integrity sha512-Vg7/sjbwDQZDj8fXtb4E48U4gA+6RC1iSt7onGnH2NyR0E25uds1KnqSKMzqcphdJXrz5GXrgHWc747XPGibzg== +"@atproto/api@^0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.20.tgz#3fdbb7b7fae90bd59101970c2b56cc31e8cf417d" + integrity sha512-BZYZkh2VJIFCXEnc/vzKwAwWjAQQTgbNJ8FBxpBK+z+KYh99O0uPCsRYKoCQsRrnkgrhzdU9+g2G+7zanTIGbw== dependencies: - "@atproto/common-web" "^0.4.14" + "@atproto/common-web" "^0.4.15" "@atproto/lexicon" "^0.6.1" "@atproto/syntax" "^0.4.3" "@atproto/xrpc" "^0.7.7" @@ -96,12 +96,12 @@ tlds "^1.234.0" zod "^3.23.8" -"@atproto/api@^0.18.19", "@atproto/api@^0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.20.tgz#3fdbb7b7fae90bd59101970c2b56cc31e8cf417d" - integrity sha512-BZYZkh2VJIFCXEnc/vzKwAwWjAQQTgbNJ8FBxpBK+z+KYh99O0uPCsRYKoCQsRrnkgrhzdU9+g2G+7zanTIGbw== +"@atproto/api@^0.18.21": + version "0.18.21" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.21.tgz#19dbea422de22e7f5253f1c5475e3588c3a9d4a0" + integrity sha512-s35MIJerGT/pKe2xJtKKswqlIr/ola2r2iURBKBL0Mk1OKe6jP4YvTMh1N2d2PEANFzNNTbKoDaLfJPo2Uvc/w== dependencies: - "@atproto/common-web" "^0.4.15" + "@atproto/common-web" "^0.4.16" "@atproto/lexicon" "^0.6.1" "@atproto/syntax" "^0.4.3" "@atproto/xrpc" "^0.7.7" @@ -128,23 +128,23 @@ multiformats "^9.9.0" uint8arrays "3.0.0" -"@atproto/bsky@^0.0.214": - version "0.0.214" - resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.214.tgz#54183957e323fe0800606acdcc3137acadf6672e" - integrity sha512-Fn+o3WcaX57EmcLoUdcViY4wU2UzfbyNlS/qFQu+1clADtL3pthylwTJgwUU73cZE+7MILZPfTzhQTc15snR9A== +"@atproto/bsky@^0.0.215": + version "0.0.215" + resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.215.tgz#df566ccc7bc77d27dd2f9506c96739352b395cef" + integrity sha512-1X9G4L8boh9naCtXzorFYm8VskE1xLOJslthR16Hd4uH+ryOEVxN0BLk16OP86qy00hU6VNvyGkDqBthUyy62w== dependencies: "@atproto-labs/fetch-node" "^0.2.0" "@atproto-labs/xrpc-utils" "^0.0.24" - "@atproto/api" "^0.18.20" - "@atproto/common" "^0.5.10" + "@atproto/api" "^0.18.21" + "@atproto/common" "^0.5.11" "@atproto/crypto" "^0.4.5" "@atproto/did" "^0.3.0" - "@atproto/identity" "^0.4.10" + "@atproto/identity" "^0.4.11" "@atproto/lexicon" "^0.6.1" "@atproto/repo" "^0.8.12" "@atproto/sync" "^0.1.39" "@atproto/syntax" "^0.4.3" - "@atproto/xrpc-server" "^0.10.11" + "@atproto/xrpc-server" "^0.10.12" "@bufbuild/protobuf" "^1.5.0" "@connectrpc/connect" "^1.1.4" "@connectrpc/connect-express" "^1.1.4" @@ -194,7 +194,7 @@ pino-http "^8.2.1" typed-emitter "^2.1.0" -"@atproto/common-web@^0.4.13", "@atproto/common-web@^0.4.14", "@atproto/common-web@^0.4.4", "@atproto/common-web@^0.4.7": +"@atproto/common-web@^0.4.13", "@atproto/common-web@^0.4.4", "@atproto/common-web@^0.4.7": version "0.4.14" resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.14.tgz#77545d454add08d735af00ffc29dd6abf1ab77b3" integrity sha512-rMU8Q+kpyPpirUS9OqT7aOD1hxKa+diem3vc7BA0lOkj0tU6wcAxegxmbPZ8JaOsR7SSYhP/jCt/5wbT4qqkuQ== @@ -214,6 +214,16 @@ "@atproto/syntax" "^0.4.3" zod "^3.23.8" +"@atproto/common-web@^0.4.16": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.16.tgz#1bee6073ee7a102ea6c47a512a8f6992c683012e" + integrity sha512-Ufvaff5JgxUyUyTAG0/3o7ltpy3lnZ1DvLjyAnvAf+hHfiK7OMQg+8byr+orN+KP9MtIQaRTsCgYPX+PxMKUoA== + dependencies: + "@atproto/lex-data" "^0.0.11" + "@atproto/lex-json" "^0.0.11" + "@atproto/syntax" "^0.4.3" + zod "^3.23.8" + "@atproto/common@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" @@ -246,14 +256,14 @@ multiformats "^9.9.0" pino "^8.21.0" -"@atproto/common@^0.5.10": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.10.tgz#f0d6b7e012b3dd3991d7e2975e26913d4f41e90f" - integrity sha512-A1+4W3JmjZIgmtJFLJBAaoVruZhRL0ANtyjZ91aJR4rjHcZuaQ+v4IFR1UcE6yyTATacLdBk6ADy8OtxXzq14g== +"@atproto/common@^0.5.11": + version "0.5.11" + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.11.tgz#88499978d3b29c451e7ad9a71f9d2f89446e89f6" + integrity sha512-WRlT4s+wv80WdQuzkQub9D5vTD82O8dH2p91u4b+x3O17q5IQbmA3Lj+1NICINNSy2voqloqAWdqXEkRfdlAPw== dependencies: - "@atproto/common-web" "^0.4.15" - "@atproto/lex-cbor" "^0.0.10" - "@atproto/lex-data" "^0.0.10" + "@atproto/common-web" "^0.4.16" + "@atproto/lex-cbor" "^0.0.11" + "@atproto/lex-data" "^0.0.11" iso-datestring-validator "^2.2.2" multiformats "^9.9.0" pino "^8.21.0" @@ -278,23 +288,23 @@ "@noble/hashes" "^1.6.1" uint8arrays "3.0.0" -"@atproto/dev-env@^0.3.208": - version "0.3.208" - resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.208.tgz#01d07d4b1ef2404adb07bdddf324393989231a52" - integrity sha512-ttx+MIT21iFICW7sZNaFJoCPlxZAqgFy1ztOEn1ebW2MkJzsNHSq2oQC8MHxmpbYEOMPekTXC2iMHW6PYVQJxQ== +"@atproto/dev-env@^0.3.209": + version "0.3.209" + resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.209.tgz#0df234a7feab7628ad376773de0e8bd612fb7f2a" + integrity sha512-i4IVqoyCAqULNeye9xpo5YVfrNjFQjGwAJZ4QAA26L9LuFlXmudnAX17KOCMvmJiPgtRe2ofIy6h+ZJ6qm52tQ== dependencies: - "@atproto/api" "^0.18.20" - "@atproto/bsky" "^0.0.214" + "@atproto/api" "^0.18.21" + "@atproto/bsky" "^0.0.215" "@atproto/bsync" "^0.0.23" - "@atproto/common-web" "^0.4.15" + "@atproto/common-web" "^0.4.16" "@atproto/crypto" "^0.4.5" - "@atproto/identity" "^0.4.10" + "@atproto/identity" "^0.4.11" "@atproto/lexicon" "^0.6.1" - "@atproto/ozone" "^0.1.162" - "@atproto/pds" "^0.4.207" + "@atproto/ozone" "^0.1.163" + "@atproto/pds" "^0.4.209" "@atproto/sync" "^0.1.39" "@atproto/syntax" "^0.4.3" - "@atproto/xrpc-server" "^0.10.11" + "@atproto/xrpc-server" "^0.10.12" "@did-plc/lib" "^0.0.1" "@did-plc/server" "^0.0.1" dotenv "^16.0.3" @@ -319,6 +329,14 @@ "@atproto/common-web" "^0.4.4" "@atproto/crypto" "^0.4.4" +"@atproto/identity@^0.4.11": + version "0.4.11" + resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.4.11.tgz#9622496499919cb2c05d21ab1ba45132dd1e3b52" + integrity sha512-/xXPPojR0PRD0pqoEPmgKMeclUCrkaMKfFGFkssAmXSuT39aCtiibxNqvhp+S2jOdeM6rKrs2QgH91OCGvwPcg== + dependencies: + "@atproto/common-web" "^0.4.16" + "@atproto/crypto" "^0.4.5" + "@atproto/jwk-jose@^0.1.11": version "0.1.11" resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz#ef64bce940a66e267fc3cf0db8df4dbd062bb28a" @@ -343,22 +361,22 @@ "@atproto/lex-data" "0.0.9" tslib "^2.8.1" -"@atproto/lex-cbor@^0.0.10": - version "0.0.10" - resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz#712abc4fedef4854c341e32d0fd1608e45616984" - integrity sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA== +"@atproto/lex-cbor@^0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.11.tgz#7a464a2032eb61a1dfc254bae1e2759c8279d6a6" + integrity sha512-A7ETtPsEsJ/VuPJOFw4bPNTKxHvFN1JbTQ2NjLuisd3ry7fVxgMpo/qGXsUQsAh/I/uziGbhpNqdS6GnI2p/Wg== dependencies: - "@atproto/lex-data" "^0.0.10" + "@atproto/lex-data" "^0.0.11" tslib "^2.8.1" -"@atproto/lex-client@^0.0.11": - version "0.0.11" - resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.11.tgz#b8e8d0da81ed27f1093d0a1e1c7fdb994394e323" - integrity sha512-2DCidAlhATtZc1Z11PUd+C98BiW/Od4pWtDlQSAxkjHSC/56ZwuSkZQVx27ISk1HldfKVc9qUvQWA9nhmrxYIw== +"@atproto/lex-client@^0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.12.tgz#87be8075746e32dafc3d23f282e53daadb50ea66" + integrity sha512-ef4jQQ7SOtBsXr+Gf1UHuBfCiAGYZxO5PCCXl3eT4ObO83SROtIf7pyO06jBQI/IZChSVsXqXsgakR0aru6lYQ== dependencies: - "@atproto/lex-data" "^0.0.10" - "@atproto/lex-json" "^0.0.10" - "@atproto/lex-schema" "^0.0.11" + "@atproto/lex-data" "^0.0.11" + "@atproto/lex-json" "^0.0.11" + "@atproto/lex-schema" "^0.0.12" tslib "^2.8.1" "@atproto/lex-data@0.0.9": @@ -381,12 +399,22 @@ uint8arrays "3.0.0" unicode-segmenter "^0.14.0" -"@atproto/lex-document@^0.0.12": - version "0.0.12" - resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.12.tgz#18e0ca344101bb742a2930f663473e0213721f52" - integrity sha512-+no+ZXyCNdOdjkOj6a4n4WHAQzZz3M6VJTwx7IQQC2+to41/4fFr6k8U1y3Jtq2lYcbHqapkJj3RGIxzFcrtwA== +"@atproto/lex-data@^0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.11.tgz#de98894a80ff907f9cd6c3faa561d3a16e21dd8d" + integrity sha512-4+KTtHdqwlhiTKA7D4SACea4jprsNpCQsNALW09wsZ6IHhCDGO5tr1cmV+QnLYe3G3mu1E1yXHXbPUHrUUDT/A== dependencies: - "@atproto/lex-schema" "^0.0.11" + multiformats "^9.9.0" + tslib "^2.8.1" + uint8arrays "3.0.0" + unicode-segmenter "^0.14.0" + +"@atproto/lex-document@^0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.13.tgz#fcf794addf6cb328c78d4393e91ec0050a43c623" + integrity sha512-LWsBsKIbyuG7jFObTtnCFQNYHxWWVpVVspqv6UtnS/QsaCyCMg1GIz5vlgi8QBnmGvaPiQxIzGt6mERpTvEXpg== + dependencies: + "@atproto/lex-schema" "^0.0.12" core-js "^3" tslib "^2.8.1" @@ -406,27 +434,35 @@ "@atproto/lex-data" "^0.0.10" tslib "^2.8.1" -"@atproto/lex-resolver@^0.0.13": - version "0.0.13" - resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.13.tgz#6407b813001d3a3f9e4048197755deccb3cc6f62" - integrity sha512-CcqCE6W3ZVMVzAihatpVbXLxO15mtvddzRzovIJ9QxBTpUmtkdmIk9/gle0oAsRToTJYQy2a6dwAmnAosxP/XQ== +"@atproto/lex-json@^0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@atproto/lex-json/-/lex-json-0.0.11.tgz#56be96a4d2366113da3911020cedfb0fb97fbb4f" + integrity sha512-2IExAoQ4KsR5fyPa1JjIvtR316PvdgRH/l3BVGLBd3cSxM3m5MftIv1B6qZ9HjNiK60SgkWp0mi9574bTNDhBQ== + dependencies: + "@atproto/lex-data" "^0.0.11" + tslib "^2.8.1" + +"@atproto/lex-resolver@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.14.tgz#8bc32e49c231bc29ca0d1ea927b33d7a21447de3" + integrity sha512-jBkYRmMKap2OM1zm0VDvs7Heuf3pGjw9xJEHQx1ohkMmM5f+cHPS40RQ8x8SNE+Vl9gMuOrgmgKyPDIuYSIBTw== dependencies: "@atproto-labs/did-resolver" "^0.2.6" "@atproto/crypto" "^0.4.5" - "@atproto/lex-client" "^0.0.11" - "@atproto/lex-data" "^0.0.10" - "@atproto/lex-document" "^0.0.12" - "@atproto/lex-schema" "^0.0.11" + "@atproto/lex-client" "^0.0.12" + "@atproto/lex-data" "^0.0.11" + "@atproto/lex-document" "^0.0.13" + "@atproto/lex-schema" "^0.0.12" "@atproto/repo" "^0.8.12" "@atproto/syntax" "^0.4.3" tslib "^2.8.1" -"@atproto/lex-schema@^0.0.11": - version "0.0.11" - resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.11.tgz#7ff8a1e94971cb750cacaa40e30f546a64db0acd" - integrity sha512-1vLUPQIMeawKP6ehSx2RiqaJDkiseFTXyUk3C4PaoFCktaH8FgVgPVKgUSSy02m1pxVMKnsOBV5psKeN53HG+Q== +"@atproto/lex-schema@^0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.12.tgz#3519b1e4040503e1e6e5a47df1012660f4c38d8c" + integrity sha512-l1RNYmqNwIEjgMEjC9i2o6FLsUFdpAc610xQYK/CRxN31cRzY0lAJ2GFbp2GZ4rRAD3FGYCXid6gZ42KsieUcw== dependencies: - "@atproto/lex-data" "^0.0.10" + "@atproto/lex-data" "^0.0.11" "@atproto/syntax" "^0.4.3" tslib "^2.8.1" @@ -441,7 +477,7 @@ multiformats "^9.9.0" zod "^3.23.8" -"@atproto/oauth-provider-api@0.3.7", "@atproto/oauth-provider-api@^0.3.7": +"@atproto/oauth-provider-api@0.3.7": version "0.3.7" resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.3.7.tgz#7b911256536a72dbba6f38081a200836a3ab50b8" integrity sha512-7yU9vuQFt/hy4NzlDtn+LuhIGvVKkhgWAkCmopnseMPBw6oGPT90uOsTxMkVGtHuKVvBSz7hOXoELXpnZq3gDQ== @@ -449,41 +485,41 @@ "@atproto/jwk" "0.6.0" "@atproto/oauth-types" "0.6.2" -"@atproto/oauth-provider-frontend@^0.2.8": - version "0.2.8" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.2.8.tgz#97f6dc33257b0f846229839dd74f606528f49846" - integrity sha512-wHypQrsbwE6LUlyDADfiJfOH5pDAHWm4l/v1dwkQhRndOai7L+knsbdLAOZVXuz6bRs7oV2Frb3mSS03OS4Gdw== +"@atproto/oauth-provider-frontend@0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.2.9.tgz#68486c0d9710a1325a02a303a6b22c733239b219" + integrity sha512-m1NhqC7ydEpF5ELV13CxkgCawyMX8wbAXls/4af3Tsz3IOkdM6zPgQeYK7Zj+7Nywjml4mZTkJ8rZiT6O9+5sw== optionalDependencies: "@atproto/oauth-provider-api" "0.3.7" -"@atproto/oauth-provider-ui@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.4.2.tgz#4edd41c71fd1bf5aaa1df219fb8a20c0b8d90738" - integrity sha512-j3Afu23JYy68GY8L4t/cTEZz1PnrvxLrjiG/nPhXPURZbCnqZ1puIU+HRVCBJfqKyCwaWthXgUjwaL+SFlPGKA== +"@atproto/oauth-provider-ui@0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.4.3.tgz#c80b076c2d2a7cc45e45866796f09b959bcf1c03" + integrity sha512-BLNZmtOwoHu2qk/Oi6dUR8TcXQaJre6wgW8YjkW5bKf+Vftn3PGzh8bKgr1fQZDYweZ6AF148imA2OzVSAaLHQ== optionalDependencies: "@atproto/oauth-provider-api" "0.3.7" -"@atproto/oauth-provider@^0.15.7": - version "0.15.7" - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.7.tgz#fc5bd420984dac60f726949d2473bbdb3ddff991" - integrity sha512-rsPez44TJGVC5vFdwSTfSOLIIG5O1JF8NXLe1BjjsUbzRuflN6TyM6HyfBJVsnOVF2bOq6W6xPcnBFUJHCSyUQ== +"@atproto/oauth-provider@^0.15.9": + version "0.15.9" + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.9.tgz#3ec21380711e34f4303a0782ecfffffb8080f144" + integrity sha512-nuyOVw5piS/iEk7xKEtdulRbcFZ7HBB0gPXL62z5IpxmluEvLem/VLqsntj2OK/mtP6xhnIfFHP5pb5+PnLuYQ== dependencies: "@atproto-labs/fetch" "^0.2.3" "@atproto-labs/fetch-node" "^0.2.0" "@atproto-labs/pipe" "^0.1.1" "@atproto-labs/simple-store" "^0.3.0" "@atproto-labs/simple-store-memory" "^0.1.4" - "@atproto/common" "^0.5.10" + "@atproto/common" "^0.5.11" "@atproto/did" "^0.3.0" "@atproto/jwk" "^0.6.0" "@atproto/jwk-jose" "^0.1.11" - "@atproto/lex-document" "^0.0.12" - "@atproto/lex-resolver" "^0.0.13" - "@atproto/oauth-provider-api" "^0.3.7" - "@atproto/oauth-provider-frontend" "^0.2.8" - "@atproto/oauth-provider-ui" "^0.4.2" + "@atproto/lex-document" "^0.0.13" + "@atproto/lex-resolver" "^0.0.14" + "@atproto/oauth-provider-api" "0.3.7" + "@atproto/oauth-provider-frontend" "0.2.9" + "@atproto/oauth-provider-ui" "0.4.3" "@atproto/oauth-scopes" "^0.3.1" - "@atproto/oauth-types" "^0.6.2" + "@atproto/oauth-types" "^0.6.3" "@atproto/syntax" "^0.4.3" "@hapi/accept" "^6.0.3" "@hapi/address" "^5.1.1" @@ -505,7 +541,7 @@ "@atproto/did" "^0.3.0" "@atproto/syntax" "^0.4.3" -"@atproto/oauth-types@0.6.2", "@atproto/oauth-types@^0.6.2": +"@atproto/oauth-types@0.6.2": version "0.6.2" resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.2.tgz#d829fae63421dcea7ac703e84460175d2f8d9299" integrity sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg== @@ -514,20 +550,29 @@ "@atproto/jwk" "0.6.0" zod "^3.23.8" -"@atproto/ozone@^0.1.162": - version "0.1.162" - resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.162.tgz#2c1775c1c45fb03b96087937c7675f7f1825c429" - integrity sha512-UeSbHJ4qHIrWcRelifPm4BFh8PFN7DG9tpKuXSqFjZFG3hO2ZDyBzxV6e9zh7Qg+m2tibdst4Hrk4vyT/7wO6Q== +"@atproto/oauth-types@^0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.3.tgz#4fc996d0af61874830079d1b1bddc7c0774ad6b2" + integrity sha512-jdKuoPknJuh/WjI+mYk7agSbx9mNVMbS6Dr3k1z2YMY2oRiCQjxYBuo4MLKATbxj05nMQaZRWlHRUazoAu5Cng== dependencies: - "@atproto/api" "^0.18.18" - "@atproto/common" "^0.5.9" + "@atproto/did" "^0.3.0" + "@atproto/jwk" "^0.6.0" + zod "^3.23.8" + +"@atproto/ozone@^0.1.163": + version "0.1.163" + resolved "https://registry.yarnpkg.com/@atproto/ozone/-/ozone-0.1.163.tgz#7935f9180b6c7f3bd05043d42e14b6055c268596" + integrity sha512-Rz5p0Bf9K3KVo6VuPNe5KTiuJVM5eV7fXSIeWidC1WbDgfEEM8jJu2YRQHvDQq5r95SKKY0gTFYqU6vW0DKSJA== + dependencies: + "@atproto/api" "^0.18.20" + "@atproto/common" "^0.5.11" "@atproto/crypto" "^0.4.5" - "@atproto/identity" "^0.4.10" + "@atproto/identity" "^0.4.11" "@atproto/lexicon" "^0.6.1" "@atproto/syntax" "^0.4.3" "@atproto/ws-client" "^0.0.4" "@atproto/xrpc" "^0.7.7" - "@atproto/xrpc-server" "^0.10.10" + "@atproto/xrpc-server" "^0.10.12" "@did-plc/lib" "^0.0.1" compression "^1.7.4" cors "^2.8.5" @@ -545,30 +590,30 @@ undici "^6.14.1" ws "^8.12.0" -"@atproto/pds@^0.4.207": - version "0.4.207" - resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.207.tgz#20ff3afcf1541b96f016a6843d69d3731b562f19" - integrity sha512-LtzgqeD4aB3O1efzDbhdEopdujB4GG/klLT4N/3v35+0Jpylrkht5Ux2yJGKAqJkzFCfpBdRhNMWpJqS2ssu8A== +"@atproto/pds@^0.4.209": + version "0.4.209" + resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.209.tgz#eb12f325c55692a6487c0620a7754b13f0914fe1" + integrity sha512-jW9dnrrqj65u6FZoj/idIez+idGgYBG4KGVB75i7pqrRkE1a91Bj5MXX60JfIQ0VY9YpfBthSnJG9Pzz5OzA/Q== dependencies: "@atproto-labs/fetch-node" "^0.2.0" "@atproto-labs/simple-store" "^0.3.0" "@atproto-labs/simple-store-memory" "^0.1.4" "@atproto-labs/simple-store-redis" "^0.0.1" "@atproto-labs/xrpc-utils" "^0.0.24" - "@atproto/api" "^0.18.19" + "@atproto/api" "^0.18.21" "@atproto/aws" "^0.2.31" - "@atproto/common" "^0.5.10" + "@atproto/common" "^0.5.11" "@atproto/crypto" "^0.4.5" - "@atproto/identity" "^0.4.10" - "@atproto/lex-cbor" "^0.0.10" - "@atproto/lex-data" "^0.0.10" + "@atproto/identity" "^0.4.11" + "@atproto/lex-cbor" "^0.0.11" + "@atproto/lex-data" "^0.0.11" "@atproto/lexicon" "^0.6.1" - "@atproto/oauth-provider" "^0.15.7" + "@atproto/oauth-provider" "^0.15.9" "@atproto/oauth-scopes" "^0.3.1" "@atproto/repo" "^0.8.12" "@atproto/syntax" "^0.4.3" "@atproto/xrpc" "^0.7.7" - "@atproto/xrpc-server" "^0.10.11" + "@atproto/xrpc-server" "^0.10.12" "@did-plc/lib" "^0.0.4" "@hapi/address" "^5.1.1" better-sqlite3 "^10.0.0" @@ -642,7 +687,7 @@ "@atproto/common" "^0.5.3" ws "^8.12.0" -"@atproto/xrpc-server@^0.10.0", "@atproto/xrpc-server@^0.10.10", "@atproto/xrpc-server@^0.10.3": +"@atproto/xrpc-server@^0.10.0", "@atproto/xrpc-server@^0.10.3": version "0.10.10" resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.10.tgz#69d1a3ce8278a6b49286a5df33ebc204d42129ed" integrity sha512-USDjOZGiletqzuWHC3Q2fk30hJDk4uYt6KPgvnZidShCouTf3hzwJZ8d2eOKOofSjGXW+GC0QYp7fYJFn6lZ2Q== @@ -661,15 +706,15 @@ ws "^8.12.0" zod "^3.23.8" -"@atproto/xrpc-server@^0.10.11": - version "0.10.11" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.11.tgz#cd6f880ebb6a913db2925a23aa0ef0b05f5b02fd" - integrity sha512-7XR+n1G4j1PO33slr2Agl+lmTXbEQzk5iaCJmrcsfTC/0BbCHqSSbm+WHduz3EH4dfFioZsnDo1UesCF0EQEtg== +"@atproto/xrpc-server@^0.10.12": + version "0.10.12" + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.12.tgz#6b88c9af5d301302e5e177b5f982a3494e734d78" + integrity sha512-HuciFHxvQfWtvq+dmH3vK9uXii49vqkJgZFLcHums1xMvLvNBY5bgtUBdpQdsdEuq8B4DTuanZRs+z3zGariyg== dependencies: - "@atproto/common" "^0.5.10" + "@atproto/common" "^0.5.11" "@atproto/crypto" "^0.4.5" - "@atproto/lex-cbor" "^0.0.10" - "@atproto/lex-data" "^0.0.10" + "@atproto/lex-cbor" "^0.0.11" + "@atproto/lex-data" "^0.0.11" "@atproto/lexicon" "^0.6.1" "@atproto/ws-client" "^0.0.4" "@atproto/xrpc" "^0.7.7" -- 2.51.2 From 93d2f3a8dbd78a23dcbe3642897474d0f37168e2 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Feb 2026 23:51:19 +0000 Subject: [PATCH 14/20] Germ DM button (#9848) * germ link (wip) * add todos * just yeet declaration for now * ensure not proxied * allow undo delete * tweak styles * ignore unknown values * tweak styles * fix boolean logic * skip IAB * fix mutationFn * add link warning interstitial * Logging and metrics * Little more error handling * Update copy --------- Co-authored-by: Eric Bailey --- assets/images/germ_logo.webp | Bin 0 -> 7840 bytes src/analytics/metrics/types.ts | 5 + src/components/dialogs/LinkWarning.tsx | 19 +- .../Profile/Header/ProfileHeaderStandard.tsx | 5 + src/screens/Profile/components/GermButton.tsx | 334 ++++++++++++++++++ src/state/session/index.tsx | 2 +- 6 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 assets/images/germ_logo.webp create mode 100644 src/screens/Profile/components/GermButton.tsx diff --git a/assets/images/germ_logo.webp b/assets/images/germ_logo.webp new file mode 100644 index 0000000000000000000000000000000000000000..a85048381ac71eee1131022b862b807875f6ad85 GIT binary patch literal 7840 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 - + ) } -function InAppBrowserConsentInner({ +export function CustomLinkWarningDialog({ + control, + link, +}: { + control: Dialog.DialogControlProps + link?: {href: string; displayText: string; share?: boolean} +}) { + return ( + + + + + ) +} + +function LinkWarningDialogInner({ link, }: { link?: {href: string; displayText: string; share?: boolean} diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 6467756d4..c60fcd032 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -39,6 +39,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' import {IS_IOS} from '#/env' +import {GermButton} from '../components/GermButton' import {EditProfileDialog} from './EditProfileDialog' import {ProfileHeaderHandle} from './Handle' import {ProfileHeaderMetrics} from './Metrics' @@ -163,6 +164,10 @@ let ProfileHeaderStandard = ({ ) : undefined} + {profile.associated?.germ && ( + + )} + {!isMe && !isBlockedUser && shouldShowKnownFollowers(profile.viewer?.knownFollowers) && ( diff --git a/src/screens/Profile/components/GermButton.tsx b/src/screens/Profile/components/GermButton.tsx new file mode 100644 index 000000000..c5af7d536 --- /dev/null +++ b/src/screens/Profile/components/GermButton.tsx @@ -0,0 +1,334 @@ +import {Platform, View} from 'react-native' +import {Image} from 'expo-image' +import { + type AppBskyActorDefs, + type AppBskyActorGetProfile, + type AtpAgent, +} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMutation, useQueryClient} from '@tanstack/react-query' + +import {until} from '#/lib/async/until' +import {isNetworkError} from '#/lib/strings/errors' +import {RQKEY} from '#/state/queries/profile' +import {useAgent, useSession} from '#/state/session' +import {atoms as a, useTheme, web} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {CustomLinkWarningDialog} from '#/components/dialogs/LinkWarning' +import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRightIcon} from '#/components/icons/Arrow' +import {Link} from '#/components/Link' +import {Loader} from '#/components/Loader' +import * as Toast from '#/components/Toast' +import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' +import type * as bsky from '#/types/bsky' + +export function GermButton({ + germ, + profile, +}: { + germ: AppBskyActorDefs.ProfileAssociatedGerm + profile: bsky.profile.AnyProfileView +}) { + const t = useTheme() + const ax = useAnalytics() + const {_} = useLingui() + const {currentAccount} = useSession() + const linkWarningControl = Dialog.useDialogControl() + + // exclude `none` and all unknown values + if ( + !(germ.showButtonTo === 'everyone' || germ.showButtonTo === 'usersIFollow') + ) { + return null + } + + if (currentAccount?.did === profile.did) { + return + } + + if (germ.showButtonTo === 'usersIFollow' && !profile.viewer?.followedBy) { + return null + } + + const url = constructGermUrl(germ, profile, currentAccount?.did) + + if (!url) { + return null + } + + return ( + <> + { + ax.metric('profile:associated:germ:click-to-chat', {}) + if (isCustomGermDomain(url)) { + evt.preventDefault() + linkWarningControl.open() + return false + } + }} + label={_(msg`Open Germ DM`)} + overridePresentation={false} + shouldProxy={false} + style={[ + t.atoms.bg_contrast_50, + a.rounded_full, + a.self_start, + {padding: 6}, + ]}> + + + Germ DM + + + + + + ) +} + +function GermLogo({size}: {size: 'small' | 'large'}) { + return ( + + ) +} + +function GermSelfButton({did}: {did: string}) { + const t = useTheme() + const ax = useAnalytics() + const {_} = useLingui() + const selfExplanationDialogControl = Dialog.useDialogControl() + const agent = useAgent() + const queryClient = useQueryClient() + + const {mutate: deleteDeclaration, isPending} = useMutation({ + mutationFn: async () => { + const previousRecord = await agent.com.germnetwork.declaration + .get({ + repo: did, + rkey: 'self', + }) + .then(res => res.value) + .catch(() => null) + + await agent.com.germnetwork.declaration.delete({ + repo: did, + rkey: 'self', + }) + + await whenAppViewReady(agent, did, res => !res.data.associated?.germ) + + return previousRecord + }, + onSuccess: previousRecord => { + ax.metric('profile:associated:germ:self-disconnect', {}) + + async function undo() { + if (!previousRecord) return + try { + await agent.com.germnetwork.declaration.put( + { + repo: did, + rkey: 'self', + }, + previousRecord, + ) + await whenAppViewReady(agent, did, res => !!res.data.associated?.germ) + await queryClient.refetchQueries({queryKey: RQKEY(did)}) + + Toast.show(_(msg`Germ DM reconnected`)) + ax.metric('profile:associated:germ:self-reconnect', {}) + } catch (e: any) { + Toast.show( + _(msg`Failed to reconnect Germ DM. Error: ${e?.message}`), + { + type: 'error', + }, + ) + if (!isNetworkError(e)) { + ax.logger.error('Failed to reconnect Germ DM link', { + safeMessage: e, + }) + } + } + } + + selfExplanationDialogControl.close(() => { + void queryClient.refetchQueries({queryKey: RQKEY(did)}) + Toast.show( + + + + Germ DM disconnected + + {previousRecord && ( + void undo()}> + Undo + + )} + , + ) + }) + }, + onError: error => { + Toast.show( + _(msg`Failed to disconnect Germ DM. Error: ${error?.message}`), + { + type: 'error', + }, + ) + if (!isNetworkError(error)) { + ax.logger.error('Failed to disconnect Germ DM link', { + safeMessage: error, + }) + } + }, + }) + + return ( + <> + + + + + + + + + Germ DM Link + + + + + + This button lets others open the Germ DM app to send you a + message. You can manage its visibility from the Germ DM app, or + you can disconnect your Bluesky account from Germ DM altogether by + clicking the button below. + + + + + + + + + + ) +} + +function constructGermUrl( + declaration: AppBskyActorDefs.ProfileAssociatedGerm, + profile: bsky.profile.AnyProfileView, + viewerDid?: string, +) { + try { + const urlp = new URL(declaration.messageMeUrl) + + if (urlp.pathname.endsWith('/')) { + urlp.pathname = urlp.pathname.slice(0, -1) + } + + urlp.pathname += `/${platform()}` + + if (viewerDid) { + urlp.hash = `#${profile.did}+${viewerDid}` + } else { + urlp.hash = `#${profile.did}` + } + + return urlp.toString() + } catch { + return null + } +} + +function isCustomGermDomain(url: string) { + try { + const urlp = new URL(url) + return urlp.hostname !== 'landing.ger.mx' + } catch { + return false + } +} + +function platform() { + switch (Platform.OS) { + case 'ios': + return 'iOS' + case 'android': + return 'android' + default: + return 'web' + } +} + +async function whenAppViewReady( + agent: AtpAgent, + actor: string, + fn: (res: AppBskyActorGetProfile.Response) => boolean, +) { + await until( + 5, // 5 tries + 1e3, // 1s delay between tries + fn, + () => agent.app.bsky.actor.getProfile({actor}), + ) +} diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index 4a70fc4c0..e63e180a4 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -430,7 +430,7 @@ export function useRequireAuth() { const {signinDialogControl} = useGlobalDialogsControlContext() return useCallback( - (fn: () => void) => { + (fn: () => unknown) => { if (hasSession) { fn() } else { -- 2.51.2 From 8c4820b1f1054f72289b50a0e16fa9737c0c28e9 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 12 Feb 2026 01:52:22 +0000 Subject: [PATCH 15/20] [Web] Fix splash flicker and add animation (#9860) * manually hide non-React splash logo * tweak comment * animate out splash --- bskyweb/templates/base.html | 8 +-- src/App.web.tsx | 123 ++++++++++++++++++------------------ src/Splash.web.tsx | 96 ++++++++++++++++++++++++---- web/index.html | 8 +-- 4 files changed, 151 insertions(+), 84 deletions(-) diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html index 0c4a91df6..e33479e69 100644 --- a/bskyweb/templates/base.html +++ b/bskyweb/templates/base.html @@ -148,11 +148,11 @@ {%- block body_all %} +
+ + +
-
- - -
+
+ + +
-
- - -
-- 2.51.2 From cbac380c9696f60e23d8aab50b8fa021dcb4b091 Mon Sep 17 00:00:00 2001 From: surfdude29 <149612116+surfdude29@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:55:54 +0000 Subject: [PATCH 16/20] Add profile error messages to cleanError (#9842) --- src/lib/strings/errors.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/strings/errors.ts b/src/lib/strings/errors.ts index a70659714..e68cadeda 100644 --- a/src/lib/strings/errors.ts +++ b/src/lib/strings/errors.ts @@ -30,6 +30,18 @@ export function cleanError(str: any): string { if (str.includes('Bad token scope') || str.includes('Bad token method')) { return t`This feature is not available while using an App Password. Please sign in with your main password.` } + if (str.includes('Account has been suspended')) { + return t`Account has been suspended` + } + if (str.includes('Account is deactivated')) { + return t`Account is deactivated` + } + if (str.includes('Profile not found')) { + return t`Profile not found` + } + if (str.includes('Unable to resolve handle')) { + return t`Unable to resolve handle` + } if (str.startsWith('Error: ')) { return str.slice('Error: '.length) } -- 2.51.2 From 6d68a5bd212dd4eeee816828ffe4e27601cdd7f3 Mon Sep 17 00:00:00 2001 From: pfrazee <1270099+pfrazee@users.noreply.github.com> Date: Thu, 12 Feb 2026 03:12:33 +0000 Subject: [PATCH 17/20] Nightly source-language update --- src/locale/locales/en/messages.po | 302 ++++++++++++++++++------------ 1 file changed, 184 insertions(+), 118 deletions(-) diff --git a/src/locale/locales/en/messages.po b/src/locale/locales/en/messages.po index cb66d55f5..49beb8aee 100644 --- a/src/locale/locales/en/messages.po +++ b/src/locale/locales/en/messages.po @@ -132,7 +132,7 @@ msgstr "" msgid "{0, plural, other {# people have}} joined Bluesky via this starter pack!" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:361 +#: src/components/dialogs/StarterPackDialog.tsx:358 msgid "{0, plural, other {+# more}}" msgstr "" @@ -643,6 +643,14 @@ msgctxt "toast" msgid "Account followed" msgstr "" +#: src/lib/strings/errors.ts:34 +msgid "Account has been suspended" +msgstr "" + +#: src/lib/strings/errors.ts:37 +msgid "Account is deactivated" +msgstr "" + #: src/components/PostControls/PostMenu/PostMenuItems.tsx:446 #: src/view/com/profile/ProfileMenu.tsx:158 msgctxt "toast" @@ -670,8 +678,8 @@ msgstr "" msgid "Account removed from quick access" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:84 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:290 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:85 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:295 #: src/view/com/profile/ProfileMenu.tsx:172 msgctxt "toast" msgid "Account unblocked" @@ -704,8 +712,8 @@ msgstr "" #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:169 #: src/components/dialogs/MutedWords.tsx:333 -#: src/components/dialogs/StarterPackDialog.tsx:375 -#: src/components/dialogs/StarterPackDialog.tsx:381 +#: src/components/dialogs/StarterPackDialog.tsx:372 +#: src/components/dialogs/StarterPackDialog.tsx:379 #: src/view/com/modals/UserAddRemoveLists.tsx:235 msgid "Add" msgstr "" @@ -835,8 +843,8 @@ msgstr "" msgid "Add this feed to your feeds" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:343 -#: src/view/com/profile/ProfileMenu.tsx:346 +#: src/view/com/profile/ProfileMenu.tsx:345 +#: src/view/com/profile/ProfileMenu.tsx:348 msgid "Add to lists" msgstr "" @@ -844,9 +852,9 @@ msgstr "" msgid "Add to saved posts" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:176 -#: src/view/com/profile/ProfileMenu.tsx:334 -#: src/view/com/profile/ProfileMenu.tsx:337 +#: src/components/dialogs/StarterPackDialog.tsx:182 +#: src/view/com/profile/ProfileMenu.tsx:335 +#: src/view/com/profile/ProfileMenu.tsx:338 msgid "Add to starter packs" msgstr "" @@ -863,7 +871,7 @@ msgstr "" msgid "Added to list" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:259 +#: src/components/dialogs/StarterPackDialog.tsx:270 msgid "Added to starter pack" msgstr "" @@ -1435,7 +1443,7 @@ msgstr "" msgid "Before creating a post or replying, you must first verify your email." msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:71 +#: src/components/dialogs/StarterPackDialog.tsx:70 #: src/components/StarterPack/ProfileStarterPacks.tsx:263 #: src/components/StarterPack/ProfileStarterPacks.tsx:273 #: src/view/screens/Profile.tsx:351 @@ -1482,8 +1490,8 @@ msgid "Birthday" msgstr "" #: src/components/PostControls/PostMenu/PostMenuItems.tsx:821 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 -#: src/view/com/profile/ProfileMenu.tsx:551 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:195 +#: src/view/com/profile/ProfileMenu.tsx:553 msgid "Block" msgstr "" @@ -1493,13 +1501,13 @@ msgstr "" #: src/components/PostControls/PostMenu/PostMenuItems.tsx:708 #: src/screens/Messages/components/RequestButtons.tsx:144 #: src/screens/Messages/components/RequestButtons.tsx:146 -#: src/view/com/profile/ProfileMenu.tsx:457 -#: src/view/com/profile/ProfileMenu.tsx:464 +#: src/view/com/profile/ProfileMenu.tsx:459 +#: src/view/com/profile/ProfileMenu.tsx:466 msgid "Block account" msgstr "" #: src/components/PostControls/PostMenu/PostMenuItems.tsx:816 -#: src/view/com/profile/ProfileMenu.tsx:534 +#: src/view/com/profile/ProfileMenu.tsx:536 msgid "Block Account?" msgstr "" @@ -1551,7 +1559,7 @@ msgid "Blocked Accounts" msgstr "" #: src/components/PostControls/PostMenu/PostMenuItems.tsx:818 -#: src/view/com/profile/ProfileMenu.tsx:546 +#: src/view/com/profile/ProfileMenu.tsx:548 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "" @@ -1567,7 +1575,7 @@ msgstr "" msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:543 +#: src/view/com/profile/ProfileMenu.tsx:545 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." msgstr "" @@ -1601,7 +1609,7 @@ msgstr "" msgid "Bluesky is an open network where you can choose your own provider. If you're new here, we recommend sticking with the default Bluesky Social option." msgstr "" -#: src/components/ProgressGuide/List.tsx:103 +#: src/components/ProgressGuide/List.tsx:108 msgid "Bluesky is better with friends!" msgstr "" @@ -2104,7 +2112,7 @@ msgstr "" #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:190 #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:198 #: src/components/dialogs/SearchablePeopleList.tsx:295 -#: src/components/dialogs/StarterPackDialog.tsx:179 +#: src/components/dialogs/StarterPackDialog.tsx:185 #: src/components/dms/AfterReportDialog.tsx:93 #: src/components/dms/AfterReportDialog.tsx:98 #: src/components/dms/AfterReportDialog.tsx:208 @@ -2477,8 +2485,8 @@ msgstr "" msgid "Copy App Password" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:494 -#: src/view/com/profile/ProfileMenu.tsx:497 +#: src/view/com/profile/ProfileMenu.tsx:496 +#: src/view/com/profile/ProfileMenu.tsx:499 msgid "Copy at:// URI" msgstr "" @@ -2493,8 +2501,8 @@ msgid "Copy code" msgstr "" #: src/screens/Settings/components/ChangeHandleDialog.tsx:502 -#: src/view/com/profile/ProfileMenu.tsx:503 -#: src/view/com/profile/ProfileMenu.tsx:506 +#: src/view/com/profile/ProfileMenu.tsx:505 +#: src/view/com/profile/ProfileMenu.tsx:508 msgid "Copy DID" msgstr "" @@ -2594,8 +2602,8 @@ msgid "Could not load feed" msgstr "" #: src/screens/ProfileList/components/ErrorScreen.tsx:26 -#: src/screens/ProfileList/index.tsx:79 -#: src/screens/ProfileList/index.tsx:101 +#: src/screens/ProfileList/index.tsx:80 +#: src/screens/ProfileList/index.tsx:102 msgid "Could not load list" msgstr "" @@ -2629,8 +2637,8 @@ msgstr "" #. Text on button to create a new starter pack #. Text on button to create a new starter pack -#: src/components/dialogs/StarterPackDialog.tsx:112 -#: src/components/dialogs/StarterPackDialog.tsx:201 +#: src/components/dialogs/StarterPackDialog.tsx:111 +#: src/components/dialogs/StarterPackDialog.tsx:208 #: src/components/StarterPack/ProfileStarterPacks.tsx:328 msgid "Create" msgstr "" @@ -2711,8 +2719,8 @@ msgstr "" msgid "Create report for {0}" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:107 -#: src/components/dialogs/StarterPackDialog.tsx:196 +#: src/components/dialogs/StarterPackDialog.tsx:106 +#: src/components/dialogs/StarterPackDialog.tsx:203 msgid "Create starter pack" msgstr "" @@ -3025,6 +3033,11 @@ msgstr "" msgid "Discard post?" msgstr "" +#: src/screens/Profile/components/GermButton.tsx:260 +#: src/screens/Profile/components/GermButton.tsx:267 +msgid "Disconnect Germ DM" +msgstr "" + #: src/screens/Settings/components/PwiOptOut.tsx:87 #: src/screens/Settings/components/PwiOptOut.tsx:91 msgid "Discourage apps from showing my account to logged-out users" @@ -3052,7 +3065,7 @@ msgstr "" msgid "Dismiss error" msgstr "" -#: src/components/ProgressGuide/List.tsx:67 +#: src/components/ProgressGuide/List.tsx:73 msgid "Dismiss getting started guide" msgstr "" @@ -3279,8 +3292,8 @@ msgstr "" msgid "Edit list details" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:357 -#: src/view/com/profile/ProfileMenu.tsx:377 +#: src/view/com/profile/ProfileMenu.tsx:359 +#: src/view/com/profile/ProfileMenu.tsx:379 msgid "Edit live status" msgstr "" @@ -3309,12 +3322,12 @@ msgstr "" #: src/screens/Profile/Header/EditProfileDialog.tsx:268 #: src/screens/Profile/Header/EditProfileDialog.tsx:274 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:295 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:324 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:329 msgid "Edit profile" msgstr "" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:298 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:326 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:331 msgid "Edit Profile" msgstr "" @@ -3524,7 +3537,7 @@ msgstr "" msgid "Enter your username and password" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:146 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:148 msgid "Enters full screen" msgstr "" @@ -3710,7 +3723,7 @@ msgstr "" msgid "Failed to add emoji reaction" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:271 +#: src/components/dialogs/StarterPackDialog.tsx:276 msgid "Failed to add to starter pack" msgstr "" @@ -3749,6 +3762,10 @@ msgstr "" msgid "Failed to delete starter pack" msgstr "" +#: src/screens/Profile/components/GermButton.tsx:194 +msgid "Failed to disconnect Germ DM. Error: {0}" +msgstr "" + #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:133 msgid "Failed to follow all suggested accounts, please try again" msgstr "" @@ -3823,6 +3840,10 @@ msgstr "" msgid "Failed to pin post" msgstr "" +#: src/screens/Profile/components/GermButton.tsx:162 +msgid "Failed to reconnect Germ DM. Error: {0}" +msgstr "" + #: src/screens/Settings/FindContactsSettings.tsx:487 msgid "Failed to remove data due to a network error, please check your internet connection." msgstr "" @@ -3836,7 +3857,7 @@ msgstr "" msgid "Failed to remove emoji reaction" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:290 +#: src/components/dialogs/StarterPackDialog.tsx:289 msgid "Failed to remove from starter pack" msgstr "" @@ -4126,7 +4147,7 @@ msgstr "" #: src/components/ProfileHoverCard/index.web.tsx:497 #: src/components/ProfileHoverCard/index.web.tsx:508 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:152 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:381 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:386 #: src/screens/VideoFeed/index.tsx:874 #: src/view/com/notifications/NotificationFeedItem.tsx:841 #: src/view/com/notifications/NotificationFeedItem.tsx:848 @@ -4134,7 +4155,7 @@ msgid "Follow" msgstr "" #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:139 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:369 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:374 msgid "Follow {0}" msgstr "" @@ -4146,11 +4167,11 @@ msgstr "" msgid "Follow 10 accounts" msgstr "" -#: src/components/ProgressGuide/List.tsx:60 +#: src/components/ProgressGuide/List.tsx:66 msgid "Follow 10 people to get started" msgstr "" -#: src/components/ProgressGuide/List.tsx:102 +#: src/components/ProgressGuide/List.tsx:107 msgid "Follow 7 accounts" msgstr "" @@ -4178,7 +4199,7 @@ msgstr "" #. User is not following this account, click to follow back #: src/components/ProfileCard.tsx:553 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:150 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:379 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:384 #: src/view/com/notifications/NotificationFeedItem.tsx:841 #: src/view/com/notifications/NotificationFeedItem.tsx:848 msgid "Follow back" @@ -4223,7 +4244,7 @@ msgstr "" #: src/components/ProfileHoverCard/index.web.tsx:496 #: src/components/ProfileHoverCard/index.web.tsx:507 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:155 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:377 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:382 #: src/screens/VideoFeed/index.tsx:872 #: src/view/com/notifications/NotificationFeedItem.tsx:819 #: src/view/com/notifications/NotificationFeedItem.tsx:836 @@ -4237,7 +4258,7 @@ msgid "Following" msgstr "" #: src/components/ProfileCard.tsx:509 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:244 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:249 #: src/view/com/notifications/NotificationFeedItem.tsx:772 msgid "Following {0}" msgstr "" @@ -4339,6 +4360,24 @@ msgstr "" msgid "Generate a starter pack" msgstr "" +#: src/screens/Profile/components/GermButton.tsx:85 +#: src/screens/Profile/components/GermButton.tsx:223 +msgid "Germ DM" +msgstr "" + +#: src/screens/Profile/components/GermButton.tsx:181 +msgid "Germ DM disconnected" +msgstr "" + +#: src/screens/Profile/components/GermButton.tsx:232 +#: src/screens/Profile/components/GermButton.tsx:237 +msgid "Germ DM Link" +msgstr "" + +#: src/screens/Profile/components/GermButton.tsx:158 +msgid "Germ DM reconnected" +msgstr "" + #: src/view/shell/Drawer.tsx:374 msgid "Get help" msgstr "" @@ -4421,8 +4460,8 @@ msgstr "" msgid "Glorification of violence" msgstr "" -#: src/components/dialogs/LinkWarning.tsx:111 -#: src/components/dialogs/LinkWarning.tsx:117 +#: src/components/dialogs/LinkWarning.tsx:126 +#: src/components/dialogs/LinkWarning.tsx:132 #: src/components/Layout/Header/index.tsx:128 #: src/components/moderation/ScreenHider.tsx:160 #: src/components/moderation/ScreenHider.tsx:169 @@ -4471,8 +4510,8 @@ msgstr "" msgid "Go Home" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:358 -#: src/view/com/profile/ProfileMenu.tsx:379 +#: src/view/com/profile/ProfileMenu.tsx:360 +#: src/view/com/profile/ProfileMenu.tsx:381 msgid "Go live" msgstr "" @@ -4483,8 +4522,8 @@ msgstr "" msgid "Go Live" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:355 -#: src/view/com/profile/ProfileMenu.tsx:375 +#: src/view/com/profile/ProfileMenu.tsx:357 +#: src/view/com/profile/ProfileMenu.tsx:377 msgid "Go live (disabled)" msgstr "" @@ -4529,6 +4568,11 @@ msgstr "" msgid "Going live is currently disabled for your account" msgstr "" +#: src/screens/Profile/components/GermButton.tsx:251 +#: src/screens/Profile/components/GermButton.tsx:256 +msgid "Got it" +msgstr "" + #: src/lib/moderation/useGlobalLabelStrings.ts:46 #: src/lib/moderation/useGlobalLabelStrings.ts:50 #: src/view/com/composer/labels/LabelsBtn.tsx:200 @@ -5284,6 +5328,10 @@ msgstr "" msgid "Learn more about what is public on Bluesky." msgstr "" +#: src/screens/Profile/components/GermButton.tsx:210 +msgid "Learn more about your Germ DM link" +msgstr "" + #: src/components/ageAssurance/AgeAssuranceAdmonition.tsx:89 msgid "Learn more in your <0>account settings." msgstr "" @@ -5314,8 +5362,8 @@ msgstr "" msgid "Leave them all unselected to see any language." msgstr "" -#: src/components/dialogs/LinkWarning.tsx:67 -#: src/components/dialogs/LinkWarning.tsx:75 +#: src/components/dialogs/LinkWarning.tsx:82 +#: src/components/dialogs/LinkWarning.tsx:90 msgid "Leaving Bluesky" msgstr "" @@ -5356,7 +5404,7 @@ msgstr "" msgid "Like ({0, plural, one {# like} other {# likes}})" msgstr "" -#: src/components/ProgressGuide/List.tsx:96 +#: src/components/ProgressGuide/List.tsx:101 msgid "Like 10 posts" msgstr "" @@ -5473,7 +5521,7 @@ msgstr "" msgid "List has been hidden" msgstr "" -#: src/screens/ProfileList/index.tsx:172 +#: src/screens/ProfileList/index.tsx:176 msgid "List Hidden" msgstr "" @@ -5643,7 +5691,7 @@ msgstr "" msgid "Make one for me" msgstr "" -#: src/components/dialogs/LinkWarning.tsx:85 +#: src/components/dialogs/LinkWarning.tsx:100 msgid "Make sure this is where you intend to go!" msgstr "" @@ -5870,7 +5918,7 @@ msgstr "" msgid "Music" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:167 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:169 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:97 msgctxt "video" msgid "Mute" @@ -5883,8 +5931,8 @@ msgstr "" #: src/components/PostControls/PostMenu/PostMenuItems.tsx:689 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:695 -#: src/view/com/profile/ProfileMenu.tsx:436 -#: src/view/com/profile/ProfileMenu.tsx:443 +#: src/view/com/profile/ProfileMenu.tsx:438 +#: src/view/com/profile/ProfileMenu.tsx:445 msgid "Mute account" msgstr "" @@ -6014,7 +6062,7 @@ msgid "Nevermind, create a handle for me" msgstr "" #: src/screens/Search/modules/ExploreTrendingTopics.tsx:193 -#: src/view/com/profile/ProfileMenu.tsx:391 +#: src/view/com/profile/ProfileMenu.tsx:393 msgid "New" msgstr "" @@ -6081,8 +6129,8 @@ msgid "New password" msgstr "" #: src/screens/Profile/ProfileFeed/index.tsx:250 -#: src/screens/ProfileList/index.tsx:246 -#: src/screens/ProfileList/index.tsx:284 +#: src/screens/ProfileList/index.tsx:250 +#: src/screens/ProfileList/index.tsx:299 #: src/view/screens/Feeds.tsx:552 #: src/view/screens/Notifications.tsx:166 #: src/view/screens/Profile.tsx:594 @@ -6107,7 +6155,7 @@ msgstr "" msgid "New posts from {firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}}" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:193 +#: src/components/dialogs/StarterPackDialog.tsx:200 msgid "New starter pack" msgstr "" @@ -6211,7 +6259,7 @@ msgid "No lists" msgstr "" #: src/components/ProfileCard.tsx:531 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:269 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:274 #: src/view/com/notifications/NotificationFeedItem.tsx:792 msgid "No longer following {0}" msgstr "" @@ -6373,7 +6421,7 @@ msgstr "" msgid "Not ready to hit post? Keep your best ideas in Drafts until the timing is just right." msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:558 +#: src/view/com/profile/ProfileMenu.tsx:560 msgid "Note about sharing" msgstr "" @@ -6586,6 +6634,10 @@ msgstr "" msgid "Open full emoji list" msgstr "" +#: src/screens/Profile/components/GermButton.tsx:74 +msgid "Open Germ DM" +msgstr "" + #: src/components/Post/Embed/ExternalEmbed/index.tsx:79 msgid "Open link to {niceUrl}" msgstr "" @@ -6703,7 +6755,7 @@ msgstr "" msgid "Opens image picker" msgstr "" -#: src/components/dialogs/LinkWarning.tsx:97 +#: src/components/dialogs/LinkWarning.tsx:112 msgid "Opens link {0}" msgstr "" @@ -6846,7 +6898,7 @@ msgstr "" msgid "Password updated!" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:151 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:385 msgid "Pause" msgstr "" @@ -6859,7 +6911,7 @@ msgstr "" msgid "Pause video" msgstr "" -#: src/screens/ProfileList/index.tsx:166 +#: src/screens/ProfileList/index.tsx:168 #: src/screens/Search/SearchResults.tsx:72 #: src/screens/StarterPack/StarterPackScreen.tsx:192 msgid "People" @@ -6960,7 +7012,7 @@ msgstr "" msgid "Pinned to your feeds" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:151 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:386 msgid "Play" msgstr "" @@ -6986,7 +7038,7 @@ msgstr "" msgid "Plays or pauses the GIF" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:154 msgid "Plays or pauses the video" msgstr "" @@ -7256,7 +7308,7 @@ msgstr "" #: src/components/activity-notifications/SubscribeProfileDialog.tsx:250 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:262 -#: src/screens/ProfileList/index.tsx:166 +#: src/screens/ProfileList/index.tsx:168 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:216 #: src/screens/StarterPack/StarterPackScreen.tsx:194 #: src/view/screens/Profile.tsx:234 @@ -7275,11 +7327,11 @@ msgstr "" msgid "Posts, Replies" msgstr "" -#: src/components/dialogs/LinkWarning.tsx:73 +#: src/components/dialogs/LinkWarning.tsx:88 msgid "Potentially misleading link" msgstr "" -#: src/components/dialogs/LinkWarning.tsx:66 +#: src/components/dialogs/LinkWarning.tsx:81 msgid "Potentially misleading link warning" msgstr "" @@ -7380,6 +7432,10 @@ msgstr "" msgid "Profile" msgstr "" +#: src/lib/strings/errors.ts:40 +msgid "Profile not found" +msgstr "" + #: src/screens/Profile/Header/EditProfileDialog.tsx:189 msgctxt "toast" msgid "Profile updated" @@ -7601,8 +7657,8 @@ msgstr "" #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:171 #: src/components/dialogs/MutedWords.tsx:443 -#: src/components/dialogs/StarterPackDialog.tsx:375 -#: src/components/dialogs/StarterPackDialog.tsx:381 +#: src/components/dialogs/StarterPackDialog.tsx:372 +#: src/components/dialogs/StarterPackDialog.tsx:379 #: src/components/FeedCard.tsx:375 #: src/components/StarterPack/Wizard/WizardListCard.tsx:105 #: src/components/StarterPack/Wizard/WizardListCard.tsx:112 @@ -7726,8 +7782,8 @@ msgstr "" #: src/components/verification/VerificationRemovePrompt.tsx:46 #: src/components/verification/VerificationsDialog.tsx:249 -#: src/view/com/profile/ProfileMenu.tsx:409 -#: src/view/com/profile/ProfileMenu.tsx:412 +#: src/view/com/profile/ProfileMenu.tsx:411 +#: src/view/com/profile/ProfileMenu.tsx:414 msgid "Remove verification" msgstr "" @@ -7757,7 +7813,7 @@ msgstr "" msgid "Removed from saved posts" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:278 +#: src/components/dialogs/StarterPackDialog.tsx:283 msgid "Removed from starter pack" msgstr "" @@ -7862,8 +7918,8 @@ msgstr "" msgid "Report" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:476 -#: src/view/com/profile/ProfileMenu.tsx:479 +#: src/view/com/profile/ProfileMenu.tsx:478 +#: src/view/com/profile/ProfileMenu.tsx:481 msgid "Report account" msgstr "" @@ -8758,7 +8814,7 @@ msgstr "" msgid "Share a fun fact!" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:563 +#: src/view/com/profile/ProfileMenu.tsx:565 msgid "Share anyway" msgstr "" @@ -8767,8 +8823,8 @@ msgstr "" msgid "Share author DID" msgstr "" -#: src/components/dialogs/LinkWarning.tsx:96 -#: src/components/dialogs/LinkWarning.tsx:104 +#: src/components/dialogs/LinkWarning.tsx:111 +#: src/components/dialogs/LinkWarning.tsx:119 #: src/components/StarterPack/ShareDialog.tsx:114 #: src/components/StarterPack/ShareDialog.tsx:123 msgid "Share link" @@ -9437,7 +9493,7 @@ msgstr "" msgid "Task complete - 10 likes!" msgstr "" -#: src/components/ProgressGuide/List.tsx:97 +#: src/components/ProgressGuide/List.tsx:102 msgid "Teach our algorithm what you like" msgstr "" @@ -9522,9 +9578,9 @@ msgstr "" msgid "That's everything!" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:186 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:394 -#: src/view/com/profile/ProfileMenu.tsx:539 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:191 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:399 +#: src/view/com/profile/ProfileMenu.tsx:541 msgid "The account will be able to interact with you after unblocking." msgstr "" @@ -9707,10 +9763,10 @@ msgstr "" #: src/components/PostControls/PostMenu/PostMenuItems.tsx:450 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:116 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:127 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:88 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:253 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:279 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:294 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:89 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:258 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:284 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:299 #: src/view/com/profile/ProfileMenu.tsx:152 #: src/view/com/profile/ProfileMenu.tsx:162 #: src/view/com/profile/ProfileMenu.tsx:176 @@ -9789,6 +9845,10 @@ msgstr "" msgid "This author has chosen to make their posts visible only to people who are signed in." msgstr "" +#: src/screens/Profile/components/GermButton.tsx:242 +msgid "This button lets others open the Germ DM app to send you a message. You can manage its visibility from the Germ DM app, or you can disconnect your Bluesky account from Germ DM altogether by clicking the button below." +msgstr "" + #: src/screens/Messages/components/MessageListError.tsx:18 msgid "This chat was disconnected" msgstr "" @@ -9897,7 +9957,7 @@ msgstr "" msgid "This labeler hasn't declared what labels it publishes, and may not be active." msgstr "" -#: src/components/dialogs/LinkWarning.tsx:79 +#: src/components/dialogs/LinkWarning.tsx:94 msgid "This link is taking you to the following website:" msgstr "" @@ -9941,7 +10001,7 @@ msgstr "" msgid "This post's author has disabled quote posts." msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:560 +#: src/view/com/profile/ProfileMenu.tsx:562 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in." msgstr "" @@ -10070,7 +10130,7 @@ msgstr "" msgid "Toggle to enable or disable adult content" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:169 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:171 msgid "Toggles the sound" msgstr "" @@ -10189,6 +10249,10 @@ msgstr "" msgid "Unable to delete" msgstr "" +#: src/lib/strings/errors.ts:43 +msgid "Unable to resolve handle" +msgstr "" + #: src/screens/Settings/Settings.tsx:523 msgid "Unapply Pull Request" msgstr "" @@ -10209,30 +10273,30 @@ msgstr "" #: src/components/dms/MessagesListBlockedFooter.tsx:104 #: src/components/dms/MessagesListBlockedFooter.tsx:112 #: src/components/dms/MessagesListBlockedFooter.tsx:119 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:337 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:397 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:195 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:342 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:402 #: src/screens/ProfileList/components/Header.tsx:165 #: src/screens/ProfileList/components/Header.tsx:172 -#: src/view/com/profile/ProfileMenu.tsx:551 +#: src/view/com/profile/ProfileMenu.tsx:553 msgid "Unblock" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:341 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:346 msgctxt "action" msgid "Unblock" msgstr "" #: src/components/dms/ConvoMenu.tsx:261 #: src/components/dms/ConvoMenu.tsx:264 -#: src/view/com/profile/ProfileMenu.tsx:456 -#: src/view/com/profile/ProfileMenu.tsx:462 +#: src/view/com/profile/ProfileMenu.tsx:458 +#: src/view/com/profile/ProfileMenu.tsx:464 msgid "Unblock account" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:184 -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:392 -#: src/view/com/profile/ProfileMenu.tsx:533 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:189 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:397 +#: src/view/com/profile/ProfileMenu.tsx:535 msgid "Unblock Account?" msgstr "" @@ -10252,6 +10316,8 @@ msgstr "" #: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:78 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:39 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:45 +#: src/screens/Profile/components/GermButton.tsx:184 +#: src/screens/Profile/components/GermButton.tsx:185 msgid "Undo" msgstr "" @@ -10265,7 +10331,7 @@ msgstr "" msgid "Undo repost ({0, plural, one {# repost} other {# reposts}})" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:368 +#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:373 msgid "Unfollow {0}" msgstr "" @@ -10311,7 +10377,7 @@ msgstr "" msgid "Unlike ({0, plural, one {# like} other {# likes}})" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:166 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:168 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96 msgctxt "video" msgid "Unmute" @@ -10329,8 +10395,8 @@ msgstr "" #: src/components/PostControls/PostMenu/PostMenuItems.tsx:688 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:694 -#: src/view/com/profile/ProfileMenu.tsx:435 -#: src/view/com/profile/ProfileMenu.tsx:441 +#: src/view/com/profile/ProfileMenu.tsx:437 +#: src/view/com/profile/ProfileMenu.tsx:443 msgid "Unmute account" msgstr "" @@ -10646,8 +10712,8 @@ msgstr "" #: src/components/verification/VerificationCreatePrompt.tsx:84 #: src/components/verification/VerificationCreatePrompt.tsx:86 -#: src/view/com/profile/ProfileMenu.tsx:419 -#: src/view/com/profile/ProfileMenu.tsx:422 +#: src/view/com/profile/ProfileMenu.tsx:421 +#: src/view/com/profile/ProfileMenu.tsx:424 msgid "Verify account" msgstr "" @@ -10728,8 +10794,8 @@ msgstr "" msgid "Version {0}" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:84 -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:145 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:86 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:147 msgid "Video" msgstr "" @@ -10774,7 +10840,7 @@ msgstr "" msgid "Video uploaded" msgstr "" -#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:84 +#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:86 msgid "Video: {0}" msgstr "" @@ -10918,8 +10984,8 @@ msgstr "" msgid "Violent or threatening content" msgstr "" -#: src/components/dialogs/LinkWarning.tsx:96 -#: src/components/dialogs/LinkWarning.tsx:106 +#: src/components/dialogs/LinkWarning.tsx:111 +#: src/components/dialogs/LinkWarning.tsx:121 msgid "Visit site" msgstr "" @@ -11090,7 +11156,7 @@ msgstr "" msgid "We're sorry, but based on your device's location, you are currently located in a region that requires age assurance." msgstr "" -#: src/screens/ProfileList/index.tsx:87 +#: src/screens/ProfileList/index.tsx:88 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." msgstr "" @@ -11494,7 +11560,7 @@ msgstr "" msgid "You have no lists." msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:101 +#: src/components/dialogs/StarterPackDialog.tsx:100 msgid "You have no starter packs." msgstr "" -- 2.51.2 From c46219bc4d55104e0cb90813f8ae89a25a6d3f09 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 12 Feb 2026 15:07:26 +0000 Subject: [PATCH 18/20] Fix keyboard flicker when selecting a draft in composer (#9864) --- src/view/com/composer/drafts/DraftsListDialog.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/view/com/composer/drafts/DraftsListDialog.tsx b/src/view/com/composer/drafts/DraftsListDialog.tsx index 0ee587a43..138337c8c 100644 --- a/src/view/com/composer/drafts/DraftsListDialog.tsx +++ b/src/view/com/composer/drafts/DraftsListDialog.tsx @@ -1,5 +1,5 @@ import {useCallback, useEffect, useMemo} from 'react' -import {View} from 'react-native' +import {Keyboard, View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -54,6 +54,12 @@ export function DraftsListDialog({ const handleSelectDraft = useCallback( (summary: DraftSummary) => { + // Dismiss keyboard immediately to prevent flicker. Without this, + // the text input regains focus (showing the keyboard) after the + // drafts sheet closes, then loses it again when the post component + // remounts with the draft content, causing a show-hide-show cycle -sfn + Keyboard.dismiss() + control.close(() => { onSelectDraft(summary) }) -- 2.51.2 From 2f156bb9067bd968a089426a85dfa07a39ff07a7 Mon Sep 17 00:00:00 2001 From: Spence Pope Date: Thu, 12 Feb 2026 10:24:38 -0500 Subject: [PATCH 19/20] [APP-1833] Handle errors on saving a draft over char limit (#9850) * show composer error if draft over char limit * adjust cancel discarding if over the limit * use richtext for validation * don't allow saving a draft that can't actually be saved * account for 1k chars * update discard sheet * show composer error if draft over char limit * adjust cancel discarding if over the limit * use richtext for validation * don't allow saving a draft that can't actually be saved * account for 1k chars * update discard sheet * pr comment fixes * pluralization --- src/lib/constants.ts | 2 + src/view/com/composer/Composer.tsx | 140 ++++++++++++++---- src/view/com/composer/drafts/DraftItem.tsx | 1 + src/view/com/composer/drafts/DraftsButton.tsx | 54 ++++--- 4 files changed, 148 insertions(+), 49 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 96e1d8e2e..ba19765d4 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -62,6 +62,8 @@ export const MAX_DESCRIPTION = 256 export const MAX_GRAPHEME_LENGTH = 300 +export const MAX_DRAFT_GRAPHEME_LENGTH = 1000 + export const MAX_DM_GRAPHEME_LENGTH = 1000 // Recommended is 100 per: https://www.w3.org/WAI/GL/WCAG20/tests/test3.html diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 425440609..53d50bca8 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -45,6 +45,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as FileSystem from 'expo-file-system' import {type ImagePickerAsset} from 'expo-image-picker' import { + AppBskyDraftCreateDraft, AppBskyUnspeccedDefs, type AppBskyUnspeccedGetPostThreadV2, AtUri, @@ -62,6 +63,7 @@ import {useAppState} from '#/lib/appState' import {retry} from '#/lib/async/retry' import {until} from '#/lib/async/until' import { + MAX_DRAFT_GRAPHEME_LENGTH, MAX_GRAPHEME_LENGTH, SUPPORTED_MIME_TYPES, type SupportedMimeTypes, @@ -275,6 +277,13 @@ export const ComposePost = ({ ) const thread = composerState.thread + + // Clear error when composer content changes, but only if all posts are + // back within the character limit. + const allPostsWithinLimit = thread.posts.every( + post => post.richtext.graphemeLength <= MAX_DRAFT_GRAPHEME_LENGTH, + ) + const activePost = thread.posts[composerState.activePostIndex] const nextPost: PostDraft | undefined = thread.posts[composerState.activePostIndex + 1] @@ -543,7 +552,36 @@ export const ComposePost = ({ revokeAllMediaUrls() }, [closeComposer, queryClient]) + const getDraftSaveError = React.useCallback( + (e: unknown): string => { + if (e instanceof AppBskyDraftCreateDraft.DraftLimitReachedError) { + return _(msg`You've reached the maximum number of drafts`) + } + return _(msg`Failed to save draft`) + }, + [_], + ) + + const validateDraftTextOrError = React.useCallback((): boolean => { + const tooLong = composerState.thread.posts.some( + post => post.richtext.graphemeLength > MAX_DRAFT_GRAPHEME_LENGTH, + ) + if (tooLong) { + setError( + _( + msg`One or more posts are too long to save as a draft. ${plural(MAX_DRAFT_GRAPHEME_LENGTH, {one: 'The maximum number of characters is # character.', other: 'The maximum number of characters is # characters.'})}`, + ), + ) + return false + } + return true + }, [composerState.thread.posts, _]) + const handleSaveDraft = React.useCallback(async () => { + setError('') + if (!validateDraftTextOrError()) { + return + } const isNewDraft = !composerState.draftId try { const result = await saveDraft({ @@ -569,18 +607,44 @@ export const ComposePost = ({ onClose() } catch (e) { logger.error('Failed to save draft', {error: e}) - setError(_(msg`Failed to save draft`)) + setError(getDraftSaveError(e)) } - }, [saveDraft, composerState, composerDispatch, onClose, _, ax]) + }, [ + saveDraft, + composerState, + composerDispatch, + onClose, + ax, + validateDraftTextOrError, + getDraftSaveError, + ]) // Save without closing - for use by DraftsButton - const saveCurrentDraft = React.useCallback(async () => { - const result = await saveDraft({ - composerState, - existingDraftId: composerState.draftId, - }) - composerDispatch({type: 'mark_saved', draftId: result.draftId}) - }, [saveDraft, composerState, composerDispatch]) + const saveCurrentDraft = React.useCallback(async (): Promise<{ + success: boolean + }> => { + setError('') + if (!validateDraftTextOrError()) { + return {success: false} + } + try { + const result = await saveDraft({ + composerState, + existingDraftId: composerState.draftId, + }) + composerDispatch({type: 'mark_saved', draftId: result.draftId}) + return {success: true} + } catch (e) { + setError(getDraftSaveError(e)) + return {success: false} + } + }, [ + saveDraft, + composerState, + composerDispatch, + validateDraftTextOrError, + getDraftSaveError, + ]) // Handle discard action - fires metric and closes composer const handleDiscard = React.useCallback(() => { @@ -1090,6 +1154,7 @@ export const ComposePost = ({ isEmpty={isComposerEmpty} isDirty={composerState.isDirty} isEditingDraft={!!composerState.draftId} + canSaveDraft={allPostsWithinLimit} textLength={thread.posts[0].richtext.text.length}> {missingAltError && } - {composerState.draftId ? ( - Save changes? + {allPostsWithinLimit ? ( + composerState.draftId ? ( + Save changes? + ) : ( + Save draft? + ) ) : ( - Save draft? + Discard post? )} - {composerState.draftId ? ( - - You have unsaved changes to this draft, would you like to - save them? - + {allPostsWithinLimit ? ( + composerState.draftId ? ( + + You have unsaved changes to this draft, would you like to + save them? + + ) : ( + + Would you like to save this as a draft to edit later? + + ) ) : ( - - Would you like to save this as a draft to edit later? - + You can only save drafts up to 1000 characters. )} - + {allPostsWithinLimit && ( + + )} - + )} @@ -1416,6 +1491,7 @@ function ComposerTopBar({ isEmpty, isDirty, isEditingDraft, + canSaveDraft, textLength, topBarAnimatedStyle, children, @@ -1429,11 +1505,12 @@ function ComposerTopBar({ onCancel: () => void onPublish: () => void onSelectDraft: (draft: DraftSummary) => void - onSaveDraft: () => Promise + onSaveDraft: () => Promise<{success: boolean}> onDiscard: () => void isEmpty: boolean isDirty: boolean isEditingDraft: boolean + canSaveDraft: boolean textLength: number topBarAnimatedStyle: StyleProp children?: React.ReactNode @@ -1481,6 +1558,7 @@ function ComposerTopBar({ isEmpty={isEmpty} isDirty={isDirty} isEditingDraft={isEditingDraft} + canSaveDraft={canSaveDraft} textLength={textLength} /> )} diff --git a/src/view/com/composer/drafts/DraftItem.tsx b/src/view/com/composer/drafts/DraftItem.tsx index 964afe118..16274acbf 100644 --- a/src/view/com/composer/drafts/DraftItem.tsx +++ b/src/view/com/composer/drafts/DraftItem.tsx @@ -98,6 +98,7 @@ export function DraftItem({ {!!post.text.trim().length && ( void - onSaveDraft: () => Promise + onSaveDraft: () => Promise<{success: boolean}> onDiscard: () => void isEmpty: boolean isDirty: boolean isEditingDraft: boolean + canSaveDraft: boolean textLength: number }) { const {_} = useLingui() @@ -44,8 +46,10 @@ export function DraftsButton({ } const handleSaveAndOpen = async () => { - await onSaveDraft() - draftsDialogControl.open() + const {success} = await onSaveDraft() + if (success) { + draftsDialogControl.open() + } } const handleDiscardAndOpen = () => { @@ -83,37 +87,51 @@ export function DraftsButton({ - {isEditingDraft ? ( - Save changes? + {canSaveDraft ? ( + isEditingDraft ? ( + Save changes? + ) : ( + Save draft? + ) ) : ( - Save draft? + Discard draft? )} - {isEditingDraft ? ( - - You have unsaved changes. Would you like to save them before - viewing your drafts? - + {canSaveDraft ? ( + isEditingDraft ? ( + + You have unsaved changes. Would you like to save them before + viewing your drafts? + + ) : ( + + Would you like to save this as a draft before viewing your + drafts? + + ) ) : ( - Would you like to save this as a draft before viewing your drafts? + You can only save drafts up to 1000 characters. Would you like to + discard this post before viewing your drafts? )} - + {canSaveDraft && ( + + )} - + -- 2.51.2 From 82fea6da7d57b62105211742ac67598a41db4653 Mon Sep 17 00:00:00 2001 From: Spence Pope Date: Thu, 12 Feb 2026 14:34:50 -0500 Subject: [PATCH 20/20] [APP-1846] Remove trending post counts until we can improve accuracy (#9867) --- .../Search/modules/ExploreTrendingTopics.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/screens/Search/modules/ExploreTrendingTopics.tsx b/src/screens/Search/modules/ExploreTrendingTopics.tsx index 693c4710c..9eaa493b0 100644 --- a/src/screens/Search/modules/ExploreTrendingTopics.tsx +++ b/src/screens/Search/modules/ExploreTrendingTopics.tsx @@ -1,7 +1,7 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api' -import {msg, plural, Trans} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -9,7 +9,6 @@ import {useTrendingSettings} from '#/state/preferences/trending' import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery' import {useTrendingConfig} from '#/state/service-config' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' -import {formatCount} from '#/view/com/util/numeric/format' import {atoms as a, useGutters, useTheme, type ViewStyleProp, web} from '#/alf' import {AvatarStack} from '#/components/AvatarStack' import {type Props as SVGIconProps} from '#/components/icons/common' @@ -65,7 +64,7 @@ export function TrendRow({ onPress?: () => void }) { const t = useTheme() - const {_, i18n} = useLingui() + const {_} = useLingui() const gutters = useGutters([0, 'base']) const category = useCategoryDisplayName(trend?.category || 'other') @@ -74,13 +73,6 @@ export function TrendRow({ (1000 * 60 * 60), ) const badgeType = trend.status === 'hot' ? 'hot' : age < 2 ? 'new' : age - const postCount = trend.postCount - ? _( - plural(trend.postCount, { - other: `${formatCount(i18n, trend.postCount)} posts`, - }), - ) - : null const actors = useModerateTrendingActors(trend.actors) @@ -132,8 +124,6 @@ export function TrendRow({ web(a.leading_snug), ]} numberOfLines={1}> - {postCount} - {postCount && category && <> · } {category} -- 2.51.2