Add recId to suggestedUser:* events (#9764)

* add recId to onboarding

* add recId to follow dialog

* add recId to profile header suggestions

* add recId to feed interstitials, fix animation on native

* fix claude feedback

* fix yarn.lock ci
This commit is contained in:
Samuel Newman
2026-02-03 18:42:12 +02:00
committed by GitHub
parent 27d9462639
commit 017120b3e8
9 changed files with 376 additions and 439 deletions
+1 -1
View File
@@ -229,7 +229,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
"@atproto/dev-env": "^0.3.206",
"@atproto/dev-env": "^0.3.208",
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
+5 -5
View File
@@ -467,8 +467,8 @@ export type Events = {
| 'InterstitialProfile'
| 'Profile'
| 'Onboarding'
location: 'Card' | 'Profile'
recId?: number
location: 'Card' | 'Profile' | 'FollowAll'
recId?: number | string
position: number
suggestedDid: string
category: string | null
@@ -479,7 +479,7 @@ export type Events = {
| 'InterstitialDiscover'
| 'InterstitialProfile'
| 'Onboarding'
recId?: number
recId?: number | string
position: number
suggestedDid: string
category: string | null
@@ -492,7 +492,7 @@ export type Events = {
| 'Profile'
| 'Onboarding'
| 'ProgressGuide'
recId?: number
recId?: number | string
position: number
suggestedDid: string
category: string | null
@@ -507,7 +507,7 @@ export type Events = {
}
'suggestedUser:dismiss': {
logContext: 'InterstitialDiscover' | 'InterstitialProfile'
recId?: number
recId?: number | string
position: number
suggestedDid: string
}
+101 -120
View File
@@ -1,6 +1,12 @@
import React, {useCallback, useEffect, useRef} from 'react'
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {ScrollView, View} from 'react-native'
import Animated, {LinearTransition} from 'react-native-reanimated'
import Animated, {
Easing,
FadeIn,
FadeOut,
LayoutAnimationConfig,
LinearTransition,
} from 'react-native-reanimated'
import {type AppBskyFeedDefs, AtUri} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -21,6 +27,7 @@ import {type SeenPost} from '#/state/userActionHistory'
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
import {
atoms as a,
native,
useBreakpoints,
useTheme,
type ViewStyleProp,
@@ -152,7 +159,7 @@ function sortSeenPosts(postA: SeenPost, postB: SeenPost): 0 | 1 | -1 {
function useExperimentalSuggestedUsersQuery() {
const {currentAccount} = useSession()
const userActionSnapshot = userActionHistory.useActionHistorySnapshot()
const dids = React.useMemo(() => {
const dids = useMemo(() => {
const {likes, follows, followSuggestions, seen} = userActionSnapshot
const likeDids = likes
.map(l => new AtUri(l))
@@ -225,67 +232,54 @@ export function SuggestedFollowsProfile({did}: {did: string}) {
isFetchingNextPage,
} = useSuggestedFollowsQuery({limit: 25})
const [dismissedDids, setDismissedDids] = React.useState<Set<string>>(
new Set(),
)
const [dismissingDids, setDismissingDids] = React.useState<Set<string>>(
new Set(),
)
const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set())
const onDismiss = React.useCallback((dismissedDid: string) => {
// Start the fade animation
setDismissingDids(prev => new Set(prev).add(dismissedDid))
// After animation completes, actually remove from list
setTimeout(() => {
setDismissedDids(prev => new Set(prev).add(dismissedDid))
setDismissingDids(prev => {
const next = new Set(prev)
next.delete(dismissedDid)
return next
})
}, DISMISS_ANIMATION_DURATION)
const onDismiss = useCallback((dismissedDid: string) => {
setDismissedDids(prev => new Set(prev).add(dismissedDid))
}, [])
// Combine profiles from the actor-specific query with fallback suggestions
const allProfiles = React.useMemo(() => {
const allProfiles = useMemo(() => {
const actorProfiles = data?.suggestions ?? []
const fallbackProfiles =
moreSuggestions?.pages.flatMap(page => page.actors) ?? []
moreSuggestions?.pages.flatMap(page =>
page.actors.map(actor => ({actor, recId: page.recId})),
) ?? []
// Dedupe by did, preferring actor-specific profiles
const seen = new Set<string>()
const combined: bsky.profile.AnyProfileView[] = []
const combined: {actor: bsky.profile.AnyProfileView; recId?: number}[] = []
for (const profile of actorProfiles) {
if (!seen.has(profile.did)) {
seen.add(profile.did)
combined.push(profile)
combined.push({actor: profile, recId: data?.recId})
}
}
for (const profile of fallbackProfiles) {
if (!seen.has(profile.did) && profile.did !== did) {
seen.add(profile.did)
if (!seen.has(profile.actor.did) && profile.actor.did !== did) {
seen.add(profile.actor.did)
combined.push(profile)
}
}
return combined
}, [data?.suggestions, moreSuggestions?.pages, did])
}, [data?.suggestions, moreSuggestions?.pages, did, data?.recId])
const filteredProfiles = React.useMemo(() => {
return allProfiles.filter(p => !dismissedDids.has(p.did))
const filteredProfiles = useMemo(() => {
return allProfiles.filter(p => !dismissedDids.has(p.actor.did))
}, [allProfiles, dismissedDids])
// Fetch more when running low
React.useEffect(() => {
useEffect(() => {
if (
moderationOpts &&
filteredProfiles.length < maxLength &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
void fetchNextPage()
}
}, [
filteredProfiles.length,
@@ -301,11 +295,9 @@ export function SuggestedFollowsProfile({did}: {did: string}) {
isSuggestionsLoading={isSuggestionsLoading}
profiles={filteredProfiles}
totalProfileCount={allProfiles.length}
recId={data?.recId}
error={error}
viewContext="profile"
onDismiss={onDismiss}
dismissingDids={dismissingDids}
/>
)
}
@@ -327,46 +319,36 @@ export function SuggestedFollowsHome() {
error: suggestionsError,
} = useSuggestedFollowsQuery({limit: 25})
const [dismissedDids, setDismissedDids] = React.useState<Set<string>>(
new Set(),
)
const [dismissingDids, setDismissingDids] = React.useState<Set<string>>(
new Set(),
)
const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set())
const onDismiss = React.useCallback((did: string) => {
// Start the fade animation
setDismissingDids(prev => new Set(prev).add(did))
// After animation completes, actually remove from list
setTimeout(() => {
setDismissedDids(prev => new Set(prev).add(did))
setDismissingDids(prev => {
const next = new Set(prev)
next.delete(did)
return next
})
}, DISMISS_ANIMATION_DURATION)
const onDismiss = useCallback((did: string) => {
setDismissedDids(prev => new Set(prev).add(did))
}, [])
// Combine profiles from experimental query with paginated suggestions
const allProfiles = React.useMemo(() => {
const allProfiles = useMemo(() => {
const fallbackProfiles =
moreSuggestions?.pages.flatMap(page => page.actors) ?? []
moreSuggestions?.pages.flatMap(page =>
page.actors.map(actor => ({actor, recId: page.recId})),
) ?? []
// Dedupe by did, preferring experimental profiles
const seen = new Set<string>()
const combined: bsky.profile.AnyProfileView[] = []
const combined: Array<{
actor: bsky.profile.AnyProfileView
recId?: number
}> = []
for (const profile of experimentalProfiles) {
if (!seen.has(profile.did)) {
seen.add(profile.did)
combined.push(profile)
combined.push({actor: profile, recId: undefined})
}
}
for (const profile of fallbackProfiles) {
if (!seen.has(profile.did)) {
seen.add(profile.did)
if (!seen.has(profile.actor.did)) {
seen.add(profile.actor.did)
combined.push(profile)
}
}
@@ -374,19 +356,19 @@ export function SuggestedFollowsHome() {
return combined
}, [experimentalProfiles, moreSuggestions?.pages])
const filteredProfiles = React.useMemo(() => {
return allProfiles.filter(p => !dismissedDids.has(p.did))
const filteredProfiles = useMemo(() => {
return allProfiles.filter(p => !dismissedDids.has(p.actor.did))
}, [allProfiles, dismissedDids])
// Fetch more when running low
React.useEffect(() => {
useEffect(() => {
if (
moderationOpts &&
filteredProfiles.length < maxLength &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
void fetchNextPage()
}
}, [
filteredProfiles.length,
@@ -405,7 +387,6 @@ export function SuggestedFollowsHome() {
error={experimentalError || suggestionsError}
viewContext="feed"
onDismiss={onDismiss}
dismissingDids={dismissingDids}
/>
)
}
@@ -415,18 +396,14 @@ export function ProfileGrid({
error,
profiles,
totalProfileCount,
recId,
viewContext = 'feed',
onDismiss,
dismissingDids,
isVisible = true,
}: {
isSuggestionsLoading: boolean
profiles: bsky.profile.AnyProfileView[]
profiles: {actor: bsky.profile.AnyProfileView; recId?: number}[]
totalProfileCount?: number
recId?: number
error: Error | null
dismissingDids?: Set<string>
viewContext: 'profile' | 'profileHeader' | 'feed'
onDismiss?: (did: string) => void
isVisible?: boolean
@@ -463,18 +440,18 @@ export function ProfileGrid({
const profilesToShow = profiles.slice(0, maxLength)
profilesToShow.forEach((profile, index) => {
if (!seenProfilesRef.current.has(profile.did)) {
seenProfilesRef.current.add(profile.did)
if (!seenProfilesRef.current.has(profile.actor.did)) {
seenProfilesRef.current.add(profile.actor.did)
ax.metric('suggestedUser:seen', {
logContext,
recId,
recId: profile.recId,
position: index,
suggestedDid: profile.did,
suggestedDid: profile.actor.did,
category: null,
})
}
})
}, [ax, isLoading, error, profiles, maxLength, logContext, recId])
}, [ax, isLoading, error, profiles, maxLength, logContext])
// For profile header, fire when isVisible becomes true
useEffect(() => {
@@ -540,8 +517,15 @@ export function ProfileGrid({
? null
: profiles.slice(0, maxLength).map((profile, index) => (
<Animated.View
key={profile.did}
layout={LinearTransition.duration(DISMISS_ANIMATION_DURATION)}
key={profile.actor.did}
layout={native(
LinearTransition.delay(DISMISS_ANIMATION_DURATION).easing(
Easing.out(Easing.exp),
),
)}
exiting={FadeOut.duration(DISMISS_ANIMATION_DURATION)}
// for web, as the cards are static, not in a list
entering={web(FadeIn.delay(DISMISS_ANIMATION_DURATION * 2))}
style={[
a.flex_1,
gtMobile &&
@@ -550,22 +534,17 @@ export function ProfileGrid({
a.flex_grow,
{width: `calc(30% - ${a.gap_md.gap / 2}px)`},
]),
{
opacity: dismissingDids?.has(profile.did) ? 0 : 1,
transitionProperty: 'opacity',
transitionDuration: `${DISMISS_ANIMATION_DURATION}ms`,
},
]}>
<ProfileCard.Link
profile={profile}
profile={profile.actor}
onPress={() => {
ax.metric('suggestedUser:press', {
logContext: isFeedContext
? 'InterstitialDiscover'
: 'InterstitialProfile',
recId,
recId: profile.recId,
position: index,
suggestedDid: profile.did,
suggestedDid: profile.actor.did,
category: null,
})
}}
@@ -581,14 +560,14 @@ export function ProfileGrid({
label={_(msg`Dismiss this suggestion`)}
onPress={e => {
e.preventDefault()
onDismiss(profile.did)
onDismiss(profile.actor.did)
ax.metric('suggestedUser:dismiss', {
logContext: isFeedContext
? 'InterstitialDiscover'
: 'InterstitialProfile',
position: index,
suggestedDid: profile.did,
recId,
suggestedDid: profile.actor.did,
recId: profile.recId,
})
}}
style={[
@@ -621,18 +600,18 @@ export function ProfileGrid({
a.mb_auto,
]}>
<ProfileCard.Avatar
profile={profile}
profile={profile.actor}
moderationOpts={moderationOpts}
disabledPreview
size={88}
/>
<View style={[a.flex_col, a.align_center, a.max_w_full]}>
<ProfileCard.Name
profile={profile}
profile={profile.actor}
moderationOpts={moderationOpts}
/>
<ProfileCard.Description
profile={profile}
profile={profile.actor}
numberOfLines={2}
style={[
t.atoms.text_contrast_medium,
@@ -644,7 +623,7 @@ export function ProfileGrid({
</View>
<ProfileCard.FollowButton
profile={profile}
profile={profile.actor}
moderationOpts={moderationOpts}
logContext="FeedInterstitial"
withIcon={false}
@@ -655,9 +634,9 @@ export function ProfileGrid({
? 'InterstitialDiscover'
: 'InterstitialProfile',
location: 'Card',
recId,
recId: profile.recId,
position: index,
suggestedDid: profile.did,
suggestedDid: profile.actor.did,
category: null,
})
}}
@@ -726,35 +705,37 @@ export function ProfileGrid({
<FollowDialogWithoutGuide control={followDialogControl} />
{gtMobile ? (
<View style={[a.p_lg, a.pt_md]}>
<View style={[a.flex_1, a.flex_row, a.flex_wrap, a.gap_md]}>
{content}
<LayoutAnimationConfig skipExiting skipEntering>
{gtMobile ? (
<View style={[a.p_lg, a.pt_md]}>
<View style={[a.flex_1, a.flex_row, a.flex_wrap, a.gap_md]}>
{content}
</View>
</View>
</View>
) : (
<BlockDrawerGesture>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={[a.p_lg, a.pt_md, a.flex_row, a.gap_md]}
snapToInterval={MOBILE_CARD_WIDTH + a.gap_md.gap}
decelerationRate="fast">
{content}
) : (
<BlockDrawerGesture>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={[a.p_lg, a.pt_md, a.flex_row, a.gap_md]}
snapToInterval={MOBILE_CARD_WIDTH + a.gap_md.gap}
decelerationRate="fast">
{content}
{!isProfileHeaderContext && (
<SeeMoreSuggestedProfilesCard
onPress={() => {
followDialogControl.open()
ax.metric('suggestedUser:seeMore', {
logContext: 'Explore',
})
}}
/>
)}
</ScrollView>
</BlockDrawerGesture>
)}
{!isProfileHeaderContext && (
<SeeMoreSuggestedProfilesCard
onPress={() => {
followDialogControl.open()
ax.metric('suggestedUser:seeMore', {
logContext: 'Explore',
})
}}
/>
)}
</ScrollView>
</BlockDrawerGesture>
)}
</LayoutAnimationConfig>
</View>
)
}
@@ -795,7 +776,7 @@ export function SuggestedFeeds() {
const navigation = useNavigation<NavigationProp>()
const {gtMobile} = useBreakpoints()
const feeds = React.useMemo(() => {
const feeds = useMemo(() => {
const items: AppBskyFeedDefs.GeneratorView[] = []
if (!data) return items
+20 -15
View File
@@ -9,6 +9,7 @@ import {type ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorSearch} from '#/state/queries/actor-search'
@@ -207,6 +208,15 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
}
}
if (
hasSearchText &&
!isFetchingSearchResults &&
!_items.length &&
!isSearchResultsError
) {
_items.push({type: 'empty', key: 'empty', message: _(msg`No results`)})
}
return _items
}, [
_,
@@ -219,17 +229,9 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
currentAccount?.did,
hasSearchText,
resultsKey,
isSearchResultsError,
])
if (
searchText &&
!isFetchingSearchResults &&
!items.length &&
!isSearchResultsError
) {
items.push({type: 'empty', key: 'empty', message: _(msg`No results`)})
}
const renderItems = useCallback(
({item, index}: {item: Item; index: number}) => {
switch (item.type) {
@@ -262,7 +264,7 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
const selectedInterestRef = useRef(selectedInterest)
selectedInterestRef.current = selectedInterest
const onViewableItemsChanged = useRef(
const onViewableItemsChanged = useNonReactiveCallback(
({viewableItems}: {viewableItems: ViewToken[]}) => {
for (const viewableItem of viewableItems) {
const item = viewableItem.item as Item
@@ -274,7 +276,7 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
)
ax.metric('suggestedUser:seen', {
logContext: 'ProgressGuide',
recId: undefined,
recId: hasSearchText ? undefined : suggestions?.recId,
position: position !== -1 ? position : 0,
suggestedDid: item.profile.did,
category: selectedInterestRef.current,
@@ -283,10 +285,13 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
}
}
},
).current
const viewabilityConfig = useRef({
itemVisiblePercentThreshold: 50,
}).current
)
const viewabilityConfig = useMemo(
() => ({
itemVisiblePercentThreshold: 50,
}),
[],
)
const onSelectTab = useCallback(
(interest: string) => {
@@ -97,6 +97,17 @@ export function StepSuggestedAccounts() {
tab: selectedInterest ?? 'all',
numAccounts: followableDids.length,
})
for (let i = 0; i < followableDids.length; i++) {
const did = followableDids[i]
ax.metric('suggestedUser:follow', {
logContext: 'Onboarding',
location: 'FollowAll',
recId: suggestedUsers?.recId,
position: i,
suggestedDid: did,
category: selectedInterest,
})
}
},
mutationFn: async () => {
for (const did of followableDids) {
@@ -135,14 +146,14 @@ export function StepSuggestedAccounts() {
seenProfilesRef.current.add(did)
ax.metric('suggestedUser:seen', {
logContext: 'Onboarding',
recId: undefined,
recId: suggestedUsers?.recId,
position,
suggestedDid: did,
category: selectedInterest,
})
}
},
[ax, selectedInterest],
[ax, selectedInterest, suggestedUsers?.recId],
)
return (
@@ -220,6 +231,7 @@ export function StepSuggestedAccounts() {
position={index}
category={selectedInterest}
onSeen={onProfileSeen}
recId={suggestedUsers.recId}
/>
))}
</View>
@@ -234,7 +246,7 @@ export function StepSuggestedAccounts() {
color="secondary"
size="large"
label={_(msg`Retry`)}
onPress={() => refetch()}>
onPress={() => void refetch()}>
<ButtonText>
<Trans>Retry</Trans>
</ButtonText>
@@ -329,12 +341,14 @@ function SuggestedProfileCard({
position,
category,
onSeen,
recId,
}: {
profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts
position: number
category: string | null
onSeen: (did: string, position: number) => void
recId?: number | string
}) {
const t = useTheme()
const ax = useAnalytics()
@@ -401,7 +415,7 @@ function SuggestedProfileCard({
ax.metric('suggestedUser:follow', {
logContext: 'Onboarding',
location: 'Card',
recId: undefined,
recId,
position,
suggestedDid: profile.did,
category,
@@ -43,7 +43,7 @@ import {EditProfileDialog} from './EditProfileDialog'
import {ProfileHeaderHandle} from './Handle'
import {ProfileHeaderMetrics} from './Metrics'
import {ProfileHeaderShell} from './Shell'
import {AnimatedProfileHeaderSuggestedFollows} from './SuggestedFollows'
import {ProfileHeaderSuggestedFollows} from './SuggestedFollows'
interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed
@@ -193,7 +193,7 @@ let ProfileHeaderStandard = ({
/>
</ProfileHeaderShell>
<AnimatedProfileHeaderSuggestedFollows
<ProfileHeaderSuggestedFollows
isExpanded={showSuggestedFollows}
actorDid={profile.did}
/>
+86 -189
View File
@@ -1,5 +1,4 @@
import React from 'react'
import {type AppBskyActorDefs} from '@atproto/api'
import {useCallback, useEffect, useMemo, useState} from 'react'
import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -10,198 +9,17 @@ import {
import {useBreakpoints} from '#/alf'
import {ProfileGrid} from '#/components/FeedInterstitials'
import {IS_ANDROID} from '#/env'
import type * as bsky from '#/types/bsky'
const DISMISS_ANIMATION_DURATION = 200
export function ProfileHeaderSuggestedFollows({actorDid}: {actorDid: string}) {
const {gtMobile} = useBreakpoints()
const moderationOpts = useModerationOpts()
const maxLength = gtMobile ? 4 : 12
const {isLoading, data, error} = useSuggestedFollowsByActorQuery({
did: actorDid,
})
const {
data: moreSuggestions,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useSuggestedFollowsQuery({limit: 25})
const [dismissedDids, setDismissedDids] = React.useState<Set<string>>(
new Set(),
)
const [dismissingDids, setDismissingDids] = React.useState<Set<string>>(
new Set(),
)
const onDismiss = React.useCallback((did: string) => {
// Start the fade animation
setDismissingDids(prev => new Set(prev).add(did))
// After animation completes, actually remove from list
setTimeout(() => {
setDismissedDids(prev => new Set(prev).add(did))
setDismissingDids(prev => {
const next = new Set(prev)
next.delete(did)
return next
})
}, DISMISS_ANIMATION_DURATION)
}, [])
// Combine profiles from the actor-specific query with fallback suggestions
const allProfiles = React.useMemo(() => {
const actorProfiles = data?.suggestions ?? []
const fallbackProfiles =
moreSuggestions?.pages.flatMap(page => page.actors) ?? []
// Dedupe by did, preferring actor-specific profiles
const seen = new Set<string>()
const combined: AppBskyActorDefs.ProfileView[] = []
for (const profile of actorProfiles) {
if (!seen.has(profile.did)) {
seen.add(profile.did)
combined.push(profile)
}
}
for (const profile of fallbackProfiles) {
if (!seen.has(profile.did) && profile.did !== actorDid) {
seen.add(profile.did)
combined.push(profile)
}
}
return combined
}, [data?.suggestions, moreSuggestions?.pages, actorDid])
const filteredProfiles = React.useMemo(() => {
return allProfiles.filter(p => !dismissedDids.has(p.did))
}, [allProfiles, dismissedDids])
// Fetch more when running low
React.useEffect(() => {
if (
moderationOpts &&
filteredProfiles.length < maxLength &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
}
}, [
filteredProfiles.length,
maxLength,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
moderationOpts,
])
return (
<ProfileGrid
isSuggestionsLoading={isLoading}
profiles={filteredProfiles}
totalProfileCount={allProfiles.length}
recId={data?.recId}
error={error}
viewContext="profileHeader"
onDismiss={onDismiss}
dismissingDids={dismissingDids}
/>
)
}
export function AnimatedProfileHeaderSuggestedFollows({
export function ProfileHeaderSuggestedFollows({
isExpanded,
actorDid,
}: {
isExpanded: boolean
actorDid: string
}) {
const {gtMobile} = useBreakpoints()
const moderationOpts = useModerationOpts()
const maxLength = gtMobile ? 4 : 12
const {isLoading, data, error} = useSuggestedFollowsByActorQuery({
did: actorDid,
})
const {
data: moreSuggestions,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useSuggestedFollowsQuery({limit: 25})
const [dismissedDids, setDismissedDids] = React.useState<Set<string>>(
new Set(),
)
const [dismissingDids, setDismissingDids] = React.useState<Set<string>>(
new Set(),
)
const onDismiss = React.useCallback((did: string) => {
// Start the fade animation
setDismissingDids(prev => new Set(prev).add(did))
// After animation completes, actually remove from list
setTimeout(() => {
setDismissedDids(prev => new Set(prev).add(did))
setDismissingDids(prev => {
const next = new Set(prev)
next.delete(did)
return next
})
}, DISMISS_ANIMATION_DURATION)
}, [])
// Combine profiles from the actor-specific query with fallback suggestions
const allProfiles = React.useMemo(() => {
const actorProfiles = data?.suggestions ?? []
const fallbackProfiles =
moreSuggestions?.pages.flatMap(page => page.actors) ?? []
// Dedupe by did, preferring actor-specific profiles
const seen = new Set<string>()
const combined: AppBskyActorDefs.ProfileView[] = []
for (const profile of actorProfiles) {
if (!seen.has(profile.did)) {
seen.add(profile.did)
combined.push(profile)
}
}
for (const profile of fallbackProfiles) {
if (!seen.has(profile.did) && profile.did !== actorDid) {
seen.add(profile.did)
combined.push(profile)
}
}
return combined
}, [data?.suggestions, moreSuggestions?.pages, actorDid])
const filteredProfiles = React.useMemo(() => {
return allProfiles.filter(p => !dismissedDids.has(p.did))
}, [allProfiles, dismissedDids])
// Fetch more when running low
React.useEffect(() => {
if (
moderationOpts &&
filteredProfiles.length < maxLength &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
}
}, [
filteredProfiles.length,
maxLength,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
moderationOpts,
])
const {allProfiles, filteredProfiles, onDismiss, isLoading, error} =
useProfileHeaderSuggestions(actorDid)
if (!allProfiles.length && !isLoading) return null
@@ -218,13 +36,92 @@ export function AnimatedProfileHeaderSuggestedFollows({
isSuggestionsLoading={isLoading}
profiles={filteredProfiles}
totalProfileCount={allProfiles.length}
recId={data?.recId}
error={error}
viewContext="profileHeader"
onDismiss={onDismiss}
dismissingDids={dismissingDids}
isVisible={isExpanded}
/>
</AccordionAnimation>
)
}
function useProfileHeaderSuggestions(actorDid: string) {
const {gtMobile} = useBreakpoints()
const moderationOpts = useModerationOpts()
const maxLength = gtMobile ? 4 : 12
const {isLoading, data, error} = useSuggestedFollowsByActorQuery({
did: actorDid,
})
const {
data: moreSuggestions,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useSuggestedFollowsQuery({limit: 25})
const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set())
const onDismiss = useCallback((did: string) => {
setDismissedDids(prev => new Set(prev).add(did))
}, [])
// Combine profiles from the actor-specific query with fallback suggestions
const allProfiles = useMemo(() => {
const actorProfiles = data?.suggestions ?? []
const fallbackProfiles =
moreSuggestions?.pages.flatMap(page =>
page.actors.map(actor => ({actor, recId: page.recId})),
) ?? []
// Dedupe by did, preferring actor-specific profiles
const seen = new Set<string>()
const combined: {actor: bsky.profile.AnyProfileView; recId?: number}[] = []
for (const profile of actorProfiles) {
if (!seen.has(profile.did)) {
seen.add(profile.did)
combined.push({actor: profile, recId: data?.recId})
}
}
for (const profile of fallbackProfiles) {
if (!seen.has(profile.actor.did) && profile.actor.did !== actorDid) {
seen.add(profile.actor.did)
combined.push(profile)
}
}
return combined
}, [data?.suggestions, moreSuggestions?.pages, actorDid, data?.recId])
const filteredProfiles = useMemo(() => {
return allProfiles.filter(p => !dismissedDids.has(p.actor.did))
}, [allProfiles, dismissedDids])
// Fetch more when running low
useEffect(() => {
if (
moderationOpts &&
filteredProfiles.length < maxLength &&
hasNextPage &&
!isFetchingNextPage
) {
void fetchNextPage()
}
}, [
filteredProfiles.length,
maxLength,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
moderationOpts,
])
return {
allProfiles,
filteredProfiles,
onDismiss,
isLoading,
error,
}
}
@@ -45,6 +45,7 @@ export function useSuggestedUsers({
data: searched?.data
? {
actors: searched.data.pages.flatMap(p => p.actors) ?? [],
recId: undefined,
}
: undefined,
isLoading: searched.isLoading,
+142 -103
View File
@@ -20,7 +20,7 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@atproto-labs/did-resolver@0.2.6":
"@atproto-labs/did-resolver@^0.2.6":
version "0.2.6"
resolved "https://registry.yarnpkg.com/@atproto-labs/did-resolver/-/did-resolver-0.2.6.tgz#15f0beab797187a67279389f6503f87a257cd898"
integrity sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg==
@@ -32,7 +32,7 @@
"@atproto/did" "0.3.0"
zod "^3.23.8"
"@atproto-labs/fetch-node@0.2.0":
"@atproto-labs/fetch-node@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.2.0.tgz#438989f3165f52e21e7636fb87ea9c7317ae7f2a"
integrity sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q==
@@ -42,19 +42,19 @@
ipaddr.js "^2.1.0"
undici "^6.14.1"
"@atproto-labs/fetch@0.2.3":
"@atproto-labs/fetch@0.2.3", "@atproto-labs/fetch@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.2.3.tgz#d47afec078f630c50e291c56264cc0ff13d0c6cc"
integrity sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==
dependencies:
"@atproto-labs/pipe" "0.1.1"
"@atproto-labs/pipe@0.1.1":
"@atproto-labs/pipe@0.1.1", "@atproto-labs/pipe@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@atproto-labs/pipe/-/pipe-0.1.1.tgz#1c4232d16bf95f251e993cb6ee440f9aa4e87ce6"
integrity sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==
"@atproto-labs/simple-store-memory@0.1.4":
"@atproto-labs/simple-store-memory@0.1.4", "@atproto-labs/simple-store-memory@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz#e38c7b27e0f77c0bdba1329deb89593fbec27316"
integrity sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==
@@ -62,19 +62,19 @@
"@atproto-labs/simple-store" "0.3.0"
lru-cache "^10.2.0"
"@atproto-labs/simple-store-redis@0.0.1":
"@atproto-labs/simple-store-redis@^0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-redis/-/simple-store-redis-0.0.1.tgz#1dfc92cbec9b648c4349255aebb7bce9d7dc5eeb"
integrity sha512-hGkfDNVtTqwcRx27k6u25pgwNIHq3xDCRuojkfHf6c1B9R5rKphdZJ91Mn3lCvsyDB/lUqqLuzKuXQWFml/u5g==
dependencies:
"@atproto-labs/simple-store" "0.3.0"
"@atproto-labs/simple-store@0.3.0":
"@atproto-labs/simple-store@0.3.0", "@atproto-labs/simple-store@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz#65c0a5c949fe6c8dc3bdaf13ab40848f20073593"
integrity sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==
"@atproto-labs/xrpc-utils@0.0.24":
"@atproto-labs/xrpc-utils@^0.0.24":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@atproto-labs/xrpc-utils/-/xrpc-utils-0.0.24.tgz#0546778b9b83854d8a160dc4dea145e5a23ae8fc"
integrity sha512-wWXd2Ht47UsL/UbDCr3twMFSZrh0xSI56u4O3kz0DTU4G+530mCG71mMVE6eeYcR+j6FEjp7o2Ld6c7wFklYGw==
@@ -96,7 +96,7 @@
tlds "^1.234.0"
zod "^3.23.8"
"@atproto/api@^0.18.20":
"@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==
@@ -128,15 +128,15 @@
multiformats "^9.9.0"
uint8arrays "3.0.0"
"@atproto/bsky@^0.0.212":
version "0.0.212"
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.212.tgz#97416f5b788935a9b6c5696961aa2080f15c5c7b"
integrity sha512-xbdblgzpdKiUNMKL6qVURAUwATw9nrR7Rj8ygQDj3QEmmgMyEA5PEutCtoh3jlBr4bF2asZlyjySrtmAEixKNQ==
"@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==
dependencies:
"@atproto-labs/fetch-node" "0.2.0"
"@atproto-labs/xrpc-utils" "0.0.24"
"@atproto/api" "^0.18.18"
"@atproto/common" "^0.5.9"
"@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/crypto" "^0.4.5"
"@atproto/did" "^0.3.0"
"@atproto/identity" "^0.4.10"
@@ -144,7 +144,7 @@
"@atproto/repo" "^0.8.12"
"@atproto/sync" "^0.1.39"
"@atproto/syntax" "^0.4.3"
"@atproto/xrpc-server" "^0.10.10"
"@atproto/xrpc-server" "^0.10.11"
"@bufbuild/protobuf" "^1.5.0"
"@connectrpc/connect" "^1.1.4"
"@connectrpc/connect-express" "^1.1.4"
@@ -246,6 +246,18 @@
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==
dependencies:
"@atproto/common-web" "^0.4.15"
"@atproto/lex-cbor" "^0.0.10"
"@atproto/lex-data" "^0.0.10"
iso-datestring-validator "^2.2.2"
multiformats "^9.9.0"
pino "^8.21.0"
"@atproto/crypto@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.1.0.tgz#bc73a479f9dbe06fa025301c182d7f7ab01bc568"
@@ -257,7 +269,7 @@
one-webcrypto "^1.0.3"
uint8arrays "3.0.0"
"@atproto/crypto@0.4.5", "@atproto/crypto@^0.4.4", "@atproto/crypto@^0.4.5":
"@atproto/crypto@^0.4.4", "@atproto/crypto@^0.4.5":
version "0.4.5"
resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.5.tgz#fc6ad4fdfe8338147196c8050791cc6a22657eb6"
integrity sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw==
@@ -266,23 +278,23 @@
"@noble/hashes" "^1.6.1"
uint8arrays "3.0.0"
"@atproto/dev-env@^0.3.206":
version "0.3.206"
resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.206.tgz#2a32642e2d1e90dd979ebfa40c892bf700ae16b8"
integrity sha512-H3GfypJ8fJbGPC4DjHNXM0lFi1HzF//YN2S93QTWR+nVW3994893RsA28mdwY0p5jppF2lhQ4EbG3fPBko6F9g==
"@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==
dependencies:
"@atproto/api" "^0.18.18"
"@atproto/bsky" "^0.0.212"
"@atproto/api" "^0.18.20"
"@atproto/bsky" "^0.0.214"
"@atproto/bsync" "^0.0.23"
"@atproto/common-web" "^0.4.14"
"@atproto/common-web" "^0.4.15"
"@atproto/crypto" "^0.4.5"
"@atproto/identity" "^0.4.10"
"@atproto/lexicon" "^0.6.1"
"@atproto/ozone" "^0.1.162"
"@atproto/pds" "^0.4.206"
"@atproto/pds" "^0.4.207"
"@atproto/sync" "^0.1.39"
"@atproto/syntax" "^0.4.3"
"@atproto/xrpc-server" "^0.10.10"
"@atproto/xrpc-server" "^0.10.11"
"@did-plc/lib" "^0.0.1"
"@did-plc/server" "^0.0.1"
dotenv "^16.0.3"
@@ -307,7 +319,7 @@
"@atproto/common-web" "^0.4.4"
"@atproto/crypto" "^0.4.4"
"@atproto/jwk-jose@0.1.11":
"@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"
integrity sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q==
@@ -315,7 +327,7 @@
"@atproto/jwk" "0.6.0"
jose "^5.2.0"
"@atproto/jwk@0.6.0":
"@atproto/jwk@0.6.0", "@atproto/jwk@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.6.0.tgz#e813f77d9c89c025d4074340777fafaa2fba08a5"
integrity sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw==
@@ -323,7 +335,7 @@
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/lex-cbor@0.0.9", "@atproto/lex-cbor@^0.0.9":
"@atproto/lex-cbor@0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.9.tgz#d4b227ae37c1b44b76a2eca826b23c4b74bd4062"
integrity sha512-szkS569j1eZsIxZKh2VZHVq7pSpewy1wHh8c6nVYekHfYcJhFkevQq/DjTeatZ7YZKNReGYthQulgaZq2ytfWQ==
@@ -331,17 +343,25 @@
"@atproto/lex-data" "0.0.9"
tslib "^2.8.1"
"@atproto/lex-client@0.0.10":
"@atproto/lex-cbor@^0.0.10":
version "0.0.10"
resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.10.tgz#0baa7fee22d2efec17e8848351957a6e10917323"
integrity sha512-n3g9KoY5hw7W29mcR4TrjN5qOi6JiWty7r1heqLLfYiq5TxaRx9/QBa2hbN4h1p4xxICPZoDlNtuGq8YV4U8mg==
resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz#712abc4fedef4854c341e32d0fd1608e45616984"
integrity sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA==
dependencies:
"@atproto/lex-data" "0.0.9"
"@atproto/lex-json" "0.0.9"
"@atproto/lex-schema" "0.0.10"
"@atproto/lex-data" "^0.0.10"
tslib "^2.8.1"
"@atproto/lex-data@0.0.9", "@atproto/lex-data@^0.0.9":
"@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==
dependencies:
"@atproto/lex-data" "^0.0.10"
"@atproto/lex-json" "^0.0.10"
"@atproto/lex-schema" "^0.0.11"
tslib "^2.8.1"
"@atproto/lex-data@0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.9.tgz#19bed9399571d8d653a0bfba99b641cb98300e84"
integrity sha512-1slwe4sG0cyWtsq16+rBoWIxNDqGPkkvN+PV6JuzA7dgUK9bjUmXBGQU4eZlUPSS43X1Nhmr/9VjgKmEzU9vDw==
@@ -361,12 +381,12 @@
uint8arrays "3.0.0"
unicode-segmenter "^0.14.0"
"@atproto/lex-document@0.0.11":
version "0.0.11"
resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.11.tgz#8cfdd6ab5b5befac4d1409c76e2d5a310845c1dc"
integrity sha512-ePtFOU7yYAp1IL1mPDrAyo+ajN9V7W8z6BY4xXEM/m9U3vCVNC+SIkgkfwumqSUqOtBy4gpz52ppK+R/9S8UWg==
"@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==
dependencies:
"@atproto/lex-schema" "0.0.10"
"@atproto/lex-schema" "^0.0.11"
core-js "^3"
tslib "^2.8.1"
@@ -386,28 +406,28 @@
"@atproto/lex-data" "^0.0.10"
tslib "^2.8.1"
"@atproto/lex-resolver@0.0.12":
version "0.0.12"
resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.12.tgz#fb6cd78c78c0acfc9a92d9e42abe7ff18b4c3a41"
integrity sha512-Q3/olki+Wl3AVk/QlDfQJ6ttZuIe6RcT6UJ0eAb04f1r7vXihMyZSb4kPoDF86FoIWsxU8P8+FFHYszm7S+jAw==
"@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==
dependencies:
"@atproto-labs/did-resolver" "0.2.6"
"@atproto/crypto" "0.4.5"
"@atproto/lex-client" "0.0.10"
"@atproto/lex-data" "0.0.9"
"@atproto/lex-document" "0.0.11"
"@atproto/lex-schema" "0.0.10"
"@atproto/repo" "0.8.12"
"@atproto/syntax" "0.4.3"
"@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/repo" "^0.8.12"
"@atproto/syntax" "^0.4.3"
tslib "^2.8.1"
"@atproto/lex-schema@0.0.10":
version "0.0.10"
resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.10.tgz#ec2cf46617317afa31c5693f886f70ba4483163b"
integrity sha512-970BZVHtsLn03k2wkpYzdY2o/oZycqUReG1UblOkWYkbhQd04WqliiGrpUie/ls25oJs37ymI+fCDPcYg9tuQg==
"@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==
dependencies:
"@atproto/lex-data" "0.0.9"
"@atproto/syntax" "0.4.3"
"@atproto/lex-data" "^0.0.10"
"@atproto/syntax" "^0.4.3"
tslib "^2.8.1"
"@atproto/lexicon@^0.6.0", "@atproto/lexicon@^0.6.1":
@@ -421,7 +441,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==
@@ -429,42 +449,42 @@
"@atproto/jwk" "0.6.0"
"@atproto/oauth-types" "0.6.2"
"@atproto/oauth-provider-frontend@0.2.8":
"@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==
optionalDependencies:
"@atproto/oauth-provider-api" "0.3.7"
"@atproto/oauth-provider-ui@0.4.2":
"@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==
optionalDependencies:
"@atproto/oauth-provider-api" "0.3.7"
"@atproto/oauth-provider@^0.15.6":
version "0.15.6"
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.6.tgz#ae71dfbb0073575011b81be47ee1adf9c1dc5d30"
integrity sha512-612+MrwjqKQ56wuwTAqFvYs65TX2kPLrmlJwWjUlXm5oh0jmHZG9BBZ0I3WciAeGfIRMp9mWYMxokPxVJdkzgg==
"@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==
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.9"
"@atproto/did" "0.3.0"
"@atproto/jwk" "0.6.0"
"@atproto/jwk-jose" "0.1.11"
"@atproto/lex-document" "0.0.11"
"@atproto/lex-resolver" "0.0.12"
"@atproto/oauth-provider-api" "0.3.7"
"@atproto/oauth-provider-frontend" "0.2.8"
"@atproto/oauth-provider-ui" "0.4.2"
"@atproto/oauth-scopes" "0.3.1"
"@atproto/oauth-types" "0.6.2"
"@atproto/syntax" "0.4.3"
"@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/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/oauth-scopes" "^0.3.1"
"@atproto/oauth-types" "^0.6.2"
"@atproto/syntax" "^0.4.3"
"@hapi/accept" "^6.0.3"
"@hapi/address" "^5.1.1"
"@hapi/bourne" "^3.0.0"
@@ -477,7 +497,7 @@
jose "^5.2.0"
zod "^3.23.8"
"@atproto/oauth-scopes@0.3.1", "@atproto/oauth-scopes@^0.3.1":
"@atproto/oauth-scopes@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@atproto/oauth-scopes/-/oauth-scopes-0.3.1.tgz#cd0b3cdc31e14f6f1829f664fa1b3179f6ad9b51"
integrity sha512-eUD9C78uYH+0ZUmiV/X6pRj4BKlH9I1xxJYW1Gb/qJiATuTZkJVm02urJb/BkWX4Qpxy4rOr8EProNg1wByIEA==
@@ -485,7 +505,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==
@@ -525,30 +545,30 @@
undici "^6.14.1"
ws "^8.12.0"
"@atproto/pds@^0.4.206":
version "0.4.206"
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.206.tgz#2a0ae5b190568d0b23ad00a8c3e1d1217b212611"
integrity sha512-n2L0B3ZVe+7Bokuxvns8i/fBHuBepcvW2XSS5CYdMqWSaY6h5hezJjEM5O3nOx0CgCqXMmIMdnVtcIu0Dk5FPA==
"@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==
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.18"
"@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/aws" "^0.2.31"
"@atproto/common" "^0.5.9"
"@atproto/common" "^0.5.10"
"@atproto/crypto" "^0.4.5"
"@atproto/identity" "^0.4.10"
"@atproto/lex-cbor" "^0.0.9"
"@atproto/lex-data" "^0.0.9"
"@atproto/lex-cbor" "^0.0.10"
"@atproto/lex-data" "^0.0.10"
"@atproto/lexicon" "^0.6.1"
"@atproto/oauth-provider" "^0.15.6"
"@atproto/oauth-provider" "^0.15.7"
"@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.10"
"@atproto/xrpc-server" "^0.10.11"
"@did-plc/lib" "^0.0.4"
"@hapi/address" "^5.1.1"
better-sqlite3 "^10.0.0"
@@ -577,7 +597,7 @@
undici "^6.19.8"
zod "^3.23.8"
"@atproto/repo@0.8.12", "@atproto/repo@^0.8.11", "@atproto/repo@^0.8.12":
"@atproto/repo@^0.8.11", "@atproto/repo@^0.8.12":
version "0.8.12"
resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.8.12.tgz#f1caee2ff954a917f921569eac22ab669b39a0af"
integrity sha512-QpVTVulgfz5PUiCTELlDBiRvnsnwrFWi+6CfY88VwXzrRHd9NE8GItK7sfxQ6U65vD/idH8ddCgFrlrsn1REPQ==
@@ -641,6 +661,25 @@
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==
dependencies:
"@atproto/common" "^0.5.10"
"@atproto/crypto" "^0.4.5"
"@atproto/lex-cbor" "^0.0.10"
"@atproto/lex-data" "^0.0.10"
"@atproto/lexicon" "^0.6.1"
"@atproto/ws-client" "^0.0.4"
"@atproto/xrpc" "^0.7.7"
express "^4.17.2"
http-errors "^2.0.0"
mime-types "^2.1.35"
rate-limiter-flexible "^2.4.1"
ws "^8.12.0"
zod "^3.23.8"
"@atproto/xrpc@^0.7.6", "@atproto/xrpc@^0.7.7":
version "0.7.7"
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.7.tgz#c0e3106c854cb9bc7d3129de2f31b8256eb0ed11"