about summary refs log tree commit diff
path: root/src/state/queries
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-11-24 19:10:54 +0000
committerGitHub <noreply@github.com>2024-11-24 19:10:54 +0000
commitb9406aa011db2db7d3bcdf7d91ef929b17a07c02 (patch)
treefe590626f573c2e78e4d9f7df73f33c523e62172 /src/state/queries
parent371e6ad333e00cdfb7f4713f7fef816668011049 (diff)
downloadvoidsky-b9406aa011db2db7d3bcdf7d91ef929b17a07c02.tar.zst
Don't re-sort already fetched post thread items (#6698)
* Don't reorder already seen posts in PostThread

* Add sorting by generation

* Rip out stable order cache

It doesn't make sense because sort() doesn't call the callback for all A/B pairs, and the server returning a different ordering will cause cache misses which means there'll be no stability anyway.

* Make hotness deterministic per fetched at

* Cache random scores while in thread

* Reorder for clarity
Diffstat (limited to 'src/state/queries')
-rw-r--r--src/state/queries/post-thread.ts52
1 files changed, 43 insertions, 9 deletions
diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts
index 4784a9d75..2ebaf4f7d 100644
--- a/src/state/queries/post-thread.ts
+++ b/src/state/queries/post-thread.ts
@@ -150,6 +150,9 @@ export function sortThread(
   currentDid: string | undefined,
   justPostedUris: Set<string>,
   threadgateRecordHiddenReplies: Set<string>,
+  fetchedAtCache: Map<string, number>,
+  fetchedAt: number,
+  randomCache: Map<string, number>,
 ): ThreadNode {
   if (node.type !== 'post') {
     return node
@@ -237,9 +240,23 @@ export function sortThread(
         }
       }
 
-      if (opts.sort === 'hotness') {
-        const aHotness = getHotness(a.post)
-        const bHotness = getHotness(b.post)
+      // Split items from different fetches into separate generations.
+      let aFetchedAt = fetchedAtCache.get(a.uri)
+      if (aFetchedAt === undefined) {
+        fetchedAtCache.set(a.uri, fetchedAt)
+        aFetchedAt = fetchedAt
+      }
+      let bFetchedAt = fetchedAtCache.get(b.uri)
+      if (bFetchedAt === undefined) {
+        fetchedAtCache.set(b.uri, fetchedAt)
+        bFetchedAt = fetchedAt
+      }
+
+      if (aFetchedAt !== bFetchedAt) {
+        return aFetchedAt - bFetchedAt // older fetches first
+      } else if (opts.sort === 'hotness') {
+        const aHotness = getHotness(a.post, aFetchedAt)
+        const bHotness = getHotness(b.post, bFetchedAt /* same as aFetchedAt */)
         return bHotness - aHotness
       } else if (opts.sort === 'oldest') {
         return a.post.indexedAt.localeCompare(b.post.indexedAt)
@@ -252,9 +269,21 @@ export function sortThread(
           return (b.post.likeCount || 0) - (a.post.likeCount || 0) // most likes
         }
       } else if (opts.sort === 'random') {
-        return 0.5 - Math.random() // this is vaguely criminal but we can get away with it
+        let aRandomScore = randomCache.get(a.uri)
+        if (aRandomScore === undefined) {
+          aRandomScore = Math.random()
+          randomCache.set(a.uri, aRandomScore)
+        }
+        let bRandomScore = randomCache.get(b.uri)
+        if (bRandomScore === undefined) {
+          bRandomScore = Math.random()
+          randomCache.set(b.uri, bRandomScore)
+        }
+        // this is vaguely criminal but we can get away with it
+        return aRandomScore - bRandomScore
+      } else {
+        return b.post.indexedAt.localeCompare(a.post.indexedAt)
       }
-      return b.post.indexedAt.localeCompare(a.post.indexedAt)
     })
     node.replies.forEach(reply =>
       sortThread(
@@ -264,6 +293,9 @@ export function sortThread(
         currentDid,
         justPostedUris,
         threadgateRecordHiddenReplies,
+        fetchedAtCache,
+        fetchedAt,
+        randomCache,
       ),
     )
   }
@@ -277,10 +309,12 @@ export function sortThread(
 // We want to give recent comments a real chance (and not bury them deep below the fold)
 // while also surfacing well-liked comments from the past. In the future, we can explore
 // something more sophisticated, but we don't have much data on the client right now.
-function getHotness(post: AppBskyFeedDefs.PostView) {
-  const hoursAgo =
-    (new Date().getTime() - new Date(post.indexedAt).getTime()) /
-    (1000 * 60 * 60)
+function getHotness(post: AppBskyFeedDefs.PostView, fetchedAt: number) {
+  const hoursAgo = Math.max(
+    0,
+    (new Date(fetchedAt).getTime() - new Date(post.indexedAt).getTime()) /
+      (1000 * 60 * 60),
+  )
   const likeCount = post.likeCount ?? 0
   const likeOrder = Math.log(3 + likeCount)
   const timePenaltyExponent = 1.5 + 1.5 / (1 + Math.log(1 + likeCount))