brackt/CLAUDE.md
Chris Parsons 35fe84a1dd
Update claude.md to push more tests. (#84)
* Update claude.md to push more tests.

* Archiving old plans.

* fix: run DB migrations programmatically on server startup

Replace unreliable drizzle-kit CLI migration with drizzle-orm's built-in
migrator running in the server process before accepting connections. This
ensures migrations are always applied on deploy and fails fast if they error.

- Add runMigrations() to server.ts using drizzle-orm/postgres-js/migrator
- Skip migrations in development (handled manually via db:migrate)
- Add DATABASE_URL guard with a clear error message
- Remove start:production script (now identical to start)
- Update Dockerfile CMD to use npm run start

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 22:31:04 -08:00

9.8 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Brackt.com is a fantasy sports drafting platform built with React Router 7, featuring real-time draft functionality with Socket.IO, PostgreSQL database with DrizzleORM, and Clerk authentication.

Tech Stack

  • Framework: React Router 7 with SSR
  • Runtime: Node.js with Express server
  • Database: PostgreSQL with DrizzleORM
  • Auth: Clerk (@clerk/react-router)
  • Real-time: Socket.IO (server/socket.ts)
  • Styling: TailwindCSS + ShadCN UI components
  • Testing: Vitest (unit/component) + Cypress (E2E)
  • TypeScript: Strict mode enabled

Development Commands

# Development (HMR enabled)
npm run dev

# Build
npm run build                    # Build both Remix and server
npm run build:remix              # Build React Router app only
npm run build:server             # Build Express server only

# Database
npm run db:generate              # Generate Drizzle migrations
npm run db:migrate               # Run migrations

# Testing
npm test                         # Unit tests (watch mode)
npm run test:run                 # Unit tests (run once)
npm run test:ui                  # Unit tests with UI
npm run test:coverage            # Coverage report
npm run test:e2e                 # Cypress interactive
npm run test:e2e:headless        # Cypress headless
npm run test:all                 # Run all tests

# Type checking
npm run typecheck                # Check all TypeScript files

# Production
npm start                        # Start production server (requires build)
npm run start:production         # Run migrations + start server

Architecture Overview

Server Architecture

The application uses a dual-server architecture:

  1. server.ts - Main entry point that creates the HTTP server and initializes Socket.IO
  2. server/app.ts - Express app with React Router request handler and database context
  3. server/socket.ts - Socket.IO server for real-time draft updates
  4. server/timer.ts - Draft timer system that runs every second to update active draft timers

Important: The timer system (server/timer.ts) must be started for draft functionality. It uses a dedicated database connection and setInterval to manage all active draft timers.

Database Layer

  • Schema: database/schema.ts - All Drizzle table definitions
  • Context: Database connection provided via AsyncLocalStorage context (database/context.ts)
  • Models: app/models/ - Business logic and database queries organized by domain
    • Each model exports typed functions for CRUD operations
    • Models use Drizzle relations for joins
    • Example: app/models/league.ts, app/models/season.ts, app/models/draft-pick.ts

Frontend Architecture

  • Routes: Defined in app/routes.ts using React Router config syntax
    • IMPORTANT: React Router 7 requires ALL routes to be explicitly registered in app/routes.ts
    • When creating a new route file, you MUST also add it to app/routes.ts
    • Example: If you create app/routes/admin.sports-seasons.$id.new-feature.tsx, you must add:
      route(
        "sports-seasons/:id/new-feature",
        "routes/admin.sports-seasons.$id.new-feature.tsx"
      ),
      
  • File-based routing: Route files in app/routes/ map to URL patterns
    • $param = dynamic segment (e.g., $leagueId.tsx)
    • _index.tsx = index route
    • . separator for nested routes (e.g., leagues/$leagueId.settings.tsx)
  • Loaders/Actions: Use React Router's loader/action pattern for data fetching and mutations
  • Components: Organized in app/components/ with UI components in app/components/ui/

Real-time Features

Socket.IO is used for live draft updates:

  1. Join Draft: Clients join a room for their season (join-draft event)
  2. Timer Updates: Server broadcasts timer-update every second during active drafts
  3. Pick Made: Broadcasts pick-made when a participant is drafted
  4. Autodraft: Broadcasts autodraft-updated when teams enable/disable autodraft
  5. Team Connection: Broadcasts team-connected/team-disconnected for presence

Socket Integration: The server uses getSocketIO() from server/socket.ts to emit events. Clients use useSocket() hook (if implemented) to listen for events.

Key Domain Models

Fantasy Draft System

  1. League - Container for multiple seasons, has commissioners
  2. Season - Single year/iteration of a league with specific settings
    • Status: pre_draftdraftactivecompleted
    • Stores draft settings (rounds, timer settings, etc.)
  3. Team - Belongs to a season, owned by a user
  4. Draft Slot - Defines draft order for teams in a season
  5. Draft Pick - Record of each pick made during draft
  6. Draft Queue - Pre-ordered list of participants a team wants to draft
  7. Draft Timer - Active timer for current team on the clock
  8. Autodraft Settings - Per-team settings for automatic drafting

Sports Data System

  1. Sport - Base sport (e.g., NFL, NBA) with type (team/individual)
  2. Sports Season - Specific season of a sport (e.g., "2024 NFL Season")
  3. Participant - Individual athlete or team that can be drafted
  4. Season Template - Reusable configuration for league seasons
  5. Season Sport - Links a fantasy season to sports it covers
  6. Participant Result - Stores final standings/scores for participants

Important Patterns

Database Queries

Always use the model layer instead of direct Drizzle queries in routes:

// Good
import { findLeagueById } from "~/models/league";
const league = await findLeagueById(leagueId);

// Avoid - don't query directly in routes
const league = await db.query.leagues.findFirst({...});

Authentication

  • User data synced from Clerk via webhook (routes/api/webhooks/clerk.ts)
  • Check clerkId for ownership validation
  • Admin checks via isAdmin boolean on users table

Draft Logic

The draft system implements snake draft:

  • Draft slots define the base order (1, 2, 3, ...)
  • Each round alternates: odd rounds go 1→N, even rounds go N→1
  • Current pick determined by currentPickNumber on season
  • Timer system in server/timer.ts handles autopicks when time expires

Testing Strategy

  • Unit tests: Pure functions and utilities in __tests__/ directories
  • Component tests: React components using React Testing Library
  • E2E tests: Critical user flows in cypress/e2e/
  • Fixtures: Reusable test data in app/test/fixtures/
  • See TESTING.md for comprehensive testing guide

IMPORTANT: Tests are required for new features. When adding any new feature, you must include appropriate tests:

  • New model functions → unit tests in a co-located __tests__/ directory
  • New route loaders/actions → unit or integration tests for the logic
  • New utility functions → unit tests
  • New components with non-trivial logic → component tests
  • Critical user flows → Cypress E2E tests

Do not consider a feature complete until tests are written and passing.

Common Workflows

Adding a New Route

CRITICAL: Both steps 1 and 2 are required - the route will not work if you skip step 1!

  1. FIRST: Add route config to app/routes.ts
    • Example: route("admin/sports-seasons/:id/new-page", "routes/admin.sports-seasons.$id.new-page.tsx")
    • Nested admin routes go inside the route("admin", ...) array
  2. THEN: Create file in app/routes/ with corresponding name
  3. Export loader/action if needed
  4. TypeScript types are auto-generated via react-router typegen

Common mistake: Creating the route file but forgetting to register it in app/routes.ts - this will result in a 404!

Database Changes

  1. Update database/schema.ts with new tables/columns
  2. Run npm run db:generate to create migration
  3. Run npm run db:migrate to apply migration
  4. Add model functions in app/models/ for new queries
  5. Update TypeScript types if needed

Adding Socket.IO Events

  1. Define event types in server/socket.d.ts interfaces
  2. Add event handler in server/socket.ts
  3. Emit events from server code using getSocketIO().to(room).emit()
  4. Listen for events in client components

Admin Features

Admin routes are nested under /admin and require isAdmin: true on the user:

  • Sports management (admin.sports.*)
  • Sports seasons (admin.sports-seasons.*)
  • Season templates (admin.templates.*)
  • Data sync utilities (admin.data-sync.tsx)

File Structure Notes

  • app/: React Router application code
    • components/: React components (UI components in ui/)
    • models/: Database query functions organized by domain
    • routes/: Route handlers (loaders, actions, components)
    • lib/: Utility functions
    • hooks/: Custom React hooks
    • contexts/: React contexts
    • test/: Test setup and fixtures
  • server/: Server-side code (Express, Socket.IO, timers)
  • database/: Drizzle schema and context
  • drizzle/: Generated migrations
  • cypress/: E2E tests

Environment Variables

Required in .env:

  • DATABASE_URL - PostgreSQL connection string
  • Clerk variables for authentication (see .env.example)

Production Deployment

The app is containerized with Docker:

  • Runs migrations on startup (start:production script)
  • Server listens on port 3000 (configurable via PORT env var)
  • Serves static assets from build/client/
  • SSR handled by React Router Express adapter

Notes

  • Draft timer system must be explicitly started (usually in server initialization)
  • Socket.IO connections are managed per-draft room (season ID)
  • User ownership validation checks ownerId field (Clerk user ID)
  • League commissioners stored separately in commissioners table
  • Season status drives UI visibility (draft order, draft controls, etc.)