# 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

```bash
# 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:
    ```typescript
    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_draft` → `draft` → `active` → `completed`
   - 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:

```typescript
// 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

### Scoring Event Dates

Game/event dates are stored in **two different places** depending on the event type, and any date-based query must account for both:

1. **`scoringEvents.eventDate`** (`date` column, `YYYY-MM-DD` string) — set directly on the event. Used for non-bracket events (majors, final standings) and sometimes for bracket events when the admin sets a single date for the whole round.

2. **`playoffMatchGames.scheduledAt`** (`timestamp` column) — set on individual games within a bracket match. Bracket events (e.g. a playoff series) often have `eventDate = null` on the scoring event itself, with each game's exact time stored here instead.

**Pattern for date-range queries:** Use a two-step approach:
1. `selectDistinct` event IDs via left joins through `playoffMatches → playoffMatchGames`, filtering with `eventDate IN [dates] OR (scheduledAt >= dayStart AND scheduledAt <= dayEnd)`
2. `findMany` on the matched IDs to load full event data with relations

See `getEventsForDates` and `getUpcomingEventsForDraftedParticipants` in `app/models/scoring-event.ts` for the reference implementation. All timestamp bounds use UTC (`T00:00:00.000Z` / `T23:59:59.999Z`).

### 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. **Verify** the generated migration SQL in `drizzle/` is correct
4. **Verify** a corresponding snapshot was created in `drizzle/meta/` (e.g., `0070_snapshot.json`)
5. Run `npm run db:migrate` to apply migration
6. Add model functions in `app/models/` for new queries
7. Update TypeScript types if needed

**Drizzle Migration Rules (CRITICAL):**
- **NEVER** manually create migration SQL files or journal entries. Always use `drizzle-kit generate` or `drizzle-kit generate --custom` so that snapshot files are created automatically.
- **NEVER** edit `drizzle/meta/_journal.json` by hand. The journal and snapshots must stay in sync — drizzle-kit manages both.
- Every migration **must** have a matching snapshot in `drizzle/meta/`. Missing snapshots cause `drizzle-kit generate` to fail with "data is malformed" errors, which blocks all future migration generation.
- Snapshot JSON must only contain PostgreSQL-valid fields. Do not add `"autoincrement"` to column definitions — PostgreSQL uses sequences/serial instead, and invalid fields cause Zod validation failures.
- If `drizzle-kit generate` prompts for column renames interactively, use `drizzle-kit generate --custom` instead and write the migration SQL by hand (include data backfill logic as needed).
- After generating, run `drizzle-kit generate` again to confirm "No schema changes" — this validates the snapshot chain is healthy.
- For destructive migrations (dropping columns, renaming), make the SQL idempotent with `IF EXISTS`/`IF NOT EXISTS` guards where possible.

### 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.)