Compare commits

..
2 Commits
Author SHA1 Message Date
Michilis e79d54ba34 Merge pull request 'Order the latest-reviews strip by created_at alone.' (#9) from dev into main
Reviewed-on: #9
2026-08-25 15:45:39 +00:00
michilisandClaude Opus 5 1eade490c8 Order the latest-reviews strip by created_at alone.
The home page carousel floated reviews whose author had a kind 0 ahead of
everything else, capped at 90 days old, so a named review from two months ago
sat between two reviews from this week. Under a heading that says "Latest
reviews", with each card's foot printing the very timestamp being overruled,
that reads as broken rather than as curation.

Strict recency now, newest first, as the last thing that happens to the list,
with the event id breaking ties so two builds of the same events agree. Names
and avatars are still resolved and still shown; they no longer decide the
order.

The candidate scan stops at the limit rather than gathering twelve times it:
the scan already runs newest first, so nothing further down can outrank what
it has, and the build resolves ten profiles per locale instead of a hundred
and twenty.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 17:23:22 +02:00
+16 -28
View File
@@ -55,19 +55,6 @@ export interface BuildReview {
* characters or matching a common test string do not qualify. They still appear in
* full on the mint page, nothing is hidden, this strip is just a shop window.
*/
/** How many candidates to gather per card shown, so the ordering has a real choice. */
const POOL_FACTOR = 12;
/**
* How recent a review must still be to be promoted for having a named author.
*
* The ordering below prefers reviews whose author has a kind 0, and this is the leash
* on that preference. Without it the strip would headline a named review from two
* years ago under a heading that says "Latest reviews"; with it, a named review from
* within the season can lead and anything older cannot.
*/
const PROMOTE_MAX_AGE_S = 90 * 24 * 60 * 60;
const TEST_STRINGS = new Set([
'test', 'testing', 'test test', 'hello', 'hi', 'ok', 'okay', 'good', 'nice',
'great', 'cool', 'gm', 'a', '.', '..', '...', 'asdf', 'qwerty', '123',
@@ -227,29 +214,30 @@ export async function fetchLatestReviews(
profile: null,
});
if (candidates.length >= limit * POOL_FACTOR) break;
// The scan runs newest first, so the first `limit` survivors are the latest
// `limit` reviews. Nothing further down the list can outrank them.
if (candidates.length >= limit) break;
}
const profiles = await fetchProfiles(candidates.map((c) => c.pubkey));
for (const review of candidates) review.profile = profiles.get(review.pubkey) ?? null;
/*
* Recent reviews with a named author come first; everything else stays newest
* first behind them.
* Strict recency, newest first, as the last thing that happens to this list.
*
* Most review keys have published exactly one event in their life: the review
* itself. Nothing can be fetched for them, so a strict recency order fills this
* strip with anonymous npubs while a named review sits a few rows below the
* cut. This is the home page shop window, which already drops junk bodies, so
* it prefers a review a reader can attach a person to. Nothing is hidden:
* every review is on its mint page, and PROMOTE_MAX_AGE_S keeps "latest"
* meaning latest.
* The heading says "Latest reviews", so the order under it is the event
* `created_at` and nothing else. This used to float reviews whose author had a
* kind 0 to the front, which read as broken on the page: a named review from two
* months ago sat between two reviews from this week, and the card feet said so,
* because the "2mo ago" label is formatted from the very same `created_at` this
* sorts on. Author names are still resolved and still shown — they just no longer
* decide the order.
*
* Sorted here rather than left to the scan above, so that the invariant holds
* whatever the gathering loop does later, and `id` breaks ties between two events
* that share a second so two builds of the same events agree.
*/
const cutoff = Math.floor(Date.now() / 1000) - PROMOTE_MAX_AGE_S;
const promoted = (review: BuildReview): number =>
review.profile?.found && review.created_at >= cutoff ? 1 : 0;
candidates.sort((a, b) => promoted(b) - promoted(a) || b.created_at - a.created_at);
candidates.sort((a, b) => b.created_at - a.created_at || a.id.localeCompare(b.id));
return candidates.slice(0, limit);
} catch {