brackt/plans/draft-room-implementation.md

423 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Draft Room Implementation Plan
## Overview
Build a real-time draft room using Socket.IO for live updates, with snake draft grid, player selection, per-team queue, and chess clock timers.
---
## Phase 1: Database Schema & Models
### 1.1 New Tables
**`draft_picks`**
```sql
- id (uuid, primary key)
- season_id (uuid, references seasons)
- team_id (uuid, references teams)
- participant_id (uuid, references participants)
- pick_number (integer) - overall pick number
- round (integer)
- pick_in_round (integer)
- picked_by_user_id (varchar) - Clerk user ID
- picked_by_type (enum: 'owner', 'commissioner', 'auto') - who made the pick
- time_used (integer) - seconds used for this pick
- created_at (timestamp)
```
**`draft_queue`**
```sql
- id (uuid, primary key)
- season_id (uuid, references seasons)
- team_id (uuid, references teams)
- participant_id (uuid, references participants)
- queue_position (integer)
- created_at (timestamp)
- updated_at (timestamp)
```
**`draft_timers`**
```sql
- id (uuid, primary key)
- season_id (uuid, references seasons)
- team_id (uuid, references teams)
- time_remaining (integer) - seconds remaining
- updated_at (timestamp)
```
### 1.2 Schema Updates
**Update `seasons` table:**
```sql
- draft_initial_time (integer) - seconds, default 120 (2 minutes)
- draft_increment_time (integer) - seconds, default 30
- current_pick_number (integer) - track current pick
- draft_started_at (timestamp)
- draft_paused (boolean, default false)
```
### 1.3 Model Functions
- `createDraftPick()`
- `getDraftPicks(seasonId)`
- `getCurrentPick(seasonId)`
- `addToQueue(teamId, participantId)`
- `removeFromQueue(queueId)`
- `reorderQueue(teamId, participantIds[])`
- `getTeamQueue(teamId)`
- `updateTeamTimer(teamId, timeRemaining)`
- `getTeamTimers(seasonId)`
- `initializeDraftTimers(seasonId)` - set initial time for all teams
- `autoPickForTeam(teamId)` - pick from queue or top EV
---
## Phase 2: League Settings Updates
### 2.1 Add Timer Configuration to Settings Page
- Add fields to "Draft Rounds" card:
- Initial Time (seconds) - default 120
- Increment Time (seconds) - default 30
- Update action handler to save these values
- Show in league info panel
---
## Phase 3: Socket.IO Setup
### 3.1 Server Setup
- Install `socket.io`
- Create `server/socket.ts`
- Initialize Socket.IO server alongside Remix
- Set up draft room namespaces: `draft-${seasonId}`
### 3.2 Socket Events (Server)
**Listen for:**
- `join-draft` - user joins draft room
- `make-pick` - user makes a pick
- `add-to-queue` - add player to queue
- `remove-from-queue` - remove from queue
- `reorder-queue` - reorder queue
- `commissioner-force-pick` - commissioner forces pick
- `commissioner-start-draft` - start draft
- `commissioner-pause-draft` - pause/resume draft
**Emit:**
- `pick-made` - broadcast new pick to room
- `timer-update` - broadcast timer updates (every second)
- `current-pick-changed` - notify whose turn it is
- `draft-started` - draft has begun
- `draft-paused` - draft paused/resumed
- `draft-completed` - all picks made
- `queue-updated` - queue changed (only to team owner)
### 3.3 Client Hook
- Create `hooks/useDraftSocket.ts`
- Handle connection/disconnection
- Provide methods to emit events
- Provide listeners for events
---
## Phase 4: Draft Room Route & Layout
### 4.1 Route Setup
- Create `/app/routes/leagues/$leagueId.draft.tsx`
- Loader:
- Verify user access (team owner or commissioner)
- Load season, teams, draft slots, participants
- Load existing draft picks
- Load user's team queue
- Load timer states
- No action needed (all via Socket.IO)
### 4.2 Layout Structure
```
┌─────────────────────────────────────────────────┐
│ Draft Room Header │
│ League Name | Status | Commissioner Controls │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Draft Grid (horizontally scrollable) │
│ Team 1 Team 2 Team 3 ... │
│ 5:23 4:12 3:45 ... (timers) │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │1.01│ │1.02│ │1.03│ ... │
│ └────┘ └────┘ └────┘ │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │2.03│ │2.02│ │2.01│ ... (snake) │
│ └────┘ └────┘ └────┘ │
└─────────────────────────────────────────────────┘
┌──────────────────────┬──────────────────────────┐
│ Available Players │ My Queue │
│ [Search: ____] │ 1. Player A │
│ [x] Hide Drafted │ 2. Player B │
│ │ 3. Player C │
│ □ Player 1 - NFL │ [Clear Queue] │
│ EV: 125 │ │
│ □ Player 2 - NBA │ │
│ EV: 118 │ │
│ ⊠ Player 3 - MLB │ │
│ (DRAFTED) │ │
└──────────────────────┴──────────────────────────┘
```
### 4.3 No Navigation Bar
- Create standalone layout without main nav
- Add "Exit Draft Room" button to return to league page
---
## Phase 5: Draft Grid Component
### 5.1 Calculate Snake Order
- Use existing draft slots for team order
- Generate pick order:
- Round 1: slots in order (1→N)
- Round 2: slots reversed (N→1)
- Round 3: slots in order (1→N)
- etc.
### 5.2 Grid Display
- Show team names as column headers
- Show timer under each team name
- Render grid cells with pick numbers (1.01, 1.02, etc.)
- Highlight current pick (border, background color)
- Show drafted participant in completed picks
- Responsive: horizontal scroll on smaller screens
### 5.3 Pick Cell Component
```tsx
<PickCell
pickNumber="1.01"
round={1}
pickInRound={1}
teamId={teamId}
participant={draftedParticipant}
isCurrent={isCurrentPick}
isUserTeam={isUserTeam}
/>
```
---
## Phase 6: Player List Component
### 6.1 Player List Features
- Display all participants from season sports
- Show: "Participant Name - Sport"
- Show expected value
- Sort by: EV (desc), then alphabetical
- Search/filter by name
- Toggle to hide/show drafted players
- Click to add to queue (if not drafted)
- Visual indicator for drafted players
### 6.2 Player Item Component
```tsx
<PlayerItem
participant={participant}
isDrafted={isDrafted}
onAddToQueue={handleAddToQueue}
/>
```
---
## Phase 7: Queue Component
### 7.1 Queue Features
- Show only current user's team queue
- Display in order (1, 2, 3, ...)
- Drag & drop to reorder
- Remove button for each item
- "Clear Queue" button
- Real-time sync via Socket.IO
### 7.2 Queue Actions
- Add player → emit `add-to-queue` → update DB → emit `queue-updated` to team
- Remove → emit `remove-from-queue` → update DB → emit `queue-updated`
- Reorder → emit `reorder-queue` → update DB → emit `queue-updated`
---
## Phase 8: Timer System
### 8.1 Timer Display
- Show under each team name in grid
- Format:
- "M:SS" for times under 1 hour (e.g., "5:23")
- "H:MM:SS" for times 1 hour or more (e.g., "2:15:30")
- Color coding:
- Green: > 60s
- Yellow: 30-60s
- Red: < 30s
- Flashing red: < 10s
### 8.2 Timer Logic (Server-Side)
- When draft starts: initialize all timers with `draft_initial_time`
- During a team's turn:
- Their timer counts down every second
- Other teams' timers remain paused
- When pick is made:
- Stop countdown for current team
- Add `draft_increment_time` to current team's timer
- Move to next pick
- Start next team's timer countdown
- Emit `timer-update` every second to all clients
- When timer reaches 0: trigger `autoPickForTeam()`
### 8.3 Auto-Pick Function
```typescript
async function autoPickForTeam(teamId: string, seasonId: string) {
// 1. Check queue - pick first item
// 2. If queue empty, pick highest EV participant not drafted
// 3. Create draft pick with picked_by_type='auto'
// 4. Emit pick-made event
// 5. Move to next pick
}
```
---
## Phase 9: Draft Actions & Permissions
### 9.1 Making a Pick
- Only current team owner or commissioner can pick
- Click participant in player list (if current turn)
- Validate: participant not already drafted
- Create draft pick record
- Emit `pick-made` to room
- Update current pick
- Update timers
### 9.2 Commissioner Controls
- Start Draft button (if not started)
- Pause/Resume button
- Force Pick button (for current team)
- Show in header, only visible to commissioners
### 9.3 Draft Start Logic
- Can be started manually by commissioner
- Or auto-start when `draftDateTime` is reached (background job?)
- Change season status to "drafting"
- Initialize timers
- Emit `draft-started`
### 9.4 Draft Completion
- When all picks made (picks = teams × rounds):
- Change season status to "active"
- Emit `draft-completed`
- Stop timers
---
## Phase 10: Real-Time Updates
### 10.1 Client State Management
- Use React state for:
- Draft picks array
- Current pick number
- Team timers
- User's queue
- Available participants
- Update state when Socket.IO events received
### 10.2 Optimistic Updates
- When user makes pick: update UI immediately
- If server rejects: revert and show error
- For queue operations: update immediately
---
## Phase 11: Mobile Responsiveness
### 11.1 Mobile Layout Adjustments
- Draft grid: horizontal scroll
- Stack player list and queue vertically
- Collapsible sections for player list/queue
- Larger touch targets for buttons
- Simplified timer display
---
## Implementation Order
1. **Phase 1**: Database schema & migrations
2. **Phase 2**: League settings timer config
3. **Phase 3**: Socket.IO setup (server + client hook)
4. **Phase 4**: Draft room route & basic layout
5. **Phase 5**: Draft grid component (static first)
6. **Phase 6**: Player list component
7. **Phase 7**: Queue component
8. **Phase 8**: Timer system (server-side logic)
9. **Phase 9**: Draft actions & permissions
10. **Phase 10**: Wire up real-time updates
11. **Phase 11**: Mobile polish
---
## Open Questions / Future Considerations
- **Background job for auto-start**: How to trigger draft start at `draftDateTime`? Cron job? Polling?
- **Timer persistence**: What if server restarts during draft? Store timer state in DB?
- **Undo picks**: Not in initial scope, but may want later
- **Draft history/log**: Separate view to see all picks in order?
- **Notifications**: Alert users when it's their turn? (email, push, etc.)
---
## Requirements Summary
### User Requirements
- Route: `/leagues/$leagueId/draft`
- Queue: Per-team, private (stored in database)
- Timer: Chess clock (initial time + increment per pick)
- Real-time: WebSocket (Socket.IO)
- Mobile: Should work on mobile
- Commissioner: Can force picks
- Participant sorting: By EV (desc), then alphabetical
- Search: Filter participants by name
- Toggle: Show/hide drafted participants
- Teams: 6-16 teams typical (12 average)
- Timer config: Configurable per league/season
- Auto-pick: From queue, or top EV if queue empty
- Draft state: "drafting" status
- Start: Auto-start at draft date/time, or manual by commissioner
- Pause/Resume: Commissioner can pause/resume
- Pick log: Track who made each pick (owner/commissioner/auto)
- Grid: Horizontally scrollable
- Player display: "Player Name - Sport"
- Pre-draft: Room viewable before draft starts
- Post-draft: Change status to "active" when complete