fix(popular-posts): rank by time-decayed popularity instead of raw reply count

This commit is contained in:
plebeius
2026-03-03 14:51:42 +08:00
parent 3a9b5ab75f
commit 9216791505
+21 -8
View File
@@ -5,9 +5,24 @@ import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
const MAX_POSTS = 8;
const MAX_PER_SUB = 3;
// Activity relevance halves every 3 days
const HALF_LIFE_SECONDS = 72 * 3600;
/**
* Ranked by replyCount instead of a static threshold so the box
* adapts to both low- and high-activity periods.
* Time-decayed popularity: replyCount divided by age of latest
* activity so a stale post with many old replies loses to a newer
* post with a few recent replies.
*/
function popularityScore(post: Comment, nowSeconds: number): number {
const lastActivity = post.lastReplyTimestamp ?? post.timestamp ?? 0;
const ageSeconds = Math.max(0, nowSeconds - lastActivity);
const replies = post.replyCount ?? 0;
return Math.max(replies, 0.1) / (1 + ageSeconds / HALF_LIFE_SECONDS);
}
/**
* Ranked by time-decayed popularity so the box surfaces posts with
* recent engagement rather than stale all-time reply leaders.
*
* Grow-only commit: once a post enters the grid it never shifts or
* disappears — new posts fill remaining slots until the cap is reached.
@@ -33,6 +48,8 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
const candidates = useMemo(() => {
if (committedRef.current.posts.length >= MAX_POSTS) return [];
const nowSeconds = Math.floor(Date.now() / 1000);
try {
const uniqueLinks = new Set<string>();
const allPosts: Comment[] = [];
@@ -57,15 +74,11 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
}
}
subPosts.sort((a, b) => (b.replyCount ?? 0) - (a.replyCount ?? 0));
subPosts.sort((a, b) => popularityScore(b, nowSeconds) - popularityScore(a, nowSeconds));
allPosts.push(...subPosts.slice(0, MAX_PER_SUB));
}
// Primary: most replies first. Tiebreaker: newest first.
allPosts.sort((a, b) => {
const diff = (b.replyCount ?? 0) - (a.replyCount ?? 0);
return diff !== 0 ? diff : (b.timestamp ?? 0) - (a.timestamp ?? 0);
});
allPosts.sort((a, b) => popularityScore(b, nowSeconds) - popularityScore(a, nowSeconds));
return allPosts;
} catch (err) {