about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMinseo Lee <itoupluk427@gmail.com>2024-03-14 21:11:00 +0900
committerGitHub <noreply@github.com>2024-03-14 21:11:00 +0900
commit4813f261581e83d6d6c2c2b8063c208ecdb5de34 (patch)
tree09f498fbbbc619a2021c3bc10fada21212f28fe3 /src
parent1760043f79f6e50de3bb2df97c3d6fe9c700b035 (diff)
parent1c25c76645564c4581530ec604b7862bb3b8fdda (diff)
downloadvoidsky-4813f261581e83d6d6c2c2b8063c208ecdb5de34.tar.zst
Merge branch 'bluesky-social:main' into patch-3
Diffstat (limited to 'src')
-rw-r--r--src/Navigation.tsx11
-rw-r--r--src/lib/media/picker.shared.ts21
-rw-r--r--src/lib/statsig/events.ts47
-rw-r--r--src/lib/statsig/statsig.tsx29
-rw-r--r--src/state/queries/post.ts47
-rw-r--r--src/state/queries/profile.ts17
-rw-r--r--src/view/com/auth/onboarding/RecommendedFollowsItem.tsx7
-rw-r--r--src/view/com/composer/Composer.tsx11
-rw-r--r--src/view/com/post-thread/PostThreadFollowBtn.tsx5
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx2
-rw-r--r--src/view/com/post/Post.tsx1
-rw-r--r--src/view/com/posts/Feed.tsx16
-rw-r--r--src/view/com/posts/FeedItem.tsx1
-rw-r--r--src/view/com/profile/FollowButton.tsx7
-rw-r--r--src/view/com/profile/ProfileCard.tsx4
-rw-r--r--src/view/com/profile/ProfileHeader.tsx5
-rw-r--r--src/view/com/profile/ProfileHeaderSuggestedFollows.tsx5
-rw-r--r--src/view/com/profile/ProfileMenu.tsx5
-rw-r--r--src/view/com/util/post-ctrls/PostCtrls.tsx9
19 files changed, 209 insertions, 41 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 8a9f69b5d..77706ce34 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -78,7 +78,7 @@ import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStack
 import {msg} from '@lingui/macro'
 import {i18n, MessageDescriptor} from '@lingui/core'
 import HashtagScreen from '#/screens/Hashtag'
-import {logEvent} from './lib/statsig/statsig'
+import {logEvent, attachRouteToLogEvents} from './lib/statsig/statsig'
 
 const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
 
@@ -543,6 +543,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
       linking={LINKING}
       theme={theme}
       onReady={() => {
+        attachRouteToLogEvents(getCurrentRouteName)
         logModuleInitTime()
         onReady()
       }}>
@@ -551,6 +552,10 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
   )
 }
 
+function getCurrentRouteName() {
+  return navigationRef.getCurrentRoute()?.name
+}
+
 /**
  * These helpers can be used from outside of the RoutesContainer
  * (eg in the state models).
@@ -656,7 +661,9 @@ function logModuleInitTime() {
     performance.now() - global.__BUNDLE_START_TIME__,
   )
   console.log(`Time to first paint: ${initMs} ms`)
-  logEvent('init', initMs)
+  logEvent('init', {
+    initMs,
+  })
 
   if (__DEV__) {
     // This log is noisy, so keep false committed
diff --git a/src/lib/media/picker.shared.ts b/src/lib/media/picker.shared.ts
index 8bade34e2..96e82e4c7 100644
--- a/src/lib/media/picker.shared.ts
+++ b/src/lib/media/picker.shared.ts
@@ -18,11 +18,18 @@ export async function openPicker(opts?: ImagePickerOptions) {
     Toast.show('You may only select up to 4 images')
   }
 
-  return (response.assets ?? []).slice(0, 4).map(image => ({
-    mime: 'image/jpeg',
-    height: image.height,
-    width: image.width,
-    path: image.uri,
-    size: getDataUriSize(image.uri),
-  }))
+  return (response.assets ?? [])
+    .slice(0, 4)
+    .filter(asset => {
+      if (asset.mimeType?.startsWith('image/')) return true
+      Toast.show('Only image files are supported')
+      return false
+    })
+    .map(image => ({
+      mime: 'image/jpeg',
+      height: image.height,
+      width: image.width,
+      path: image.uri,
+      size: getDataUriSize(image.uri),
+    }))
 }
diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts
new file mode 100644
index 000000000..fa7e597fb
--- /dev/null
+++ b/src/lib/statsig/events.ts
@@ -0,0 +1,47 @@
+export type LogEvents = {
+  init: {
+    initMs: number
+  }
+  'feed:endReached': {
+    feedType: string
+    itemCount: number
+  }
+  'post:create': {
+    imageCount: number
+    isReply: boolean
+    hasLink: boolean
+    hasQuote: boolean
+    langs: string
+    logContext: 'Composer'
+  }
+  'post:like': {
+    logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
+  }
+  'post:repost': {
+    logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
+  }
+  'post:unlike': {
+    logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
+  }
+  'post:unrepost': {
+    logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
+  }
+  'profile:follow': {
+    logContext:
+      | 'RecommendedFollowsItem'
+      | 'PostThreadItem'
+      | 'ProfileCard'
+      | 'ProfileHeader'
+      | 'ProfileHeaderSuggestedFollows'
+      | 'ProfileMenu'
+  }
+  'profile:unfollow': {
+    logContext:
+      | 'RecommendedFollowsItem'
+      | 'PostThreadItem'
+      | 'ProfileCard'
+      | 'ProfileHeader'
+      | 'ProfileHeaderSuggestedFollows'
+      | 'ProfileMenu'
+  }
+}
diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx
index 6d9ebeb09..5745d204a 100644
--- a/src/lib/statsig/statsig.tsx
+++ b/src/lib/statsig/statsig.tsx
@@ -6,6 +6,9 @@ import {
 } from 'statsig-react-native-expo'
 import {useSession} from '../../state/session'
 import {sha256} from 'js-sha256'
+import {LogEvents} from './events'
+
+export type {LogEvents}
 
 const statsigOptions = {
   environment: {
@@ -17,12 +20,28 @@ const statsigOptions = {
   initTimeoutMs: 1,
 }
 
-export function logEvent(
-  eventName: string,
-  value?: string | number | null,
-  metadata?: Record<string, string> | null,
+type FlatJSONRecord = Record<
+  string,
+  string | number | boolean | null | undefined
+>
+
+let getCurrentRouteName: () => string | null | undefined = () => null
+
+export function attachRouteToLogEvents(
+  getRouteName: () => string | null | undefined,
+) {
+  getCurrentRouteName = getRouteName
+}
+
+export function logEvent<E extends keyof LogEvents>(
+  eventName: E & string,
+  rawMetadata: LogEvents[E] & FlatJSONRecord,
 ) {
-  Statsig.logEvent(eventName, value, metadata)
+  const fullMetadata = {
+    ...rawMetadata,
+  } as Record<string, string> // Statsig typings are unnecessarily strict here.
+  fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
+  Statsig.logEvent(eventName, null, fullMetadata)
 }
 
 export function useGate(gateName: string) {
diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts
index eb59f7da4..e3682e304 100644
--- a/src/state/queries/post.ts
+++ b/src/state/queries/post.ts
@@ -5,6 +5,7 @@ import {Shadow} from '#/state/cache/types'
 import {getAgent} from '#/state/session'
 import {updatePostShadow} from '#/state/cache/post-shadow'
 import {track} from '#/lib/analytics/analytics'
+import {logEvent, LogEvents} from '#/lib/statsig/statsig'
 import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
 
 export const RQKEY = (postUri: string) => ['post', postUri]
@@ -58,12 +59,14 @@ export function useGetPost() {
 
 export function usePostLikeMutationQueue(
   post: Shadow<AppBskyFeedDefs.PostView>,
+  logContext: LogEvents['post:like']['logContext'] &
+    LogEvents['post:unlike']['logContext'],
 ) {
   const postUri = post.uri
   const postCid = post.cid
   const initialLikeUri = post.viewer?.like
-  const likeMutation = usePostLikeMutation()
-  const unlikeMutation = usePostUnlikeMutation()
+  const likeMutation = usePostLikeMutation(logContext)
+  const unlikeMutation = usePostUnlikeMutation(logContext)
 
   const queueToggle = useToggleMutationQueue({
     initialState: initialLikeUri,
@@ -111,22 +114,30 @@ export function usePostLikeMutationQueue(
   return [queueLike, queueUnlike]
 }
 
-function usePostLikeMutation() {
+function usePostLikeMutation(logContext: LogEvents['post:like']['logContext']) {
   return useMutation<
     {uri: string}, // responds with the uri of the like
     Error,
     {uri: string; cid: string} // the post's uri and cid
   >({
-    mutationFn: post => getAgent().like(post.uri, post.cid),
+    mutationFn: post => {
+      logEvent('post:like', {logContext})
+      return getAgent().like(post.uri, post.cid)
+    },
     onSuccess() {
       track('Post:Like')
     },
   })
 }
 
-function usePostUnlikeMutation() {
+function usePostUnlikeMutation(
+  logContext: LogEvents['post:unlike']['logContext'],
+) {
   return useMutation<void, Error, {postUri: string; likeUri: string}>({
-    mutationFn: ({likeUri}) => getAgent().deleteLike(likeUri),
+    mutationFn: ({likeUri}) => {
+      logEvent('post:unlike', {logContext})
+      return getAgent().deleteLike(likeUri)
+    },
     onSuccess() {
       track('Post:Unlike')
     },
@@ -135,12 +146,14 @@ function usePostUnlikeMutation() {
 
 export function usePostRepostMutationQueue(
   post: Shadow<AppBskyFeedDefs.PostView>,
+  logContext: LogEvents['post:repost']['logContext'] &
+    LogEvents['post:unrepost']['logContext'],
 ) {
   const postUri = post.uri
   const postCid = post.cid
   const initialRepostUri = post.viewer?.repost
-  const repostMutation = usePostRepostMutation()
-  const unrepostMutation = usePostUnrepostMutation()
+  const repostMutation = usePostRepostMutation(logContext)
+  const unrepostMutation = usePostUnrepostMutation(logContext)
 
   const queueToggle = useToggleMutationQueue({
     initialState: initialRepostUri,
@@ -188,22 +201,32 @@ export function usePostRepostMutationQueue(
   return [queueRepost, queueUnrepost]
 }
 
-function usePostRepostMutation() {
+function usePostRepostMutation(
+  logContext: LogEvents['post:repost']['logContext'],
+) {
   return useMutation<
     {uri: string}, // responds with the uri of the repost
     Error,
     {uri: string; cid: string} // the post's uri and cid
   >({
-    mutationFn: post => getAgent().repost(post.uri, post.cid),
+    mutationFn: post => {
+      logEvent('post:repost', {logContext})
+      return getAgent().repost(post.uri, post.cid)
+    },
     onSuccess() {
       track('Post:Repost')
     },
   })
 }
 
-function usePostUnrepostMutation() {
+function usePostUnrepostMutation(
+  logContext: LogEvents['post:unrepost']['logContext'],
+) {
   return useMutation<void, Error, {postUri: string; repostUri: string}>({
-    mutationFn: ({repostUri}) => getAgent().deleteRepost(repostUri),
+    mutationFn: ({repostUri}) => {
+      logEvent('post:unrepost', {logContext})
+      return getAgent().deleteRepost(repostUri)
+    },
     onSuccess() {
       track('Post:Unrepost')
     },
diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts
index e81ea0f3f..3c9e3e41c 100644
--- a/src/state/queries/profile.ts
+++ b/src/state/queries/profile.ts
@@ -26,6 +26,7 @@ import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
 import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
 import {STALE} from '#/state/queries'
 import {track} from '#/lib/analytics/analytics'
+import {logEvent, LogEvents} from '#/lib/statsig/statsig'
 import {ThreadNode} from './post-thread'
 
 export const RQKEY = (did: string) => ['profile', did]
@@ -186,11 +187,13 @@ export function useProfileUpdateMutation() {
 
 export function useProfileFollowMutationQueue(
   profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
+  logContext: LogEvents['profile:follow']['logContext'] &
+    LogEvents['profile:unfollow']['logContext'],
 ) {
   const did = profile.did
   const initialFollowingUri = profile.viewer?.following
-  const followMutation = useProfileFollowMutation()
-  const unfollowMutation = useProfileUnfollowMutation()
+  const followMutation = useProfileFollowMutation(logContext)
+  const unfollowMutation = useProfileUnfollowMutation(logContext)
 
   const queueToggle = useToggleMutationQueue({
     initialState: initialFollowingUri,
@@ -237,9 +240,12 @@ export function useProfileFollowMutationQueue(
   return [queueFollow, queueUnfollow]
 }
 
-function useProfileFollowMutation() {
+function useProfileFollowMutation(
+  logContext: LogEvents['profile:follow']['logContext'],
+) {
   return useMutation<{uri: string; cid: string}, Error, {did: string}>({
     mutationFn: async ({did}) => {
+      logEvent('profile:follow', {logContext})
       return await getAgent().follow(did)
     },
     onSuccess(data, variables) {
@@ -248,9 +254,12 @@ function useProfileFollowMutation() {
   })
 }
 
-function useProfileUnfollowMutation() {
+function useProfileUnfollowMutation(
+  logContext: LogEvents['profile:unfollow']['logContext'],
+) {
   return useMutation<void, Error, {did: string; followUri: string}>({
     mutationFn: async ({followUri}) => {
+      logEvent('profile:unfollow', {logContext})
       track('Profile:Unfollow', {username: followUri})
       return await getAgent().deleteFollow(followUri)
     },
diff --git a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx
index 993bc34ac..3023ac6c3 100644
--- a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx
+++ b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx
@@ -57,7 +57,7 @@ export function RecommendedFollowsItem({
   )
 }
 
-export function ProfileCard({
+function ProfileCard({
   profile,
   onFollowStateChange,
   moderation,
@@ -74,7 +74,10 @@ export function ProfileCard({
   const {_} = useLingui()
   const [addingMoreSuggestions, setAddingMoreSuggestions] =
     React.useState(false)
-  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
+  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
+    profile,
+    'RecommendedFollowsItem',
+  )
 
   const onToggleFollow = React.useCallback(async () => {
     try {
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index ef965b271..97f8e5194 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -65,6 +65,7 @@ import {logger} from '#/logger'
 import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo'
 import * as Prompt from '#/components/Prompt'
 import {useDialogStateControlContext} from 'state/dialogs'
+import {logEvent} from '#/lib/statsig/statsig'
 
 type Props = ComposerOpts
 export const ComposePost = observer(function ComposePost({
@@ -255,6 +256,16 @@ export const ComposePost = observer(function ComposePost({
       setIsProcessing(false)
       return
     } finally {
+      if (postUri) {
+        logEvent('post:create', {
+          imageCount: gallery.size,
+          isReply: replyTo != null,
+          hasLink: extLink != null,
+          hasQuote: quote != null,
+          langs: langPrefs.postLanguage,
+          logContext: 'Composer',
+        })
+      }
       track('Create Post', {
         imageCount: gallery.size,
       })
diff --git a/src/view/com/post-thread/PostThreadFollowBtn.tsx b/src/view/com/post-thread/PostThreadFollowBtn.tsx
index e5b747cc9..45c3771f5 100644
--- a/src/view/com/post-thread/PostThreadFollowBtn.tsx
+++ b/src/view/com/post-thread/PostThreadFollowBtn.tsx
@@ -42,7 +42,10 @@ function PostThreadFollowBtnLoaded({
   const {isTabletOrDesktop} = useWebMediaQueries()
   const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> =
     useProfileShadow(profileUnshadowed)
-  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
+  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
+    profile,
+    'PostThreadItem',
+  )
   const requireAuth = useRequireAuth()
 
   const isFollowing = !!profile.viewer?.following
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index e0baabed9..ba5327bfa 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -407,6 +407,7 @@ let PostThreadItemLoaded = ({
                 record={record}
                 richText={richText}
                 onPressReply={onPressReply}
+                logContext="PostThreadItem"
               />
             </View>
           </View>
@@ -560,6 +561,7 @@ let PostThreadItemLoaded = ({
                   record={record}
                   richText={richText}
                   onPressReply={onPressReply}
+                  logContext="PostThreadItem"
                 />
               </View>
             </View>
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 5fa4da84e..0fe3420ba 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -220,6 +220,7 @@ function PostInner({
             record={record}
             richText={richText}
             onPressReply={onPressReply}
+            logContext="Post"
           />
         </View>
       </View>
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index cd3e98785..b86646a4d 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -33,6 +33,7 @@ import {useLingui} from '@lingui/react'
 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
 import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home'
 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {logEvent} from '#/lib/statsig/statsig'
 
 const LOADING_ITEM = {_reactKey: '__loading__'}
 const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
@@ -223,16 +224,29 @@ let Feed = ({
     setIsPTRing(false)
   }, [refetch, track, setIsPTRing, onHasNew])
 
+  const feedType = feed.split('|')[0]
   const onEndReached = React.useCallback(async () => {
     if (isFetching || !hasNextPage || isError) return
 
+    logEvent('feed:endReached', {
+      feedType: feedType,
+      itemCount: feedItems.length,
+    })
     track('Feed:onEndReached')
     try {
       await fetchNextPage()
     } catch (err) {
       logger.error('Failed to load more posts', {message: err})
     }
-  }, [isFetching, hasNextPage, isError, fetchNextPage, track])
+  }, [
+    isFetching,
+    hasNextPage,
+    isError,
+    fetchNextPage,
+    track,
+    feedType,
+    feedItems.length,
+  ])
 
   const onPressTryAgain = React.useCallback(() => {
     refetch()
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 7d29703e2..4b78dce7a 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -310,6 +310,7 @@ let FeedItemInner = ({
             showAppealLabelItem={
               post.author.did === currentAccount?.did && isModeratedPost
             }
+            logContext="FeedItem"
           />
         </View>
       </View>
diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx
index 9cc635b66..7b090ffeb 100644
--- a/src/view/com/profile/FollowButton.tsx
+++ b/src/view/com/profile/FollowButton.tsx
@@ -13,13 +13,18 @@ export function FollowButton({
   followedType = 'default',
   profile,
   labelStyle,
+  logContext,
 }: {
   unfollowedType?: ButtonType
   followedType?: ButtonType
   profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
   labelStyle?: StyleProp<TextStyle>
+  logContext: 'ProfileCard'
 }) {
-  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
+  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
+    profile,
+    logContext,
+  )
   const {_} = useLingui()
 
   const onPressFollow = async () => {
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx
index 266adc51d..019e6c10e 100644
--- a/src/view/com/profile/ProfileCard.tsx
+++ b/src/view/com/profile/ProfileCard.tsx
@@ -230,7 +230,9 @@ export function ProfileCardWithFollowBtn({
       renderButton={
         isMe
           ? undefined
-          : profileShadow => <FollowButton profile={profileShadow} />
+          : profileShadow => (
+              <FollowButton profile={profileShadow} logContext="ProfileCard" />
+            )
       }
     />
   )
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 75e06eb9b..17dc5ce1b 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -103,7 +103,10 @@ let ProfileHeader = ({
   const invalidHandle = isInvalidHandle(profile.handle)
   const {isDesktop} = useWebMediaQueries()
   const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false)
-  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
+  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
+    profile,
+    'ProfileHeader',
+  )
   const [__, queueUnblock] = useProfileBlockMutationQueue(profile)
   const unblockPromptControl = Prompt.usePromptControl()
   const moderation = useMemo(
diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
index ab406aeeb..585463f9d 100644
--- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
+++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
@@ -172,7 +172,10 @@ function SuggestedFollow({
   const {_} = useLingui()
   const moderationOpts = useModerationOpts()
   const profile = useProfileShadow(profileUnshadowed)
-  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
+  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
+    profile,
+    'ProfileHeaderSuggestedFollows',
+  )
 
   const onPressFollow = React.useCallback(async () => {
     try {
diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx
index 4153b819e..7c6db1ed8 100644
--- a/src/view/com/profile/ProfileMenu.tsx
+++ b/src/view/com/profile/ProfileMenu.tsx
@@ -52,7 +52,10 @@ let ProfileMenu = ({
 
   const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
   const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
-  const [, queueUnfollow] = useProfileFollowMutationQueue(profile)
+  const [, queueUnfollow] = useProfileFollowMutationQueue(
+    profile,
+    'ProfileMenu',
+  )
 
   const blockPromptControl = Prompt.usePromptControl()
 
diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx
index 1e26eecce..c96954a11 100644
--- a/src/view/com/util/post-ctrls/PostCtrls.tsx
+++ b/src/view/com/util/post-ctrls/PostCtrls.tsx
@@ -44,6 +44,7 @@ let PostCtrls = ({
   showAppealLabelItem,
   style,
   onPressReply,
+  logContext,
 }: {
   big?: boolean
   post: Shadow<AppBskyFeedDefs.PostView>
@@ -52,13 +53,17 @@ let PostCtrls = ({
   showAppealLabelItem?: boolean
   style?: StyleProp<ViewStyle>
   onPressReply: () => void
+  logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
 }): React.ReactNode => {
   const theme = useTheme()
   const {_} = useLingui()
   const {openComposer} = useComposerControls()
   const {closeModal} = useModalControls()
-  const [queueLike, queueUnlike] = usePostLikeMutationQueue(post)
-  const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(post)
+  const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext)
+  const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(
+    post,
+    logContext,
+  )
   const requireAuth = useRequireAuth()
 
   const defaultCtrlColor = React.useMemo(