feat: add multiplayer leaderboard with Convex backend#1
feat: add multiplayer leaderboard with Convex backend#1roomote-v0[bot] wants to merge 3 commits intomainfrom
Conversation
- Add Convex schema with leaderboard table (playerName, tileset, longestCombo, isPerfectScore, completedAt) - Add Convex query (getTopScores) and mutation (submitScore) functions - Create Leaderboard component with global/tileset filtering and top 20 scores - Update VictoryModal with score submission form for Classic mode wins - Add "View Leaderboard" button to GameModeScreen - Wrap app with ConvexProvider (graceful fallback when VITE_CONVEX_URL not set) - Add vite-env.d.ts for import.meta.env types
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Re-reviewed after 798f1c9 (leaderboard browser with settings discovery). The board selector UI is clean, but the 3 previously flagged issues remain unresolved and
Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues. |
| import React, { useEffect } from 'react'; | ||
| import React, { useEffect, useState } from 'react'; | ||
| import confetti from 'canvas-confetti'; | ||
| import { useMutation } from 'convex/react'; |
There was a problem hiding this comment.
useMutation is called unconditionally here, but when VITE_CONVEX_URL is not set, index.tsx renders the app without a ConvexProvider. This will throw a "Could not find Convex client" error as soon as the component mounts during gameplay, crashing the app. The PR states the app "falls back gracefully" without the env var, but this hook call breaks that contract. You'd need to either conditionally render this component, or guard the hook usage (e.g., a wrapper component that checks for Convex availability).
Fix it with Roo Code or mention @roomote and request a fix.
| @@ -0,0 +1,152 @@ | |||
| import React, { useState } from "react"; | |||
| import { useQuery } from "convex/react"; | |||
| import { api } from "../convex/_generated/api"; | |||
There was a problem hiding this comment.
Same issue as VictoryModal - useQuery requires a ConvexProvider ancestor. When VITE_CONVEX_URL is not set and a user clicks "View Leaderboard" on the mode selection screen, this component mounts without a provider and crashes. Consider either hiding the leaderboard button when Convex is unavailable, or wrapping this component so the hook isn't called outside a provider.
Fix it with Roo Code or mention @roomote and request a fix.
| args: { | ||
| playerName: v.string(), | ||
| tileset: v.string(), | ||
| longestCombo: v.number(), |
There was a problem hiding this comment.
longestCombo is validated only as v.number(), so a malicious client can submit negative values, fractional numbers, Infinity, or NaN. Consider adding a check that the value is a non-negative integer (e.g., if (!Number.isInteger(args.longestCombo) || args.longestCombo < 0) throw ...) to prevent bogus leaderboard entries.
Fix it with Roo Code or mention @roomote and request a fix.
- Show leaderboard inline on the victory modal for both Classic and Custom modes - Custom mode games get their own leaderboard per unique settings combination - Settings hash only encodes diffs from defaults (future-proof, no versioning) - Tileset removed from leaderboard grouping (cosmetic, separate menu already) - Score submission available for all game modes (not just Classic) - Leaderboard overlay filters by Classic vs current Custom settings hash
- Add listBoards query that aggregates all unique settings hashes with counts and top combos - Leaderboard overlay now shows clickable tabs for every board that has scores - Classic board always listed first, then sorted by popularity - Each tab shows a tooltip with score count and top combo - Pre-selects the relevant board based on current game context
| const all = await ctx.db.query("leaderboard").take(5000); | ||
|
|
||
| const map = new Map< |
There was a problem hiding this comment.
listBoards does an unindexed full-table scan capped at 5000 rows. Since Convex queries are reactive, every score insertion triggers a re-scan for all connected clients. More importantly, once the table exceeds 5000 entries the results become silently wrong: boards with only newer entries disappear, counts are underreported, and topCombo values may miss the actual best score. Consider aggregating board metadata at write time (e.g. a separate boards table updated in submitScore) so this query reads a small, bounded dataset instead of scanning the full leaderboard.
Fix it with Roo Code or mention @roomote and request a fix.
Adds a multiplayer leaderboard powered by Convex, allowing players to submit and view scores from Classic mode games.
Changes
Backend (Convex)
convex/schema.ts- Defines theleaderboardtable with fields for playerName, tileset, longestCombo, isPerfectScore, and completedAt. Includes indexes for querying by tileset and by combo score.convex/leaderboard.ts- Implements two functions:getTopScoresquery - fetches top scores globally or filtered by tileset, sorted by longest combo (descending) then by completion time (ascending)submitScoremutation - validates and inserts a new score entry (player name trimmed to 20 chars max)Frontend
components/Leaderboard.tsx- New component showing a ranked leaderboard table with global/tileset filter tabs, rank indicators (1st/2nd/3rd highlighted), perfect score markers, and loading/empty statescomponents/VictoryModal.tsx- Updated to include a score submission form (name input + submit button) that appears after winning a Classic mode game. Player name is persisted in localStorage for convenience.components/GameModeScreen.tsx- Added a "View Leaderboard" button below the mode selection cardsApp.tsx- Wired leaderboard state, passes tilesetName/isClassicMode to VictoryModal, renders Leaderboard overlayindex.tsx- Wraps app with ConvexProvider when VITE_CONVEX_URL is set, falls back gracefully without itSetup
convexdependencyvite-env.d.tsfor Vite type supportnpx convex dev --onceSetup Required
To activate the leaderboard, set
VITE_CONVEX_URLin your environment (created automatically bynpx convex dev).View task on Roo Code Cloud