From e9c761b9a29dc1655d9a459b13e34a09ba30bfd4 Mon Sep 17 00:00:00 2001 From: RMP Ops Date: Tue, 30 Jun 2026 22:46:43 +0000 Subject: [PATCH] fix(feed): pin client highlights atop Industry News for 48h --- src/app/home-page/components/ArticleFeed.tsx | 11 ++++++----- src/lib/promoted-articles.ts | 20 ++++++++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/app/home-page/components/ArticleFeed.tsx b/src/app/home-page/components/ArticleFeed.tsx index 8a86163..b6470df 100644 --- a/src/app/home-page/components/ArticleFeed.tsx +++ b/src/app/home-page/components/ArticleFeed.tsx @@ -171,14 +171,18 @@ export default function ArticleFeed({ initialPosts = [] }: ArticleFeedProps) { return matchCat && matchSource && matchSearch && matchDate; }); - // Recent: strict publish-date order (newest first). Promoted badges stay - // on cards but never reorder the feed. Trending/commented keep promo tier. + // Sort filtered articles based on sortOrder. For every order we honor + // the promoted-tier hierarchy first (premium > advertiser > general) so + // client highlights stay atop Industry News for 48h, then sink by date. function promotedTier(a: ArticleItem): number { if (a.promoted_kind === "premium") return 0; if (a.promoted_kind === "client") return 1; return 2; } const sortedFiltered = [...filtered].sort((a, b) => { + const ta = promotedTier(a); + const tb = promotedTier(b); + if (ta !== tb) return ta - tb; if (sortOrder === "recent") { const da = parseArticleDate(a.date); const db = parseArticleDate(b.date); @@ -187,9 +191,6 @@ export default function ArticleFeed({ initialPosts = [] }: ArticleFeedProps) { if (!db) return -1; return db.getTime() - da.getTime(); } - const ta = promotedTier(a); - const tb = promotedTier(b); - if (ta !== tb) return ta - tb; if (sortOrder === "trending") { // Trending: FEATURED category gets a boost, then by recency const featuredBoost = (art: ArticleItem) => (art.category === "FEATURED" ? 2 : 0); diff --git a/src/lib/promoted-articles.ts b/src/lib/promoted-articles.ts index 6a1530d..117d101 100644 --- a/src/lib/promoted-articles.ts +++ b/src/lib/promoted-articles.ts @@ -260,7 +260,23 @@ export async function decorateHomepageFeed( return { article: a, promoted_kind: null, promoted_until: null }; }); - // Preserve caller order (wp_published_at desc). Promotion flags are visual - // badges only — the Industry News feed stays newest-to-oldest. + // Sort: premium first (by position asc), then client (by date desc), then general (by date desc) + function tier(d: DecoratedArticle): number { + if (d.promoted_kind === "premium") return 0; + if (d.promoted_kind === "client") return 1; + return 2; + } + function dateTs(d: DecoratedArticle): number { + const t = new Date(d.article.date).getTime(); + return isNaN(t) ? 0 : t; + } + decorated.sort((a, b) => { + const ta = tier(a); + const tb = tier(b); + if (ta !== tb) return ta - tb; + if (ta === 0) return (a.promoted_position ?? 999) - (b.promoted_position ?? 999); + return dateTs(b) - dateTs(a); // newest first + }); + return decorated; }