Six suite failures had rotted in: three race-hub specs targeted the sub-tabbed Race Story UI deleted in the Phase 22 redesign, and the command-center specs depended on the real OpenF1 calendar, whose focus weekend drifted away from the seeded Monaco session as time passed. The e2e/visual configs now point BOXBOX_OPENF1_BASE_URL (new override) at an unreachable address so runs are hermetic and date-stable; the race-hub specs were rewritten against the current RaceStoryCanvas UI, and the one drifted mobile snapshot regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
box-box
"Box, box. Box, box." — Every F1 race engineer, ever.
box-box is a Formula 1 dashboard in Go with two surfaces:
- Web UI (primary) — React + TypeScript SPA for Race Hub analytics, local data coverage, and live timing.
- TUI (preserved) — Bubble Tea terminal app with standings, calendar, driver profiles, official live timing, track map, battles, pit window, and replay.
Historical Web data is local-first in a SQLite domain database (ingested from OpenF1). Live timing uses the official F1 SignalR feed via the Go server; the React app reads /api/v1/live/*, not OpenF1 directly.
For architecture, phase history, and design rationale, see documentations/refactor/README.md.
How it fits together
| Layer | Role |
|---|---|
| OpenF1 REST | Backfill and ingestion source; optional paid tier via OPENF1_API_KEY. Also powers the TUI’s on-demand reads and HTTP cache. |
Domain SQLite (boxbox.db) |
Local store for meetings, sessions, Race Hub datasets, and navigation APIs used by the Web UI. |
HTTP cache SQLite (cache.db) |
TTL cache for OpenF1 responses (TUI and legacy paths). Separate from the domain DB. |
| Official F1 SignalR | Live timing bridge in internal/live, exposed to Web (SSE) and TUI. |
| Go server | cmd/main.go — TUI, --web API + static SPA, or CLI ingestion. |
| React frontend | frontend/ — production build served from frontend/dist when present. |
The Web UI should call local-first Go APIs (/api/v1/...). Do not add direct OpenF1 reads in the frontend.
Prerequisites
- Go (see
go.modfor the module version) - Node.js 18+ and npm (Web UI dev, unit tests, Playwright)
- Internet for ingestion and TUI OpenF1 calls
- For E2E / visual tests:
npx playwright install(Chromium) afternpm installat the repo root
Install
git clone https://github.com/AmanTahiliani/box-box.git
cd box-box
npm install # Playwright and repo-level test scripts
npm install --prefix frontend # Vite + React app
Build
go build -o box-box ./cmd/main.go
npm run build --prefix frontend # writes frontend/dist (gitignored)
Run — TUI (default)
go run ./cmd/main.go
# or: ./box-box
Logs go to box-box.log in the project directory so the terminal stays clean.
TUI keybindings
| Key | Action |
|---|---|
1–7 |
Home, Standings, Calendar, Race Detail, Drivers, Live, Track Map |
tab / shift+tab |
Next / previous tab |
j/k, enter, b/esc |
Navigate, select, back |
s, b, p |
Live: sectors, battles, pit window |
r |
Race replay (Race Detail, race sessions) |
y |
Cycle season year |
q / ctrl+c |
Quit |
Run — Web (Go serves API + built React)
Build the frontend first, then start web mode. Go walks up from the cwd to find frontend/dist/index.html; if missing, it serves embedded legacy assets.
npm run build --prefix frontend
go run ./cmd/main.go --web
# → http://localhost:8080
Use a specific domain database or port:
go run ./cmd/main.go --web --db ~/.local/share/box-box/boxbox.db --port 8080
Run — Web dev (Vite + Go API)
Vite proxies /api to the Go server. Set BOXBOX_API_PORT to match the Go --port.
Terminal 1 — API (seeded DB is enough for UI work without ingesting):
go run ./scripts/seed-e2e-db/main.go --db /tmp/boxbox-dev.db
BOXBOX_DISABLE_LIVE=1 go run ./cmd/main.go --web --db /tmp/boxbox-dev.db --port 18080
Terminal 2 — frontend:
BOXBOX_API_PORT=18080 npm run dev --prefix frontend
# default Vite port 5173 → http://localhost:5173
BOXBOX_DISABLE_LIVE=1 skips starting the SignalR bridge (used in CI and local UI work).
Web routes
| Route | Purpose |
|---|---|
/ |
Command Center — fan-facing race-weekend home with GP identity, live status, session schedule, and analysis links |
/race-hub?session_key=<key> |
Race Hub — weekend workspace with session rail and Overview / Race Story / Strategy / Lap Data / Conditions / Race Control / Data Status tabs. Bare /race-hub auto-resolves to the focus session. |
/admin |
Admin / Data Health — ingestion coverage, local data status, and suggested CLI commands |
/data-library |
Legacy alias for Admin / Data Health |
/live |
Live Timing — timing tower and race control via SSE when a session is live |
Example after seeding: http://localhost:5173/race-hub?session_key=9472
Ingest historical data and briefing feeds
Ingestion is a CLI mode on the same binary. Only one of --ingest-year, --ingest-meeting, --ingest-session, or --ingest-news may be set per run.
# Season: discover and store meeting metadata (2023+)
go run ./cmd/main.go --ingest-year 2025
# Full race weekend: all sessions + Race Hub datasets
go run ./cmd/main.go --ingest-meeting 1229
# Single session only
go run ./cmd/main.go --ingest-session 9472
# Preview without writing
go run ./cmd/main.go --dry-run --ingest-meeting 1229
# Refresh Paddock Briefing RSS/Atom feeds
go run ./cmd/main.go --ingest-news
# Custom DB path (default: ~/.local/share/box-box/boxbox.db)
go run ./cmd/main.go --ingest-meeting 1229 --db /tmp/boxbox.db
Use --ingest-meeting for a complete weekend. --ingest-year lists meetings for the season; ingest meetings individually or by weekend as needed. Optional analytics fetches may partially fail without aborting the whole run.
OPENF1_API_KEY
export OPENF1_API_KEY=your_key_here
go run ./cmd/main.go --web
Without a key, the free OpenF1 tier is used. A key may be required for paid-tier behavior (e.g. live session access during API lockouts). Ingestion and TUI calls use the same client.
Local files
| Path | Purpose |
|---|---|
~/.local/share/box-box/boxbox.db |
Domain database (default --db) |
~/.cache/box-box/cache.db |
OpenF1 HTTP response cache (TUI / client) |
box-box.log |
TUI application log (project root) |
frontend/dist/ |
Production React build (gitignored — build locally, do not commit) |
.playwright/*.db |
Seeded DBs for automated tests |
Web mode logs to stderr.
Tests and QA
Go (targeted packages)
go test ./internal/live ./internal/models ./internal/store ./internal/ingest ./internal/query ./internal/web
All packages: go test ./...
OpenF1 integration tests (network, rate-limit aware): go test -v ./internal/api
Frontend unit tests and build
npm --prefix frontend test -- --run
npm --prefix frontend run build
E2E (Vite dev proxy + seeded API)
Starts seeded Go on port 18080 and Vite on 15173 (see playwright.config.ts).
npx playwright install # first time only
npm run test:e2e
E2E production (Go serves frontend/dist)
npm run test:e2e:prod
Visual regression (screenshots)
npm run test:visual # dev proxy stack
npm run test:visual:prod # production serving (canonical baselines)
# after intentional UI changes
npm run test:visual:update
npm run test:visual:prod:update
Snapshots live under tests/visual/__snapshots__/.
Known limitations
- Live timing only works when F1 is broadcasting timing data; there is no guaranteed live session for local dev.
- E2E / visual tests use
BOXBOX_DISABLE_LIVE=1and seeded SQLite — they do not exercise full SignalR live behavior. frontend/distis generated output; build before production web mode ortest:e2e:prod.- TUI historical views still use OpenF1 on demand with the HTTP cache; the Web UI’s local-first model does not fully replace the TUI yet.
--ingest-yearstores season meetings, not full session datasets — use--ingest-meetingor--ingest-sessionfor Race Hub data.
License
MIT © Aman Tahiliani
Unofficial project; not associated with Formula 1 or the FIA.