diff --git a/.env.example b/.env.example index aed045a..7f9e041 100644 --- a/.env.example +++ b/.env.example @@ -5,11 +5,16 @@ VITE_SUPABASE_URL=your_supabase_project_url_here VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here +# Lighthouse CI Test Credentials (for local testing only) +LIGHTHOUSE_TEST_EMAIL=your_lighthouse_test_email@example.com +LIGHTHOUSE_TEST_PASSWORD=your_lighthouse_test_password + # Instructions: # 1. Get your Supabase URL and anon key from https://supabase.com/dashboard # 2. Go to your project > Settings > API # 3. Copy the "URL" and "anon public" key # 4. Replace the placeholder values above with your actual keys -# 5. Save this file as .env.local (not .env.example) +# 5. Create a test account for Lighthouse CI and add credentials above +# 6. Save this file as .env.local (not .env.example) # Note: Never commit .env.local to git - it contains secrets! \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a538cc..e36de9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,104 +1,213 @@ -name: CI/CD Pipeline - -on: - push: - branches: [main, dev] - pull_request: - branches: [main, dev] - -jobs: - test: - name: Run Tests - runs-on: ubuntu-latest - - env: - # Environment variables to help with jsdom compatibility - NODE_OPTIONS: --experimental-vm-modules - FORCE_COLOR: 0 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Run linting - run: npm run lint - - - name: Run tests - run: npm run test - - - name: Generate test coverage - run: npm run test:coverage - - build: - name: Build Application - runs-on: ubuntu-latest - needs: test # This ensures build only runs if tests pass - - env: - NODE_OPTIONS: --experimental-vm-modules - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Build application - run: npm run build - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: build-files - path: dist/ - - deploy: - name: Deploy to Vercel Production - runs-on: ubuntu-latest - needs: test # This ensures deployment only runs if tests pass - if: github.ref == 'refs/heads/main' # Only deploy from main branch - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Deploy to Vercel - uses: amondnet/vercel-action@v25 - with: - vercel-token: ${{ secrets.VERCEL_TOKEN }} - vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} - vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} - vercel-args: "--prod" - - deploy-preview: - name: Deploy to Vercel Preview - runs-on: ubuntu-latest - needs: test # This ensures deployment only runs if tests pass - if: github.ref == 'refs/heads/dev' || github.event.pull_request.head.ref == 'dev' # Deploy preview from dev branch or PRs from dev - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Deploy to Vercel Preview - uses: amondnet/vercel-action@v25 - with: - vercel-token: ${{ secrets.VERCEL_TOKEN }} - vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} - vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} +name: CI/CD Pipeline + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + # ───────────────────────────────────────────── + # 1. Unit & component tests (Vitest — apps/web) + # ───────────────────────────────────────────── + test: + name: Unit Tests (apps/web) + runs-on: ubuntu-latest + + env: + NODE_OPTIONS: --experimental-vm-modules + FORCE_COLOR: 0 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + - name: Install dependencies (workspace root) + run: npm ci + + - name: Run linting (all workspaces) + run: npm run lint --workspace=apps/web + + - name: Run unit tests + run: npm run test --workspace=apps/web + + - name: Generate coverage report + run: npm run test:coverage --workspace=apps/web + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + if: always() + with: + name: coverage-report + path: apps/web/coverage/ + retention-days: 7 + + # ───────────────────────────────────────────── + # 2. Build (apps/web) + # ───────────────────────────────────────────── + build: + name: Build (apps/web) + runs-on: ubuntu-latest + needs: test + + env: + NODE_OPTIONS: --experimental-vm-modules + # Provide stub env vars so Vite build doesn't fail on missing Supabase config + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL || 'https://placeholder.supabase.co' }} + VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY || 'placeholder-anon-key' }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + - name: Install dependencies (workspace root) + run: npm ci + + - name: Build web app + run: npm run build --workspace=apps/web + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: web-build + path: apps/web/dist/ + retention-days: 7 + + # ───────────────────────────────────────────── + # 3. Playwright E2E tests (e2e workspace) + # ───────────────────────────────────────────── + e2e: + name: Playwright E2E Tests + runs-on: ubuntu-latest + needs: build + + env: + # Provide enough config so the Vite dev server starts + VITE_SUPABASE_URL: https://placeholder.supabase.co + VITE_SUPABASE_ANON_KEY: placeholder-anon-key + CI: true + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + - name: Install dependencies (workspace root) + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + working-directory: e2e + + - name: Run Playwright tests + run: npm run test --workspace=e2e + env: + BASE_URL: http://localhost:5173 + + - name: Upload Playwright HTML report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: e2e/playwright-report/ + retention-days: 14 + + - name: Upload Playwright test results (screenshots & videos) + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-test-results + path: e2e/test-results/ + retention-days: 14 + + - name: Upload visual regression snapshots + uses: actions/upload-artifact@v4 + if: always() + with: + name: visual-regression-snapshots + path: e2e/tests/**/*-snapshots/ + retention-days: 14 + + # ───────────────────────────────────────────── + # 4. Type-check live-bridge service + # ───────────────────────────────────────────── + typecheck-bridge: + name: Type Check (services/live-bridge) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + - name: Install dependencies (workspace root) + run: npm ci + + - name: Type-check live-bridge + run: npx tsc --noEmit + working-directory: services/live-bridge + + # ───────────────────────────────────────────── + # 6. Deploy to Vercel (Production — main branch) + # ───────────────────────────────────────────── + deploy: + name: Deploy to Vercel Production + runs-on: ubuntu-latest + needs: [test, e2e] + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Deploy to Vercel + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + vercel-args: "--prod" + # Root directory is configured in Vercel dashboard as apps/web + # vercel.json lives at apps/web/vercel.json + + # ───────────────────────────────────────────── + # 7. Deploy to Vercel (Preview — dev branch / PRs) + # ───────────────────────────────────────────── + deploy-preview: + name: Deploy to Vercel Preview + runs-on: ubuntu-latest + needs: [test, e2e] + if: github.event_name == 'pull_request' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Deploy to Vercel Preview + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml deleted file mode 100644 index 521b197..0000000 --- a/.github/workflows/quality-gate.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Quality Gate - -# This workflow runs on all pushes and PRs to ensure code quality -# Vercel will only deploy if this workflow passes when configured properly -on: - push: - branches: [main, dev] - pull_request: - branches: [main, dev] - -jobs: - quality-gate: - name: Quality Gate - Tests & Build - runs-on: ubuntu-latest - - env: - # Environment variables for better CI compatibility - NODE_OPTIONS: --experimental-vm-modules --max-old-space-size=2048 - FORCE_COLOR: 0 - CI: true - # Disable analytics in CI - VERCEL_ANALYTICS_DEBUG: false - NEXT_TELEMETRY_DISABLED: 1 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "npm" - - - name: Verify Node.js version - run: | - echo "Node.js version: $(node --version)" - echo "npm version: $(npm --version)" - - - name: Install dependencies - run: npm ci - - - name: Verify test environment - run: | - echo "Testing environment setup..." - node -e "console.log('Node.js can run basic scripts')" - npm list vitest happy-dom --depth=0 || echo "Dependencies check completed" - - - name: Run ESLint - run: npm run lint - - - name: Run test coverage - run: npm run test:coverage - - - name: Build application - run: npm run build - - name: Quality gate passed - # This step will fail the workflow if any of the above steps failed - run: echo "✅ All quality checks passed! Ready for deployment." - - # Optional: Add a status check that Vercel can depend on - deployment-ready: - name: Deployment Ready - runs-on: ubuntu-latest - needs: quality-gate - if: github.ref == 'refs/heads/main' - - steps: - - name: Mark as deployment ready - run: echo "🚀 Main branch is ready for deployment" - - preview-ready: - name: Preview Ready - runs-on: ubuntu-latest - needs: quality-gate - if: github.ref == 'refs/heads/dev' - - steps: - - name: Mark as preview ready - run: echo "🔍 Dev branch is ready for preview deployment" diff --git a/.github/workflows/test-before-deploy.yml b/.github/workflows/test-before-deploy.yml deleted file mode 100644 index c840822..0000000 --- a/.github/workflows/test-before-deploy.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Test Before Deploy - -on: - push: - branches: [main, dev] - pull_request: - branches: [main, dev] - -jobs: - test: - name: Run Tests and Build - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Run linting - run: npm run lint - - - name: Run tests - run: npm run test - - - name: Build application - run: npm run build - - - name: Generate test coverage - run: npm run test:coverage - continue-on-error: true - - - name: Test results - run: echo "✅ All tests passed! Ready for deployment." diff --git a/.gitignore b/.gitignore index 107db23..d0ca92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,121 @@ -# Logs -logs -*.log +# ============================================================ +# Derby Stat Tracker — Monorepo .gitignore +# ============================================================ + +# ── Database backups & archives ───────────────────────────── +*.backup.gz +*.storage.zip + +# ── Node ──────────────────────────────────────────────────── +node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* +.npm +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* -node_modules -dist -dist-ssr -*.local +# ── Build outputs ─────────────────────────────────────────── +dist/ +dist-ssr/ +build/ +out/ +.next/ +.nuxt/ +.output/ + +# ── TypeScript ────────────────────────────────────────────── +*.tsbuildinfo + +# ── Environment variables (NEVER commit secrets) ──────────── +.env +.env.local +.env.*.local +.env.development.local +.env.test.local +.env.production.local +# Allow example env files +!.env.example +!**/.env.example + +# ── Test & coverage ───────────────────────────────────────── +coverage/ +!coverage/.tmp +.nyc_output/ + +# ── Playwright ────────────────────────────────────────────── +e2e/test-results/ +e2e/playwright-report/ +e2e/blob-report/ +e2e/.playwright/ + +# ── Lighthouse CI ─────────────────────────────────────────── +.lighthouseci/ +.lighthouseci-local/ + +# ── Python (scoreboard-api) ───────────────────────────────── +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +.venv/ +venv/ +env/ +ENV/ +*.egg-info/ +dist/ +.eggs/ +pip-wheel-metadata/ +.mypy_cache/ +.dmypy.json +dmypy.json +.pytest_cache/ +.ruff_cache/ +htmlcov/ +.coverage +.coverage.* +coverage.xml -# Editor directories and files +# ── Editor & OS ───────────────────────────────────────────── +.DS_Store +.DS_Store? +._* +Thumbs.db +ehthumbs.db +Desktop.ini .vscode/* !.vscode/extensions.json -.idea -.DS_Store +!.vscode/settings.json +.idea/ *.suo *.ntvs* *.njsproj *.sln *.sw? -.env -*.tsbuildinfo -coverage/* -!coverage/.tmp \ No newline at end of file +*.swp +*.swo + +# ── Logs ──────────────────────────────────────────────────── +logs/ +*.log +*.local + +# ── Vercel ────────────────────────────────────────────────── +.vercel/ + +# ── Docker ────────────────────────────────────────────────── +.docker/ + +# ── Misc ──────────────────────────────────────────────────── +*.local +.cache/ +tmp/ +temp/ +*debug*.png +*debug*.cjs diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..287dc3b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,70 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working in the `derby-stat-tracker` project. + +## Project overview + +TypeScript/React monorepo (npm workspaces) for tracking roller derby statistics. Connects to the `derby-scoreboard-api` REST proxy for live game data from a CRG ScoreBoard. + +## Workspaces + +| Package | Path | Purpose | +|---|---|---| +| `@derby/web` | `apps/web` | React + Vite main web app | +| `@derby/live-tracker` | `apps/live-tracker` | React + Vite + Tailwind v4 live scoreboard tracker | +| `@derby/live-frontend` | `packages/live-frontend` | Shared React components | +| `@derby/live-bridge` | `services/live-bridge` | Node.js background poller → Supabase persistence | + +## Running + +```sh +npm run dev # Start apps/web (port 5173) +npm run tracker:dev # Start apps/live-tracker (port 5175) +npm run bridge:dev # Start services/live-bridge +npm run test # Unit tests (apps/web) +npm run test:all # Unit tests across all workspaces +npm run e2e # Playwright E2E tests +``` + +## API type contract rules + +### `derby-scoreboard-api/models.py` is the single source of truth + +The file `apps/live-tracker/src/types/scoreboard-api.ts` is **auto-generated** from the OpenAPI spec that `derby-scoreboard-api` produces from its Pydantic models. + +**Rules:** + +1. **Never hand-edit** `apps/live-tracker/src/types/scoreboard-api.ts`. It will be overwritten by the sync script. +2. **To regenerate** after an API model change: + ```sh + npm run sync:api-types + ``` + This fetches `http://localhost:5001/openapi.json` and writes the TypeScript interfaces. The scoreboard API must be running. +3. **When consuming API data**, always import types from `./types/scoreboard-api`, never define inline interfaces that duplicate the API shape. +4. **Nullable fields** — the API returns `T | null` for most fields when the scoreboard is disconnected. Always use null coalescing (`?? defaultValue`) when reading nullable fields. + +### Type sync checklist + +When you modify code that consumes `GET /live`, `GET /health`, or `GET /raw`: + +- [ ] Confirm the field names and types match `apps/live-tracker/src/types/scoreboard-api.ts` +- [ ] If the generated types look stale, remind the user to run `npm run sync:api-types` +- [ ] Never add fields to the generated file — if a field is missing, the API models need updating first + +## Overlay URL format + +When referencing the custom broadcast overlay served by CRG ScoreBoard, the correct URL path is: + +``` +/custom/view/eod-custom-overlay/index.html?home=%23HEX&away=%23HEX +``` + +- The overlay lives in CRG's `html/custom/view/` directory, **not** `html/custom/` directly. +- `#` in hex colours must be URL-encoded as `%23` (e.g. `%231f3264` for `#1f3264`). +- Only `home` and `away` params are required. `homebg` and `awaybg` are optional. + +## Conventions + +- Use `snake_case` for all API field names (matches Python/Pydantic serialization). +- Use `camelCase` for all local TypeScript/React variables and component props. +- Clock values from the API are in **milliseconds** (fields suffixed `_ms`). Human-readable clocks are parallel `string` fields (e.g. `jam_clock_ms` + `jam_clock`). \ No newline at end of file diff --git a/README.md b/README.md index 5c0d86c..cbc1e8f 100644 --- a/README.md +++ b/README.md @@ -1,176 +1,278 @@ -# Derby Stat Tracker +# Derby Stat Tracker — Monorepo -A modern web application for tracking roller derby statistics, built with React, TypeScript, Vite, and Supabase. +A roller derby statistics platform with two modes: **manual stat tracking** during a bout, and **live scoreboard integration** via the CRG ScoreBoard software. -## Features +--- -- **Player Management**: Track players, their derby names, numbers, and positions -- **Team Organization**: Manage multiple teams and their rosters -- **Bout Tracking**: Record and monitor derby bouts/games -- **Real-time Statistics**: Track detailed player performance metrics -- **User Authentication**: Secure login and user management with Supabase -- **Responsive Design**: Works on desktop and mobile devices +## Repository Structure -## Tech Stack +``` +derby-stat-tracker/ +├── apps/ +│ └── web/ # React + Vite web application (deployed to Vercel) +├── packages/ +│ └── live-frontend/ # Live overlay UI components (from spark repo — see note below) +├── services/ +│ ├── scoreboard-api/ # Python asyncio CRG WebSocket proxy (HTTP API) +│ └── live-bridge/ # Node.js service: polls scoreboard API → writes to Supabase +├── database/ +│ ├── schema.sql # Full initial database schema +│ ├── migrations/ # Incremental SQL migrations +│ └── supabase-rls-performance-fixes.sql +├── e2e/ # Playwright end-to-end tests +├── .github/ +│ └── workflows/ # CI/CD pipelines +└── package.json # npm workspaces root +``` -- **Frontend**: React 18 with TypeScript -- **Build Tool**: Vite (fast development and building) -- **Backend**: Supabase (PostgreSQL database, authentication, real-time) -- **Styling**: CSS with modern layouts and responsive design -- **Deployment**: Ready for Vercel deployment +--- -## Getting Started +## Apps & Services -### Prerequisites +| Package | Path | Description | Runtime | +|---|---|---|---| +| `@derby/web` | `apps/web` | React + Vite web app | Vercel | +| `@derby/live-frontend` | `packages/live-frontend` | Live overlay UI components | (bundled into web) | +| `scoreboard-api` | `services/scoreboard-api` | CRG WebSocket → HTTP proxy | Python 3.11+ | +| `@derby/live-bridge` | `services/live-bridge` | Polls `/live` → Supabase | Node.js 20+ | +| `@derby/e2e` | `e2e` | Playwright E2E test suite | CI / local | -- Node.js 18+ and npm -- A Supabase account and project +--- -### Setup Instructions +## Architecture Overview -1. **Clone the repository** +``` + CRG ScoreBoard (local) + │ WebSocket + ▼ + ┌──────────────────┐ + │ scoreboard-api │ Python asyncio port 5001 + │ GET /live │◄──────────────────────────────────────┐ + │ GET /health │ │ + └────────┬─────────┘ │ + │ HTTP poll HTTP poll │ + ▼ │ │ + ┌──────────────────┐ ┌─────────┴──────────┐ │ + │ live-bridge │ │ apps/web │ │ + │ Node.js service │ │ React (Vercel) │ │ + │ writes snapshots│ │ Manual tracking │ │ + └────────┬─────────┘ │ Live scoreboard UI │ │ + │ @supabase/supabase-js └────────────┬────────┘ │ + ▼ │ │ + ┌──────────────────────────────────────────────▼─────────┐ │ + │ Supabase │ │ + │ teams · players · bouts · player_stats │ │ + │ live_games · live_jam_snapshots │ │ + └─────────────────────────────────────────────────────────┘ │ + │ + VITE_SCOREBOARD_API_URL ──────────────────────────────────────┘ +``` - ```bash - git clone https://github.com/a1ly404/derby-stat-tracker.git - cd derby-stat-tracker - ``` +**Two modes after login:** +- **📊 Manual Stat Tracking** — track jams, lineups, and scores by hand during a bout; all data saved directly to Supabase from the browser. +- **📡 Live from Scoreboard** — read live data from CRG via the scoreboard API; the `live-bridge` service captures jam snapshots automatically. -2. **Install dependencies** +--- - ```bash - npm install - ``` +## Prerequisites -3. **Set up Supabase** - - Create a new project at [supabase.com](https://supabase.com) - - Go to Settings > API to get your project URL and anon key - - Run the SQL schema from `database/schema.sql` in your Supabase SQL editor +| Tool | Version | +|---|---| +| Node.js | ≥ 20 | +| npm | ≥ 10 | +| Python | ≥ 3.11 (for `scoreboard-api`) | +| Git | any recent version | -4. **Configure environment variables** - - Copy `.env.example` to `.env.local` and update with your Supabase credentials: +--- - ```bash - cp .env.example .env.local - ``` +## Quick Start - Then edit `.env.local` with your actual values: +### 1. Clone the repo - ```env - VITE_SUPABASE_URL=your_supabase_project_url - VITE_SUPABASE_ANON_KEY=your_supabase_anon_key - ``` +```sh +git clone https://github.com/a1ly404/derby-stat-tracker.git +cd derby-stat-tracker +``` -5. **Run the development server** +### 2. Install all Node dependencies (workspaces) - ```bash - npm run dev - ``` +```sh +npm install +``` -6. **Open your browser** - - Navigate to `http://localhost:5174` - - Create an account or sign in to start tracking derby stats! +This installs dependencies for `apps/web`, `packages/live-frontend`, `services/live-bridge`, and `e2e` in one command. -## Database Schema +### 3. Set up environment variables -The application uses the following main tables: +**Web app** (`apps/web/.env`): +```env +VITE_SUPABASE_URL=https://your-project.supabase.co +VITE_SUPABASE_ANON_KEY=your-anon-key +VITE_SCOREBOARD_API_URL=http://localhost:5001 +``` -- **teams**: Store team information -- **players**: Player details with team associations -- **bouts**: Derby game/match records -- **player_stats**: Detailed performance statistics per player per bout +**Live bridge** (`services/live-bridge/.env`): +```env +SCOREBOARD_API_URL=http://localhost:5001 +SUPABASE_URL=https://your-project.supabase.co +SUPABASE_SERVICE_KEY=your-service-role-key +POLL_INTERVAL_MS=1000 +``` -See `database/schema.sql` for the complete database structure. +> ⚠️ Use the **service role key** (not the anon key) for `live-bridge` — it needs to bypass RLS to write snapshots. +> Never commit `.env` files. `.env.example` files are provided in each package. -## Development +### 4. Apply the database schema -### Available Scripts +1. Create a new project at [supabase.com](https://supabase.com) +2. Open the SQL editor +3. Run `database/schema.sql` (initial tables) +4. Run `database/migrations/001_live_tables.sql` (live tracking tables) -- `npm run dev` - Start development server -- `npm run build` - Build for production -- `npm run preview` - Preview production build locally -- `npm run lint` - Run ESLint for code quality +See [`database/README.md`](database/README.md) and [`database/migrations/README.md`](database/migrations/README.md) for details. -### Project Structure +### 5. Set up the scoreboard API (Python) -```text -src/ -├── components/ # React components -├── hooks/ # Custom React hooks -├── lib/ # Utilities and configurations -├── contexts/ # React contexts (if needed) -└── assets/ # Static assets +```sh +cd services/scoreboard-api +git clone https://github.com/a1ly404/derby-scoreboard-api . # first time only +python -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +pip install -r requirements.txt +python main.py ``` +The service listens on `http://localhost:5001` by default. + +--- + +## Running the App + +From the **monorepo root**: + +```sh +# Start the React web app (http://localhost:5173) +npm run dev + +# Start the live bridge service +npm run bridge:dev + +# Run web app unit tests +npm run test + +# Run unit tests across all workspaces +npm run test:all + +# Run Playwright E2E tests (starts dev server automatically) +npm run e2e + +# Lint all workspaces +npm run lint +``` + +From individual workspaces: + +```sh +# Web app +cd apps/web && npm run dev + +# Live bridge +cd services/live-bridge && npm run dev + +# E2E tests +cd e2e && npm run test:ui +``` + +--- + ## Deployment -### Environment Variables for Production +### Web App — Vercel -⚠️ **Important**: Never commit `.env.local` to your repository! It contains secrets. +The `apps/web` app is deployed to Vercel. -For production deployment, you need to set these environment variables in your hosting platform: +**Vercel project settings** (configure in the Vercel dashboard): +- **Root Directory:** `apps/web` +- **Build Command:** `npm run build` +- **Output Directory:** `dist` +- **Install Command:** `npm ci` -- `VITE_SUPABASE_URL` - Your Supabase project URL -- `VITE_SUPABASE_ANON_KEY` - Your Supabase anonymous key +Required environment variables in Vercel: +- `VITE_SUPABASE_URL` +- `VITE_SUPABASE_ANON_KEY` +- `VITE_SCOREBOARD_API_URL` (set to your publicly accessible scoreboard API URL, or leave empty to prompt users to configure it in the UI) -### Vercel Deployment +### Live Bridge — Local / Docker -1. **Push your code to GitHub** (without `.env.local`) +The `live-bridge` service is designed to run **on the same machine as the CRG scoreboard** (or on your local network alongside it): -2. **Connect to Vercel** - - Go to [vercel.com](https://vercel.com) - - Import your GitHub repository +```sh +cd services/live-bridge +cp .env.example .env # fill in values +npm run build +npm run start +``` -3. **Add Environment Variables** - - In Vercel dashboard: Project Settings > Environment Variables - - Add: +--- - ```env - VITE_SUPABASE_URL = your_supabase_project_url - VITE_SUPABASE_ANON_KEY = your_supabase_anon_key - ``` +## Importing the Spark Frontend (`packages/live-frontend`) -4. **Deploy!** - Vercel will automatically build and deploy +The `packages/live-frontend` package is a placeholder for the live overlay UI created with GitHub Spark. Once you have access to the spark repo source: -### Netlify Deployment +1. Copy the source files into `packages/live-frontend/` +2. Ensure the package exports React components from its `index.ts` +3. Add `"@derby/live-frontend": "*"` to `apps/web/package.json` dependencies +4. Run `npm install` from the root to link the workspace package +5. Import components in `apps/web/src/components/LiveScoreboardView.tsx` -1. **Push to GitHub** (without `.env.local`) +--- -2. **Connect to Netlify** - - Go to [netlify.com](https://netlify.com) - - Connect your GitHub repository +## Testing -3. **Add Environment Variables** - - In Netlify dashboard: Site Settings > Environment Variables - - Add the same variables as above +| Test suite | Command | Location | +|---|---|---| +| Unit + component (Vitest) | `npm run test` | `apps/web/src/**/*.test.*` | +| Coverage report | `npm run test:coverage` | `apps/web/coverage/` | +| Scoreboard API (pytest) | `pytest` in `services/scoreboard-api` | `services/scoreboard-api/tests/` | +| E2E (Playwright) | `npm run e2e` | `e2e/tests/` | -4. **Build Settings** - - Build command: `npm run build` - - Publish directory: `dist` +--- -### Other Platforms +## CI/CD -For other hosting platforms (Cloudflare Pages, Firebase, etc.), the process is similar: +GitHub Actions workflows (`.github/workflows/`): -1. Connect your GitHub repository -2. Set the environment variables in the platform's dashboard -3. Configure build command as `npm run build` with output directory `dist` +| Workflow | Trigger | What it does | +|---|---|---| +| `ci.yml` | push/PR to `main`, `dev` | Lint → test → build web app; deploy to Vercel on `main` | +| `quality-gate.yml` | push/PR | Code quality checks | +| `lighthouse.yml` | push to `main` | Lighthouse performance audit | +| `codeql.yml` | scheduled + push | CodeQL security analysis | +| `test-before-deploy.yml` | push to `main` | Gate: tests must pass before any deploy | -### Error Handling +--- -If environment variables are missing in production, users will see a friendly configuration error page instead of a broken app. +## Database Schema -## Contributing +See [`database/README.md`](database/README.md) for the full schema reference. -1. Fork the repository -2. Create a feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add some amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request +**Core tables:** `teams`, `players`, `player_teams`, `bouts`, `player_stats` + +**Live tracking tables** (added by `migrations/001_live_tables.sql`): +- `live_games` — one row per scoreboard session +- `live_jam_snapshots` — one row per jam boundary, written by `live-bridge` + +--- + +## Contributing -## License +1. Create a feature branch from `dev` +2. Make your changes +3. Ensure `npm run lint` and `npm run test` pass +4. Open a PR targeting `dev` -This project is licensed under the MIT License - see the LICENSE file for details. +--- -## Support +## Licence -If you have any questions or run into issues, please open an issue on GitHub or contact the development team. +Private repository — all rights reserved. \ No newline at end of file diff --git a/apps/live-tracker/.github/dependabot.yml b/apps/live-tracker/.github/dependabot.yml new file mode 100644 index 0000000..6738561 --- /dev/null +++ b/apps/live-tracker/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: "weekly" diff --git a/apps/live-tracker/.gitignore b/apps/live-tracker/.gitignore new file mode 100644 index 0000000..6cfe203 --- /dev/null +++ b/apps/live-tracker/.gitignore @@ -0,0 +1,34 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*-dist +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env +**/agent-eval-report* +packages +pids +.file-manifest +.devcontainer/ + +.spark-workbench-id diff --git a/apps/live-tracker/LICENSE b/apps/live-tracker/LICENSE new file mode 100644 index 0000000..28a50fa --- /dev/null +++ b/apps/live-tracker/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/live-tracker/PRD.md b/apps/live-tracker/PRD.md new file mode 100644 index 0000000..1a158f9 --- /dev/null +++ b/apps/live-tracker/PRD.md @@ -0,0 +1,138 @@ +# Planning Guide + +A live roller derby scoreboard visualization that displays real-time score progression as an animated line graph, consuming data from the derby-scoreboard-api spec. + +**Experience Qualities**: +1. **Dynamic** - The graph updates smoothly in real-time as jam scores come in, creating an exciting live sports experience +2. **Clear** - Score progression is immediately readable with distinct team colors and bold typography making current scores obvious at a glance +3. **Athletic** - Bold colors, sharp contrasts, and high-energy design evoke the competitive spirit of roller derby + +**Complexity Level**: Light Application (multiple features with basic state) +This is a focused visualization tool that displays live data with real-time updates, state management for score data, and interactive controls for data input/simulation. + +## Essential Features + +**Live Score Graph Visualization** +- Functionality: Displays cumulative score progression over time (by jam) as two line graphs +- Purpose: Shows the competitive narrative of the match - who's ahead, comeback moments, and scoring momentum +- Trigger: Automatically updates as new jam data arrives +- Progression: Initial state loads → New jam score received → Graph animates to show new data point → Running totals update → Process repeats +- Success criteria: Graph displays smoothly with no jank, lines are clearly distinguishable, axis labels are readable + +**Score Input/API Integration** +- Functionality: Poll derby-scoreboard-api endpoints to fetch live game data with configurable intervals, or accept manual jam-by-jam scoring data with period/intermission tracking +- Purpose: Display real-time match data from the API or allow manual data entry during live matches with support for half-time and period breaks +- Trigger: User enables API polling with endpoint URL, manually submits jam scores, or adds intermission markers between periods +- Progression: API URL configured → Polling enabled → Fetch game data every N seconds → Parse jams array with period data → Detect period changes → Insert intermission markers → Calculate running totals → Graph updates with visual breaks → Repeat +- Success criteria: API polls successfully, connection status displays accurately, graph updates smoothly with intermission breaks shown visually, manual input available when polling disabled, period transitions clearly marked + +**Current Score Display** +- Functionality: Shows current cumulative totals for both teams prominently +- Purpose: Quick reference for current match state without analyzing the graph +- Trigger: Updates automatically with each new jam +- Progression: Score changes → Number animates/transitions → New value displayed +- Success criteria: Numbers are large, readable, and update smoothly + +**Match Progress Indicator** +- Functionality: Shows current jam number and period with intermission tracking +- Purpose: Provides context for where we are in the match timeline and indicates period transitions +- Trigger: Updates with each jam submission and intermission marker +- Progression: Jam submitted → Counter increments → Display updates → Intermission added → Period increments → Visual break shown in timeline +- Success criteria: Always in sync with graph data, periods clearly indicated in timeline with visual intermission markers + +**API Configuration Panel** +- Functionality: Allow users to configure API endpoint URL, polling interval, and enable/disable live polling +- Purpose: Connect to derby-scoreboard-api instances for live data feeds +- Trigger: User enters API URL and toggles polling on +- Progression: Settings panel visible → User enters endpoint → Sets polling interval → Enables polling → Connection status displays → Live data flows in +- Success criteria: Settings persist, connection indicator shows live/disconnected status, polling respects configured interval + +## Edge Case Handling + +- **No Data State**: Display empty graph with instructions to add first jam or enable API polling +- **Single Team Scoring**: Handle jams where only one team scores (other team gets 0) +- **Large Score Differentials**: Auto-scale Y-axis to accommodate blowout games +- **Rapid Data Entry**: Queue updates if submissions come faster than animations +- **Invalid Scores**: Validate non-negative integers, reject invalid input with toast notification +- **API Connection Failures**: Show disconnected status, display error toast, continue retrying on interval +- **Empty API Response**: Handle games with no jams gracefully +- **Malformed API Data**: Validate API response structure, show error if data doesn't match expected format +- **Period Transitions**: Automatically detect and visualize intermission breaks when period numbers change in API data +- **Multiple Intermissions**: Support multiple periods with visual breaks in the timeline for each intermission + +## Design Direction + +Bold, high-contrast sports aesthetic with electric energy. Think ESPN graphics meets modern data visualization - punchy colors, clean lines, and information that pops off the screen. The design should feel like you're trackside at a derby bout. + +## Color Selection + +High-energy sports palette with electric blues and fierce magentas representing the competing teams, set against a deep charcoal background for maximum contrast and visual impact. + +- **Primary Color**: Electric Blue (oklch(0.65 0.22 240)) - Team 1's line color, energetic and bold, communicates speed and competition +- **Secondary Colors**: + - Hot Magenta (oklch(0.62 0.28 330)) - Team 2's line color, fierce and attention-grabbing + - Deep Charcoal (oklch(0.15 0.01 270)) - Background, provides dramatic contrast for colored elements + - Soft White (oklch(0.97 0.005 90)) - Primary text and axis labels +- **Accent Color**: Neon Yellow (oklch(0.88 0.19 95)) - Highlight color for current values and CTAs - Ratio with Charcoal 12.5:1 ✓ +- **Foreground/Background Pairings**: + - Background (Deep Charcoal): Soft White text - Ratio 11.8:1 ✓ + - Electric Blue: White text - Ratio 5.2:1 ✓ + - Hot Magenta: White text - Ratio 4.8:1 ✓ + +## Font Selection + +Strong geometric sans-serif with excellent legibility at all sizes, conveying athleticism and modern sports broadcasting. + +- **Typographic Hierarchy**: + - H1 (Current Scores): Teko Bold/72px/tight tracking - Stadium scoreboard feel + - H2 (Team Names): Teko SemiBold/32px/normal tracking + - Body (Labels/Jam Numbers): Inter Medium/16px/normal tracking + - Small (Axis Labels): Inter Regular/13px/wide tracking + +## Animations + +Smooth, purposeful animations reinforce the live nature of the event. Score updates should feel immediate and exciting with quick number transitions. Graph lines draw in with elastic easing to create anticipation. Avoid slow, laggy animations - everything should feel snappy and responsive like live sports coverage. + +## Component Selection + +- **Components**: + - Card - Contain the main graph visualization and API config panel with subtle shadow + - Input - Number inputs for jam score entry and text input for API URL with validation + - Button - Submit scores with "Add Jam" primary action style, "Add Intermission" for period breaks + - Badge - Show current jam number, period, connection status (Live/Disconnected) + - Separator - Divide sections cleanly + - Switch - Toggle API polling on/off + - Label - Form field labels for accessibility +- **Customizations**: + - Custom D3.js graph component for the line chart visualization with intermission markers + - Intermission breaks shown as vertical highlighted regions with dashed borders and rotated "INTERMISSION" labels + - Custom score display with large animated numbers + - Custom color scheme override for team-specific elements + - Connection status badges with green (connected) and red (disconnected) states + - Period indicator badge showing current period number +- **States**: + - Buttons: Default has solid fill, hover brightens 10%, active scales 98%, disabled is muted at 50% opacity + - Inputs: Default has subtle border, focus has 2px accent ring, error state shows red border with shake animation, disabled state when polling is active + - Switch: Off state is muted, on state uses accent color + - Connection badge: Green pulsing when live, red when disconnected + - Intermission markers: Semi-transparent accent-colored vertical bands with dashed borders in the graph +- **Icon Selection**: + - Plus (add jam data) + - Coffee (add intermission break) + - Timer (jam/period indicators) + - WifiHigh (connected status) + - WifiSlash (disconnected status) +- **Spacing**: + - Outer container: p-6 + - Card padding: p-8 + - Graph margins: m-4 + - Form fields: gap-4 + - Section spacing: space-y-6 +- **Mobile**: + - Stack score displays vertically instead of horizontal + - Reduce graph height from 500px to 350px + - Form inputs stack full-width + - API config fields stack vertically + - Font sizes scale down: H1 to 48px, H2 to 24px + - Reduce outer padding to p-4 + - Intermission labels remain visible but scale appropriately diff --git a/apps/live-tracker/README.md b/apps/live-tracker/README.md new file mode 100644 index 0000000..358beec --- /dev/null +++ b/apps/live-tracker/README.md @@ -0,0 +1,23 @@ +# ✨ Welcome to Your Spark Template! +You've just launched your brand-new Spark Template Codespace — everything’s fired up and ready for you to explore, build, and create with Spark! + +This template is your blank canvas. It comes with a minimal setup to help you get started quickly with Spark development. + +🚀 What's Inside? +- A clean, minimal Spark environment +- Pre-configured for local development +- Ready to scale with your ideas + +🧠 What Can You Do? + +Right now, this is just a starting point — the perfect place to begin building and testing your Spark applications. + +🧹 Just Exploring? +No problem! If you were just checking things out and don’t need to keep this code: + +- Simply delete your Spark. +- Everything will be cleaned up — no traces left behind. + +📄 License For Spark Template Resources + +The Spark Template files and resources from GitHub are licensed under the terms of the MIT license, Copyright GitHub, Inc. diff --git a/apps/live-tracker/SECURITY.md b/apps/live-tracker/SECURITY.md new file mode 100644 index 0000000..67a9cbf --- /dev/null +++ b/apps/live-tracker/SECURITY.md @@ -0,0 +1,31 @@ +Thanks for helping make GitHub safe for everyone. + +# Security + +GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). + +Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. + +## Reporting Security Issues + +If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please send an email to opensource-security[@]github.com. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + + * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Policy + +See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) diff --git a/apps/live-tracker/components.json b/apps/live-tracker/components.json new file mode 100644 index 0000000..858c35a --- /dev/null +++ b/apps/live-tracker/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/main.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/apps/live-tracker/index.html b/apps/live-tracker/index.html new file mode 100644 index 0000000..d8a9645 --- /dev/null +++ b/apps/live-tracker/index.html @@ -0,0 +1,19 @@ + + + +
+ + ++ Last update: {lastUpdate.toLocaleTimeString()} +
+ )} +No jams recorded yet
++ {pollEnabled ? 'Waiting for API data...' : 'Add your first jam below or enable API polling'} +
+Live Derby Scoreboard • {pollEnabled ? 'Polling API for updates' : 'Ready for manual input'}
+
+ {error.message}
+
+ ${JSON.stringify(body, null, 2)}
+
+