# GridPilot Alpha Development Plan

## Overview

**Mission:** Prove automation + unified management value proposition through functional alpha.

**Scope:** In-memory web app demonstrating core league management workflows integrated with companion automation.

**Timeline:** Alpha validates technical approach; production follows with persistent infrastructure.

**Value Proof:**
- Automation reduces admin workload (companion integration)
- Unified platform consolidates fragmented tools (web app)
- Clean Architecture enables rapid iteration (in-memory → production swap)

---

## Technical Approach

### Architecture Strategy

**Production domain logic, swappable infrastructure:**

```
Domain (Production)         ← Business rules, invariants
Application (Production)    ← Use cases, orchestration
Infrastructure (Swappable)  ← In-memory adapters for alpha
Presentation (Production)   ← Next.js UI components
```

**Alpha = Real code + Fake persistence**

- Domain entities enforce actual business rules
- Application use cases implement real workflows
- Infrastructure uses in-memory adapters (no database)
- Presentation renders production-ready UI

**Post-Alpha Migration:**

```typescript
// Alpha
const driverRepo = new InMemoryDriverRepository();

// Production
const driverRepo = new PostgresDriverRepository(pool);
```

Same ports, same use cases, different adapters.

---

### Mode System Design

**Current:** `pre-launch` | `post-launch`  
**Alpha:** `pre-launch` | `alpha`

**Mode Behavior:**

- `pre-launch`: Landing page only (current state)
- `alpha`: Full app UI with in-memory data (no auth, no persistence)

**Implementation:**

```typescript
// lib/mode.ts
export type AppMode = 'pre-launch' | 'alpha';

// Middleware gates
if (mode === 'alpha') {
  // Enable /dashboard, /leagues, /profile routes
  // Inject in-memory repositories
}
```

**Environment Control:**

```
GRIDPILOT_MODE=alpha npm run dev
```

---

## Features

### F1: Driver Profile Creation

**User Story:** Driver creates single profile to participate in leagues.

**Acceptance Criteria:**
- Driver enters: name, racing number, iRacing ID
- System validates: unique number per league, valid ID format
- Profile displays: basic info, empty race history
- Single profile per user (alpha constraint)

**Domain Model:**

```typescript
class Driver {
  id: DriverId
  name: string
  racingNumber: number
  iRacingId: string
  createdAt: Date
}
```

**UI Flow:**
1. `/profile/create` form
2. Validation feedback (inline)
3. Success → redirect to `/dashboard`
4. Profile visible at `/profile`

---

### F2: League Management

**User Story:** Admin creates league, drivers join, schedule displays.

**Acceptance Criteria:**
- Admin creates league: name, description, rules
- System generates: league ID, creation timestamp
- Drivers browse available leagues
- Drivers join single league (alpha constraint)
- League page shows: roster, schedule placeholder, standings

**Domain Model:**

```typescript
class League {
  id: LeagueId
  name: string
  adminId: DriverId
  description: string
  rules: string
  members: DriverId[]
  createdAt: Date
}
```

**UI Flow:**
1. `/leagues/create` (admin only)
2. `/leagues` (browse/join)
3. `/leagues/:id` (league detail)
4. Join button → updates roster
5. Leave button available

---

### F3: Race Scheduling via Companion

**User Story:** Admin schedules race through companion, web app displays schedule.

**Acceptance Criteria:**
- Admin configures race in companion app
- Companion sends race metadata to web app (IPC/API stub)
- Web app displays: track, cars, date/time, session settings
- Schedule shows upcoming races
- Past races marked as completed

**Domain Model:**

```typescript
class Race {
  id: RaceId
  leagueId: LeagueId
  trackId: string
  carIds: string[]
  scheduledAt: Date
  sessionName: string
  status: 'scheduled' | 'completed' | 'cancelled'
}
```

**Integration:**

```typescript
// Companion → Web App (simulated in alpha)
interface RaceScheduleRequest {
  leagueId: string
  sessionConfig: HostedSessionConfig
  scheduledAt: Date
}
```

**UI Flow:**
1. `/leagues/:id/schedule` displays races
2. Each race shows: date, track, cars
3. Status badge: upcoming/completed
4. Click race → detail view

---

### F4: Results Display (Manual Import)

**User Story:** Admin imports results CSV, system displays classification and updates standings.

**Acceptance Criteria:**
- Admin uploads iRacing results CSV
- System parses: positions, drivers, incidents, lap times
- Classification displays immediately
- Standings auto-calculate
- Results linked to scheduled race

**Domain Model:**

```typescript
class RaceResult {
  id: ResultId
  raceId: RaceId
  driverId: DriverId
  position: number
  incidents: number
  fastestLap?: number
  status: 'finished' | 'dnf' | 'dsq'
}
```

**UI Flow:**
1. `/leagues/:id/races/:raceId/results/import`
2. CSV upload field
3. Preview parsed results
4. Confirm → save
5. Redirect to `/leagues/:id/races/:raceId/results`

---

### F5: Standings Calculation

**User Story:** System calculates driver and team standings from race results.

**Acceptance Criteria:**
- Points awarded per position (configurable preset)
- Standings update after each result import
- Display: position, driver, points, races
- Team standings aggregate member points
- Drop weeks not implemented (alpha limitation)

**Domain Model:**

```typescript
class Standing {
  leagueId: LeagueId
  driverId: DriverId
  position: number
  points: number
  racesCompleted: number
}

class PointsPreset {
  name: string
  pointsMap: Map<number, number> // position → points
}
```

**Calculation:**

```typescript
class CalculateStandingsUseCase {
  execute(leagueId: LeagueId): Standing[] {
    const results = resultRepo.findByLeague(leagueId);
    const points = applyPointsPreset(results);
    return sortByPoints(points);
  }
}
```

**UI Flow:**
1. `/leagues/:id/standings` (driver view)
2. Table: pos, driver, points, races
3. Auto-updates after result import
4. Team standings at `/leagues/:id/standings/teams`

---

## Data Model (In-Memory)

### Core Entities

```typescript
// Domain/entities/Driver.ts
class Driver {
  id: DriverId
  name: string
  racingNumber: number
  iRacingId: string
  currentLeagueId?: LeagueId
  createdAt: Date
}

// Domain/entities/League.ts
class League {
  id: LeagueId
  name: string
  adminId: DriverId
  description: string
  rules: string
  memberIds: DriverId[]
  pointsPreset: PointsPreset
  createdAt: Date
}

// Domain/entities/Race.ts
class Race {
  id: RaceId
  leagueId: LeagueId
  trackId: string
  carIds: string[]
  scheduledAt: Date
  sessionName: string
  status: RaceStatus
  hostedSessionId?: string // Links to companion
}

// Domain/entities/RaceResult.ts
class RaceResult {
  id: ResultId
  raceId: RaceId
  driverId: DriverId
  position: number
  incidents: number
  fastestLapTime?: number
  status: ResultStatus
}

// Domain/entities/Standing.ts
class Standing {
  leagueId: LeagueId
  driverId: DriverId
  position: number
  points: number
  racesCompleted: number
  updatedAt: Date
}
```

### Value Objects

```typescript
// Domain/value-objects/DriverId.ts
class DriverId {
  value: string
  static generate(): DriverId
}

// Domain/value-objects/RaceStatus.ts
type RaceStatus = 'scheduled' | 'completed' | 'cancelled';

// Domain/value-objects/ResultStatus.ts
type ResultStatus = 'finished' | 'dnf' | 'dsq';

// Domain/value-objects/PointsPreset.ts
class PointsPreset {
  name: string
  pointsMap: Map<number, number>
  
  static F1_2024 = new PointsPreset('F1 2024', new Map([
    [1, 25], [2, 18], [3, 15], [4, 12], [5, 10],
    [6, 8], [7, 6], [8, 4], [9, 2], [10, 1]
  ]));
}
```

---

## UI/UX Flow

### User Journey: Driver Registration → Race → Results

**Step 1: Landing (Pre-Launch Mode)**
- Visit `/`
- See landing page
- Email signup (not functional in alpha)

**Step 2: Switch to Alpha Mode**
- `GRIDPILOT_MODE=alpha`
- Reload page
- Landing replaced with dashboard redirect

**Step 3: Create Profile**
- Visit `/profile/create`
- Enter: "John Doe", number 42, iRacing ID "123456"
- Submit → profile created
- Redirect `/dashboard`

**Step 4: Join League**
- Visit `/leagues`
- See available leagues
- Click "European GT League"
- Review schedule, rules
- Click "Join League"
- Confirmation modal
- Success → league roster updated

**Step 5: View Schedule**
- Dashboard shows next race
- Visit `/leagues/:id/schedule`
- See upcoming: "Monza GP - March 15, 2025"
- Race details: GT3, 60min, 19:00 CET

**Step 6: Race Day (External)**
- Driver joins iRacing hosted session
- Companion automation created session
- Driver races (outside GridPilot)

**Step 7: Results Import (Admin)**
- Admin downloads iRacing CSV
- Visit `/leagues/:id/races/:raceId/results/import`
- Upload CSV
- Preview shows classification
- Confirm import

**Step 8: View Results & Standings**
- Driver visits `/leagues/:id/races/:raceId/results`
- Sees: 3rd place, 2x incidents, 1:42.5 fastest lap
- Click "Standings"
- Driver standings show: 15 points, 1 race
- Team standings aggregate teammates

---

## Companion Integration

### Race Scheduling Flow

**Companion Side:**

```typescript
// Companion creates hosted session
const sessionConfig: HostedSessionConfig = {
  sessionName: 'European GT - Monza GP',
  trackId: 'monza-gp',
  carIds: ['porsche-911-gt3-r'],
  scheduledAt: new Date('2025-03-15T19:00:00Z'),
  // ... full config
};

// Send to web app (alpha: IPC stub)
await webAppClient.scheduleRace({
  leagueId: 'league-123',
  sessionConfig,
});
```

**Web App Side:**

```typescript
// Application/use-cases/ScheduleRaceUseCase.ts
class ScheduleRaceUseCase {
  async execute(req: ScheduleRaceRequest): Promise<Race> {
    // Validate league exists
    const league = await leagueRepo.findById(req.leagueId);
    
    // Create race entity
    const race = Race.create({
      leagueId: req.leagueId,
      trackId: req.sessionConfig.trackId,
      carIds: req.sessionConfig.carIds,
      scheduledAt: req.scheduledAt,
      sessionName: req.sessionConfig.sessionName,
      status: 'scheduled',
    });
    
    // Persist (in-memory for alpha)
    await raceRepo.save(race);
    
    return race;
  }
}
```

**Alpha Implementation:**

- Companion → Web App via simulated IPC
- Web app stores race in `InMemoryRaceRepository`
- Schedule view renders from repository
- No actual Playwright automation in web app

**Post-Alpha:**

- Companion → Web App via REST API
- Web app stores in PostgreSQL
- Companion polls for scheduled races
- Full automation execution

---

### Result Import Flow

**Manual Import (Alpha):**

```typescript
// Application/use-cases/ImportRaceResultsUseCase.ts
class ImportRaceResultsUseCase {
  async execute(raceId: RaceId, csvData: string): Promise<RaceResult[]> {
    // Parse CSV
    const parsed = parseIRacingCSV(csvData);
    
    // Validate race exists and is completed
    const race = await raceRepo.findById(raceId);
    
    // Map to domain entities
    const results = parsed.map(row => RaceResult.create({
      raceId,
      driverId: findDriverByIRacingId(row.customerId),
      position: row.finishPos,
      incidents: row.incidents,
      fastestLapTime: row.bestLapTime,
      status: row.reasonOut ? 'dnf' : 'finished',
    }));
    
    // Persist
    await resultRepo.saveMany(results);
    
    // Trigger standings recalculation
    await calculateStandingsUseCase.execute(race.leagueId);
    
    return results;
  }
}
```

**Post-Alpha Automation:**

- Web app calls iRacing API directly
- Automated result fetch after session end
- No CSV upload required

---

## Testing Strategy

### Unit Tests

**Domain Layer:**

```typescript
describe('Driver', () => {
  it('validates racing number uniqueness per league', () => {
    // Test domain invariants
  });
});

describe('Standing', () => {
  it('calculates points from results', () => {
    // Test scoring logic
  });
});

describe('PointsPreset', () => {
  it('applies F1 2024 points correctly', () => {
    // Test point mapping
  });
});
```

**Application Layer:**

```typescript
describe('CreateDriverProfileUseCase', () => {
  it('creates driver with valid data', async () => {
    const repo = new InMemoryDriverRepository();
    const useCase = new CreateDriverProfileUseCase(repo);
    
    const driver = await useCase.execute({
      name: 'John Doe',
      racingNumber: 42,
      iRacingId: '123456',
    });
    
    expect(driver.name).toBe('John Doe');
  });
  
  it('rejects duplicate racing number in same league', async () => {
    // Test business rule enforcement
  });
});
```

### Integration Tests

**Repository Layer:**

```typescript
describe('InMemoryDriverRepository', () => {
  it('persists and retrieves driver', async () => {
    const repo = new InMemoryDriverRepository();
    const driver = Driver.create({...});
    
    await repo.save(driver);
    const found = await repo.findById(driver.id);
    
    expect(found).toEqual(driver);
  });
  
  it('handles concurrent writes', async () => {
    // Test in-memory consistency
  });
});
```

**Use Case Workflows:**

```typescript
describe('Race Result Import Workflow', () => {
  it('imports CSV and updates standings', async () => {
    // Setup: league, drivers, scheduled race
    // Execute: import CSV
    // Assert: results saved, standings updated
  });
});
```

### E2E Tests (Playwright)

**User Flows:**

```typescript
test('driver can create profile and join league', async ({ page }) => {
  await page.goto('/profile/create');
  await page.fill('[name="name"]', 'John Doe');
  await page.fill('[name="racingNumber"]', '42');
  await page.fill('[name="iRacingId"]', '123456');
  await page.click('button[type="submit"]');
  
  await expect(page).toHaveURL('/dashboard');
  
  await page.goto('/leagues');
  await page.click('text=European GT League');
  await page.click('text=Join League');
  
  await expect(page.locator('.roster')).toContainText('John Doe');
});
```

**Integration with Companion:**

```typescript
test('admin schedules race via companion integration', async ({ page }) => {
  // Simulate companion IPC call
  await simulateCompanionScheduleRace({
    leagueId: 'league-123',
    sessionConfig: { /* ... */ },
  });
  
  // Verify web app displays scheduled race
  await page.goto('/leagues/league-123/schedule');
  await expect(page.locator('.race-card')).toContainText('Monza GP');
});
```

---

## Success Metrics

### Technical Validation

**Architecture Proof:**
- [ ] All use cases testable in isolation
- [ ] Domain logic has zero infrastructure dependencies
- [ ] In-memory adapters swap cleanly with production stubs
- [ ] No business logic in presentation layer

**Feature Completeness:**
- [ ] Driver can create profile
- [ ] Driver can join single league
- [ ] Admin can create league
- [ ] Schedule displays companion-scheduled races
- [ ] Results import updates standings
- [ ] Standings calculate correctly

### User Experience Validation

**Admin Workflow:**
- [ ] Admin creates league in <2 minutes
- [ ] Admin imports results in <30 seconds
- [ ] Admin views standings with zero manual calculation

**Driver Workflow:**
- [ ] Driver creates profile in <1 minute
- [ ] Driver finds and joins league in <2 minutes
- [ ] Driver views schedule without asking admin
- [ ] Driver sees results immediately after import

### Value Proposition Validation

**Automation Integration:**
- [ ] Companion schedules race → web app displays schedule (proves connectivity)
- [ ] Web app schedule guides companion automation (proves unified workflow)

**Unified Management:**
- [ ] Single source of truth for league data
- [ ] No Discord/spreadsheet required for standings
- [ ] Driver profile persists across sessions (in-memory during session)

---

## Implementation Phases

### Phase 1: Foundation (Week 1-2)

**Deliverables:**
- [ ] Mode system: `pre-launch` → `alpha` transition
- [ ] Middleware: mode-gated routes
- [ ] Domain models: Driver, League, Race, RaceResult, Standing
- [ ] In-memory repositories: all entities
- [ ] Basic UI layout: dashboard, navigation

**Dependencies:**
- Clean Architecture patterns from companion
- Next.js 15 app router
- TypeScript strict mode

---

### Phase 2: Driver & League Core (Week 3-4)

**Deliverables:**
- [ ] Use case: CreateDriverProfile
- [ ] Use case: CreateLeague
- [ ] Use case: JoinLeague
- [ ] UI: `/profile/create`
- [ ] UI: `/leagues` (browse)
- [ ] UI: `/leagues/create`
- [ ] UI: `/leagues/:id` (detail)
- [ ] Unit tests: domain validation
- [ ] Integration tests: repository operations

**Dependencies:**
- Phase 1 complete
- Driver/League value objects

---

### Phase 3: Scheduling & Companion Integration (Week 5-6)

**Deliverables:**
- [ ] Use case: ScheduleRace (from companion)
- [ ] IPC stub: companion → web app communication
- [ ] Domain model: Race status transitions
- [ ] UI: `/leagues/:id/schedule`
- [ ] UI: Race detail cards
- [ ] Integration tests: companion scheduling flow

**Dependencies:**
- Phase 2 complete
- Companion POC running
- HostedSessionConfig understanding

---

### Phase 4: Results & Standings (Week 7-8)

**Deliverables:**
- [ ] Use case: ImportRaceResults
- [ ] Use case: CalculateStandings
- [ ] CSV parser: iRacing results format
- [ ] PointsPreset: F1 2024 preset
- [ ] UI: `/leagues/:id/races/:raceId/results/import`
- [ ] UI: `/leagues/:id/races/:raceId/results`
- [ ] UI: `/leagues/:id/standings`
- [ ] Unit tests: points calculation
- [ ] Integration tests: results → standings pipeline

**Dependencies:**
- Phase 3 complete
- iRacing CSV samples

---

### Phase 5: Polish & Testing (Week 9-10)

**Deliverables:**
- [ ] E2E tests: full user flows
- [ ] Error handling: all use cases
- [ ] Loading states: all async operations
- [ ] Mobile responsive: all views
- [ ] Theme compliance: THEME.md
- [ ] Voice compliance: VOICE.md
- [ ] Documentation: alpha usage guide

**Dependencies:**
- Phase 4 complete
- Design system from landing page

---

## Known Limitations (Alpha Scope)

### Explicitly Deferred Features

**Authentication & Authorization:**
- No login/signup
- No user sessions
- No permission checks
- **Rationale:** In-memory data resets on restart; auth irrelevant for testing workflows

**Data Persistence:**
- No database
- No file storage
- Data clears on server restart
- **Rationale:** Alpha validates domain logic and UI flows, not persistence

**Multi-League Support:**
- Driver joins single league only
- No cross-league profile
- No multi-league standings
- **Rationale:** Simplifies alpha scope; multi-league in production

**Advanced Scoring:**
- No drop weeks
- No bonus points (fastest lap, etc.)
- Single points preset only
- **Rationale:** Complex scoring deferred to production

**Team Competition:**
- No team management
- No team standings
- No team profiles
- **Rationale:** Driver/admin flows first; teams in production

**Result Automation:**
- Manual CSV import only
- No iRacing API integration
- No live result fetch
- **Rationale:** Proves import pipeline; automation post-alpha

**Complaint & Penalty System:**
- Not implemented in alpha
- **Rationale:** Core competition flows first; penalties in production

**Multi-Sim Support:**
- iRacing only
- **Rationale:** Alpha validates single-sim workflows

**Rating System:**
- Not implemented in alpha
- **Rationale:** Requires production data volume

---

## Alpha Success Definition

**Alpha is successful if:**

1. **Technical:** In-memory adapters swap cleanly with production stubs (no domain/app changes)
2. **Functional:** All 5 core features work end-to-end
3. **Integration:** Companion schedules race, web app displays it
4. **Value:** Admin workload reduced by automation visibility
5. **Architecture:** Clean Architecture boundaries respected (testable via unit tests)

**Alpha is ready for production when:**

1. Use cases remain unchanged
2. Only infrastructure layer needs replacement
3. UI components are production-ready
4. Theme and voice guidelines followed
5. E2E tests pass consistently

---

## Post-Alpha Migration Path

### Infrastructure Swap

**Repositories:**

```typescript
// Alpha
const driverRepo = new InMemoryDriverRepository();

// Production
const driverRepo = new PostgresDriverRepository(pool);
```

**Authentication:**

```typescript
// Alpha: No-op adapter
class NoAuthService implements IAuthService {
  async getCurrentUser() { return mockDriver; }
}

// Production
class NextAuthService implements IAuthService {
  async getCurrentUser() { return getServerSession(); }
}
```

**API Integration:**

```typescript
// Alpha: IPC stub
class StubCompanionClient implements ICompanionClient {
  async scheduleRace() { /* in-memory */ }
}

// Production
class HTTPCompanionClient implements ICompanionClient {
  async scheduleRace() { return fetch('/api/races'); }
}
```

### Zero Domain Changes

- `Driver`, `League`, `Race` entities unchanged
- `CreateDriverProfileUseCase` unchanged
- `CalculateStandingsUseCase` unchanged
- UI components unchanged

**Only wiring changes:**

```typescript
// apps/website/app/providers.tsx
const mode = getAppMode();

const repositories = mode === 'alpha'
  ? createInMemoryRepositories()
  : createProductionRepositories(db);
```

---

## Theme & Voice Compliance

### Visual Guidelines (THEME.md)

**Colors:**
- Deep Graphite `#0E0F11` (background)
- Primary Blue `#198CFF` (accents)
- Performance Green `#6FE37A` (success states)

**Typography:**
- Inter font family
- Semibold headings
- Light/regular body

**Animations:**
- 150-250ms transitions
- Subtle hover states
- Smooth scrolling

**Components:**
- 6-8px rounded cards
- Soft shadows (blur 20-28px)
- Hover glow on interactive elements

### Voice Guidelines (VOICE.md)

**Tone:**
- Calm, clear, direct
- No corporate jargon
- No startup hype
- No gamer slang

**Examples:**

✅ "Profile created."  
❌ "Awesome! Your profile is ready to rock!"

✅ "League joined."  
❌ "Congratulations! You're now part of an amazing community!"

✅ "Results imported."  
❌ "Success! We've synced your race data!"

---

## Architectural Decisions

### ADR-001: In-Memory Adapters for Alpha

**Decision:** Use in-memory repositories, not database, for alpha.

**Rationale:**
- Validates domain logic without infrastructure complexity
- Faster iteration on business rules
- Clean Architecture principles: domain independent of persistence
- Production migration only touches infrastructure layer

**Trade-offs:**
- Data lost on restart (acceptable for alpha testing)
- No concurrent user support (single-user alpha)
- Limited data volume testing (acceptable for workflows)

---

### ADR-002: Mode System vs Feature Flags

**Decision:** Use environment-based mode system (`GRIDPILOT_MODE=alpha`), not feature flags.

**Rationale:**
- Clear separation: landing vs alpha vs production
- No flag management complexity
- Environment-appropriate defaults
- Aligns with existing `pre-launch`/`post-launch` pattern

**Trade-offs:**
- Requires redeployment to switch modes (acceptable for alpha)
- No gradual rollout (not needed for internal alpha)

---

### ADR-003: Manual Result Import for Alpha

**Decision:** Admin uploads CSV, no iRacing API integration in alpha.

**Rationale:**
- Validates result processing pipeline
- Avoids iRacing API rate limits during testing
- Simpler alpha scope
- CSV upload pattern works for production fallback

**Trade-offs:**
- Manual admin step (acceptable for alpha)
- No live result sync (deferred to production)

---

### ADR-004: Single League Per Driver for Alpha

**Decision:** Drivers join one league maximum in alpha.

**Rationale:**
- Simplifies UI flows
- Reduces data model complexity
- Validates core competition workflow
- Multi-league requires persistent identity (production concern)

**Trade-offs:**
- Cannot test cross-league scenarios (acceptable for alpha)
- Multi-league profile in production

---

### ADR-005: No Authentication in Alpha

**Decision:** No login, no sessions, no permissions.

**Rationale:**
- In-memory data has no persistent identity
- Auth complexity distracts from workflow testing
- Production auth requires database anyway
- Alpha tests business logic, not security

**Trade-offs:**
- Cannot test multi-user scenarios (acceptable for alpha)
- Auth added in production with database

---

## Summary

**Alpha proves:** Automation + unified management reduces admin workload and consolidates fragmented tools.

**Technical approach:** Production domain/application logic with in-memory infrastructure.

**Core features:** Driver profiles, league management, race scheduling, result import, standings calculation.

**Success criteria:** Clean Architecture validated, workflows functional, companion integration working.

**Next step:** Production migration swaps only infrastructure adapters; domain and UI unchanged.

**Timeline:** 10 weeks to functional alpha demonstrating value proposition.