Feed: strict newest-first sort; promos are badges only

This commit is contained in:
2026-06-29 05:45:46 +00:00
parent 7a84c94c3b
commit aa0d9540e8
3 changed files with 8 additions and 27 deletions

View File

@@ -33,8 +33,7 @@ export async function GET(request: Request) {
source: "imported" as const, source: "imported" as const,
})); }));
// Decorate + reorder so premium and current-advertiser stories surface // Decorate with promotion badges; preserve wp_published_at order.
// at the top of the feed.
const decorated = await decorateHomepageFeed("avbeat", base); const decorated = await decorateHomepageFeed("avbeat", base);
const payload = decorated.map((d) => ({ const payload = decorated.map((d) => ({
...d.article, ...d.article,

View File

@@ -171,19 +171,14 @@ export default function ArticleFeed({ initialPosts = [] }: ArticleFeedProps) {
return matchCat && matchSource && matchSearch && matchDate; return matchCat && matchSource && matchSearch && matchDate;
}); });
// Sort filtered articles based on sortOrder. For every order we honor // Recent: strict publish-date order (newest first). Promoted badges stay
// the promoted-tier hierarchy first (premium > advertiser > general) so // on cards but never reorder the feed. Trending/commented keep promo tier.
// paid placements and advertiser news always lead. Within each tier the
// user's chosen sort applies.
function promotedTier(a: ArticleItem): number { function promotedTier(a: ArticleItem): number {
if (a.promoted_kind === "premium") return 0; if (a.promoted_kind === "premium") return 0;
if (a.promoted_kind === "client") return 1; if (a.promoted_kind === "client") return 1;
return 2; return 2;
} }
const sortedFiltered = [...filtered].sort((a, b) => { const sortedFiltered = [...filtered].sort((a, b) => {
const ta = promotedTier(a);
const tb = promotedTier(b);
if (ta !== tb) return ta - tb;
if (sortOrder === "recent") { if (sortOrder === "recent") {
const da = parseArticleDate(a.date); const da = parseArticleDate(a.date);
const db = parseArticleDate(b.date); const db = parseArticleDate(b.date);
@@ -192,6 +187,9 @@ export default function ArticleFeed({ initialPosts = [] }: ArticleFeedProps) {
if (!db) return -1; if (!db) return -1;
return db.getTime() - da.getTime(); return db.getTime() - da.getTime();
} }
const ta = promotedTier(a);
const tb = promotedTier(b);
if (ta !== tb) return ta - tb;
if (sortOrder === "trending") { if (sortOrder === "trending") {
// Trending: FEATURED category gets a boost, then by recency // Trending: FEATURED category gets a boost, then by recency
const featuredBoost = (art: ArticleItem) => (art.category === "FEATURED" ? 2 : 0); const featuredBoost = (art: ArticleItem) => (art.category === "FEATURED" ? 2 : 0);

View File

@@ -260,23 +260,7 @@ export async function decorateHomepageFeed<T extends PromotableArticle>(
return { article: a, promoted_kind: null, promoted_until: null }; return { article: a, promoted_kind: null, promoted_until: null };
}); });
// Sort: premium first (by position asc), then client (by date desc), then general (by date desc) // Preserve caller order (wp_published_at desc). Promotion flags are visual
function tier(d: DecoratedArticle<T>): number { // badges only — the Industry News feed stays newest-to-oldest.
if (d.promoted_kind === "premium") return 0;
if (d.promoted_kind === "client") return 1;
return 2;
}
function dateTs(d: DecoratedArticle<T>): 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; return decorated;
} }