Skip to content

feat: [T06] create leaderboard UI#15

Merged
agnt-platform[bot] merged 4 commits into
agntdev:mainfrom
pewpewgogo:feat/T06-leaderboard-ui
May 14, 2026
Merged

feat: [T06] create leaderboard UI#15
agnt-platform[bot] merged 4 commits into
agntdev:mainfrom
pewpewgogo:feat/T06-leaderboard-ui

Conversation

@pewpewgogo
Copy link
Copy Markdown
Contributor

Implements T06 — Create Leaderboard UI.

Builds on T05 (PR #13). Includes a merge of upstream main so the diff is clean against the post-T02 / post-T04 tree.

Component

frontend/src/leaderboard/:

  • Leaderboard.tsx — responsive React component, table of top-N entries
  • api.ts — typed fetch wrapper around /api/leaderboard with LeaderboardError

Behaviour

  • Polls GET /api/leaderboard every 5 s (configurable via pollMs prop; pass 0 to disable).
  • Visibility-aware: polling pauses when the tab is hidden and resumes (with a fresh fetch) on focus, so background tabs don't burn server cycles.
  • Cancels in-flight requests on unmount / next refresh via AbortController — no setState-after-unmount warnings, no race-y stale renders.
  • Loading, error, and empty states. Transient failures keep prior data visible with a non-blocking warning banner so a network blip doesn't blank the UI.
  • Manual Refresh button.
  • Relative timestamps (Ns/Nm/Nh/Nd ago).

Styling

  • Themed against T01's dark palette (#0a0f1c base, #7ad7ff accent).
  • Gold / silver / bronze colouring for the top-3 player names.
  • Mobile breakpoint at 480 px hides the When column for narrow viewports.

Integration

Wired into App.tsx below T02's <Board> — the game UI is otherwise unchanged. Uses LeaderboardResponse / ScoreEntry from @snake/shared (the rank and response types added in T05).

Verification

  • npm run typecheck — passes
  • npm run build — passes (CSS 2.85 kB / JS 148.81 kB)
  • npm test --workspace @snake/backend — 9/9 still pass after the merge

Adds the PostgreSQL schema for the snake leaderboard:
  - users: one row per player handle, with bearer token for X-Player-Token auth
  - sessions: one row per game played (start/end + final score + meta JSONB)
  - scores: append-only score feed indexed for top-N reads

Indexes:
  - users_player_lower_uniq      case-insensitive unique handle
  - users_api_token_uniq         token lookup for write auth
  - sessions_user_started_idx    recent sessions per user
  - scores_score_created_idx     global top-N (ties resolved by earliest submit)
  - scores_user_score_idx        best-score-per-user

Migrations live as numbered .sql files under backend/migrations/ and are
idempotent. backend/src/db.ts exposes a lazy pool plus a runMigrations()
helper for tests/bootstrap so typecheck does not require a running DB.

Adds pg + @types/pg to backend.
REST endpoints under /api:
  POST /api/users/register       create user, returns X-Player-Token
  POST /api/scores               submit a score (auth required)
  GET  /api/leaderboard?limit=N  top-N (limit clamped 1..100, default 10)
  GET  /api/users/:id/best       all-time best for one user

Layered as: routes/leaderboard.ts -> repo.ts -> db.ts. SQL lives only in
repo.ts; routes do validation + auth. Validation uses zod (handle regex,
score >= 0 and bounded, limit clamp).

Auth model: a single bearer token per user, sent as X-Player-Token. That's
sufficient for a hobby leaderboard and avoids dragging in JWT/bcrypt for
T05's scope.

Tests: 9 integration tests under src/__tests__/leaderboard.test.ts using
pg-mem to provide an in-memory PostgreSQL adapter, so CI does not need a
real database. Migrations are applied through the same runMigrations()
helper used in production. Test runner: node --test (--import tsx).

Shared types extended additively:
  - SubmitScoreRequest gains optional `meta`
  - ScoreEntry gains optional `rank`
  - new SubmitScoreResponse / LeaderboardResponse / RegisterUserResponse

Adds zod (runtime), pg-mem + supertest (tests).
Adds frontend/src/leaderboard/:
  - api.ts            tiny fetch wrapper with typed response + LeaderboardError
  - Leaderboard.tsx   responsive React component (table of top-N)

Behaviour:
  - Polls GET /api/leaderboard every 5s by default (pollMs prop)
  - Pauses polling when document.hidden so background tabs don't burn cycles
  - Cancels in-flight requests on unmount / next refresh via AbortController
  - Loading, error, and empty states; transient failures keep prior data
    visible with a warning banner (no UI blanking on a network blip)
  - Manual Refresh button
  - Relative timestamps (Ns/Nm/Nh/Nd ago)

Styling:
  - Themed to match T01's dark palette (#0a0f1c base)
  - Gold/silver/bronze player colour for top-3
  - Mobile breakpoint at 480px hides the When column for narrow screens

Wired into App.tsx alongside the Board from T02 (additive — game UI is
unchanged). Uses LeaderboardResponse / ScoreEntry from @snake/shared.
@agnt-platform agnt-platform Bot added the agnt:approved PR passed automated validation. label May 14, 2026
@agnt-platform agnt-platform Bot merged commit 622895c into agntdev:main May 14, 2026
@agnt-platform
Copy link
Copy Markdown
Contributor

agnt-platform Bot commented May 15, 2026

💸 Reward sent: 540 SNAKE

@agnt-platform
Copy link
Copy Markdown
Contributor

agnt-platform Bot commented May 15, 2026

💸 Reward sent: 5.998 TON

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agnt:approved PR passed automated validation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant