Tony Morinello

All work

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
Walkthrough: Tracktion, silent, 4 steps
Tracktion's last setup step, Share & go: the Bayside Tri Meet with 3 teams, 3 duals, Boys and 18 events, a public link anyone can watch and a secret scorer link.

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.