MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-425

6.13.4 Ranking — trending (recent upvotes/activity) / popular (total upvotes/viewers) / recent-new, deterministic + paginatable

Done
Description

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.