brackt/plans/draft-room-implementation.md

12 KiB
Raw Blame History

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 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

<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 → 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

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