Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[D1X] Pull out follow-backs for higher signal (#4719)

* Pull out follow-backs for higher signal

* Gate it

* Fix early gate check

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by

Eric Bailey
Dan Abramov
and committed by
GitHub
4f02da96 0ed99b84

+41 -10
+3 -2
src/lib/statsig/gates.ts
··· 2 2 // Keep this alphabetic please. 3 3 | 'debug_show_feedcontext' 4 4 | 'native_pwi_disabled' 5 + | 'new_user_guided_tour' 6 + | 'new_user_progress_guide' 5 7 | 'onboarding_minimum_interests' 6 8 | 'request_notifications_permission_after_onboarding_v2' 7 9 | 'show_avi_follow_button' 8 10 | 'show_follow_back_label_v2' 9 - | 'new_user_guided_tour' 10 - | 'new_user_progress_guide' 11 11 | 'suggested_feeds_interstitial' 12 12 | 'suggested_follows_interstitial' 13 + | 'ungroup_follow_backs'
+3
src/state/queries/notifications/feed.ts
··· 26 26 useQueryClient, 27 27 } from '@tanstack/react-query' 28 28 29 + import {useGate} from '#/lib/statsig/statsig' 29 30 import {useAgent} from '#/state/session' 30 31 import {useModerationOpts} from '../../preferences/moderation-opts' 31 32 import {STALE} from '..' ··· 56 57 const unreads = useUnreadNotificationsApi() 57 58 const enabled = opts?.enabled !== false 58 59 const lastPageCountRef = useRef(0) 60 + const gate = useGate() 59 61 60 62 const query = useInfiniteQuery< 61 63 FeedPage, ··· 81 83 queryClient, 82 84 moderationOpts, 83 85 fetchAdditionalData: true, 86 + shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'), 84 87 }) 85 88 ).page 86 89 }
+4 -1
src/state/queries/notifications/unread.tsx
··· 8 8 import EventEmitter from 'eventemitter3' 9 9 10 10 import BroadcastChannel from '#/lib/broadcast' 11 + import {useGate} from '#/lib/statsig/statsig' 11 12 import {logger} from '#/logger' 12 13 import {useAgent, useSession} from '#/state/session' 13 14 import {resetBadgeCount} from 'lib/notifications/notifications' ··· 47 48 const agent = useAgent() 48 49 const queryClient = useQueryClient() 49 50 const moderationOpts = useModerationOpts() 51 + const gate = useGate() 50 52 51 53 const [numUnread, setNumUnread] = React.useState('') 52 54 ··· 149 151 // only fetch subjects when the page is going to be used 150 152 // in the notifications query, otherwise skip it 151 153 fetchAdditionalData: !!invalidate, 154 + shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'), 152 155 }) 153 156 const unreadCount = countUnread(page) 154 157 const unreadCountStr = ··· 189 192 } 190 193 }, 191 194 } 192 - }, [setNumUnread, queryClient, moderationOpts, agent]) 195 + }, [setNumUnread, queryClient, moderationOpts, agent, gate]) 193 196 checkUnreadRef.current = api.checkUnread 194 197 195 198 return (
+18 -5
src/state/queries/notifications/util.ts
··· 30 30 queryClient, 31 31 moderationOpts, 32 32 fetchAdditionalData, 33 + shouldUngroupFollowBacks, 33 34 }: { 34 35 agent: BskyAgent 35 36 cursor: string | undefined ··· 37 38 queryClient: QueryClient 38 39 moderationOpts: ModerationOpts | undefined 39 40 fetchAdditionalData: boolean 41 + shouldUngroupFollowBacks?: () => boolean 40 42 }): Promise<{page: FeedPage; indexedAt: string | undefined}> { 41 43 const res = await agent.listNotifications({ 42 44 limit, ··· 51 53 ) 52 54 53 55 // group notifications which are essentially similar (follows, likes on a post) 54 - let notifsGrouped = groupNotifications(notifs) 56 + let notifsGrouped = groupNotifications(notifs, {shouldUngroupFollowBacks}) 55 57 56 58 // we fetch subjects of notifications (usually posts) now instead of lazily 57 59 // in the UI to avoid relayouts ··· 109 111 110 112 export function groupNotifications( 111 113 notifs: AppBskyNotificationListNotifications.Notification[], 114 + options?: {shouldUngroupFollowBacks?: () => boolean}, 112 115 ): FeedNotification[] { 113 116 const groupedNotifs: FeedNotification[] = [] 114 117 for (const notif of notifs) { ··· 123 126 notif.reasonSubject === groupedNotif.notification.reasonSubject && 124 127 notif.author.did !== groupedNotif.notification.author.did 125 128 ) { 126 - groupedNotif.additional = groupedNotif.additional || [] 127 - groupedNotif.additional.push(notif) 128 - grouped = true 129 - break 129 + const nextIsFollowBack = 130 + notif.reason === 'follow' && notif.author.viewer?.following 131 + const prevIsFollowBack = 132 + groupedNotif.notification.reason === 'follow' && 133 + groupedNotif.notification.author.viewer?.following 134 + const shouldUngroup = 135 + (nextIsFollowBack || prevIsFollowBack) && 136 + options?.shouldUngroupFollowBacks?.() 137 + if (!shouldUngroup) { 138 + groupedNotif.additional = groupedNotif.additional || [] 139 + groupedNotif.additional.push(notif) 140 + grouped = true 141 + break 142 + } 130 143 } 131 144 } 132 145 }
+13 -2
src/view/com/notifications/FeedItem.tsx
··· 22 22 import {useLingui} from '@lingui/react' 23 23 import {useQueryClient} from '@tanstack/react-query' 24 24 25 + import {useGate} from '#/lib/statsig/statsig' 25 26 import {FeedNotification} from '#/state/queries/notifications/feed' 26 27 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' 27 28 import {usePalette} from 'lib/hooks/usePalette' ··· 86 87 const pal = usePalette('default') 87 88 const {_} = useLingui() 88 89 const t = useTheme() 90 + const gate = useGate() 89 91 const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false) 90 92 const itemHref = useMemo(() => { 91 93 if (item.type === 'post-like' || item.type === 'repost') { ··· 168 170 ) 169 171 } 170 172 173 + let isFollowBack = false 171 174 let action = '' 172 175 let icon = ( 173 176 <HeartIconFilled ··· 184 187 action = _(msg`reposted your post`) 185 188 icon = <RepostIcon size="xl" style={{color: t.palette.positive_600}} /> 186 189 } else if (item.type === 'follow') { 187 - action = _(msg`followed you`) 190 + if ( 191 + item.notification.author.viewer?.following && 192 + gate('ungroup_follow_backs') 193 + ) { 194 + isFollowBack = true 195 + action = _(msg`followed you back`) 196 + } else { 197 + action = _(msg`followed you`) 198 + } 188 199 icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} /> 189 200 } else if (item.type === 'feedgen-like') { 190 201 action = _(msg`liked your custom feed`) ··· 260 271 visible={!isAuthorsExpanded} 261 272 authors={authors} 262 273 onToggleAuthorsExpanded={onToggleAuthorsExpanded} 263 - showDmButton={item.type === 'starterpack-joined'} 274 + showDmButton={item.type === 'starterpack-joined' || isFollowBack} 264 275 /> 265 276 <ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} /> 266 277 <Text style={styles.meta}>