Bluesky app fork with some witchin' additions 💫

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

authored by samuel.fm and committed by

GitHub 017120b3 27d94626

+343 -406
+1 -1
package.json
··· 229 229 "zod": "^3.20.2" 230 230 }, 231 231 "devDependencies": { 232 - "@atproto/dev-env": "^0.3.206", 232 + "@atproto/dev-env": "^0.3.208", 233 233 "@babel/core": "^7.26.0", 234 234 "@babel/preset-env": "^7.26.0", 235 235 "@babel/runtime": "^7.26.0",
+5 -5
src/analytics/metrics/types.ts
··· 467 467 | 'InterstitialProfile' 468 468 | 'Profile' 469 469 | 'Onboarding' 470 - location: 'Card' | 'Profile' 471 - recId?: number 470 + location: 'Card' | 'Profile' | 'FollowAll' 471 + recId?: number | string 472 472 position: number 473 473 suggestedDid: string 474 474 category: string | null ··· 479 479 | 'InterstitialDiscover' 480 480 | 'InterstitialProfile' 481 481 | 'Onboarding' 482 - recId?: number 482 + recId?: number | string 483 483 position: number 484 484 suggestedDid: string 485 485 category: string | null ··· 492 492 | 'Profile' 493 493 | 'Onboarding' 494 494 | 'ProgressGuide' 495 - recId?: number 495 + recId?: number | string 496 496 position: number 497 497 suggestedDid: string 498 498 category: string | null ··· 507 507 } 508 508 'suggestedUser:dismiss': { 509 509 logContext: 'InterstitialDiscover' | 'InterstitialProfile' 510 - recId?: number 510 + recId?: number | string 511 511 position: number 512 512 suggestedDid: string 513 513 }
+101 -120
src/components/FeedInterstitials.tsx
··· 1 - import React, {useCallback, useEffect, useRef} from 'react' 1 + import {useCallback, useEffect, useMemo, useRef, useState} from 'react' 2 2 import {ScrollView, View} from 'react-native' 3 - import Animated, {LinearTransition} from 'react-native-reanimated' 3 + import Animated, { 4 + Easing, 5 + FadeIn, 6 + FadeOut, 7 + LayoutAnimationConfig, 8 + LinearTransition, 9 + } from 'react-native-reanimated' 4 10 import {type AppBskyFeedDefs, AtUri} from '@atproto/api' 5 11 import {msg, Trans} from '@lingui/macro' 6 12 import {useLingui} from '@lingui/react' ··· 21 27 import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' 22 28 import { 23 29 atoms as a, 30 + native, 24 31 useBreakpoints, 25 32 useTheme, 26 33 type ViewStyleProp, ··· 152 159 function useExperimentalSuggestedUsersQuery() { 153 160 const {currentAccount} = useSession() 154 161 const userActionSnapshot = userActionHistory.useActionHistorySnapshot() 155 - const dids = React.useMemo(() => { 162 + const dids = useMemo(() => { 156 163 const {likes, follows, followSuggestions, seen} = userActionSnapshot 157 164 const likeDids = likes 158 165 .map(l => new AtUri(l)) ··· 225 232 isFetchingNextPage, 226 233 } = useSuggestedFollowsQuery({limit: 25}) 227 234 228 - const [dismissedDids, setDismissedDids] = React.useState<Set<string>>( 229 - new Set(), 230 - ) 231 - const [dismissingDids, setDismissingDids] = React.useState<Set<string>>( 232 - new Set(), 233 - ) 235 + const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set()) 234 236 235 - const onDismiss = React.useCallback((dismissedDid: string) => { 236 - // Start the fade animation 237 - setDismissingDids(prev => new Set(prev).add(dismissedDid)) 238 - // After animation completes, actually remove from list 239 - setTimeout(() => { 240 - setDismissedDids(prev => new Set(prev).add(dismissedDid)) 241 - setDismissingDids(prev => { 242 - const next = new Set(prev) 243 - next.delete(dismissedDid) 244 - return next 245 - }) 246 - }, DISMISS_ANIMATION_DURATION) 237 + const onDismiss = useCallback((dismissedDid: string) => { 238 + setDismissedDids(prev => new Set(prev).add(dismissedDid)) 247 239 }, []) 248 240 249 241 // Combine profiles from the actor-specific query with fallback suggestions 250 - const allProfiles = React.useMemo(() => { 242 + const allProfiles = useMemo(() => { 251 243 const actorProfiles = data?.suggestions ?? [] 252 244 const fallbackProfiles = 253 - moreSuggestions?.pages.flatMap(page => page.actors) ?? [] 245 + moreSuggestions?.pages.flatMap(page => 246 + page.actors.map(actor => ({actor, recId: page.recId})), 247 + ) ?? [] 254 248 255 249 // Dedupe by did, preferring actor-specific profiles 256 250 const seen = new Set<string>() 257 - const combined: bsky.profile.AnyProfileView[] = [] 251 + const combined: {actor: bsky.profile.AnyProfileView; recId?: number}[] = [] 258 252 259 253 for (const profile of actorProfiles) { 260 254 if (!seen.has(profile.did)) { 261 255 seen.add(profile.did) 262 - combined.push(profile) 256 + combined.push({actor: profile, recId: data?.recId}) 263 257 } 264 258 } 265 259 266 260 for (const profile of fallbackProfiles) { 267 - if (!seen.has(profile.did) && profile.did !== did) { 268 - seen.add(profile.did) 261 + if (!seen.has(profile.actor.did) && profile.actor.did !== did) { 262 + seen.add(profile.actor.did) 269 263 combined.push(profile) 270 264 } 271 265 } 272 266 273 267 return combined 274 - }, [data?.suggestions, moreSuggestions?.pages, did]) 268 + }, [data?.suggestions, moreSuggestions?.pages, did, data?.recId]) 275 269 276 - const filteredProfiles = React.useMemo(() => { 277 - return allProfiles.filter(p => !dismissedDids.has(p.did)) 270 + const filteredProfiles = useMemo(() => { 271 + return allProfiles.filter(p => !dismissedDids.has(p.actor.did)) 278 272 }, [allProfiles, dismissedDids]) 279 273 280 274 // Fetch more when running low 281 - React.useEffect(() => { 275 + useEffect(() => { 282 276 if ( 283 277 moderationOpts && 284 278 filteredProfiles.length < maxLength && 285 279 hasNextPage && 286 280 !isFetchingNextPage 287 281 ) { 288 - fetchNextPage() 282 + void fetchNextPage() 289 283 } 290 284 }, [ 291 285 filteredProfiles.length, ··· 301 295 isSuggestionsLoading={isSuggestionsLoading} 302 296 profiles={filteredProfiles} 303 297 totalProfileCount={allProfiles.length} 304 - recId={data?.recId} 305 298 error={error} 306 299 viewContext="profile" 307 300 onDismiss={onDismiss} 308 - dismissingDids={dismissingDids} 309 301 /> 310 302 ) 311 303 } ··· 327 319 error: suggestionsError, 328 320 } = useSuggestedFollowsQuery({limit: 25}) 329 321 330 - const [dismissedDids, setDismissedDids] = React.useState<Set<string>>( 331 - new Set(), 332 - ) 333 - const [dismissingDids, setDismissingDids] = React.useState<Set<string>>( 334 - new Set(), 335 - ) 322 + const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set()) 336 323 337 - const onDismiss = React.useCallback((did: string) => { 338 - // Start the fade animation 339 - setDismissingDids(prev => new Set(prev).add(did)) 340 - // After animation completes, actually remove from list 341 - setTimeout(() => { 342 - setDismissedDids(prev => new Set(prev).add(did)) 343 - setDismissingDids(prev => { 344 - const next = new Set(prev) 345 - next.delete(did) 346 - return next 347 - }) 348 - }, DISMISS_ANIMATION_DURATION) 324 + const onDismiss = useCallback((did: string) => { 325 + setDismissedDids(prev => new Set(prev).add(did)) 349 326 }, []) 350 327 351 328 // Combine profiles from experimental query with paginated suggestions 352 - const allProfiles = React.useMemo(() => { 329 + const allProfiles = useMemo(() => { 353 330 const fallbackProfiles = 354 - moreSuggestions?.pages.flatMap(page => page.actors) ?? [] 331 + moreSuggestions?.pages.flatMap(page => 332 + page.actors.map(actor => ({actor, recId: page.recId})), 333 + ) ?? [] 355 334 356 335 // Dedupe by did, preferring experimental profiles 357 336 const seen = new Set<string>() 358 - const combined: bsky.profile.AnyProfileView[] = [] 337 + const combined: Array<{ 338 + actor: bsky.profile.AnyProfileView 339 + recId?: number 340 + }> = [] 359 341 360 342 for (const profile of experimentalProfiles) { 361 343 if (!seen.has(profile.did)) { 362 344 seen.add(profile.did) 363 - combined.push(profile) 345 + combined.push({actor: profile, recId: undefined}) 364 346 } 365 347 } 366 348 367 349 for (const profile of fallbackProfiles) { 368 - if (!seen.has(profile.did)) { 369 - seen.add(profile.did) 350 + if (!seen.has(profile.actor.did)) { 351 + seen.add(profile.actor.did) 370 352 combined.push(profile) 371 353 } 372 354 } ··· 374 356 return combined 375 357 }, [experimentalProfiles, moreSuggestions?.pages]) 376 358 377 - const filteredProfiles = React.useMemo(() => { 378 - return allProfiles.filter(p => !dismissedDids.has(p.did)) 359 + const filteredProfiles = useMemo(() => { 360 + return allProfiles.filter(p => !dismissedDids.has(p.actor.did)) 379 361 }, [allProfiles, dismissedDids]) 380 362 381 363 // Fetch more when running low 382 - React.useEffect(() => { 364 + useEffect(() => { 383 365 if ( 384 366 moderationOpts && 385 367 filteredProfiles.length < maxLength && 386 368 hasNextPage && 387 369 !isFetchingNextPage 388 370 ) { 389 - fetchNextPage() 371 + void fetchNextPage() 390 372 } 391 373 }, [ 392 374 filteredProfiles.length, ··· 405 387 error={experimentalError || suggestionsError} 406 388 viewContext="feed" 407 389 onDismiss={onDismiss} 408 - dismissingDids={dismissingDids} 409 390 /> 410 391 ) 411 392 } ··· 415 396 error, 416 397 profiles, 417 398 totalProfileCount, 418 - recId, 419 399 viewContext = 'feed', 420 400 onDismiss, 421 - dismissingDids, 422 401 isVisible = true, 423 402 }: { 424 403 isSuggestionsLoading: boolean 425 - profiles: bsky.profile.AnyProfileView[] 404 + profiles: {actor: bsky.profile.AnyProfileView; recId?: number}[] 426 405 totalProfileCount?: number 427 - recId?: number 428 406 error: Error | null 429 - dismissingDids?: Set<string> 430 407 viewContext: 'profile' | 'profileHeader' | 'feed' 431 408 onDismiss?: (did: string) => void 432 409 isVisible?: boolean ··· 463 440 464 441 const profilesToShow = profiles.slice(0, maxLength) 465 442 profilesToShow.forEach((profile, index) => { 466 - if (!seenProfilesRef.current.has(profile.did)) { 467 - seenProfilesRef.current.add(profile.did) 443 + if (!seenProfilesRef.current.has(profile.actor.did)) { 444 + seenProfilesRef.current.add(profile.actor.did) 468 445 ax.metric('suggestedUser:seen', { 469 446 logContext, 470 - recId, 447 + recId: profile.recId, 471 448 position: index, 472 - suggestedDid: profile.did, 449 + suggestedDid: profile.actor.did, 473 450 category: null, 474 451 }) 475 452 } 476 453 }) 477 - }, [ax, isLoading, error, profiles, maxLength, logContext, recId]) 454 + }, [ax, isLoading, error, profiles, maxLength, logContext]) 478 455 479 456 // For profile header, fire when isVisible becomes true 480 457 useEffect(() => { ··· 540 517 ? null 541 518 : profiles.slice(0, maxLength).map((profile, index) => ( 542 519 <Animated.View 543 - key={profile.did} 544 - layout={LinearTransition.duration(DISMISS_ANIMATION_DURATION)} 520 + key={profile.actor.did} 521 + layout={native( 522 + LinearTransition.delay(DISMISS_ANIMATION_DURATION).easing( 523 + Easing.out(Easing.exp), 524 + ), 525 + )} 526 + exiting={FadeOut.duration(DISMISS_ANIMATION_DURATION)} 527 + // for web, as the cards are static, not in a list 528 + entering={web(FadeIn.delay(DISMISS_ANIMATION_DURATION * 2))} 545 529 style={[ 546 530 a.flex_1, 547 531 gtMobile && ··· 550 534 a.flex_grow, 551 535 {width: `calc(30% - ${a.gap_md.gap / 2}px)`}, 552 536 ]), 553 - { 554 - opacity: dismissingDids?.has(profile.did) ? 0 : 1, 555 - transitionProperty: 'opacity', 556 - transitionDuration: `${DISMISS_ANIMATION_DURATION}ms`, 557 - }, 558 537 ]}> 559 538 <ProfileCard.Link 560 - profile={profile} 539 + profile={profile.actor} 561 540 onPress={() => { 562 541 ax.metric('suggestedUser:press', { 563 542 logContext: isFeedContext 564 543 ? 'InterstitialDiscover' 565 544 : 'InterstitialProfile', 566 - recId, 545 + recId: profile.recId, 567 546 position: index, 568 - suggestedDid: profile.did, 547 + suggestedDid: profile.actor.did, 569 548 category: null, 570 549 }) 571 550 }} ··· 581 560 label={_(msg`Dismiss this suggestion`)} 582 561 onPress={e => { 583 562 e.preventDefault() 584 - onDismiss(profile.did) 563 + onDismiss(profile.actor.did) 585 564 ax.metric('suggestedUser:dismiss', { 586 565 logContext: isFeedContext 587 566 ? 'InterstitialDiscover' 588 567 : 'InterstitialProfile', 589 568 position: index, 590 - suggestedDid: profile.did, 591 - recId, 569 + suggestedDid: profile.actor.did, 570 + recId: profile.recId, 592 571 }) 593 572 }} 594 573 style={[ ··· 621 600 a.mb_auto, 622 601 ]}> 623 602 <ProfileCard.Avatar 624 - profile={profile} 603 + profile={profile.actor} 625 604 moderationOpts={moderationOpts} 626 605 disabledPreview 627 606 size={88} 628 607 /> 629 608 <View style={[a.flex_col, a.align_center, a.max_w_full]}> 630 609 <ProfileCard.Name 631 - profile={profile} 610 + profile={profile.actor} 632 611 moderationOpts={moderationOpts} 633 612 /> 634 613 <ProfileCard.Description 635 - profile={profile} 614 + profile={profile.actor} 636 615 numberOfLines={2} 637 616 style={[ 638 617 t.atoms.text_contrast_medium, ··· 644 623 </View> 645 624 646 625 <ProfileCard.FollowButton 647 - profile={profile} 626 + profile={profile.actor} 648 627 moderationOpts={moderationOpts} 649 628 logContext="FeedInterstitial" 650 629 withIcon={false} ··· 655 634 ? 'InterstitialDiscover' 656 635 : 'InterstitialProfile', 657 636 location: 'Card', 658 - recId, 637 + recId: profile.recId, 659 638 position: index, 660 - suggestedDid: profile.did, 639 + suggestedDid: profile.actor.did, 661 640 category: null, 662 641 }) 663 642 }} ··· 726 705 727 706 <FollowDialogWithoutGuide control={followDialogControl} /> 728 707 729 - {gtMobile ? ( 730 - <View style={[a.p_lg, a.pt_md]}> 731 - <View style={[a.flex_1, a.flex_row, a.flex_wrap, a.gap_md]}> 732 - {content} 708 + <LayoutAnimationConfig skipExiting skipEntering> 709 + {gtMobile ? ( 710 + <View style={[a.p_lg, a.pt_md]}> 711 + <View style={[a.flex_1, a.flex_row, a.flex_wrap, a.gap_md]}> 712 + {content} 713 + </View> 733 714 </View> 734 - </View> 735 - ) : ( 736 - <BlockDrawerGesture> 737 - <ScrollView 738 - horizontal 739 - showsHorizontalScrollIndicator={false} 740 - contentContainerStyle={[a.p_lg, a.pt_md, a.flex_row, a.gap_md]} 741 - snapToInterval={MOBILE_CARD_WIDTH + a.gap_md.gap} 742 - decelerationRate="fast"> 743 - {content} 715 + ) : ( 716 + <BlockDrawerGesture> 717 + <ScrollView 718 + horizontal 719 + showsHorizontalScrollIndicator={false} 720 + contentContainerStyle={[a.p_lg, a.pt_md, a.flex_row, a.gap_md]} 721 + snapToInterval={MOBILE_CARD_WIDTH + a.gap_md.gap} 722 + decelerationRate="fast"> 723 + {content} 744 724 745 - {!isProfileHeaderContext && ( 746 - <SeeMoreSuggestedProfilesCard 747 - onPress={() => { 748 - followDialogControl.open() 749 - ax.metric('suggestedUser:seeMore', { 750 - logContext: 'Explore', 751 - }) 752 - }} 753 - /> 754 - )} 755 - </ScrollView> 756 - </BlockDrawerGesture> 757 - )} 725 + {!isProfileHeaderContext && ( 726 + <SeeMoreSuggestedProfilesCard 727 + onPress={() => { 728 + followDialogControl.open() 729 + ax.metric('suggestedUser:seeMore', { 730 + logContext: 'Explore', 731 + }) 732 + }} 733 + /> 734 + )} 735 + </ScrollView> 736 + </BlockDrawerGesture> 737 + )} 738 + </LayoutAnimationConfig> 758 739 </View> 759 740 ) 760 741 } ··· 795 776 const navigation = useNavigation<NavigationProp>() 796 777 const {gtMobile} = useBreakpoints() 797 778 798 - const feeds = React.useMemo(() => { 779 + const feeds = useMemo(() => { 799 780 const items: AppBskyFeedDefs.GeneratorView[] = [] 800 781 801 782 if (!data) return items
+20 -15
src/components/ProgressGuide/FollowDialog.tsx
··· 9 9 import {msg, Trans} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 11 12 + import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 12 13 import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' 13 14 import {useModerationOpts} from '#/state/preferences/moderation-opts' 14 15 import {useActorSearch} from '#/state/queries/actor-search' ··· 207 208 } 208 209 } 209 210 211 + if ( 212 + hasSearchText && 213 + !isFetchingSearchResults && 214 + !_items.length && 215 + !isSearchResultsError 216 + ) { 217 + _items.push({type: 'empty', key: 'empty', message: _(msg`No results`)}) 218 + } 219 + 210 220 return _items 211 221 }, [ 212 222 _, ··· 219 229 currentAccount?.did, 220 230 hasSearchText, 221 231 resultsKey, 232 + isSearchResultsError, 222 233 ]) 223 234 224 - if ( 225 - searchText && 226 - !isFetchingSearchResults && 227 - !items.length && 228 - !isSearchResultsError 229 - ) { 230 - items.push({type: 'empty', key: 'empty', message: _(msg`No results`)}) 231 - } 232 - 233 235 const renderItems = useCallback( 234 236 ({item, index}: {item: Item; index: number}) => { 235 237 switch (item.type) { ··· 262 264 const selectedInterestRef = useRef(selectedInterest) 263 265 selectedInterestRef.current = selectedInterest 264 266 265 - const onViewableItemsChanged = useRef( 267 + const onViewableItemsChanged = useNonReactiveCallback( 266 268 ({viewableItems}: {viewableItems: ViewToken[]}) => { 267 269 for (const viewableItem of viewableItems) { 268 270 const item = viewableItem.item as Item ··· 274 276 ) 275 277 ax.metric('suggestedUser:seen', { 276 278 logContext: 'ProgressGuide', 277 - recId: undefined, 279 + recId: hasSearchText ? undefined : suggestions?.recId, 278 280 position: position !== -1 ? position : 0, 279 281 suggestedDid: item.profile.did, 280 282 category: selectedInterestRef.current, ··· 283 285 } 284 286 } 285 287 }, 286 - ).current 287 - const viewabilityConfig = useRef({ 288 - itemVisiblePercentThreshold: 50, 289 - }).current 288 + ) 289 + const viewabilityConfig = useMemo( 290 + () => ({ 291 + itemVisiblePercentThreshold: 50, 292 + }), 293 + [], 294 + ) 290 295 291 296 const onSelectTab = useCallback( 292 297 (interest: string) => {
+18 -4
src/screens/Onboarding/StepSuggestedAccounts/index.tsx
··· 97 97 tab: selectedInterest ?? 'all', 98 98 numAccounts: followableDids.length, 99 99 }) 100 + for (let i = 0; i < followableDids.length; i++) { 101 + const did = followableDids[i] 102 + ax.metric('suggestedUser:follow', { 103 + logContext: 'Onboarding', 104 + location: 'FollowAll', 105 + recId: suggestedUsers?.recId, 106 + position: i, 107 + suggestedDid: did, 108 + category: selectedInterest, 109 + }) 110 + } 100 111 }, 101 112 mutationFn: async () => { 102 113 for (const did of followableDids) { ··· 135 146 seenProfilesRef.current.add(did) 136 147 ax.metric('suggestedUser:seen', { 137 148 logContext: 'Onboarding', 138 - recId: undefined, 149 + recId: suggestedUsers?.recId, 139 150 position, 140 151 suggestedDid: did, 141 152 category: selectedInterest, 142 153 }) 143 154 } 144 155 }, 145 - [ax, selectedInterest], 156 + [ax, selectedInterest, suggestedUsers?.recId], 146 157 ) 147 158 148 159 return ( ··· 220 231 position={index} 221 232 category={selectedInterest} 222 233 onSeen={onProfileSeen} 234 + recId={suggestedUsers.recId} 223 235 /> 224 236 ))} 225 237 </View> ··· 234 246 color="secondary" 235 247 size="large" 236 248 label={_(msg`Retry`)} 237 - onPress={() => refetch()}> 249 + onPress={() => void refetch()}> 238 250 <ButtonText> 239 251 <Trans>Retry</Trans> 240 252 </ButtonText> ··· 329 341 position, 330 342 category, 331 343 onSeen, 344 + recId, 332 345 }: { 333 346 profile: bsky.profile.AnyProfileView 334 347 moderationOpts: ModerationOpts 335 348 position: number 336 349 category: string | null 337 350 onSeen: (did: string, position: number) => void 351 + recId?: number | string 338 352 }) { 339 353 const t = useTheme() 340 354 const ax = useAnalytics() ··· 401 415 ax.metric('suggestedUser:follow', { 402 416 logContext: 'Onboarding', 403 417 location: 'Card', 404 - recId: undefined, 418 + recId, 405 419 position, 406 420 suggestedDid: profile.did, 407 421 category,
+2 -2
src/screens/Profile/Header/ProfileHeaderStandard.tsx
··· 43 43 import {ProfileHeaderHandle} from './Handle' 44 44 import {ProfileHeaderMetrics} from './Metrics' 45 45 import {ProfileHeaderShell} from './Shell' 46 - import {AnimatedProfileHeaderSuggestedFollows} from './SuggestedFollows' 46 + import {ProfileHeaderSuggestedFollows} from './SuggestedFollows' 47 47 48 48 interface Props { 49 49 profile: AppBskyActorDefs.ProfileViewDetailed ··· 193 193 /> 194 194 </ProfileHeaderShell> 195 195 196 - <AnimatedProfileHeaderSuggestedFollows 196 + <ProfileHeaderSuggestedFollows 197 197 isExpanded={showSuggestedFollows} 198 198 actorDid={profile.did} 199 199 />
+53 -156
src/screens/Profile/Header/SuggestedFollows.tsx
··· 1 - import React from 'react' 2 - import {type AppBskyActorDefs} from '@atproto/api' 1 + import {useCallback, useEffect, useMemo, useState} from 'react' 3 2 4 3 import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation' 5 4 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 10 9 import {useBreakpoints} from '#/alf' 11 10 import {ProfileGrid} from '#/components/FeedInterstitials' 12 11 import {IS_ANDROID} from '#/env' 12 + import type * as bsky from '#/types/bsky' 13 13 14 - const DISMISS_ANIMATION_DURATION = 200 14 + export function ProfileHeaderSuggestedFollows({ 15 + isExpanded, 16 + actorDid, 17 + }: { 18 + isExpanded: boolean 19 + actorDid: string 20 + }) { 21 + const {allProfiles, filteredProfiles, onDismiss, isLoading, error} = 22 + useProfileHeaderSuggestions(actorDid) 15 23 16 - export function ProfileHeaderSuggestedFollows({actorDid}: {actorDid: string}) { 17 - const {gtMobile} = useBreakpoints() 18 - const moderationOpts = useModerationOpts() 19 - const maxLength = gtMobile ? 4 : 12 20 - const {isLoading, data, error} = useSuggestedFollowsByActorQuery({ 21 - did: actorDid, 22 - }) 23 - const { 24 - data: moreSuggestions, 25 - fetchNextPage, 26 - hasNextPage, 27 - isFetchingNextPage, 28 - } = useSuggestedFollowsQuery({limit: 25}) 24 + if (!allProfiles.length && !isLoading) return null 29 25 30 - const [dismissedDids, setDismissedDids] = React.useState<Set<string>>( 31 - new Set(), 32 - ) 33 - const [dismissingDids, setDismissingDids] = React.useState<Set<string>>( 34 - new Set(), 35 - ) 36 - 37 - const onDismiss = React.useCallback((did: string) => { 38 - // Start the fade animation 39 - setDismissingDids(prev => new Set(prev).add(did)) 40 - // After animation completes, actually remove from list 41 - setTimeout(() => { 42 - setDismissedDids(prev => new Set(prev).add(did)) 43 - setDismissingDids(prev => { 44 - const next = new Set(prev) 45 - next.delete(did) 46 - return next 47 - }) 48 - }, DISMISS_ANIMATION_DURATION) 49 - }, []) 50 - 51 - // Combine profiles from the actor-specific query with fallback suggestions 52 - const allProfiles = React.useMemo(() => { 53 - const actorProfiles = data?.suggestions ?? [] 54 - const fallbackProfiles = 55 - moreSuggestions?.pages.flatMap(page => page.actors) ?? [] 56 - 57 - // Dedupe by did, preferring actor-specific profiles 58 - const seen = new Set<string>() 59 - const combined: AppBskyActorDefs.ProfileView[] = [] 60 - 61 - for (const profile of actorProfiles) { 62 - if (!seen.has(profile.did)) { 63 - seen.add(profile.did) 64 - combined.push(profile) 65 - } 66 - } 67 - 68 - for (const profile of fallbackProfiles) { 69 - if (!seen.has(profile.did) && profile.did !== actorDid) { 70 - seen.add(profile.did) 71 - combined.push(profile) 72 - } 73 - } 74 - 75 - return combined 76 - }, [data?.suggestions, moreSuggestions?.pages, actorDid]) 77 - 78 - const filteredProfiles = React.useMemo(() => { 79 - return allProfiles.filter(p => !dismissedDids.has(p.did)) 80 - }, [allProfiles, dismissedDids]) 81 - 82 - // Fetch more when running low 83 - React.useEffect(() => { 84 - if ( 85 - moderationOpts && 86 - filteredProfiles.length < maxLength && 87 - hasNextPage && 88 - !isFetchingNextPage 89 - ) { 90 - fetchNextPage() 91 - } 92 - }, [ 93 - filteredProfiles.length, 94 - maxLength, 95 - hasNextPage, 96 - isFetchingNextPage, 97 - fetchNextPage, 98 - moderationOpts, 99 - ]) 26 + /* NOTE (caidanw): 27 + * Android does not work well with this feature yet. 28 + * This issue stems from Android not allowing dragging on clickable elements in the profile header. 29 + * Blocking the ability to scroll on Android is too much of a trade-off for now. 30 + **/ 31 + if (IS_ANDROID) return null 100 32 101 33 return ( 102 - <ProfileGrid 103 - isSuggestionsLoading={isLoading} 104 - profiles={filteredProfiles} 105 - totalProfileCount={allProfiles.length} 106 - recId={data?.recId} 107 - error={error} 108 - viewContext="profileHeader" 109 - onDismiss={onDismiss} 110 - dismissingDids={dismissingDids} 111 - /> 34 + <AccordionAnimation isExpanded={isExpanded}> 35 + <ProfileGrid 36 + isSuggestionsLoading={isLoading} 37 + profiles={filteredProfiles} 38 + totalProfileCount={allProfiles.length} 39 + error={error} 40 + viewContext="profileHeader" 41 + onDismiss={onDismiss} 42 + isVisible={isExpanded} 43 + /> 44 + </AccordionAnimation> 112 45 ) 113 46 } 114 47 115 - export function AnimatedProfileHeaderSuggestedFollows({ 116 - isExpanded, 117 - actorDid, 118 - }: { 119 - isExpanded: boolean 120 - actorDid: string 121 - }) { 48 + function useProfileHeaderSuggestions(actorDid: string) { 122 49 const {gtMobile} = useBreakpoints() 123 50 const moderationOpts = useModerationOpts() 124 51 const maxLength = gtMobile ? 4 : 12 ··· 132 59 isFetchingNextPage, 133 60 } = useSuggestedFollowsQuery({limit: 25}) 134 61 135 - const [dismissedDids, setDismissedDids] = React.useState<Set<string>>( 136 - new Set(), 137 - ) 138 - const [dismissingDids, setDismissingDids] = React.useState<Set<string>>( 139 - new Set(), 140 - ) 62 + const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set()) 141 63 142 - const onDismiss = React.useCallback((did: string) => { 143 - // Start the fade animation 144 - setDismissingDids(prev => new Set(prev).add(did)) 145 - // After animation completes, actually remove from list 146 - setTimeout(() => { 147 - setDismissedDids(prev => new Set(prev).add(did)) 148 - setDismissingDids(prev => { 149 - const next = new Set(prev) 150 - next.delete(did) 151 - return next 152 - }) 153 - }, DISMISS_ANIMATION_DURATION) 64 + const onDismiss = useCallback((did: string) => { 65 + setDismissedDids(prev => new Set(prev).add(did)) 154 66 }, []) 155 67 156 68 // Combine profiles from the actor-specific query with fallback suggestions 157 - const allProfiles = React.useMemo(() => { 69 + const allProfiles = useMemo(() => { 158 70 const actorProfiles = data?.suggestions ?? [] 159 71 const fallbackProfiles = 160 - moreSuggestions?.pages.flatMap(page => page.actors) ?? [] 72 + moreSuggestions?.pages.flatMap(page => 73 + page.actors.map(actor => ({actor, recId: page.recId})), 74 + ) ?? [] 161 75 162 76 // Dedupe by did, preferring actor-specific profiles 163 77 const seen = new Set<string>() 164 - const combined: AppBskyActorDefs.ProfileView[] = [] 78 + const combined: {actor: bsky.profile.AnyProfileView; recId?: number}[] = [] 165 79 166 80 for (const profile of actorProfiles) { 167 81 if (!seen.has(profile.did)) { 168 82 seen.add(profile.did) 169 - combined.push(profile) 83 + combined.push({actor: profile, recId: data?.recId}) 170 84 } 171 85 } 172 86 173 87 for (const profile of fallbackProfiles) { 174 - if (!seen.has(profile.did) && profile.did !== actorDid) { 175 - seen.add(profile.did) 88 + if (!seen.has(profile.actor.did) && profile.actor.did !== actorDid) { 89 + seen.add(profile.actor.did) 176 90 combined.push(profile) 177 91 } 178 92 } 179 93 180 94 return combined 181 - }, [data?.suggestions, moreSuggestions?.pages, actorDid]) 95 + }, [data?.suggestions, moreSuggestions?.pages, actorDid, data?.recId]) 182 96 183 - const filteredProfiles = React.useMemo(() => { 184 - return allProfiles.filter(p => !dismissedDids.has(p.did)) 97 + const filteredProfiles = useMemo(() => { 98 + return allProfiles.filter(p => !dismissedDids.has(p.actor.did)) 185 99 }, [allProfiles, dismissedDids]) 186 100 187 101 // Fetch more when running low 188 - React.useEffect(() => { 102 + useEffect(() => { 189 103 if ( 190 104 moderationOpts && 191 105 filteredProfiles.length < maxLength && 192 106 hasNextPage && 193 107 !isFetchingNextPage 194 108 ) { 195 - fetchNextPage() 109 + void fetchNextPage() 196 110 } 197 111 }, [ 198 112 filteredProfiles.length, ··· 203 117 moderationOpts, 204 118 ]) 205 119 206 - if (!allProfiles.length && !isLoading) return null 207 - 208 - /* NOTE (caidanw): 209 - * Android does not work well with this feature yet. 210 - * This issue stems from Android not allowing dragging on clickable elements in the profile header. 211 - * Blocking the ability to scroll on Android is too much of a trade-off for now. 212 - **/ 213 - if (IS_ANDROID) return null 214 - 215 - return ( 216 - <AccordionAnimation isExpanded={isExpanded}> 217 - <ProfileGrid 218 - isSuggestionsLoading={isLoading} 219 - profiles={filteredProfiles} 220 - totalProfileCount={allProfiles.length} 221 - recId={data?.recId} 222 - error={error} 223 - viewContext="profileHeader" 224 - onDismiss={onDismiss} 225 - dismissingDids={dismissingDids} 226 - isVisible={isExpanded} 227 - /> 228 - </AccordionAnimation> 229 - ) 120 + return { 121 + allProfiles, 122 + filteredProfiles, 123 + onDismiss, 124 + isLoading, 125 + error, 126 + } 230 127 }
+1
src/screens/Search/util/useSuggestedUsers.ts
··· 45 45 data: searched?.data 46 46 ? { 47 47 actors: searched.data.pages.flatMap(p => p.actors) ?? [], 48 + recId: undefined, 48 49 } 49 50 : undefined, 50 51 isLoading: searched.isLoading,
+142 -103
yarn.lock
··· 20 20 "@jridgewell/gen-mapping" "^0.3.0" 21 21 "@jridgewell/trace-mapping" "^0.3.9" 22 22 23 - "@atproto-labs/did-resolver@0.2.6": 23 + "@atproto-labs/did-resolver@^0.2.6": 24 24 version "0.2.6" 25 25 resolved "https://registry.yarnpkg.com/@atproto-labs/did-resolver/-/did-resolver-0.2.6.tgz#15f0beab797187a67279389f6503f87a257cd898" 26 26 integrity sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg== ··· 32 32 "@atproto/did" "0.3.0" 33 33 zod "^3.23.8" 34 34 35 - "@atproto-labs/fetch-node@0.2.0": 35 + "@atproto-labs/fetch-node@^0.2.0": 36 36 version "0.2.0" 37 37 resolved "https://registry.yarnpkg.com/@atproto-labs/fetch-node/-/fetch-node-0.2.0.tgz#438989f3165f52e21e7636fb87ea9c7317ae7f2a" 38 38 integrity sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q== ··· 42 42 ipaddr.js "^2.1.0" 43 43 undici "^6.14.1" 44 44 45 - "@atproto-labs/fetch@0.2.3": 45 + "@atproto-labs/fetch@0.2.3", "@atproto-labs/fetch@^0.2.3": 46 46 version "0.2.3" 47 47 resolved "https://registry.yarnpkg.com/@atproto-labs/fetch/-/fetch-0.2.3.tgz#d47afec078f630c50e291c56264cc0ff13d0c6cc" 48 48 integrity sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw== 49 49 dependencies: 50 50 "@atproto-labs/pipe" "0.1.1" 51 51 52 - "@atproto-labs/pipe@0.1.1": 52 + "@atproto-labs/pipe@0.1.1", "@atproto-labs/pipe@^0.1.1": 53 53 version "0.1.1" 54 54 resolved "https://registry.yarnpkg.com/@atproto-labs/pipe/-/pipe-0.1.1.tgz#1c4232d16bf95f251e993cb6ee440f9aa4e87ce6" 55 55 integrity sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg== 56 56 57 - "@atproto-labs/simple-store-memory@0.1.4": 57 + "@atproto-labs/simple-store-memory@0.1.4", "@atproto-labs/simple-store-memory@^0.1.4": 58 58 version "0.1.4" 59 59 resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz#e38c7b27e0f77c0bdba1329deb89593fbec27316" 60 60 integrity sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw== ··· 62 62 "@atproto-labs/simple-store" "0.3.0" 63 63 lru-cache "^10.2.0" 64 64 65 - "@atproto-labs/simple-store-redis@0.0.1": 65 + "@atproto-labs/simple-store-redis@^0.0.1": 66 66 version "0.0.1" 67 67 resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store-redis/-/simple-store-redis-0.0.1.tgz#1dfc92cbec9b648c4349255aebb7bce9d7dc5eeb" 68 68 integrity sha512-hGkfDNVtTqwcRx27k6u25pgwNIHq3xDCRuojkfHf6c1B9R5rKphdZJ91Mn3lCvsyDB/lUqqLuzKuXQWFml/u5g== 69 69 dependencies: 70 70 "@atproto-labs/simple-store" "0.3.0" 71 71 72 - "@atproto-labs/simple-store@0.3.0": 72 + "@atproto-labs/simple-store@0.3.0", "@atproto-labs/simple-store@^0.3.0": 73 73 version "0.3.0" 74 74 resolved "https://registry.yarnpkg.com/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz#65c0a5c949fe6c8dc3bdaf13ab40848f20073593" 75 75 integrity sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ== 76 76 77 - "@atproto-labs/xrpc-utils@0.0.24": 77 + "@atproto-labs/xrpc-utils@^0.0.24": 78 78 version "0.0.24" 79 79 resolved "https://registry.yarnpkg.com/@atproto-labs/xrpc-utils/-/xrpc-utils-0.0.24.tgz#0546778b9b83854d8a160dc4dea145e5a23ae8fc" 80 80 integrity sha512-wWXd2Ht47UsL/UbDCr3twMFSZrh0xSI56u4O3kz0DTU4G+530mCG71mMVE6eeYcR+j6FEjp7o2Ld6c7wFklYGw== ··· 96 96 tlds "^1.234.0" 97 97 zod "^3.23.8" 98 98 99 - "@atproto/api@^0.18.20": 99 + "@atproto/api@^0.18.19", "@atproto/api@^0.18.20": 100 100 version "0.18.20" 101 101 resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.18.20.tgz#3fdbb7b7fae90bd59101970c2b56cc31e8cf417d" 102 102 integrity sha512-BZYZkh2VJIFCXEnc/vzKwAwWjAQQTgbNJ8FBxpBK+z+KYh99O0uPCsRYKoCQsRrnkgrhzdU9+g2G+7zanTIGbw== ··· 128 128 multiformats "^9.9.0" 129 129 uint8arrays "3.0.0" 130 130 131 - "@atproto/bsky@^0.0.212": 132 - version "0.0.212" 133 - resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.212.tgz#97416f5b788935a9b6c5696961aa2080f15c5c7b" 134 - integrity sha512-xbdblgzpdKiUNMKL6qVURAUwATw9nrR7Rj8ygQDj3QEmmgMyEA5PEutCtoh3jlBr4bF2asZlyjySrtmAEixKNQ== 131 + "@atproto/bsky@^0.0.214": 132 + version "0.0.214" 133 + resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.214.tgz#54183957e323fe0800606acdcc3137acadf6672e" 134 + integrity sha512-Fn+o3WcaX57EmcLoUdcViY4wU2UzfbyNlS/qFQu+1clADtL3pthylwTJgwUU73cZE+7MILZPfTzhQTc15snR9A== 135 135 dependencies: 136 - "@atproto-labs/fetch-node" "0.2.0" 137 - "@atproto-labs/xrpc-utils" "0.0.24" 138 - "@atproto/api" "^0.18.18" 139 - "@atproto/common" "^0.5.9" 136 + "@atproto-labs/fetch-node" "^0.2.0" 137 + "@atproto-labs/xrpc-utils" "^0.0.24" 138 + "@atproto/api" "^0.18.20" 139 + "@atproto/common" "^0.5.10" 140 140 "@atproto/crypto" "^0.4.5" 141 141 "@atproto/did" "^0.3.0" 142 142 "@atproto/identity" "^0.4.10" ··· 144 144 "@atproto/repo" "^0.8.12" 145 145 "@atproto/sync" "^0.1.39" 146 146 "@atproto/syntax" "^0.4.3" 147 - "@atproto/xrpc-server" "^0.10.10" 147 + "@atproto/xrpc-server" "^0.10.11" 148 148 "@bufbuild/protobuf" "^1.5.0" 149 149 "@connectrpc/connect" "^1.1.4" 150 150 "@connectrpc/connect-express" "^1.1.4" ··· 246 246 multiformats "^9.9.0" 247 247 pino "^8.21.0" 248 248 249 + "@atproto/common@^0.5.10": 250 + version "0.5.10" 251 + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.5.10.tgz#f0d6b7e012b3dd3991d7e2975e26913d4f41e90f" 252 + integrity sha512-A1+4W3JmjZIgmtJFLJBAaoVruZhRL0ANtyjZ91aJR4rjHcZuaQ+v4IFR1UcE6yyTATacLdBk6ADy8OtxXzq14g== 253 + dependencies: 254 + "@atproto/common-web" "^0.4.15" 255 + "@atproto/lex-cbor" "^0.0.10" 256 + "@atproto/lex-data" "^0.0.10" 257 + iso-datestring-validator "^2.2.2" 258 + multiformats "^9.9.0" 259 + pino "^8.21.0" 260 + 249 261 "@atproto/crypto@0.1.0": 250 262 version "0.1.0" 251 263 resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.1.0.tgz#bc73a479f9dbe06fa025301c182d7f7ab01bc568" ··· 257 269 one-webcrypto "^1.0.3" 258 270 uint8arrays "3.0.0" 259 271 260 - "@atproto/crypto@0.4.5", "@atproto/crypto@^0.4.4", "@atproto/crypto@^0.4.5": 272 + "@atproto/crypto@^0.4.4", "@atproto/crypto@^0.4.5": 261 273 version "0.4.5" 262 274 resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.5.tgz#fc6ad4fdfe8338147196c8050791cc6a22657eb6" 263 275 integrity sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw== ··· 266 278 "@noble/hashes" "^1.6.1" 267 279 uint8arrays "3.0.0" 268 280 269 - "@atproto/dev-env@^0.3.206": 270 - version "0.3.206" 271 - resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.206.tgz#2a32642e2d1e90dd979ebfa40c892bf700ae16b8" 272 - integrity sha512-H3GfypJ8fJbGPC4DjHNXM0lFi1HzF//YN2S93QTWR+nVW3994893RsA28mdwY0p5jppF2lhQ4EbG3fPBko6F9g== 281 + "@atproto/dev-env@^0.3.208": 282 + version "0.3.208" 283 + resolved "https://registry.yarnpkg.com/@atproto/dev-env/-/dev-env-0.3.208.tgz#01d07d4b1ef2404adb07bdddf324393989231a52" 284 + integrity sha512-ttx+MIT21iFICW7sZNaFJoCPlxZAqgFy1ztOEn1ebW2MkJzsNHSq2oQC8MHxmpbYEOMPekTXC2iMHW6PYVQJxQ== 273 285 dependencies: 274 - "@atproto/api" "^0.18.18" 275 - "@atproto/bsky" "^0.0.212" 286 + "@atproto/api" "^0.18.20" 287 + "@atproto/bsky" "^0.0.214" 276 288 "@atproto/bsync" "^0.0.23" 277 - "@atproto/common-web" "^0.4.14" 289 + "@atproto/common-web" "^0.4.15" 278 290 "@atproto/crypto" "^0.4.5" 279 291 "@atproto/identity" "^0.4.10" 280 292 "@atproto/lexicon" "^0.6.1" 281 293 "@atproto/ozone" "^0.1.162" 282 - "@atproto/pds" "^0.4.206" 294 + "@atproto/pds" "^0.4.207" 283 295 "@atproto/sync" "^0.1.39" 284 296 "@atproto/syntax" "^0.4.3" 285 - "@atproto/xrpc-server" "^0.10.10" 297 + "@atproto/xrpc-server" "^0.10.11" 286 298 "@did-plc/lib" "^0.0.1" 287 299 "@did-plc/server" "^0.0.1" 288 300 dotenv "^16.0.3" ··· 307 319 "@atproto/common-web" "^0.4.4" 308 320 "@atproto/crypto" "^0.4.4" 309 321 310 - "@atproto/jwk-jose@0.1.11": 322 + "@atproto/jwk-jose@^0.1.11": 311 323 version "0.1.11" 312 324 resolved "https://registry.yarnpkg.com/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz#ef64bce940a66e267fc3cf0db8df4dbd062bb28a" 313 325 integrity sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q== ··· 315 327 "@atproto/jwk" "0.6.0" 316 328 jose "^5.2.0" 317 329 318 - "@atproto/jwk@0.6.0": 330 + "@atproto/jwk@0.6.0", "@atproto/jwk@^0.6.0": 319 331 version "0.6.0" 320 332 resolved "https://registry.yarnpkg.com/@atproto/jwk/-/jwk-0.6.0.tgz#e813f77d9c89c025d4074340777fafaa2fba08a5" 321 333 integrity sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw== ··· 323 335 multiformats "^9.9.0" 324 336 zod "^3.23.8" 325 337 326 - "@atproto/lex-cbor@0.0.9", "@atproto/lex-cbor@^0.0.9": 338 + "@atproto/lex-cbor@0.0.9": 327 339 version "0.0.9" 328 340 resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.9.tgz#d4b227ae37c1b44b76a2eca826b23c4b74bd4062" 329 341 integrity sha512-szkS569j1eZsIxZKh2VZHVq7pSpewy1wHh8c6nVYekHfYcJhFkevQq/DjTeatZ7YZKNReGYthQulgaZq2ytfWQ== ··· 331 343 "@atproto/lex-data" "0.0.9" 332 344 tslib "^2.8.1" 333 345 334 - "@atproto/lex-client@0.0.10": 346 + "@atproto/lex-cbor@^0.0.10": 335 347 version "0.0.10" 336 - resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.10.tgz#0baa7fee22d2efec17e8848351957a6e10917323" 337 - integrity sha512-n3g9KoY5hw7W29mcR4TrjN5qOi6JiWty7r1heqLLfYiq5TxaRx9/QBa2hbN4h1p4xxICPZoDlNtuGq8YV4U8mg== 348 + resolved "https://registry.yarnpkg.com/@atproto/lex-cbor/-/lex-cbor-0.0.10.tgz#712abc4fedef4854c341e32d0fd1608e45616984" 349 + integrity sha512-5RtV90iIhRNCXXvvETd3KlraV8XGAAAgOmiszUb+l8GySDU/sGk7AlVvArFfXnj/S/GXJq8DP6IaUxCw/sPASA== 350 + dependencies: 351 + "@atproto/lex-data" "^0.0.10" 352 + tslib "^2.8.1" 353 + 354 + "@atproto/lex-client@^0.0.11": 355 + version "0.0.11" 356 + resolved "https://registry.yarnpkg.com/@atproto/lex-client/-/lex-client-0.0.11.tgz#b8e8d0da81ed27f1093d0a1e1c7fdb994394e323" 357 + integrity sha512-2DCidAlhATtZc1Z11PUd+C98BiW/Od4pWtDlQSAxkjHSC/56ZwuSkZQVx27ISk1HldfKVc9qUvQWA9nhmrxYIw== 338 358 dependencies: 339 - "@atproto/lex-data" "0.0.9" 340 - "@atproto/lex-json" "0.0.9" 341 - "@atproto/lex-schema" "0.0.10" 359 + "@atproto/lex-data" "^0.0.10" 360 + "@atproto/lex-json" "^0.0.10" 361 + "@atproto/lex-schema" "^0.0.11" 342 362 tslib "^2.8.1" 343 363 344 - "@atproto/lex-data@0.0.9", "@atproto/lex-data@^0.0.9": 364 + "@atproto/lex-data@0.0.9": 345 365 version "0.0.9" 346 366 resolved "https://registry.yarnpkg.com/@atproto/lex-data/-/lex-data-0.0.9.tgz#19bed9399571d8d653a0bfba99b641cb98300e84" 347 367 integrity sha512-1slwe4sG0cyWtsq16+rBoWIxNDqGPkkvN+PV6JuzA7dgUK9bjUmXBGQU4eZlUPSS43X1Nhmr/9VjgKmEzU9vDw== ··· 361 381 uint8arrays "3.0.0" 362 382 unicode-segmenter "^0.14.0" 363 383 364 - "@atproto/lex-document@0.0.11": 365 - version "0.0.11" 366 - resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.11.tgz#8cfdd6ab5b5befac4d1409c76e2d5a310845c1dc" 367 - integrity sha512-ePtFOU7yYAp1IL1mPDrAyo+ajN9V7W8z6BY4xXEM/m9U3vCVNC+SIkgkfwumqSUqOtBy4gpz52ppK+R/9S8UWg== 384 + "@atproto/lex-document@^0.0.12": 385 + version "0.0.12" 386 + resolved "https://registry.yarnpkg.com/@atproto/lex-document/-/lex-document-0.0.12.tgz#18e0ca344101bb742a2930f663473e0213721f52" 387 + integrity sha512-+no+ZXyCNdOdjkOj6a4n4WHAQzZz3M6VJTwx7IQQC2+to41/4fFr6k8U1y3Jtq2lYcbHqapkJj3RGIxzFcrtwA== 368 388 dependencies: 369 - "@atproto/lex-schema" "0.0.10" 389 + "@atproto/lex-schema" "^0.0.11" 370 390 core-js "^3" 371 391 tslib "^2.8.1" 372 392 ··· 386 406 "@atproto/lex-data" "^0.0.10" 387 407 tslib "^2.8.1" 388 408 389 - "@atproto/lex-resolver@0.0.12": 390 - version "0.0.12" 391 - resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.12.tgz#fb6cd78c78c0acfc9a92d9e42abe7ff18b4c3a41" 392 - integrity sha512-Q3/olki+Wl3AVk/QlDfQJ6ttZuIe6RcT6UJ0eAb04f1r7vXihMyZSb4kPoDF86FoIWsxU8P8+FFHYszm7S+jAw== 409 + "@atproto/lex-resolver@^0.0.13": 410 + version "0.0.13" 411 + resolved "https://registry.yarnpkg.com/@atproto/lex-resolver/-/lex-resolver-0.0.13.tgz#6407b813001d3a3f9e4048197755deccb3cc6f62" 412 + integrity sha512-CcqCE6W3ZVMVzAihatpVbXLxO15mtvddzRzovIJ9QxBTpUmtkdmIk9/gle0oAsRToTJYQy2a6dwAmnAosxP/XQ== 393 413 dependencies: 394 - "@atproto-labs/did-resolver" "0.2.6" 395 - "@atproto/crypto" "0.4.5" 396 - "@atproto/lex-client" "0.0.10" 397 - "@atproto/lex-data" "0.0.9" 398 - "@atproto/lex-document" "0.0.11" 399 - "@atproto/lex-schema" "0.0.10" 400 - "@atproto/repo" "0.8.12" 401 - "@atproto/syntax" "0.4.3" 414 + "@atproto-labs/did-resolver" "^0.2.6" 415 + "@atproto/crypto" "^0.4.5" 416 + "@atproto/lex-client" "^0.0.11" 417 + "@atproto/lex-data" "^0.0.10" 418 + "@atproto/lex-document" "^0.0.12" 419 + "@atproto/lex-schema" "^0.0.11" 420 + "@atproto/repo" "^0.8.12" 421 + "@atproto/syntax" "^0.4.3" 402 422 tslib "^2.8.1" 403 423 404 - "@atproto/lex-schema@0.0.10": 405 - version "0.0.10" 406 - resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.10.tgz#ec2cf46617317afa31c5693f886f70ba4483163b" 407 - integrity sha512-970BZVHtsLn03k2wkpYzdY2o/oZycqUReG1UblOkWYkbhQd04WqliiGrpUie/ls25oJs37ymI+fCDPcYg9tuQg== 424 + "@atproto/lex-schema@^0.0.11": 425 + version "0.0.11" 426 + resolved "https://registry.yarnpkg.com/@atproto/lex-schema/-/lex-schema-0.0.11.tgz#7ff8a1e94971cb750cacaa40e30f546a64db0acd" 427 + integrity sha512-1vLUPQIMeawKP6ehSx2RiqaJDkiseFTXyUk3C4PaoFCktaH8FgVgPVKgUSSy02m1pxVMKnsOBV5psKeN53HG+Q== 408 428 dependencies: 409 - "@atproto/lex-data" "0.0.9" 410 - "@atproto/syntax" "0.4.3" 429 + "@atproto/lex-data" "^0.0.10" 430 + "@atproto/syntax" "^0.4.3" 411 431 tslib "^2.8.1" 412 432 413 433 "@atproto/lexicon@^0.6.0", "@atproto/lexicon@^0.6.1": ··· 421 441 multiformats "^9.9.0" 422 442 zod "^3.23.8" 423 443 424 - "@atproto/oauth-provider-api@0.3.7": 444 + "@atproto/oauth-provider-api@0.3.7", "@atproto/oauth-provider-api@^0.3.7": 425 445 version "0.3.7" 426 446 resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.3.7.tgz#7b911256536a72dbba6f38081a200836a3ab50b8" 427 447 integrity sha512-7yU9vuQFt/hy4NzlDtn+LuhIGvVKkhgWAkCmopnseMPBw6oGPT90uOsTxMkVGtHuKVvBSz7hOXoELXpnZq3gDQ== ··· 429 449 "@atproto/jwk" "0.6.0" 430 450 "@atproto/oauth-types" "0.6.2" 431 451 432 - "@atproto/oauth-provider-frontend@0.2.8": 452 + "@atproto/oauth-provider-frontend@^0.2.8": 433 453 version "0.2.8" 434 454 resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-frontend/-/oauth-provider-frontend-0.2.8.tgz#97f6dc33257b0f846229839dd74f606528f49846" 435 455 integrity sha512-wHypQrsbwE6LUlyDADfiJfOH5pDAHWm4l/v1dwkQhRndOai7L+knsbdLAOZVXuz6bRs7oV2Frb3mSS03OS4Gdw== 436 456 optionalDependencies: 437 457 "@atproto/oauth-provider-api" "0.3.7" 438 458 439 - "@atproto/oauth-provider-ui@0.4.2": 459 + "@atproto/oauth-provider-ui@^0.4.2": 440 460 version "0.4.2" 441 461 resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-ui/-/oauth-provider-ui-0.4.2.tgz#4edd41c71fd1bf5aaa1df219fb8a20c0b8d90738" 442 462 integrity sha512-j3Afu23JYy68GY8L4t/cTEZz1PnrvxLrjiG/nPhXPURZbCnqZ1puIU+HRVCBJfqKyCwaWthXgUjwaL+SFlPGKA== 443 463 optionalDependencies: 444 464 "@atproto/oauth-provider-api" "0.3.7" 445 465 446 - "@atproto/oauth-provider@^0.15.6": 447 - version "0.15.6" 448 - resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.6.tgz#ae71dfbb0073575011b81be47ee1adf9c1dc5d30" 449 - integrity sha512-612+MrwjqKQ56wuwTAqFvYs65TX2kPLrmlJwWjUlXm5oh0jmHZG9BBZ0I3WciAeGfIRMp9mWYMxokPxVJdkzgg== 466 + "@atproto/oauth-provider@^0.15.7": 467 + version "0.15.7" 468 + resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.15.7.tgz#fc5bd420984dac60f726949d2473bbdb3ddff991" 469 + integrity sha512-rsPez44TJGVC5vFdwSTfSOLIIG5O1JF8NXLe1BjjsUbzRuflN6TyM6HyfBJVsnOVF2bOq6W6xPcnBFUJHCSyUQ== 450 470 dependencies: 451 - "@atproto-labs/fetch" "0.2.3" 452 - "@atproto-labs/fetch-node" "0.2.0" 453 - "@atproto-labs/pipe" "0.1.1" 454 - "@atproto-labs/simple-store" "0.3.0" 455 - "@atproto-labs/simple-store-memory" "0.1.4" 456 - "@atproto/common" "^0.5.9" 457 - "@atproto/did" "0.3.0" 458 - "@atproto/jwk" "0.6.0" 459 - "@atproto/jwk-jose" "0.1.11" 460 - "@atproto/lex-document" "0.0.11" 461 - "@atproto/lex-resolver" "0.0.12" 462 - "@atproto/oauth-provider-api" "0.3.7" 463 - "@atproto/oauth-provider-frontend" "0.2.8" 464 - "@atproto/oauth-provider-ui" "0.4.2" 465 - "@atproto/oauth-scopes" "0.3.1" 466 - "@atproto/oauth-types" "0.6.2" 467 - "@atproto/syntax" "0.4.3" 471 + "@atproto-labs/fetch" "^0.2.3" 472 + "@atproto-labs/fetch-node" "^0.2.0" 473 + "@atproto-labs/pipe" "^0.1.1" 474 + "@atproto-labs/simple-store" "^0.3.0" 475 + "@atproto-labs/simple-store-memory" "^0.1.4" 476 + "@atproto/common" "^0.5.10" 477 + "@atproto/did" "^0.3.0" 478 + "@atproto/jwk" "^0.6.0" 479 + "@atproto/jwk-jose" "^0.1.11" 480 + "@atproto/lex-document" "^0.0.12" 481 + "@atproto/lex-resolver" "^0.0.13" 482 + "@atproto/oauth-provider-api" "^0.3.7" 483 + "@atproto/oauth-provider-frontend" "^0.2.8" 484 + "@atproto/oauth-provider-ui" "^0.4.2" 485 + "@atproto/oauth-scopes" "^0.3.1" 486 + "@atproto/oauth-types" "^0.6.2" 487 + "@atproto/syntax" "^0.4.3" 468 488 "@hapi/accept" "^6.0.3" 469 489 "@hapi/address" "^5.1.1" 470 490 "@hapi/bourne" "^3.0.0" ··· 477 497 jose "^5.2.0" 478 498 zod "^3.23.8" 479 499 480 - "@atproto/oauth-scopes@0.3.1", "@atproto/oauth-scopes@^0.3.1": 500 + "@atproto/oauth-scopes@^0.3.1": 481 501 version "0.3.1" 482 502 resolved "https://registry.yarnpkg.com/@atproto/oauth-scopes/-/oauth-scopes-0.3.1.tgz#cd0b3cdc31e14f6f1829f664fa1b3179f6ad9b51" 483 503 integrity sha512-eUD9C78uYH+0ZUmiV/X6pRj4BKlH9I1xxJYW1Gb/qJiATuTZkJVm02urJb/BkWX4Qpxy4rOr8EProNg1wByIEA== ··· 485 505 "@atproto/did" "^0.3.0" 486 506 "@atproto/syntax" "^0.4.3" 487 507 488 - "@atproto/oauth-types@0.6.2": 508 + "@atproto/oauth-types@0.6.2", "@atproto/oauth-types@^0.6.2": 489 509 version "0.6.2" 490 510 resolved "https://registry.yarnpkg.com/@atproto/oauth-types/-/oauth-types-0.6.2.tgz#d829fae63421dcea7ac703e84460175d2f8d9299" 491 511 integrity sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg== ··· 525 545 undici "^6.14.1" 526 546 ws "^8.12.0" 527 547 528 - "@atproto/pds@^0.4.206": 529 - version "0.4.206" 530 - resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.206.tgz#2a0ae5b190568d0b23ad00a8c3e1d1217b212611" 531 - integrity sha512-n2L0B3ZVe+7Bokuxvns8i/fBHuBepcvW2XSS5CYdMqWSaY6h5hezJjEM5O3nOx0CgCqXMmIMdnVtcIu0Dk5FPA== 548 + "@atproto/pds@^0.4.207": 549 + version "0.4.207" 550 + resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.4.207.tgz#20ff3afcf1541b96f016a6843d69d3731b562f19" 551 + integrity sha512-LtzgqeD4aB3O1efzDbhdEopdujB4GG/klLT4N/3v35+0Jpylrkht5Ux2yJGKAqJkzFCfpBdRhNMWpJqS2ssu8A== 532 552 dependencies: 533 - "@atproto-labs/fetch-node" "0.2.0" 534 - "@atproto-labs/simple-store" "0.3.0" 535 - "@atproto-labs/simple-store-memory" "0.1.4" 536 - "@atproto-labs/simple-store-redis" "0.0.1" 537 - "@atproto-labs/xrpc-utils" "0.0.24" 538 - "@atproto/api" "^0.18.18" 553 + "@atproto-labs/fetch-node" "^0.2.0" 554 + "@atproto-labs/simple-store" "^0.3.0" 555 + "@atproto-labs/simple-store-memory" "^0.1.4" 556 + "@atproto-labs/simple-store-redis" "^0.0.1" 557 + "@atproto-labs/xrpc-utils" "^0.0.24" 558 + "@atproto/api" "^0.18.19" 539 559 "@atproto/aws" "^0.2.31" 540 - "@atproto/common" "^0.5.9" 560 + "@atproto/common" "^0.5.10" 541 561 "@atproto/crypto" "^0.4.5" 542 562 "@atproto/identity" "^0.4.10" 543 - "@atproto/lex-cbor" "^0.0.9" 544 - "@atproto/lex-data" "^0.0.9" 563 + "@atproto/lex-cbor" "^0.0.10" 564 + "@atproto/lex-data" "^0.0.10" 545 565 "@atproto/lexicon" "^0.6.1" 546 - "@atproto/oauth-provider" "^0.15.6" 566 + "@atproto/oauth-provider" "^0.15.7" 547 567 "@atproto/oauth-scopes" "^0.3.1" 548 568 "@atproto/repo" "^0.8.12" 549 569 "@atproto/syntax" "^0.4.3" 550 570 "@atproto/xrpc" "^0.7.7" 551 - "@atproto/xrpc-server" "^0.10.10" 571 + "@atproto/xrpc-server" "^0.10.11" 552 572 "@did-plc/lib" "^0.0.4" 553 573 "@hapi/address" "^5.1.1" 554 574 better-sqlite3 "^10.0.0" ··· 577 597 undici "^6.19.8" 578 598 zod "^3.23.8" 579 599 580 - "@atproto/repo@0.8.12", "@atproto/repo@^0.8.11", "@atproto/repo@^0.8.12": 600 + "@atproto/repo@^0.8.11", "@atproto/repo@^0.8.12": 581 601 version "0.8.12" 582 602 resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.8.12.tgz#f1caee2ff954a917f921569eac22ab669b39a0af" 583 603 integrity sha512-QpVTVulgfz5PUiCTELlDBiRvnsnwrFWi+6CfY88VwXzrRHd9NE8GItK7sfxQ6U65vD/idH8ddCgFrlrsn1REPQ== ··· 631 651 "@atproto/crypto" "^0.4.5" 632 652 "@atproto/lex-cbor" "0.0.9" 633 653 "@atproto/lex-data" "0.0.9" 654 + "@atproto/lexicon" "^0.6.1" 655 + "@atproto/ws-client" "^0.0.4" 656 + "@atproto/xrpc" "^0.7.7" 657 + express "^4.17.2" 658 + http-errors "^2.0.0" 659 + mime-types "^2.1.35" 660 + rate-limiter-flexible "^2.4.1" 661 + ws "^8.12.0" 662 + zod "^3.23.8" 663 + 664 + "@atproto/xrpc-server@^0.10.11": 665 + version "0.10.11" 666 + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.10.11.tgz#cd6f880ebb6a913db2925a23aa0ef0b05f5b02fd" 667 + integrity sha512-7XR+n1G4j1PO33slr2Agl+lmTXbEQzk5iaCJmrcsfTC/0BbCHqSSbm+WHduz3EH4dfFioZsnDo1UesCF0EQEtg== 668 + dependencies: 669 + "@atproto/common" "^0.5.10" 670 + "@atproto/crypto" "^0.4.5" 671 + "@atproto/lex-cbor" "^0.0.10" 672 + "@atproto/lex-data" "^0.0.10" 634 673 "@atproto/lexicon" "^0.6.1" 635 674 "@atproto/ws-client" "^0.0.4" 636 675 "@atproto/xrpc" "^0.7.7"