Don't make code changes outside of this file. Help me edit this prompt so that Townie with Claude Opus 4.5 gives best results.
I want to change the postgame workflow to support club leaderboards.
After the user gets their scored results,
header
resultsTable
clubNameInput
userNameInput
submitToLeaderboardButton
club names should be a string of only 26 letters in the alphabet. no digits or other characters or spaces.(ie, can be input using the keyboard component)
club names should be no more than 25 characters long
likewise for players:
each submission gets a unique id, two players can submit with the same userName and clubName
Here's what I was thinking for data needed to be collected to support the leaderboard view. Although submitted at is not currently needed, it should be included. if this is insufficient, or could be handled better, make those changes.
submission_id
game_id
club_name
user_name
submitted_at: timestamp when the user submitted to finish the final round, ending the game.
total_points: sum of length of every valid submission, as displayed on the postGame resultsTable.
submissions: array of strings containing the words the user played, in order
submission_validity : array of booleans of same length of submission, true for valid words and false for invalid,
create a file scripts/initDbResults.ts to initialize a game_results table where these are stored.
Use these 5 prompts in sequence to deliver a leaderboard feature. Each builds on the previous one. Here is prompt 1
Goal: Create the database table for storing game results.
File to create: scripts/initDbResults.ts
Context:
import { sqlite } from "https://esm.town/v/std/sqlite"Schema requirements:
CREATE TABLE IF NOT EXISTS game_results (
submission_id TEXT PRIMARY KEY,
game_id TEXT NOT NULL,
club_name TEXT NOT NULL,
user_name TEXT NOT NULL,
submitted_at TEXT NOT NULL,
total_points INTEGER NOT NULL,
submissions TEXT NOT NULL,
submission_validity TEXT NOT NULL
)
Also create an index on club_name for efficient leaderboard queries.
Notes:
submissions and submission_validity are JSON-stringified arraysclub_name and user_name should be stored uppercase for case-insensitive matchingsubmitted_at is an ISO 8601 timestampsubmission_id is a UUID generated at insert timegame_id is a UUID generated per game session (passed from frontend)Goal: Add API endpoints for submitting results and fetching leaderboards.
File to modify: index.ts
Context:
import { sqlite } from "https://esm.town/v/std/sqlite"game_results table exists (from Prompt 1)Endpoint 1: POST /api/submit-result
Request body:
{
game_id: string, // UUID from frontend
club_name: string, // A-Z only, max 25 chars
user_name: string, // A-Z only, max 25 chars
total_points: number,
submissions: string[],
submission_validity: boolean[]
}
Behavior:
submission_id using crypto.randomUUID()submitted_at as new Date().toISOString()club_name and user_name to uppercase{ submission_id } on successEndpoint 2: GET /api/leaderboard/:clubName
Behavior:
clubName param to uppercasetotal_points DESC, user_name ASC{ club_name, entries: [...] }Add routes for serving frontend at club URLs:
app.get("/club/:clubName", () => serveFile("/frontend/index.html"));
app.get("/club/:clubName/", () => serveFile("/frontend/index.html"));
app.get("/club/:clubName/leaderboard", () => serveFile("/frontend/index.html"));
app.get("/club/:clubName/leaderboard/", () => serveFile("/frontend/index.html"));
Goal: Add club name and player name inputs to the postGame view.
File to modify: frontend/components/App.tsx
Context:
"preGame" | "inGame" | "postGame"results state contains: { round, word, isValid }[]Changes needed:
const [clubName, setClubName] = useState("");
const [playerName, setPlayerName] = useState("");
Add URL parsing on mount (useEffect with empty deps):
window.location.pathname/club/{name} or /club/{name}/, extract name and set clubName (uppercase)/club/{name}/leaderboard, also set gameState to "leaderboard"Update GameState type to include "leaderboard"
In the postGame view, after ResultsTable, add:
Add handleSubmitToLeaderboard function:
total_points from results (sum of word lengths where isValid is true)game_id with crypto.randomUUID()/api/submit-resultgameState to "leaderboard" and update URL with window.history.pushStateInput validation:
/[^A-Z]/g to strip invalid chars)Styling: Add appropriate CSS classes. Match the existing app style.
Goal: Create the leaderboard component and integrate it into App.
Files to create/modify:
frontend/components/Leaderboard.tsxfrontend/components/App.tsxfrontend/components/RoundIndicator.tsxfrontend/style.cssContext:
frontend/components/RoundIndicator.tsx)frontend/rounds.ts)Leaderboard.tsx requirements:
Props: { clubName: string }
State:
entries: array of leaderboard entries from APIloading: booleansortByRound: number | null (null means sort by total, 0-4 means sort by that round)On mount, fetch /api/leaderboard/{clubName}
Display:
Sorting logic:
total_points DESC, tiebreaker: user_name ASCuser_nameRoundIndicator.tsx modifications:
Add optional props:
showTotal?: boolean; // If true, add one extra dot (disconnected) for "Total"
onRoundClick?: (roundIndex: number) => void; // Make dots clickable
selectedRound?: number; // Which dot is selected (for leaderboard sorting)
When showTotal is true:
totalRounds + 1 dotsWhen onRoundClick is provided:
onRoundClick(index) when clickedselectedRound to highlight which dot is currently selectedApp.tsx integration:
Add leaderboard gameState handling:
if (gameState === "leaderboard") {
return (
<div className="app-wrapper">
<div className="game-container">
<Header />
<div className="content-area">
<Leaderboard clubName={clubName} />
<button onClick={handleStartGame} className="start-button">Play Again</button>
</div>
</div>
</div>
);
}
CSS additions needed:
.leaderboard container.leaderboard-table with scrollable containerGoal: Test the full flow and fix any issues.
Test scenarios:
/club/TESTCLUB → Club name pre-filled, can start game/club/TESTCLUB/leaderboard → Shows leaderboard directlyPotential issues to check:
Polish items: