App built for OpenAI × MongoDB table tennis tournament — Wed Sep 9 2026, SPIN San Francisco.
→ LOCAL_SETUP.md — copy-paste steps for Mac, Linux, and Windows. No VM, no tunnels, no Docker, no Mongo required.
npm install
cp .env.example .env
npm run dev
# open http://localhost:3000 PIN 0909
Tournament state lives in data/tournament.json (STORE=file by default).
Night-of ops: two physical boards, two operators, same PIN. Generate writes first-round names only (ceil(N/2) matches, not a padded 64). Later rounds stay empty until someone taps a winner (or the one real odd-N bye) — one next-round slot at a time. No skip-to-finals, auto-win, or prefilled podium.
STORE=file writes data/tournament.json. Works offline on one machine.MONGODB_URI with STORE=mongo. One document in tournaments with _id: "battle-of-the-paddles"./setup + /now, operator 2 marking /board/a or /board/b while copying onto the paper sheet. Same PIN. /tv on a projector if you have one./setup → upload the Luma registration CSV (the Touranment? (yes/no) typo is expected). Parser keeps Touranment? = Yes and approval_status = approved, skips declined , merges duplicate names and flags them in the UI. Sample file: public/sample-players.csv (names + skill + competing only).A-R64-07, B-R8-02, …). Later-round slots are blank until a tap fills them. Digital twin updates both devices via 1s poll (SSE if the browser keeps the stream)./now (two-player matches only). Same quoteId is stored on the match so both screens show the same original one-liner.FINALS-3RD-01) while finalists rest, then Grand Final (FINALS-GF-01). Podium fills from those two matches only — no fake names.| Path | Who |
|---|---|
/ |
PIN gate → hub |
/setup |
CSV, check-in, pairing, generate, reset |
/board/a /board/b |
Visual boards (real first-round matches only) |
/now |
Ready + called |
/finals |
3rd, grand final, podium |
/tv |
Read-only display (no PIN, no emails) |
Mutating APIs require the PIN cookie (POST /api/auth) or header x-operator-pin.
npm test
Vitest covers Luma CSV columns, duplicate merge, declined skip, first-round-only generate, even board split, seed placement, one-slot winner/bye advance, cascade undo, N=86 → 0 byes, and N=87 → 1 bye.
Next.js 15 App Router, TypeScript, Tailwind, Framer Motion, MongoDB Node driver (optional; file store default). PWA manifest at /manifest.json. Fonts: Bangers, Archivo Black, Archivo Narrow.