# Draft Room Implementation Plan ## āœ… IMPLEMENTATION STATUS: 90% COMPLETE **Last Updated:** October 18, 2025 ### Quick Summary - āœ… **Phases 1-7**: COMPLETE (Database, Settings, Socket.IO, Draft Room, Grid, Player List, Queue) - 🟔 **Phase 8**: PARTIAL (Timer display done, server-side countdown NOT implemented) - āœ… **Phase 9**: COMPLETE (Draft actions, commissioner controls, force pick) - 🟔 **Phase 10**: PARTIAL (Pick updates work, timer sync not implemented) - āŒ **Phase 11**: NOT STARTED (Mobile polish) ### What Works Now - Full draft room with snake draft grid - Real-time pick updates via Socket.IO - Team queues with add/remove/clear - Commissioner force pick (auto + manual) - Search, filter, and hide drafted toggle - Start draft functionality - Timer display (client-side only) ### What's Missing - Server-side timer countdown - Auto-pick when timer expires - Pause/Resume draft - Timer persistence across server restarts - Mobile responsive tweaks --- ## 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 āœ… COMPLETE ### 1.1 New Tables āœ… COMPLETE **`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 āœ… COMPLETE **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 🟔 PARTIAL āœ… **Implemented:** - `getTeamQueue(teamId)` - in `app/models/draft-queue.ts` - `addToQueue()`, `removeFromQueue()`, `clearTeamQueue()`, `reorderQueueWithSeason()` - in `app/models/draft-queue.ts` - Draft pick creation - in API routes āŒ **Not Implemented:** - `updateTeamTimer(teamId, timeRemaining)` - not needed yet (no server-side countdown) - `getTeamTimers(seasonId)` - not needed yet - Standalone model functions (using inline queries in API routes instead) --- ## Phase 2: League Settings Updates āœ… COMPLETE ### 2.1 Add Timer Configuration to Settings Page āœ… COMPLETE - 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 āœ… COMPLETE ### Overview āœ… COMPLETE Socket.IO requires access to the HTTP server instance (not just the Express app). The key pattern is: ```javascript const server = http.createServer(app); const io = socketIo(server); server.listen(port); // NOT app.listen() ``` ### Architecture Decision **Option A: Modify server.js (Recommended)** - Change `app.listen()` to `http.createServer(app)` + `server.listen()` - Initialize Socket.IO with the HTTP server - Keep everything in one process - āœ… Simple, standard pattern - āœ… Socket.IO and Express share same port - āœ… Works with React Router v7 SSR - āŒ Requires modifying server.js **Option B: Separate Socket Server** - Run Socket.IO on different port (e.g., 3001) - Keep server.js untouched - āœ… No changes to existing server - āŒ CORS complexity - āŒ Two processes to manage - āŒ Different ports for HTTP and WebSocket **Decision: Use Option A** - Industry standard, simpler deployment, better DX ### 3.1 Server Setup #### Step 1: Modify server.js to use HTTP server **File: `server.js`** Change from: ```javascript app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); ``` To: ```javascript import { createServer } from "http"; const httpServer = createServer(app); // Import and initialize Socket.IO const { initializeSocketIO } = await import("./server/socket.js"); initializeSocketIO(httpServer); httpServer.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); ``` #### Step 2: Create Socket.IO module **File: `server/socket.js`** (use .js not .ts to avoid bundling issues) ```javascript import { Server as SocketIOServer } from "socket.io"; let io; export function initializeSocketIO(httpServer) { if (io) { console.log("Socket.IO already initialized"); return io; } io = new SocketIOServer(httpServer, { cors: { origin: process.env.NODE_ENV === "production" ? process.env.APP_URL : `http://localhost:${process.env.PORT || 3000}`, credentials: true, }, }); io.on("connection", (socket) => { console.log("Client connected:", socket.id); // Join draft room socket.on("join-draft", (seasonId) => { socket.join(`draft-${seasonId}`); console.log(`Socket ${socket.id} joined draft-${seasonId}`); }); // Leave draft room socket.on("leave-draft", (seasonId) => { socket.leave(`draft-${seasonId}`); console.log(`Socket ${socket.id} left draft-${seasonId}`); }); socket.on("disconnect", () => { console.log("Client disconnected:", socket.id); }); }); // Store globally for access in route handlers global.__socketIO = io; console.log("Socket.IO initialized"); return io; } export function getSocketIO() { if (!io && !global.__socketIO) { throw new Error("Socket.IO not initialized"); } return io || global.__socketIO; } ``` #### Step 3: Add global type definition **File: `server/types.d.ts`** ```typescript import type { Server as SocketIOServer } from "socket.io"; declare global { var __socketIO: SocketIOServer | undefined; } export {}; ``` #### Step 4: Update tsconfig.node.json Add server directory to includes: ```json { "include": [ "server.js", "server/**/*.js", "server/**/*.ts", "vite.config.ts" ] } ``` ### 3.2 Socket Events (Server) **Listen for:** - `join-draft` - user joins draft room - `leave-draft` - user leaves draft room - `make-pick` - user makes a pick (handled via HTTP POST, not socket) - `add-to-queue` - add player to queue (handled via HTTP POST) - `remove-from-queue` - remove from queue (handled via HTTP POST) - `reorder-queue` - reorder queue (handled via HTTP POST) **Note:** Write operations (picks, queue changes) should use HTTP POST requests for reliability and error handling. Socket.IO is for broadcasting updates to all clients. **Emit (from server to clients):** - `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 **File: `app/hooks/useDraftSocket.ts`** ```typescript import { useEffect, useRef, useState } from "react"; import { io, Socket } from "socket.io-client"; export function useDraftSocket(seasonId: string) { const socketRef = useRef(null); const [isConnected, setIsConnected] = useState(false); useEffect(() => { // Connect to Socket.IO server const socket = io({ path: "/socket.io", transports: ["websocket", "polling"], }); socketRef.current = socket; socket.on("connect", () => { console.log("Connected to Socket.IO"); setIsConnected(true); // Join the draft room socket.emit("join-draft", seasonId); }); socket.on("disconnect", () => { console.log("Disconnected from Socket.IO"); setIsConnected(false); }); // Cleanup on unmount return () => { if (socket) { socket.emit("leave-draft", seasonId); socket.disconnect(); } }; }, [seasonId]); // Helper to subscribe to events const on = (event: string, callback: (...args: any[]) => void) => { socketRef.current?.on(event, callback); }; // Helper to unsubscribe from events const off = (event: string, callback?: (...args: any[]) => void) => { socketRef.current?.off(event, callback); }; return { socket: socketRef.current, isConnected, on, off }; } ``` ### 3.4 Using Socket in Route Handlers When a pick is made via HTTP POST, broadcast to all clients: **Example in `app/routes/api/draft-pick.ts`:** ```typescript import { getSocketIO } from "~/server/socket.js"; export async function action({ request }: ActionFunctionArgs) { // ... validate and create pick ... const pick = await createDraftPick({...}); // Broadcast to all clients in the draft room const io = getSocketIO(); io.to(`draft-${seasonId}`).emit("pick-made", { pick, nextPickNumber, isDraftComplete, }); return json({ success: true, pick }); } ``` ### 3.5 Testing Socket.IO 1. Start dev server: `npm run dev` 2. Check console for "Socket.IO initialized" 3. Open browser console on draft page 4. Should see "Connected to Socket.IO" 5. Check server logs for "Client connected: [socket-id]" ### 3.6 Gotchas & Important Notes #### āš ļø Gotcha #1: Import path in route handlers ```typescript // āŒ WRONG - This won't work in React Router v7 import { getSocketIO } from "~/server/socket.js"; // āœ… CORRECT - Use relative path or configure alias import { getSocketIO } from "../../../server/socket.js"; ``` **Why:** React Router v7 runs server code, but `~` alias typically only works for app code. You may need to configure the import path or use relative imports. #### āš ļø Gotcha #2: Client hook creates new connection on every render The current `useDraftSocket` hook will reconnect if `seasonId` changes. This is correct, but be aware: - Don't call this hook multiple times in the same component tree - Don't pass a changing `seasonId` unless you want to switch rooms - Consider memoizing the `seasonId` prop #### āš ļø Gotcha #3: Event listener cleanup ```typescript // āŒ WRONG - Listeners not cleaned up useEffect(() => { socket.on("pick-made", handlePickMade); }, []); // āœ… CORRECT - Clean up listeners useEffect(() => { socket.on("pick-made", handlePickMade); return () => { socket.off("pick-made", handlePickMade); }; }, [handlePickMade]); ``` #### āš ļø Gotcha #4: Socket.IO path in production If you deploy behind a reverse proxy (nginx, etc.), you may need to configure the Socket.IO path: ```javascript // Client const socket = io({ path: "/socket.io", // Make sure this matches your proxy config }); // Server (if needed) const io = new SocketIOServer(httpServer, { path: "/socket.io", }); ``` #### āš ļø Gotcha #5: CORS might not be needed Since Socket.IO runs on the same server/port as your app, CORS might be unnecessary for same-origin requests. You can simplify: ```javascript // Simpler config if same-origin const io = new SocketIOServer(httpServer); ``` #### āš ļø Gotcha #6: Global storage in development The global `__socketIO` pattern works, but in development with HMR, you might get multiple instances. The `if (io)` check helps, but be aware of potential stale connections during hot reloads. #### āš ļø Gotcha #7: TypeScript import in route handlers ```typescript // This might cause issues because socket.js is JavaScript import { getSocketIO } from "~/server/socket.js"; // Consider making it TypeScript with proper types // Or use @ts-ignore if necessary // @ts-ignore import { getSocketIO } from "~/server/socket.js"; ``` ### 3.7 Production Considerations - Socket.IO will automatically upgrade from polling to WebSocket - Ensure load balancer supports WebSocket (sticky sessions required!) - Consider Redis adapter for multi-server deployments (future) - Monitor connection counts and memory usage - Set up proper error handling for disconnections - Consider implementing reconnection logic with exponential backoff --- ## Phase 4: Draft Room Route & Layout āœ… COMPLETE ### 4.1 Route Setup āœ… COMPLETE - 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 āœ… COMPLETE ### 5.1 Calculate Snake Order āœ… COMPLETE - 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 āœ… COMPLETE ### 6.1 Player List Features āœ… COMPLETE - 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 āœ… COMPLETE ### 7.1 Queue Features 🟔 MOSTLY COMPLETE āœ… **Implemented:** - Show only current user's team queue - Display in order (1, 2, 3, ...) - Remove button for each item - "Clear Queue" button - Queue icon buttons next to Pick button āŒ **Not Implemented:** - Drag & drop to reorder (API route exists, UI not implemented) - Real-time Socket.IO sync (using HTTP requests + page state instead) ### 7.2 Queue Actions āœ… COMPLETE āœ… **Implemented via API routes:** - `/api/queue/add` - Add player to queue - `/api/queue/remove` - Remove from queue - `/api/queue/clear` - Clear entire queue - `/api/queue/reorder` - Reorder queue (API ready, UI not implemented) --- ## Phase 8: Timer System 🟔 PARTIAL (50% Complete) ### 8.1 Timer Display āœ… COMPLETE - 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) āŒ NOT IMPLEMENTED **What's Missing:** - Server-side countdown interval - Emit `timer-update` every second - Auto-pick when timer reaches 0 - Timer increment after pick - Timer persistence **Note:** Timer display shows `--:--` because no server-side countdown is running. This is the main missing piece for a fully functional draft. ### 8.3 Auto-Pick Function 🟔 PARTIAL āœ… **Implemented:** - `/api/draft/force-autopick` - Commissioner can force auto-pick - Checks queue first, then picks highest EV - Creates pick with `picked_by_type='auto'` āŒ **Not Implemented:** - Automatic trigger when timer reaches 0 - Server-side timer countdown to trigger it --- ## Phase 9: Draft Actions & Permissions āœ… COMPLETE ### 9.1 Making a Pick āœ… COMPLETE - 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 🟔 MOSTLY COMPLETE āœ… **Implemented:** - Start Draft button (shows when status is "pre_draft") - Force Auto Pick (right-click context menu on current pick) - Force Manual Pick (right-click context menu, opens participant dialog) - `/api/draft/start` - Starts draft, initializes timers - `/api/draft/make-pick` - Regular pick by team owner - `/api/draft/force-autopick` - Commissioner force auto-pick - `/api/draft/force-manual-pick` - Commissioner manual pick for any team āŒ **Not Implemented:** - Pause/Resume button - Pause/Resume API routes ### 9.3 Draft Start Logic āœ… COMPLETE āœ… **Implemented:** - Manual start by commissioner via "Start Draft" button - Changes season status to "draft" - Initializes timers for all teams - Emits `draft-started` socket event āŒ **Not Implemented:** - Auto-start when `draftDateTime` is reached (would need background job/cron) ### 9.4 Draft Completion āœ… COMPLETE āœ… **Implemented:** - Detects when all picks made - Changes season status to "active" - Emits `draft-completed` socket event --- ## Phase 10: Real-Time Updates 🟔 PARTIAL (60% Complete) ### 10.1 Client State Management āœ… COMPLETE - 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 🟔 PARTIAL āœ… **Implemented:** - Pick updates broadcast via Socket.IO `pick-made` event - All clients receive and display picks in real-time - Queue operations update local state immediately āŒ **Not Implemented:** - Timer updates via Socket.IO (no server-side countdown) - Revert on server rejection (currently just shows alert) **Note:** Real-time updates work for picks but not for timers since server-side countdown isn't implemented. --- ## Phase 11: Mobile Responsiveness āŒ NOT STARTED ### 11.1 Mobile Layout Adjustments āŒ NOT STARTED **What's Needed:** - Draft grid: horizontal scroll (partially works) - Stack player list and queue vertically on mobile - Collapsible sections for player list/queue - Larger touch targets for buttons - Simplified timer display - Test on actual mobile devices - Responsive breakpoints optimization **Current State:** Desktop layout works, mobile usable but not optimized. --- ## 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 --- ## āœ… IMPLEMENTATION COMPLETION SUMMARY ### Files Created/Modified **API Routes:** - āœ… `/app/routes/api/draft.make-pick.ts` - Team owner makes pick - āœ… `/app/routes/api/draft.start.ts` - Commissioner starts draft - āœ… `/app/routes/api/draft.force-autopick.ts` - Commissioner force auto-pick - āœ… `/app/routes/api/draft.force-manual-pick.ts` - Commissioner manual pick - āœ… `/app/routes/api/queue.add.ts` - Add to queue - āœ… `/app/routes/api/queue.remove.ts` - Remove from queue - āœ… `/app/routes/api/queue.clear.ts` - Clear queue - āœ… `/app/routes/api/queue.reorder.ts` - Reorder queue (API ready, UI not implemented) **Models:** - āœ… `/app/models/draft-queue.ts` - Queue management functions **Routes:** - āœ… `/app/routes/leagues/$leagueId.draft.$seasonId.tsx` - Main draft room **Hooks:** - āœ… `/app/hooks/useDraftSocket.ts` - Socket.IO client hook **Server:** - āœ… `/server/socket.js` - Socket.IO server setup - āœ… `server.js` - Modified to use HTTP server for Socket.IO **Components:** - āœ… `/app/components/ui/context-menu.tsx` - Added for commissioner controls **Database:** - āœ… Schema updated with draft tables and timer fields - āœ… `draftQueueRelations` added to schema ### What Works Right Now 1. **Full Draft Flow:** - Commissioner can start draft - Team owners can make picks on their turn - Commissioner can force picks (auto or manual) - Real-time pick updates via Socket.IO - Draft completion detection 2. **Queue Management:** - Add/remove/clear queue - Queue persists in database - Queue icon buttons next to Pick button - Queue used by force autopick 3. **UI Features:** - Snake draft grid with pick numbers - Search participants by name - Filter by sport - Hide/show drafted toggle - Context menu for commissioner force pick - Responsive grid (horizontal scroll) 4. **Real-Time:** - Socket.IO connection indicator - Pick updates broadcast to all users - Room-based isolation (multiple drafts can run simultaneously) ### What's Missing (10%) 1. **Server-Side Timer Countdown** (Main Missing Piece): - No interval running on server - No `timer-update` socket events - Timer display shows `--:--` - No automatic trigger for auto-pick 2. **Pause/Resume:** - No pause/resume buttons - No API routes for pause/resume 3. **Minor Features:** - Drag & drop queue reorder UI - Mobile optimization - Auto-start at `draftDateTime` ### To Complete the Draft Room (Remaining 10%) **Priority 1: Server-Side Timer (Critical)** 1. Add interval in `server/socket.js` to countdown every second 2. Emit `timer-update` to draft room 3. Trigger auto-pick when timer reaches 0 4. Add timer increment after pick **Priority 2: Pause/Resume (Nice to Have)** 1. Add pause/resume buttons for commissioner 2. Create `/api/draft/pause` and `/api/draft/resume` routes 3. Update timer logic to respect pause state **Priority 3: Polish (Optional)** 1. Drag & drop queue reorder UI 2. Mobile responsive improvements 3. Auto-start background job --- ## 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 and resume? - **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.) - **Multi-server**: If scaling to multiple servers, need Redis adapter for Socket.IO --- ## 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