diff --git a/src/hooks/use-popular-posts.ts b/src/hooks/use-popular-posts.ts index 95187145..b9eeede3 100644 --- a/src/hooks/use-popular-posts.ts +++ b/src/hooks/use-popular-posts.ts @@ -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(); 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) {