The entry form, live game links, accounts, and the two other player-facing views (bracket comparisons, historical simulation) — and how account email works today.
01The entry form
Submitting a bracket means making 22 separate picks across 5 groups without losing track of where you are — the entry form's whole design is keeping that manageable.
Tabbed by pick group. One Bootstrap tab per group (seeds 1–4, 5–8, 9–12, 13–16, and coaches), each showing a live counter like 3/6 that updates the instant a checkbox is toggled — and once a group hits its max, its remaining unchecked boxes simply disable themselves, so it's not possible to over-pick a group.
Sub-tabs by seed. Inside a group, players are further split into pill-tabs by seed (or, for coaches, by seed range) — each pill carries its own small pick count, so it's easy to see at a glance which seed you still need to pick from.
Eliminated players are handled, not hidden. A player from a team that's lost its play-in game is visibly flagged and can't be newly picked — but if you already had that player on your roster before they were eliminated, they stay visible and you can still un-pick them; you just can't add them back.
Tabs flag their own state. A tab turns red if the server rejected that group on a previous submit, and separately colors as "partial" or "complete" live as you pick — so problems are visible without having to open every tab to check.
Where validation actually happens: the in-browser counters and colors are there to guide you, but they don't block submission by themselves — the one thing that does intercept submission client-side is a confirmation popup if you've picked a still-undecided play-in team, asking you to confirm you understand that pick could still be eliminated. The real enforcement of "did you pick exactly the right number in every group" happens server-side on submit, which is what actually decides whether the entry is accepted.
02Live games, linked back to ESPN
Anywhere a game, player, or team shows up in the app, it links straight back to that exact ESPN page — so nothing in a standing or a bracket asks you to just trust it.
Every scored game badge on an entry, prediction, or comparison view links to that specific ESPN box score by game ID.
Every player name links to their ESPN player page by ESPN player ID; every team name or logo links to their ESPN team page the same way.
The day's games grid colors each game by its live ESPN state — in progress, final, or not yet started — and links each one straight to its ESPN box score.
None of this is proxied through the app's own routes — the links are built directly from the same ESPN IDs the scoring pipeline already uses (see Special Features §01), so a link and the score next to it are always describing the same underlying ESPN record.
03Accounts
Account lifecycle is intentionally plain: register, log in, submit, edit until locked, reset a password if needed.
Register with name, email, and cell number (both unique); the password is hashed, and registering logs you in immediately.
Log in / log out — rate-limited to guard against repeated guessing.
Submit or edit an entry — editing is blocked once the admin locks entries, and one account can only edit its own entries (checked on every request, not just hidden in the UI).
Forgot password — generates a one-time reset link, valid 24 hours, and always reports "an email was sent" either way, so the response itself can't be used to check whether an email address is registered.
04Email
Two account emails go through Amazon SES: an entry-confirmation email after you submit a bracket, and the password-reset link above.
The reset flow works end to end independent of delivery. The link itself is generated and accepted correctly by the app; email delivery goes through Amazon SES, configured per environment the same way every credential in this app is.
05The bracket, and comparing two of them
The centerpiece view on any entry: a full tournament bracket that fills itself in as the tournament plays out, so it always looks complete rather than half-empty.
Once a game is final, that team locks in as a win or a loss — no waiting on anyone to update it.
Games that haven't been played yet still show a projected winner, favoring whichever team the entry actually drafted players from.
If an entry's own picks are on a collision course to face each other, the bracket flags the earliest round that could happen with a 💣 warning icon, before it happens.
See Special Features §02 for how the projection and collision logic actually works, and what's automatically tested.
Pick any two entries and see them projected side by side, not just as two separate numbers.
The comparison view reuses that exact same round-by-round projection logic — it doesn't duplicate it, it calls the same shared projection function twice, once per entry, and then colors each slot by whether entry A picked that team, entry B did, both did (a genuine collision), or neither did. Eliminated teams still show struck through, exactly as they do on a single bracket.
06Historical data fetching
Before trusting the scoring engine with a live, once-a-year tournament, it needs to be tested against a tournament that already happened.
The historical-year workflow re-points the active tournament year at a past season and re-runs the exact same import-and-score pipeline against real ESPN data from that year — same tables, same scoring logic, just scoped by year rather than fenced off in separate tables. That reuse is deliberate: it means the historical mode is testing the actual production scoring path, not a simulated stand-in for it. A separate, unrelated setting (daily simulation mode) exists for a different kind of test — replaying single days of the regular season rather than a full historical tournament year — and the two don't share any wiring. Because both modes work by pointing the same "active year" setting at a different value, running one deliberately means the current live tournament year is not the active one for that session — an admin has to switch back explicitly when historical testing is done.