Rules and data · Full-stack
Tracktion
Track meet scoring that gets the dual-meet math right, ties included.
- Role
- Sole designer and developer
- Built with
- React, TypeScript, IndexedDB, Supabase, Vitest
Tracktion scores track-and-field meets in which every pair of teams is scored as its own dual meet. It’s for whoever keeps score beside the track, and it works with no network connection. It replaces Dual Meet Manager, a C# program I wrote earlier to score dual meets.
The problem
A meet with several teams is a stack of small meets. Each pair of teams has its own score and its own winner, so six teams make fifteen duals. A runner who finishes third overall can be second against one opponent and first against another, and the points follow those places within each pair. One result can change several duals at once, and people follow the standings while the meet is still going.
The event types add their own rules. Ties split points, relays are winner-take-all, and a DNF, DQ or scratch takes no place. Field events can be scored on the best mark or on a full series of six attempts. Vertical jumps rank by the highest bar cleared, then fewest attempts at that height, then fewest misses.
What I built
Setup takes two to six teams, boys and girls divisions, and the events. To enter a result you type a mark, tap a team and press Enter. Places sort as you type, and each event shows its duals updating underneath. Entry handles DNF, DQ and scratches, relay B-squads, and field marks in feet and inches or metric.
After each event the scoreboard updates every pair’s running total, and the meet standings come straight from those duals. Rosters and seasons are saved, with cross-meet records, athlete bests and CSV export, and results print. Teams choose their own colors, and there are light and dark themes.
With the optional Supabase backend switched on, a meet also gets accounts, public spectator links that update live, and secret scorer links that let a helper enter results without making an account. Scorer links can be rotated, and new scorers can wait in an approval queue.
Key decisions
Score each pair on its own
I wrote the scoring rules as a small engine in plain TypeScript, with no dependencies and nothing from the screens inside it. For each pair of teams it filters an event’s results to those two teams, ranks them again, and awards 5, 3 and 1 (or 5 to a relay winner). Equal marks split the points for the places they share. The shortcut is to score each dual straight from the overall places. That breaks as soon as a third team finishes between the other two. So every event is ranked again for every dual, and the database keeps only raw results; the scores are worked out from them wherever they’re shown.
Save on the device first, and let the last write win
Meet data is saved in the browser before it goes anywhere else. Changes go out through an outbox that keeps only the newest version of each meet, so an edit made while a sync is running isn’t lost, and a failed send leaves everything queued. When two devices disagree, the last write wins. That keeps syncing simple. It also means two people shouldn’t score the same event at once.
Make the backend optional
Without Supabase settings, the app runs fully local with no sign-in and keeps meets in IndexedDB. With them, it switches to the cloud on its own. Both modes sit behind one MeetRepo interface, so the screens don’t change between them. Sharing, accounts and cross-device sync exist only in cloud mode.
A closer look
Setup starts with the meet’s name, the Boys division and the first team, Lincoln (LIN).
In the 200m, Carter (LIN) runs 22.00, Patel (ROO) 22.50 and Nguyen (WES) 23.00. Nguyen is third overall but second against Lincoln alone, so all three duals score 5–3.
In the 100m, Okafor (ROO) and Diaz (LIN) tie at 11.52. The points for second and third are pooled and split, 2 each, and a banner says they’re split for now; a scorer can resolve the order later.
Once the events are finalized, the scoreboard builds from the duals, with Lincoln 2–0, Roosevelt 1–1 and Westfield 0–2 after 2 of 18 events.
How I checked it
The scoring engine has 59 unit tests, covering mark parsing, ranking, vertical countback, the duals and whole-meet totals. All 59 passed in the last run. They include a runner who is third overall but second in a dual, a tie for second and third, a relay dead heat, B-squads that don’t score, and a dual where only two runners finish. The outbox has five tests of its own, among them a newer save that arrives during a sync and survives it.
The screens on this page come from the real app in local mode. I entered an invented three-team meet through its own screens, and the numbers match the tests.
More screens

Tracktion scoring the 200m dash, with each pair of teams scored as its own dual. 
Tracktion splitting the points for a tie in the 100m.