poker league score
A weekly poker league gets a public leaderboard that is correct the same night it plays, from about two minutes of typing after the last hand.
Visit pokerleaguescore.netlify.app ↗client
Internal / product
role
Solo build, from scratch
launched
August 2026
status
Live

// the problem
The league had no reliable record. Results lived in somebody's phone or in memory, standings were worked out by hand when anyone bothered, and points were argued about because the arithmetic was never written down anywhere the players could see. Software for this exists, but it assumes somebody will operate it during the game, and the person who would have to do that is the dealer, who is busy dealing. Anything that competes with running the game does not get used twice.

// what I built
A public leaderboard anyone in the league can open on their phone, backed by an admin only they cannot see. Results go in through a three step walkthrough: tap who played, drag them into finishing order, publish. The scoring itself is a pure module with its own test suite, and standings are recomputed from the raw finishing positions on every page load, so changing the league's rules re-scores the whole season and no stored total can ever drift from the results it came from. A night can be a single game or several rounds, and each round is scored on its own field before the season's rule folds them together. There is also a full live table mode, with a seat map, a dealer button and a shared tournament clock, for nights somebody actually wants to run it that way.

// technical highlights
- ▹Scoring is a pure module with no database access, covered by 140 unit tests. Standings are derived on every read, never stored, so editing the rules re-scores every night in the season.
- ▹A night can hold several rounds. Each round is scored against its own field size, then folded by a per season rule: sum every round, count only the last, or take each player at their best.
- ▹The tournament clock is derived from server timestamps rather than a browser interval, so it survives a refresh, a locked phone, and several people watching it at once.
- ▹Every league time is pinned to America/New_York and tested across the daylight saving change in both directions, because a tournament that moves an hour is a tournament nobody trusts.
- ▹A fail-closed database guard runs ahead of every destructive command. It requires both an environment marker and a pinned database host to match, so a stale environment file cannot point a local command at production.
- ▹A daily off-site backup runs as a scheduled function writing to blob storage, because the database tier's point-in-time recovery only goes back six hours. A failed backup writes itself to an errors flag in the admin, since silence is the one failure mode a backup cannot have.
- ▹The public board degrades rather than breaks. A tournament whose finishing positions do not form a valid ranking is left out of the standings and flagged for the admin, instead of throwing on a page the whole league is looking at.
- ▹53 Playwright specs with database fixtures, so any single spec can run on its own in about twenty seconds instead of replaying the whole league.


// the impact
This runs one real league, not a product with users, so there are no adoption numbers to report. The live table mode shipped behind a feature flag and was only switched on in production recently, because after three real nights the league decided it preferred typing results in afterwards. Knockout and bounty scoring is built but switched off, and there is no analytics and no CI.

Have a similar problem?
Let's talk about what to build.