Estimate: 60m · Depends on: 6.13.2, 6.12.6
The three ranks over the 6.13.2 directory, computed from the 6.12.6 votes + project activity — the GitHub-Trending / GitLab-Explore-tabs set. Each rank is a DETERMINISTIC total order and rides the 6.13.2 cursor (so the tab is paginatable, never load-all).
- Trending — RECENT demand: upvotes + project activity inside a recency WINDOW (the GitHub daily / weekly / monthly bucket — decide the window / whether the bucket is selectable), so a freshly-surging public project rises above a higher-lifetime-but-stale one. Computed over the 6.12.6 vote rows’ timestamps + the project activity signal, NOT a raw lifetime count.
- Popular — LIFETIME demand: total upvotes + total viewers (the "most-starred" tab). A stable order over the lifetime 6.12.6 totals.
- Recent / New — newly-made-public: ordered by the moment the project became
public (the made-public timestamp — the "newest" axis). (If no made-public timestamp exists yet, this card adds it as part of the 6.12.3 access foundation’s data — a madePublicAt set when access flips to public.)
- Deterministic + paginatable (finding #57): each rank is a stable TOTAL order — the rank key with a stable tiebreak on project id — so the 6.13.2 keyset cursor never skips/duplicates a row across pages. Ranking is computed at the READ layer over the 6.12.6 signals (an aggregate / window over the vote + activity rows), NOT precomputed into a denormalized column this story must keep fresh; if the aggregate is too costly at scale, a bounded materialized read is the documented durable shape (still deterministic + cursored), not a load-all-then-sort-in-memory shortcut.
Stay 4-layer: the route parses the rank (+ window) param + calls ONE service method selecting the rank’s sort key over the 6.13.2 cursored read; the aggregate/window lives in the service/repository read; no raw Prisma in the route.
Acceptance criteria
- Trending orders by recent (windowed) upvotes + activity (a project given a fresh upvote burst rises above a higher-lifetime-but-stale one — asserted with timestamped seed votes); Popular orders by total upvotes / viewers; Recent orders by the made-public timestamp (newest first).
- Each rank is a deterministic total order (stable id tiebreak) and rides the 6.13.2 cursor — paging a rank skips/duplicates no row; no rank loads-all-then-sorts in memory.
- Ranking is computed at the read layer over the 6.12.6 vote/activity signals (no denormalized rank column this story must keep fresh; if a bounded materialized read is used it stays deterministic + cursored).
- 4-layer respected (the aggregate/window in the service/repository, no raw Prisma in the route); composes with the 6.13.3 search/tag filter.
Context refs
- 6.13.2 (the cursored directory read the ranks order) + 6.12.6 (the upvote / viewer / activity signals the ranks compute from).
scripts/plan-seed/data/story-6.12.ts § 6.12.6 (the PublicRequestVote + activity demand signals) + § 6.12.3 (the access foundation the madePublicAt lands with).
- GitHub Trending (https://github.com/trending) — the daily/weekly/monthly recency bucket + weighted-recent-signal trending; GitLab Explore (https://gitlab.com/explore/projects/trending) — the Trending / Most-starred / Newest tabs mirrored.
- finding #57 (deterministic + cursor-paginated, no load-all);
motir-core/CLAUDE.md § 4-layer.