feat(leaderboard): public profiles, handles, achievements and tier scoring
Moves the leaderboard off leaderboard_participants onto a handles + public_profiles pair, adds per-profile achievements, and replaces the min()-of-axes tier rule with a weighted score.
Profiles now sit at the root of the marketing namespace (superset.sh/<handle>), with a reserved-handle list guarding real routes, a markdown view at /md/user/<handle> for agents, search and self-pinning on the board, and a Super Fights page.
Decisions worth knowing
Several of these were previously explained in code comments, which this repo does not keep — recording them here instead.
- Cost ceilings recalibrated from
[15, 9, 7, 3.5]to[2000, 750, 300, 100]. The originals sat below the 2nd percentile of the live board: the median participant spends $452 per merged agent PR, so clearing $9 would have taken 419 merges in a month, capping every account with any PR telemetry at the bottom tier. Theefficientachievement was recalibrated to match ([750, 300, 100]) andCATALOG_VERSIONbumped so existing profiles re-evaluate on their next publish. leaderboard_participantsis kept declared but unused. It is never dropped, renamed or written to here, so the rows remain a fallback until the copy is confirmed good and the table is dropped deliberately. Note it stops receiving writes as of this PR, so it goes stale immediately — it is a rollback window, not a live mirror.- Migrations are ordered create, then copy-and-repoint.
0107creates the tables.0108copies the rows and then moves theleaderboard_dailyforeign keys ontopublic_profiles, in that order and in one transaction — the copy has to land before the constraints move, because the new FKs validate everyleaderboard_dailyrow againstpublic_profiles. Keeping both in one migration means you cannot end up with the data moved but the constraints not. The FK statements are drizzle-generated; theINSERT … SELECTcopy is hand-written on top. - Profiles are rewritten in the proxy rather than living at
[lang]/[handle].(legal)is a route group, so(legal)/[slug]already occupies "one dynamic segment under[lang]" and Next rejects a second under a different param name. Rewriting keeps the legal pages statically generated while profiles revalidate. RESERVED_HANDLESdecides what is a profile. A bare segment resolves to a profile only when it is not a route, a locale, or a reserved word.reserved-handles.test.tsfails the build if a new route directory under[lang]is missing from the list — without it, shipping/careerswould silently 404 into a profile lookup./api/vieweris a same-origin route so the browser attaches the session cookie without CORS credentials. The marketing pages stay ISR: personalisation is a client fetch, never a per-user render.
Known gaps
Deliberately left, rather than overlooked:
[lang]/fighthas no Lingui coverage — it renders English in every locale. It is not inENFORCED_DIRS, so CI does not catch it.public_profiles.github_handleis only written byjoin, so participants migrated by0108have it null until they next go through that flow. Their profiles show no verified GitHub link./api/vieweris a REST route rather than tRPC. It proxies toleaderboard.viewer, so no business logic lives in it, but the response shape is asserted rather than derived.searchParticipantsinterpolates the query into anILIKEpattern without escaping%/_, and has no minimum length.- The markdown profile renders
biointo an agent-facing document with onlyhttps?://stripped, so markdown structure in a bio survives.
Verification
typecheck · check:i18n (all 17 locales compile strict) · lint:fix · sherif all pass. Scoped tests pass for trpc, marketing, i18n and the desktop leaderboard.
Repo-wide bun test has 303 failures that are environmental and present independent of this change — globalThis.navigator is read-only in this Bun version, and the git-backed desktop tests need real git operations.
The migrations have not been applied against a database. NEON_API_KEY is empty in this environment, so they were generated and verified drift-free (drizzle-kit generate reports no schema changes, snapshot chain intact) but never executed. They should be applied on a Neon branch before merge.
https://claude.ai/code/session_01BFDRwxVyxSyQEf9ErJYAcA
Summary by CodeRabbit
-
New Features
- Added public developer profiles with bios, social links, contribution graphs, achievements, tier objectives, and agent-friendly views.
- Added leaderboard search, pinned viewer results, metric-aware filtering, and profile navigation.
- Added achievement notifications and profile editing for bios, X handles, and websites.
- Added “Super Fights” with dinosaur battles, combat logs, sharing, and rematches.
-
Improvements
- Updated tier scoring and progress displays with weighted metrics and clearer objectives.
- Expanded localized copy across supported languages.
- Improved profile URL handling, sitemap coverage, and temporary error responses.