12 KiB
12 KiB
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
- 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
- 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
- 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:
- 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 teamsautoPickForTeam(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 roommake-pick- user makes a pickadd-to-queue- add player to queueremove-from-queue- remove from queuereorder-queue- reorder queuecommissioner-force-pick- commissioner forces pickcommissioner-start-draft- start draftcommissioner-pause-draft- pause/resume draft
Emit:
pick-made- broadcast new pick to roomtimer-update- broadcast timer updates (every second)current-pick-changed- notify whose turn it isdraft-started- draft has begundraft-paused- draft paused/resumeddraft-completed- all picks madequeue-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
<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
<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 → emitqueue-updatedto team - Remove → emit
remove-from-queue→ update DB → emitqueue-updated - Reorder → emit
reorder-queue→ update DB → emitqueue-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_timeto current team's timer - Move to next pick
- Start next team's timer countdown
- Emit
timer-updateevery second to all clients - When timer reaches 0: trigger
autoPickForTeam()
8.3 Auto-Pick Function
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-madeto 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
draftDateTimeis 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
- Phase 1: Database schema & migrations
- Phase 2: League settings timer config
- Phase 3: Socket.IO setup (server + client hook)
- Phase 4: Draft room route & basic layout
- Phase 5: Draft grid component (static first)
- Phase 6: Player list component
- Phase 7: Queue component
- Phase 8: Timer system (server-side logic)
- Phase 9: Draft actions & permissions
- Phase 10: Wire up real-time updates
- 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