Bluesky app fork with some witchin' additions 💫

tweak ordering of explore components (#8432)

* tweak ordering of explore components

* use better feed suggestions, metrics

* tweak default limit

* tweak

* remove unnecessary if

authored by hailey.at and committed by GitHub 45a3e6c0 ce7b9dc4

Changed files
+187 -95
src
components
logger
screens
Search
state
queries
+2 -2
src/components/FeedCard.tsx
··· 21 21 import {useSession} from '#/state/session' 22 22 import * as Toast from '#/view/com/util/Toast' 23 23 import {UserAvatar} from '#/view/com/util/UserAvatar' 24 - import {useTheme} from '#/alf' 25 - import {atoms as a} from '#/alf' 24 + import {atoms as a, useTheme} from '#/alf' 26 25 import { 27 26 Button, 28 27 ButtonIcon, ··· 40 39 41 40 type Props = { 42 41 view: AppBskyFeedDefs.GeneratorView 42 + onPress?: () => void 43 43 } 44 44 45 45 export function Default(props: Props) {
+6
src/logger/metrics.ts
··· 151 151 'feed:share': { 152 152 feedUrl: string 153 153 } 154 + 'feed:suggestion:seen': { 155 + feedUrl: string 156 + } 157 + 'feed:suggestion:press': { 158 + feedUrl: string 159 + } 154 160 'discover:showMore': { 155 161 feedContext: string 156 162 }
+178 -92
src/screens/Search/Explore.tsx
··· 10 10 import {useQueryClient} from '@tanstack/react-query' 11 11 import * as bcp47Match from 'bcp-47-match' 12 12 13 - import {useGate} from '#/lib/statsig/statsig' 14 13 import {cleanError} from '#/lib/strings/errors' 15 14 import {sanitizeHandle} from '#/lib/strings/handles' 16 15 import {logger} from '#/logger' ··· 35 34 createSuggestedStarterPacksQueryKey, 36 35 useSuggestedStarterPacksQuery, 37 36 } from '#/state/queries/useSuggestedStarterPacksQuery' 38 - import {useProgressGuide} from '#/state/shell/progress-guide' 39 37 import {isThreadChildAt, isThreadParentAt} from '#/view/com/posts/PostFeed' 40 38 import {PostFeedItem} from '#/view/com/posts/PostFeedItem' 41 39 import {ViewFullThread} from '#/view/com/posts/ViewFullThread' ··· 61 59 import * as FeedCard from '#/components/FeedCard' 62 60 import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron' 63 61 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 64 - import {type Props as IcoProps} from '#/components/icons/common' 65 - import {type Props as SVGIconProps} from '#/components/icons/common' 62 + import { 63 + type Props as IcoProps, 64 + type Props as SVGIconProps, 65 + } from '#/components/icons/common' 66 66 import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' 67 67 import {StarterPack} from '#/components/icons/StarterPack' 68 - import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending' 69 68 import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' 70 69 import {Loader} from '#/components/Loader' 71 70 import * as ProfileCard from '#/components/ProfileCard' ··· 216 215 const t = useTheme() 217 216 const {data: preferences, error: preferencesError} = usePreferencesQuery() 218 217 const moderationOpts = useModerationOpts() 219 - const gate = useGate() 220 - const guide = useProgressGuide('follow-10') 221 218 const [selectedInterest, setSelectedInterest] = useState<string | null>(null) 222 219 223 220 /* ··· 284 281 hasPressedLoadMoreFeeds, 285 282 ]) 286 283 287 - const {data: suggestedFeeds} = useGetSuggestedFeedsQuery({ 288 - enabled: useFullExperience, 289 - }) 284 + const {data: suggestedFeeds, error: suggestedFeedsError} = 285 + useGetSuggestedFeedsQuery({ 286 + enabled: useFullExperience, 287 + }) 290 288 const { 291 289 data: feedPreviewSlices, 292 290 query: { ··· 442 440 }, 443 441 }) 444 442 445 - if (feeds && preferences) { 446 - // Currently the responses contain duplicate items. 447 - // Needs to be fixed on backend, but let's dedupe to be safe. 448 - let seen = new Set() 449 - const feedItems: ExploreScreenItems[] = [] 450 - for (const page of feeds.pages) { 451 - for (const feed of page.feeds) { 443 + if (useFullExperience) { 444 + if (suggestedFeeds && preferences) { 445 + let seen = new Set() 446 + const feedItems: ExploreScreenItems[] = [] 447 + for (const feed of suggestedFeeds.feeds) { 452 448 if (!seen.has(feed.uri)) { 453 449 seen.add(feed.uri) 454 450 feedItems.push({ ··· 458 454 }) 459 455 } 460 456 } 461 - } 462 457 463 - // feeds errors can occur during pagination, so feeds is truthy 464 - if (feedsError) { 465 - i.push({ 466 - type: 'error', 467 - key: 'feedsError', 468 - message: _(msg`Failed to load suggested feeds`), 469 - error: cleanError(feedsError), 470 - }) 471 - } else if (preferencesError) { 472 - i.push({ 473 - type: 'error', 474 - key: 'preferencesError', 475 - message: _(msg`Failed to load feeds preferences`), 476 - error: cleanError(preferencesError), 477 - }) 478 - } else { 479 - if (feedItems.length === 0) { 480 - if (!hasNextFeedsPage) { 458 + // feeds errors can occur during pagination, so feeds is truthy 459 + if (suggestedFeedsError) { 460 + i.push({ 461 + type: 'error', 462 + key: 'feedsError', 463 + message: _(msg`Failed to load suggested feeds`), 464 + error: cleanError(feedsError), 465 + }) 466 + } else if (preferencesError) { 467 + i.push({ 468 + type: 'error', 469 + key: 'preferencesError', 470 + message: _(msg`Failed to load feeds preferences`), 471 + error: cleanError(preferencesError), 472 + }) 473 + } else { 474 + if (feedItems.length === 0) { 481 475 i.pop() 476 + } else { 477 + // This query doesn't follow the limit very well, so the first press of the 478 + // load more button just unslices the array back to ~10 items 479 + if (!hasPressedLoadMoreFeeds) { 480 + i.push(...feedItems.slice(0, 6)) 481 + } else { 482 + i.push(...feedItems) 483 + } 484 + 485 + for (const [index, item] of feedItems.entries()) { 486 + if (item.type !== 'feed') { 487 + continue 488 + } 489 + // don't log the ones we've already sent 490 + if (hasPressedLoadMoreFeeds && index < 6) { 491 + continue 492 + } 493 + logger.metric( 494 + 'feed:suggestion:seen', 495 + {feedUrl: item.feed.uri}, 496 + {statsig: false}, 497 + ) 498 + } 482 499 } 483 - } else { 484 - // This query doesn't follow the limit very well, so the first press of the 485 - // load more button just unslices the array back to ~10 items 486 500 if (!hasPressedLoadMoreFeeds) { 487 - i.push(...feedItems.slice(0, 3)) 488 - } else { 489 - i.push(...feedItems) 501 + i.push({ 502 + type: 'loadMore', 503 + key: 'loadMoreFeeds', 504 + message: _(msg`Load more suggested feeds`), 505 + isLoadingMore: isLoadingMoreFeeds, 506 + onLoadMore: onLoadMoreFeeds, 507 + }) 490 508 } 491 509 } 492 - if (hasNextFeedsPage) { 510 + } else { 511 + if (feedsError) { 512 + i.push({ 513 + type: 'error', 514 + key: 'feedsError', 515 + message: _(msg`Failed to load suggested feeds`), 516 + error: cleanError(feedsError), 517 + }) 518 + } else if (preferencesError) { 493 519 i.push({ 494 - type: 'loadMore', 495 - key: 'loadMoreFeeds', 496 - message: _(msg`Load more suggested feeds`), 497 - isLoadingMore: isLoadingMoreFeeds, 498 - onLoadMore: onLoadMoreFeeds, 520 + type: 'error', 521 + key: 'preferencesError', 522 + message: _(msg`Failed to load feeds preferences`), 523 + error: cleanError(preferencesError), 499 524 }) 525 + } else { 526 + i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'}) 500 527 } 501 528 } 502 529 } else { 503 - if (feedsError) { 504 - i.push({ 505 - type: 'error', 506 - key: 'feedsError', 507 - message: _(msg`Failed to load suggested feeds`), 508 - error: cleanError(feedsError), 509 - }) 510 - } else if (preferencesError) { 511 - i.push({ 512 - type: 'error', 513 - key: 'preferencesError', 514 - message: _(msg`Failed to load feeds preferences`), 515 - error: cleanError(preferencesError), 516 - }) 530 + if (feeds && preferences) { 531 + // Currently the responses contain duplicate items. 532 + // Needs to be fixed on backend, but let's dedupe to be safe. 533 + let seen = new Set() 534 + const feedItems: ExploreScreenItems[] = [] 535 + for (const page of feeds.pages) { 536 + for (const feed of page.feeds) { 537 + if (!seen.has(feed.uri)) { 538 + seen.add(feed.uri) 539 + feedItems.push({ 540 + type: 'feed', 541 + key: feed.uri, 542 + feed, 543 + }) 544 + } 545 + } 546 + } 547 + 548 + // feeds errors can occur during pagination, so feeds is truthy 549 + if (feedsError) { 550 + i.push({ 551 + type: 'error', 552 + key: 'feedsError', 553 + message: _(msg`Failed to load suggested feeds`), 554 + error: cleanError(feedsError), 555 + }) 556 + } else if (preferencesError) { 557 + i.push({ 558 + type: 'error', 559 + key: 'preferencesError', 560 + message: _(msg`Failed to load feeds preferences`), 561 + error: cleanError(preferencesError), 562 + }) 563 + } else { 564 + if (feedItems.length === 0) { 565 + if (!hasNextFeedsPage) { 566 + i.pop() 567 + } 568 + } else { 569 + // This query doesn't follow the limit very well, so the first press of the 570 + // load more button just unslices the array back to ~10 items 571 + if (!hasPressedLoadMoreFeeds) { 572 + i.push(...feedItems.slice(0, 3)) 573 + } else { 574 + i.push(...feedItems) 575 + } 576 + } 577 + if (hasNextFeedsPage) { 578 + i.push({ 579 + type: 'loadMore', 580 + key: 'loadMoreFeeds', 581 + message: _(msg`Load more suggested feeds`), 582 + isLoadingMore: isLoadingMoreFeeds, 583 + onLoadMore: onLoadMoreFeeds, 584 + }) 585 + } 586 + } 517 587 } else { 518 - i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'}) 588 + if (feedsError) { 589 + i.push({ 590 + type: 'error', 591 + key: 'feedsError', 592 + message: _(msg`Failed to load suggested feeds`), 593 + error: cleanError(feedsError), 594 + }) 595 + } else if (preferencesError) { 596 + i.push({ 597 + type: 'error', 598 + key: 'preferencesError', 599 + message: _(msg`Failed to load feeds preferences`), 600 + error: cleanError(preferencesError), 601 + }) 602 + } else { 603 + i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'}) 604 + } 519 605 } 520 606 } 521 607 return i 522 608 }, [ 523 - feeds, 524 609 _, 610 + useFullExperience, 611 + suggestedFeeds, 612 + preferences, 613 + suggestedFeedsError, 614 + preferencesError, 525 615 feedsError, 526 616 hasNextFeedsPage, 527 617 hasPressedLoadMoreFeeds, 528 618 isLoadingMoreFeeds, 529 619 onLoadMoreFeeds, 530 - preferences, 531 - preferencesError, 620 + feeds, 532 621 ]) 622 + 533 623 const suggestedStarterPacksModule = useMemo(() => { 534 624 const i: ExploreScreenItems[] = [] 535 625 i.push({ ··· 589 679 ] 590 680 }, [showInterestsNux]) 591 681 592 - const isNewUser = guide?.guide === 'follow-10' && !guide.isComplete 593 682 const items = useMemo<ExploreScreenItems[]>(() => { 594 683 const i: ExploreScreenItems[] = [] 595 684 ··· 599 688 i.push(...interestsNuxModule) 600 689 601 690 if (useFullExperience) { 602 - if (isNewUser) { 603 - i.push(...suggestedFollowsModule) 604 - i.push(...suggestedStarterPacksModule) 605 - i.push({ 606 - type: 'header', 607 - key: 'trending-topics-header', 608 - title: _(msg`Trending topics`), 609 - icon: Graph, 610 - bottomBorder: true, 611 - }) 612 - i.push(trendingTopicsModule) 613 - } else { 614 - i.push(trendingTopicsModule) 615 - i.push(...suggestedFollowsModule) 616 - i.push(...suggestedStarterPacksModule) 617 - } 618 - if (gate('explore_show_suggested_feeds')) { 619 - i.push(...suggestedFeedsModule) 620 - } 691 + i.push(trendingTopicsModule) 692 + i.push(...suggestedFeedsModule) 693 + i.push(...suggestedFollowsModule) 694 + i.push(...suggestedStarterPacksModule) 621 695 i.push(...feedPreviewsModule) 622 696 } else { 623 697 i.push(...suggestedFollowsModule) ··· 625 699 626 700 return i 627 701 }, [ 628 - _, 629 702 topBorder, 630 - isNewUser, 631 703 suggestedFollowsModule, 632 704 suggestedStarterPacksModule, 633 705 suggestedFeedsModule, 634 706 trendingTopicsModule, 635 707 feedPreviewsModule, 636 708 interestsNuxModule, 637 - gate, 638 709 useFullExperience, 639 710 ]) 640 711 ··· 731 802 a.px_lg, 732 803 a.py_lg, 733 804 ]}> 734 - <FeedCard.Default view={item.feed} /> 805 + <FeedCard.Default 806 + view={item.feed} 807 + onPress={() => { 808 + if (!useFullExperience) { 809 + return 810 + } 811 + logger.metric('feed:suggestion:press', { 812 + feedUrl: item.feed.uri, 813 + }) 814 + }} 815 + /> 735 816 </View> 736 817 ) 737 818 } ··· 918 999 } 919 1000 }, 920 1001 [ 921 - t, 1002 + t.atoms.border_contrast_low, 1003 + t.atoms.bg_contrast_25, 1004 + t.atoms.text_contrast_medium, 1005 + t.atoms.bg, 1006 + t.palette.negative_400, 922 1007 focusSearchInput, 923 - moderationOpts, 924 1008 selectedInterest, 1009 + moderationOpts, 925 1010 interestsDisplayNames, 1011 + useFullExperience, 926 1012 _, 927 1013 fetchNextPageFeedPreviews, 928 1014 ],
+1 -1
src/state/queries/trending/useGetSuggestedFeedsQuery.ts
··· 9 9 import {usePreferencesQuery} from '#/state/queries/preferences' 10 10 import {useAgent} from '#/state/session' 11 11 12 - export const DEFAULT_LIMIT = 5 12 + export const DEFAULT_LIMIT = 15 13 13 14 14 export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds'] 15 15