All work
Own product · in weekly use

The Keep

Keeper, trade and live-draft system for a 16-team fantasy league. It replaced a spreadsheet that had drifted.

Role
Sole engineer and co-commissioner
Year
2026
Stack
Next.js, TypeScript, Supabase Postgres, Row Level Security, Supabase Realtime, Vercel Cron, Claude API, Vitest
the-keep
Demo dataDraft night: a pick lands on the 16 by 16 board, the clock resets, and Fit zoom shows the whole draft as a color map. A demo draft with invented teams, recorded at real speed.
manager accounts across 16 teams, in weekly useReported by Ryan · source
19
SQL migrations, each a reviewed changeCounted in the repo · source
29
test files over a framework-free rules engineCounted in the repo · source
31

The problem

A 16-team keeper league ran on a shared Google Sheet. Costs were transcribed wrong, player names had decayed, the grid could not show two picks in one round, and nothing recorded why a pick changed hands. An external data source got the keeper round wrong about half the time.

What I built

  1. 01Trades accepted atomically. One row-locked SECURITY DEFINER function checks that the caller is the counterpart, that every asset is still owned by the side offering it, and that neither team ends above three keepers. Only then does it move contracts and picks. A proposer can never accept his own offer, commissioners included.
  2. 02A keeper-cost cascade from rulebook section 4.5, written as pure functions. Tests check that the set of consumed picks is the same under all six declaration orders.
  3. 03A live draft board on Supabase Realtime that treats the socket as a doorbell, not a delivery: a change triggers a debounced server refresh, with a polling heartbeat underneath when the socket drops.
  4. 04A draft clock derived from timestamps, not a ticking value, so all sixteen screens agree.
  5. 05Ask the rules: retrieval over the rulebook runs first, a weak match is refused before any API call, and Claude answers only from the retrieved passages and cites the rule numbers.
  6. 06Team colors derived for accessibility. Each of the 16 colors is adjusted until it clears WCAG AA on both themes, and CI fails if one does not.

More screens

Demo dataRule 4.5 on a phone: one keeper slides later because his round is spent, another moves earlier because the pick was traded away.
The home screen on a phone: the roster ranked by keeper value and a verdict on who is worth keeping, with a deadline countdown
Demo dataThe roster ranked by keeper value, with a plain-English verdict on who is worth keeping.
the-keep
Demo dataA keeper whose cost cannot be paid blocks the trade until a first-round pick comes back, then the impact review checks both teams. Nothing is sent.
the-keep
Review impact for both teams: keepers after the trade, picks per season with changes, and a payable cost check
Demo dataKeepers, picks by season and the cost check for both teams, before anything is sent.
the-keep
The Teams directory: each team's color, managers, roster strip with keeper costs, and its pick chips
Demo dataEvery roster and every pick in one scroll, each team in its own color.
the-keep
The draft recap: grading on the curve explained, and one team's grade with value per pick, holes, best pick and biggest reach
Demo dataDraft grades on the curve, with value kept separate from roster.

Decisions

Invariants in the database, not the UI

A stale page must never produce a partial trade. Moving the checks into one locked transaction made that impossible, with no commissioner exemption.

Audited the data source against the league's own contracts

The external draft data disagreed with 48 real contracts about half the time. Unverified records now carry a trust flag and never prefill a form.

Found security gaps by querying the live database

The migrations looked right, but the schema had no grants, so every Row Level Security policy was moot. Reads now require a claimed seat.

Not claimed

  • Screens on this site use invented team and manager names on an in-memory copy of the database. The real league is private.
  • In production, rank, ADP and projections are ESPN's numbers, not a model of mine. In these screens they are demo figures.
  • The Live indicator in the draft film is stubbed. Each pick goes through the real server action.
  • Ask the rules needs a server key, so it is not claimed as switched on in production.