my fork of the bluesky client
0
fork

Configure Feed

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

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

authored by danabra.mov and committed by

GitHub b9406aa0 371e6ad3

+52 -9
+43 -9
src/state/queries/post-thread.ts
··· 150 150 currentDid: string | undefined, 151 151 justPostedUris: Set<string>, 152 152 threadgateRecordHiddenReplies: Set<string>, 153 + fetchedAtCache: Map<string, number>, 154 + fetchedAt: number, 155 + randomCache: Map<string, number>, 153 156 ): ThreadNode { 154 157 if (node.type !== 'post') { 155 158 return node ··· 237 240 } 238 241 } 239 242 240 - if (opts.sort === 'hotness') { 241 - const aHotness = getHotness(a.post) 242 - const bHotness = getHotness(b.post) 243 + // Split items from different fetches into separate generations. 244 + let aFetchedAt = fetchedAtCache.get(a.uri) 245 + if (aFetchedAt === undefined) { 246 + fetchedAtCache.set(a.uri, fetchedAt) 247 + aFetchedAt = fetchedAt 248 + } 249 + let bFetchedAt = fetchedAtCache.get(b.uri) 250 + if (bFetchedAt === undefined) { 251 + fetchedAtCache.set(b.uri, fetchedAt) 252 + bFetchedAt = fetchedAt 253 + } 254 + 255 + if (aFetchedAt !== bFetchedAt) { 256 + return aFetchedAt - bFetchedAt // older fetches first 257 + } else if (opts.sort === 'hotness') { 258 + const aHotness = getHotness(a.post, aFetchedAt) 259 + const bHotness = getHotness(b.post, bFetchedAt /* same as aFetchedAt */) 243 260 return bHotness - aHotness 244 261 } else if (opts.sort === 'oldest') { 245 262 return a.post.indexedAt.localeCompare(b.post.indexedAt) ··· 252 269 return (b.post.likeCount || 0) - (a.post.likeCount || 0) // most likes 253 270 } 254 271 } else if (opts.sort === 'random') { 255 - return 0.5 - Math.random() // this is vaguely criminal but we can get away with it 272 + let aRandomScore = randomCache.get(a.uri) 273 + if (aRandomScore === undefined) { 274 + aRandomScore = Math.random() 275 + randomCache.set(a.uri, aRandomScore) 276 + } 277 + let bRandomScore = randomCache.get(b.uri) 278 + if (bRandomScore === undefined) { 279 + bRandomScore = Math.random() 280 + randomCache.set(b.uri, bRandomScore) 281 + } 282 + // this is vaguely criminal but we can get away with it 283 + return aRandomScore - bRandomScore 284 + } else { 285 + return b.post.indexedAt.localeCompare(a.post.indexedAt) 256 286 } 257 - return b.post.indexedAt.localeCompare(a.post.indexedAt) 258 287 }) 259 288 node.replies.forEach(reply => 260 289 sortThread( ··· 264 293 currentDid, 265 294 justPostedUris, 266 295 threadgateRecordHiddenReplies, 296 + fetchedAtCache, 297 + fetchedAt, 298 + randomCache, 267 299 ), 268 300 ) 269 301 } ··· 277 309 // We want to give recent comments a real chance (and not bury them deep below the fold) 278 310 // while also surfacing well-liked comments from the past. In the future, we can explore 279 311 // something more sophisticated, but we don't have much data on the client right now. 280 - function getHotness(post: AppBskyFeedDefs.PostView) { 281 - const hoursAgo = 282 - (new Date().getTime() - new Date(post.indexedAt).getTime()) / 283 - (1000 * 60 * 60) 312 + function getHotness(post: AppBskyFeedDefs.PostView, fetchedAt: number) { 313 + const hoursAgo = Math.max( 314 + 0, 315 + (new Date(fetchedAt).getTime() - new Date(post.indexedAt).getTime()) / 316 + (1000 * 60 * 60), 317 + ) 284 318 const likeCount = post.likeCount ?? 0 285 319 const likeOrder = Math.log(3 + likeCount) 286 320 const timePenaltyExponent = 1.5 + 1.5 / (1 + Math.log(1 + likeCount))
+9
src/view/com/post-thread/PostThread.tsx
··· 104 104 error: threadError, 105 105 refetch, 106 106 data: {thread, threadgate} = {}, 107 + dataUpdatedAt: fetchedAt, 107 108 } = usePostThreadQuery(uri) 108 109 109 110 const treeView = React.useMemo( ··· 171 172 () => new Set<string>(), 172 173 ) 173 174 175 + const [fetchedAtCache] = React.useState(() => new Map<string, number>()) 176 + const [randomCache] = React.useState(() => new Map<string, number>()) 174 177 const skeleton = React.useMemo(() => { 175 178 const threadViewPrefs = preferences?.threadViewPrefs 176 179 if (!threadViewPrefs || !thread) return null ··· 183 186 currentDid, 184 187 justPostedUris, 185 188 threadgateHiddenReplies, 189 + fetchedAtCache, 190 + fetchedAt, 191 + randomCache, 186 192 ), 187 193 currentDid, 188 194 treeView, ··· 199 205 hiddenRepliesState, 200 206 justPostedUris, 201 207 threadgateHiddenReplies, 208 + fetchedAtCache, 209 + fetchedAt, 210 + randomCache, 202 211 ]) 203 212 204 213 const error = React.useMemo(() => {