Skip to content

Leaderboard and private chat - #71

Merged
aichannode merged 61 commits into
mainfrom
feat/leaderboard-and-chat
Aug 7, 2026
Merged

Leaderboard and private chat#71
aichannode merged 61 commits into
mainfrom
feat/leaderboard-and-chat

Conversation

@heyradcode

Copy link
Copy Markdown
Collaborator

Description

Builds tier 1 of docs/plan-future-features-roadmap.md: the leaderboard (§1) and
player-to-player chat (§2), plus the pet-art and layout work that came out of using
them. 62 commits, 257 files.

Leaderboard

Ranks on the merged battle record, not on pet_roster.win_count / loss_count.
The roadmap's original proposal assumed the roster counters, which froze at §L Phase 6
when battles left the chain: ranking on them returns zero rows on this deployment.
docs/plan-future-features-roadmap.md §1 carries a "Built" banner recording the
correction.

The merge happens inside the query rather than as an overlay afterwards, for the same
reason findReadyOpponents does it: the ordering is the merge, and an overlay applied
to a page can only rewrite rows the query already chose. The cost is no gRPC fast path,
since indexer-go's cache has no view of pet_battle_progress.

Worth review:

  • Rank comes from ROW_NUMBER() inside a subquery, with search applied outside it.
    A rank is a position on the whole board, so filtering before ranking would renumber
    matches from one and a pet's rank would depend on what someone typed.
  • ILIKE metacharacters are escaped. Without it a pet named 100% matches every
    row. Verified against fixed values in the database.
  • Search matches a pet name or a wallet address on both boards, so nobody has to know
    which board indexes what. The player board aggregates its owners' pet names in the
    ranked subquery and strips them from the result.
  • UI is a podium over a ledger: the top three are a result, the rest are a record.
    Podium membership follows rank <= 3, so page 2 grows none and a searched leader
    keeps its medal.

Private chat (v1)

Married-pet threads: chat_thread, chat_message, chat_read, chat_reaction, a
notification-only WebSocket channel, and REST reads that reauthorize every time.

Worth review:

  • Access is derived per request, never stored. chat_thread deliberately does not
    record the marriage that justifies it, so a divorce closes the conversation with no
    revocation step that could be forgotten. A non-participant gets 404, not 403: 403
    would confirm a thread id to anyone probing.
  • One upgrade listener for all WebSocket channels (backend/src/ws/channel.ts).
    Constructing new WebSocketServer({ server, path }) per channel attaches one listener
    each, so every connection is handled twice and the client gets two HTTP 101 responses.
    That broke both channels, and per-channel tests could not catch it because each builds
    its own server. tests/ws/channel.test.ts is the regression test.
  • Read receipts are a per-participant watermark, so a thread costs two rows rather
    than two per message.
  • thread-read and thread-reacted are distinct frame types. Both name a message
    the client already holds, which is the point of each, so folding them into
    thread-updated would make every one look like the echo of the client's own send and
    no tick or reaction would ever appear.
  • The reaction emoji list is shared between client and server, so the picker cannot
    offer what the API refuses, and it is append-only: a reaction is removed by tapping it
    again, and that tap is validated against the list.

Verification

pnpm lint clean across backend, shared, protocol, verifier, frontend, website, mobile.
814 backend, 480 shared and 377 frontend tests pass; backend and frontend build clean.

Behaviour that could not be tested in unit tests was checked against the live database
and in headless Chrome: ranks surviving a search, wildcard escaping, the read watermark
refusing to move backwards, the reaction toggle, and the panel and bubble layouts with a
control to prove the fix does the work.

@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
do-not-stop-frontend Ready Ready Preview Aug 7, 2026 7:25pm
do-not-stop-website Ready Ready Preview Aug 7, 2026 7:25pm

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

🧪 Coverage

Package Statements Branches Functions Lines Overall
backend 86.00% (2127/2473) 78.55% (1066/1357) 85.13% (418/491) 86.54% (2014/2327) 84.61% (5625/6648)
frontend 70.53% (1592/2257) 72.07% (1355/1880) 67.86% (397/585) 71.26% (1436/2015) 70.95% (4780/6737)
shared 58.42% (1463/2504) 59.44% (922/1551) 59.05% (300/508) 60.26% (1344/2230) 59.31% (4029/6793)

@aichannode aichannode left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks nice! 👍

@aichannode
aichannode merged commit f92ef7a into main Aug 7, 2026
11 checks passed
@aichannode
aichannode deleted the feat/leaderboard-and-chat branch August 7, 2026 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants