From 6e19a23e3580b957dbbf015a7cfae243d8482a04 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Thu, 16 Oct 2025 00:19:44 -0700 Subject: [PATCH] docs: add draft room implementation plan with database schema and UI components --- plans/draft-room-implementation.md | 423 +++++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 plans/draft-room-implementation.md diff --git a/plans/draft-room-implementation.md b/plans/draft-room-implementation.md new file mode 100644 index 0000000..0cd2f45 --- /dev/null +++ b/plans/draft-room-implementation.md @@ -0,0 +1,423 @@ +# 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 + +``` + +--- + +## 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 + +``` + +--- + +## 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