Skip to content

feat(leaderboard): per-account and per-model leaderboard (Phase 12) - #22

Merged
withrvr merged 3 commits into
masterfrom
phase-12-leaderboard
Sep 14, 2026
Merged

withrvr merged 3 commits into
masterfrom
phase-12-leaderboard

Conversation

@withrvr

@withrvr withrvr commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • DECISIONS.md §11: "leaderboard per human account and per model, with aggregate admin views."
  • app.leaderboard joins game_record to room_seat on room_id -- a room plays exactly one game for its whole lifetime (RoomService.start() refuses anything but "waiting", and nothing ever reopens one), so room_seat already is that game's durable participant record. No new snapshot table needed. Only outcome == "finished" games count.
  • GET /leaderboard/accounts?game=, GET /leaderboard/models?game=, both gated by a new leaderboard:view permission granted to every default role (admin/player/spectator) -- a leaderboard is meant to be seen, not admin-only.
  • /leaderboard page: Accounts/Models tabs sharing one column set, plus a game filter. Extracted CreateRoomDialog's hardcoded game catalog into lib/games.ts so the filter could reuse it.
  • Investigated and reverted a plausible-looking "fix" in RoomService.start() (persisting a resolved default model back onto an agent seat) after empirically verifying RoomService.create() already does this at seat-creation time -- the bug doesn't exist. See docs/leaderboard.md and PROGRESS.md's Phase 12 section for the full account.
  • New Alembic migration (30c74160ca50) seeding the new permission, since the original Phase 6 seed migration's own constants are live and re-running it would collide.

Test plan

  • Backend suite: 779 tests green (uv run pytest tests)
  • prek run --all-files clean (ruff, mypy, ty, biome)
  • bun run build / bun run lint clean
  • Full Playwright suite: 59 tests green against the real docker-compose stack, including 2 new leaderboard specs
  • New backend tests: aggregation correctness (wins/games-played across rooms, stopped games excluded, model grouping ignores persona, game filter), both HTTP routes, permission gate

Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW

RoomSeat is already each finished game's durable participant record (a
room plays exactly one game for its whole lifetime), so app.leaderboard
just joins game_record to room_seat on room_id -- no new snapshot table.
Two aggregate views (leaderboard_by_account, leaderboard_by_model), an
optional per-game filter, a new leaderboard:view permission granted to
every default role, and a /leaderboard page with an Accounts/Models tab
split.

Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW
…d on a fresh DB

a1b2c3d4e5f6 (Phase 6's seed migration) imports ALL_PERMISSIONS/
DEFAULT_ROLES live from permissions.py, so on any fresh database it
already seeds leaderboard:view itself before 30c74160ca50 runs -- a
plain bulk_insert then collided on the unique constraint (real CI
failure on PR #22, never reproduced locally since the dev DB predates
this permission). Switched both inserts to
ON CONFLICT DO NOTHING; verified against two throwaway Postgres
containers covering both a fresh DB and one migrated before this
permission existed.

Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW
- leaderboard.tsx: add aria-label to the game filter, distinguish
  loading/error from a genuine empty result (shared LeaderboardTable)
- leaderboard.py: correct docstring overclaiming finished games always
  have a winner_seat
- persistence.md: document the RBAC-seed-migration trap this PR hit,
  so future phases don't rediscover it

Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW
@withrvr
withrvr merged commit 484b79e into master Sep 14, 2026
23 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant