Skip to content
Work

Web app · NBA salary cap

Live the-cap-room.vercel.app

The Cap Room

Explore NBA team salaries, compare players, and test two-team trades against the 2023 collective bargaining agreement. Every rule shows its math.

the-cap-room.vercel.app/trade
The Cap Room trade machine: moving cash above the $8,495,000 limit changes the verdict from LEGAL to ILLEGAL with 2 rules violated. Lowering it restores LEGAL.
A two-team trade checked against each rule as the cash amount changes.
Role
Solo: design, code, release
Timeline
Jul 2026 to now
Stack
TypeScript, Next.js, React, Zod, Vitest, Python

Live at the-cap-room.vercel.app. Public code on GitHub. Status checked Oct 22, 2026.

By the numbers

44

engine tests, including payrolls one dollar over every apron and tax line

19

rule checks in a single detailed trade verdict

582

players in the dated league statistics snapshot

9

team rosters checked by the engine against published totals, to the dollar

Fig. 1 What runs inside the engine

Before the app runs

  1. On my machine etl/pull_stats.py A Python ETL pulls the league stats. stats.nba.com blocks cloud servers, so this never runs in production.
  2. In the repo Dated snapshot 582 players, plus 9 team rosters.
  3. Before every build scripts/validate-data.ts Recalculates each roster total with the engine’s salary-cap rules. A one-dollar difference from the published total fails the build.
engine/ Pure TypeScript The engine imports only its own files, with no React, Next.js or I/O. Each CBA rule returns a pass or fail result with the numbers behind it.
  • tradeRules.ts
  • capsheet.ts
  • picks.ts
  • constants.ts
2023 CBA: first apron $209,015,000, $250,000 buffer, $8,495,000 a year in trade cash.

On every trade check

  1. Browser The URL stores the trade Every trade is a shareable link that reopens the same deal.
  2. API POST /api/trade/validate Checks the body, loads the cap sheets and picks, and calls validateTrade.
  3. Response An itemized ledger A legal or illegal verdict, followed by each rule and its calculations. The sample trade returns 19 rows.

The idea

The Cap Room helps you work through NBA salary-cap decisions. Browse team cap sheets, compare players by league percentile and check two-team trades against the 2023 collective bargaining agreement (CBA). Each trade verdict shows the rule checks and salary calculations that support it.

What I built

I built the CBA engine in pure TypeScript. Every import in engine/ points to another engine file, keeping it independent of React, Next.js and I/O. It implements the 2023 rules for salary matching and cash, including the $209,015,000 first apron, $250,000 buffer and $8,495,000 annual trade-cash limit. The API returns one row per rule with the relevant calculations.

A complete trade proposal fits in a shareable URL. The interface uses the same REST routes available through curl. In October, I upgraded to Next.js 16 and React 19 and redesigned the app with a phone-friendly league board, one-click sample trades with expected verdicts, and Cmd-K player search that ignores accents.

I process league stats offline with a Python ETL pipeline and commit dated snapshots. On every push to main, CI checks types, lint, engine tests, data validation and a production build.

The hard parts

  1. 01 Breaking down the CBA rules

    CBA rules affect one another. The engine gives each rule a small check with a pass or fail result and its own calculations. Apron restrictions apply only above the threshold, so a payroll exactly on the line remains under it. Tests cover payrolls one dollar over each line.

    Where in the code: engine/tradeRules.ts (opens in a new tab)

  2. 02 Data that has to add up

    The engine recalculates each seeded roster’s cap total and compares it with the published team total. A one-dollar difference fails the build, catching mismatches between the data and the engine.

    Where in the code: scripts/validate-data.ts (opens in a new tab)

  3. 03 Keeping trade verdicts in sync

    Moving the cash slider can create several proposals per second. Validation waits 220 ms, cancels the previous request and pairs each response with its proposal. The last verdict stays dimmed until the current one arrives, preventing an older response from validating a newer trade.

    Where in the code: components/trade/TradeClient.tsx (opens in a new tab)

Before and after

BeforeAfterSource
FrameworkNext.js 14, with a critical security advisory that had no 14.x fix.Next.js 16 and React 19; npm audit of the runtime dependencies reports 0.Commit ee79ff3, October 9, 2026
Accessibility (axe-core)6 failing rules across about 190 elements, including faded text and a 3.6:1 contrast ratio for the red FAIL label.0 violations across 75 axe-core 4.14 runs, covering every page with panels and menus open at widths from 1440 to 320 px.Commits 189d4f0 and 24b396f, October 9, 2026
The league board on a phoneA cropped chart that scrolled sideways.Horizontal bars that fit a 390 px screen, with the cap, tax and apron lines marked.Commit 189d4f0, October 9, 2026

What’s next

Three-team trades, pick ledgers for every team, and a fresh roster snapshot.

the-cap-room.vercel.app/cap
The Cap Room team cap sheet: payroll relative to the salary floor, cap, tax and both aprons, with the dollar difference for each threshold. Four exceptions are listed, one available to the team.
the-cap-room.vercel.app/trade
The Cap Room rule ledger for a legal trade: 19 checks, with the hard-cap flag first and full calculations for both salary-matching rules.

Try it

Shared approaches