From 46332ea7353e95fdf08880b2881e063d30c9437e Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:26:18 -0800 Subject: [PATCH] =?UTF-8?q?feat:=20enhance=20autodraft=20settings=20with?= =?UTF-8?q?=20detailed=20option=20descriptions=20an=E2=80=A6=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: enhance autodraft settings with detailed option descriptions and improved state handling * test: update AutodraftSettings tests for button interactions and API payloads --- app/components/AutodraftSettings.tsx | 229 +++++++----- .../__tests__/AutodraftSettings.test.tsx | 348 +++++++++--------- .../leagues/$leagueId.draft.$seasonId.tsx | 2 +- 3 files changed, 311 insertions(+), 268 deletions(-) diff --git a/app/components/AutodraftSettings.tsx b/app/components/AutodraftSettings.tsx index 80075c0..88ae76e 100644 --- a/app/components/AutodraftSettings.tsx +++ b/app/components/AutodraftSettings.tsx @@ -1,19 +1,85 @@ -import { useState, useEffect } from "react"; -import { Switch } from "~/components/ui/switch"; +import { useState, useEffect, useRef } from "react"; +import { Check, Info } from "lucide-react"; +import { toast } from "sonner"; import { Label } from "~/components/ui/label"; -import { Button } from "~/components/ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; type AutodraftMode = "next_pick" | "while_on"; -// The three visible states map to isEnabled + mode combinations: -// "off" → isEnabled: false -// "next_pick" → isEnabled: true, mode: "next_pick" -// "all_picks" → isEnabled: true, mode: "while_on" -type AutodraftState = "off" | "next_pick" | "all_picks"; +// 4 visible states map to isEnabled + mode + queueOnly: +// "off" → isEnabled: false +// "next_queue" → isEnabled: true, mode: "next_pick", queueOnly: true +// "all_queue" → isEnabled: true, mode: "while_on", queueOnly: true +// "all_picks" → isEnabled: true, mode: "while_on", queueOnly: false +type AutodraftState = "off" | "next_queue" | "all_queue" | "all_picks"; -function toAutodraftState(isEnabled: boolean, mode: AutodraftMode): AutodraftState { +type OptionConfig = { + label: string; + desc: string; + isEnabled: boolean; + mode: AutodraftMode; + queueOnly: boolean; +}; + +// Record ensures all AutodraftState values are covered — no runtime find() needed +const OPTIONS: Record = { + off: { + label: "Off", + desc: "You pick manually every round.", + isEnabled: false, + mode: "next_pick", + queueOnly: false, + }, + next_queue: { + label: "Next in Queue", + desc: "Autodrafts your next pick from your queue, then turns off.", + isEnabled: true, + mode: "next_pick", + queueOnly: true, + }, + all_queue: { + label: "All in Queue", + desc: "Keeps autodrafting from your queue until it runs out, then turns off automatically.", + isEnabled: true, + mode: "while_on", + queueOnly: true, + }, + all_picks: { + label: "All Picks", + desc: "Keeps autodrafting all your picks. Uses your queue first, then falls back to the highest-ranked undrafted player.", + isEnabled: true, + mode: "while_on", + queueOnly: false, + }, +}; + +const OPTION_ORDER: AutodraftState[] = ["off", "next_queue", "all_queue", "all_picks"]; + +// border-l-[3px] is applied to ALL options (transparent when inactive) so the +// content never shifts when the active border color is applied. +function getButtonClassName(state: AutodraftState, isActive: boolean): string { + if (!isActive) { + return "border-l-[3px] border-l-transparent text-muted-foreground hover:bg-muted/40"; + } + if (state === "off") { + return "border-l-[3px] border-l-muted-foreground/50 text-foreground"; + } + return "border-l-[3px] border-l-electric bg-electric text-background"; +} + +function getCheckClassName(state: AutodraftState): string { + if (state === "off") return "text-muted-foreground"; + return "text-background"; +} + +function toAutodraftState( + isEnabled: boolean, + mode: AutodraftMode, + queueOnly: boolean +): AutodraftState { if (!isEnabled) return "off"; - return mode === "next_pick" ? "next_pick" : "all_picks"; + if (mode === "next_pick") return "next_queue"; + return queueOnly ? "all_queue" : "all_picks"; } interface AutodraftSettingsProps { @@ -35,83 +101,99 @@ export function AutodraftSettings({ isMyTurn, onUpdate, }: AutodraftSettingsProps) { - const [localState, setLocalState] = useState(toAutodraftState(isEnabled, mode)); - const [localQueueOnly, setLocalQueueOnly] = useState(queueOnly); - const [isUpdating, setIsUpdating] = useState(false); + const [localState, setLocalState] = useState( + toAutodraftState(isEnabled, mode, queueOnly) + ); + // Holds the abort controller for any in-flight save so rapid selections + // cancel the previous request rather than racing to update the server. + const abortRef = useRef(null); // Sync local state with props when they change (from socket events) useEffect(() => { - setLocalState(toAutodraftState(isEnabled, mode)); - }, [isEnabled, mode]); + setLocalState(toAutodraftState(isEnabled, mode, queueOnly)); + }, [isEnabled, mode, queueOnly]); - useEffect(() => { - setLocalQueueOnly(queueOnly); - }, [queueOnly]); + const sendUpdate = async (newState: AutodraftState) => { + // Cancel any in-flight request before starting a new one + abortRef.current?.abort(); + abortRef.current = new AbortController(); - const sendUpdate = async (newState: AutodraftState, newQueueOnly: boolean) => { - if (isUpdating) return; - setIsUpdating(true); - - const newEnabled = newState !== "off"; - const newMode: AutodraftMode = newState === "all_picks" ? "while_on" : "next_pick"; + const option = OPTIONS[newState]; try { const formData = new FormData(); formData.append("seasonId", seasonId); formData.append("teamId", teamId); - formData.append("isEnabled", newEnabled.toString()); - formData.append("mode", newMode); - formData.append("queueOnly", newQueueOnly.toString()); + formData.append("isEnabled", option.isEnabled.toString()); + formData.append("mode", option.mode); + formData.append("queueOnly", option.queueOnly.toString()); const response = await fetch("/api/autodraft/update", { method: "POST", body: formData, + signal: abortRef.current.signal, }); if (response.ok) { - onUpdate(newEnabled, newMode, newQueueOnly); + onUpdate(option.isEnabled, option.mode, option.queueOnly); + toast.success( + option.isEnabled ? `Autodraft set to "${option.label}"` : "Autodraft turned off" + ); } else { - // Revert on error - setLocalState(toAutodraftState(isEnabled, mode)); - setLocalQueueOnly(queueOnly); - console.error("Failed to update autodraft settings"); + setLocalState(toAutodraftState(isEnabled, mode, queueOnly)); + toast.error("Failed to save autodraft settings"); } } catch (error) { - // Revert on error - setLocalState(toAutodraftState(isEnabled, mode)); - setLocalQueueOnly(queueOnly); - console.error("Error updating autodraft settings:", error); - } finally { - setIsUpdating(false); + // Ignore cancellations — a newer selection has already taken over + if (error instanceof Error && error.name === "AbortError") return; + setLocalState(toAutodraftState(isEnabled, mode, queueOnly)); + toast.error("Failed to save autodraft settings"); } }; const handleStateChange = (newState: AutodraftState) => { - if (isMyTurn || isUpdating) return; + if (newState === localState || isMyTurn) return; setLocalState(newState); - sendUpdate(newState, localQueueOnly); + sendUpdate(newState); }; - const handleQueueOnlyChange = async (checked: boolean) => { - if (isMyTurn || isUpdating) return; - setLocalQueueOnly(checked); - sendUpdate(localState, checked); - }; - - const isDisabled = isMyTurn || isUpdating; + const isDisabled = isMyTurn; return (
- + {/* Header with info icon inline */} +
+ + + + + + +

Autodraft Options

+
+ {OPTION_ORDER.map((state) => { + const { label, desc } = OPTIONS[state]; + return ( +
+

{label}

+

{desc}

+
+ ); + })} +
+
+
+
- {/* Three-state button group */} -
- {(["off", "next_pick", "all_picks"] as AutodraftState[]).map((state) => { - const labels: Record = { - off: "Off", - next_pick: "Next Pick", - all_picks: "All Picks", - }; +
+ {OPTION_ORDER.map((state) => { + const { label } = OPTIONS[state]; const isActive = localState === state; return ( ); })}
- {/* Queue-only toggle — only visible when autodraft is active */} - {localState !== "off" && ( -
- - -
- )} - {isMyTurn && ( -

- Autodraft settings are disabled during your pick -

+

You're on the clock!

)}
); diff --git a/app/components/__tests__/AutodraftSettings.test.tsx b/app/components/__tests__/AutodraftSettings.test.tsx index 09cee7a..22e7246 100644 --- a/app/components/__tests__/AutodraftSettings.test.tsx +++ b/app/components/__tests__/AutodraftSettings.test.tsx @@ -2,7 +2,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { AutodraftSettings } from '~/components/AutodraftSettings'; -// Mock fetch +vi.mock('sonner', () => ({ + toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() }, +})); + global.fetch = vi.fn(); describe('AutodraftSettings Component', () => { @@ -11,8 +14,8 @@ describe('AutodraftSettings Component', () => { teamId: 'team-456', isEnabled: false, mode: 'next_pick' as const, - isMyTurn: false, queueOnly: false, + isMyTurn: false, onUpdate: vi.fn(), }; @@ -24,63 +27,59 @@ describe('AutodraftSettings Component', () => { }); }); + // ─── Rendering ──────────────────────────────────────────────────────────── + describe('Rendering', () => { - it('should render three autodraft state buttons', () => { + it('renders all four option buttons', () => { render(); expect(screen.getByRole('button', { name: 'Off' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Next Pick' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Next in Queue' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'All in Queue' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'All Picks' })).toBeInTheDocument(); }); - it('should not show the queue-only toggle when autodraft is Off', () => { - render(); - - expect(screen.queryByRole('switch')).not.toBeInTheDocument(); - expect(screen.queryByText('Only autodraft from queue')).not.toBeInTheDocument(); - }); - - it('should show the queue-only toggle when autodraft is active', () => { + it('does not render a queue-only toggle switch', () => { render(); - - expect(screen.getByRole('switch')).toBeInTheDocument(); - expect(screen.getByText('Only autodraft from queue')).toBeInTheDocument(); + expect(screen.queryByRole('switch')).not.toBeInTheDocument(); }); - it('should show disabled message when it is the user\'s turn', () => { + it('shows "You\'re on the clock!" when isMyTurn=true', () => { render(); - - expect(screen.getByText(/Autodraft settings are disabled during your pick/i)).toBeInTheDocument(); + expect(screen.getByText(/You're on the clock!/i)).toBeInTheDocument(); }); - it('should mark Off as active when isEnabled=false', () => { + it('marks Off as active (muted border) when isEnabled=false', () => { render(); - - const offButton = screen.getByRole('button', { name: 'Off' }); - expect(offButton.className).toContain('bg-electric'); + expect(screen.getByRole('button', { name: 'Off' }).className).toContain( + 'border-l-muted-foreground' + ); }); - it('should mark Next Pick as active when isEnabled=true and mode=next_pick', () => { - render(); - - const nextPickButton = screen.getByRole('button', { name: 'Next Pick' }); - expect(nextPickButton.className).toContain('bg-electric'); + it('marks Next in Queue as active when isEnabled=true, mode=next_pick', () => { + render(); + expect(screen.getByRole('button', { name: 'Next in Queue' }).className).toContain('bg-electric'); }); - it('should mark All Picks as active when isEnabled=true and mode=while_on', () => { - render(); + it('marks All in Queue as active when isEnabled=true, mode=while_on, queueOnly=true', () => { + render(); + expect(screen.getByRole('button', { name: 'All in Queue' }).className).toContain('bg-electric'); + }); - const allPicksButton = screen.getByRole('button', { name: 'All Picks' }); - expect(allPicksButton.className).toContain('bg-electric'); + it('marks All Picks as active when isEnabled=true, mode=while_on, queueOnly=false', () => { + render(); + expect(screen.getByRole('button', { name: 'All Picks' }).className).toContain('bg-electric'); }); }); - describe('Button Group Interaction', () => { - it('should switch to Next Pick when that button is clicked', async () => { - const onUpdate = vi.fn(); - render(); + // ─── Interaction ────────────────────────────────────────────────────────── - fireEvent.click(screen.getByRole('button', { name: 'Next Pick' })); + describe('Button interaction', () => { + it('switches to Next in Queue and calls onUpdate with correct args', async () => { + const onUpdate = vi.fn(); + render(); + + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( @@ -88,195 +87,176 @@ describe('AutodraftSettings Component', () => { expect.objectContaining({ method: 'POST' }) ); }); - - await waitFor(() => { - expect(onUpdate).toHaveBeenCalledWith(true, 'next_pick', false); - }); + await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'next_pick', true)); }); - it('should switch to All Picks when that button is clicked', async () => { + it('switches to All in Queue and calls onUpdate with correct args', async () => { const onUpdate = vi.fn(); - render(); + render(); + + fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); + + await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'while_on', true)); + }); + + it('switches to All Picks and calls onUpdate with correct args', async () => { + const onUpdate = vi.fn(); + render(); fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); - await waitFor(() => { - expect(onUpdate).toHaveBeenCalledWith(true, 'while_on', false); - }); + await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'while_on', false)); }); - it('should switch to Off when that button is clicked from an active state', async () => { + it('switches to Off and calls onUpdate with isEnabled=false', async () => { const onUpdate = vi.fn(); - render(); + render( + + ); fireEvent.click(screen.getByRole('button', { name: 'Off' })); - await waitFor(() => { - expect(onUpdate).toHaveBeenCalledWith(false, 'next_pick', false); - }); + await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(false, 'next_pick', false)); }); - it('should disable all buttons when it is the user\'s turn', () => { + it('disables all buttons when isMyTurn=true', () => { render(); expect(screen.getByRole('button', { name: 'Off' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Next Pick' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Next in Queue' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'All in Queue' })).toBeDisabled(); expect(screen.getByRole('button', { name: 'All Picks' })).toBeDisabled(); }); - it('should not fire API call when a button is clicked during user\'s turn', () => { + it('does not call fetch when a button is clicked during the user\'s turn', () => { render(); - - fireEvent.click(screen.getByRole('button', { name: 'Next Pick' })); - + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); expect(global.fetch).not.toHaveBeenCalled(); }); - it('should handle API errors by reverting state', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - (global.fetch as any).mockResolvedValueOnce({ - ok: false, - json: async () => ({ error: 'Server error' }), - }); - + it('does not re-fire when the already-active option is clicked', async () => { render(); - - fireEvent.click(screen.getByRole('button', { name: 'Next Pick' })); - - await waitFor(() => { - expect(consoleSpy).toHaveBeenCalled(); - }); - - // Off button should still be active (reverted) - const offButton = screen.getByRole('button', { name: 'Off' }); - expect(offButton.className).toContain('bg-electric'); - - consoleSpy.mockRestore(); - }); - - it('should handle network errors by reverting state', async () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); - - render(); - - fireEvent.click(screen.getByRole('button', { name: 'Next Pick' })); - - await waitFor(() => { - expect(consoleSpy).toHaveBeenCalled(); - }); - - consoleSpy.mockRestore(); - }); - }); - - describe('Queue-Only Toggle', () => { - it('should send queueOnly=true when the toggle is switched on', async () => { - const onUpdate = vi.fn(); - render(); - - fireEvent.click(screen.getByRole('switch')); - - await waitFor(() => { - expect(onUpdate).toHaveBeenCalledWith(true, 'next_pick', true); - }); - }); - - it('should send queueOnly=false when the toggle is switched off', async () => { - const onUpdate = vi.fn(); - render(); - - fireEvent.click(screen.getByRole('switch')); - - await waitFor(() => { - expect(onUpdate).toHaveBeenCalledWith(true, 'next_pick', false); - }); - }); - - it('should disable the queue-only toggle when it is the user\'s turn', () => { - render(); - - expect(screen.getByRole('switch')).toBeDisabled(); - }); - }); - - describe('API Integration', () => { - it('should post correct formData when switching to Next Pick', async () => { - render(); - - fireEvent.click(screen.getByRole('button', { name: 'Next Pick' })); - - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith( - '/api/autodraft/update', - expect.objectContaining({ method: 'POST', body: expect.any(FormData) }) - ); - }); - - const formData = (global.fetch as any).mock.calls[0][1].body as FormData; - expect(formData.get('seasonId')).toBe('season-123'); - expect(formData.get('teamId')).toBe('team-456'); - expect(formData.get('isEnabled')).toBe('true'); - expect(formData.get('mode')).toBe('next_pick'); - expect(formData.get('queueOnly')).toBe('false'); - }); - - it('should post mode=while_on when switching to All Picks', async () => { - render(); - - fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); - - await waitFor(() => { - expect(global.fetch).toHaveBeenCalled(); - }); - - const formData = (global.fetch as any).mock.calls[0][1].body as FormData; - expect(formData.get('mode')).toBe('while_on'); - expect(formData.get('isEnabled')).toBe('true'); - }); - - it('should post isEnabled=false when switching to Off', async () => { - render(); - fireEvent.click(screen.getByRole('button', { name: 'Off' })); - - await waitFor(() => { - expect(global.fetch).toHaveBeenCalled(); - }); - - const formData = (global.fetch as any).mock.calls[0][1].body as FormData; - expect(formData.get('isEnabled')).toBe('false'); + expect(global.fetch).not.toHaveBeenCalled(); }); + }); - it('should disable buttons during an in-flight API call', async () => { - let resolve: any; + // ─── Optimistic UI ──────────────────────────────────────────────────────── + + describe('Optimistic UI', () => { + it('does not disable buttons while a fetch is in flight', async () => { + let resolve: (v: any) => void; (global.fetch as any).mockReturnValueOnce(new Promise((r) => { resolve = r; })); render(); + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); - fireEvent.click(screen.getByRole('button', { name: 'Next Pick' })); + // Buttons stay enabled — optimistic UI does not block interaction + expect(screen.getByRole('button', { name: 'Off' })).not.toBeDisabled(); + + resolve!({ ok: true, json: async () => ({ success: true }) }); + }); + + it('fires a fetch for every rapid click, aborting previous in-flight requests', async () => { + render(); + + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); + fireEvent.click(screen.getByRole('button', { name: 'Off' })); + + await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(3)); + }); + }); + + // ─── Error handling ─────────────────────────────────────────────────────── + + describe('Error handling', () => { + it('reverts to the previous state on a non-ok API response', async () => { + (global.fetch as any).mockResolvedValueOnce({ ok: false }); + + render(); + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => { - expect(screen.getByRole('button', { name: 'Off' })).toBeDisabled(); - }); - - resolve({ ok: true, json: async () => ({ success: true }) }); - - await waitFor(() => { - expect(screen.getByRole('button', { name: 'Off' })).not.toBeDisabled(); + expect(screen.getByRole('button', { name: 'Off' }).className).toContain( + 'border-l-muted-foreground' + ); }); }); - it('should only process the first click when buttons are clicked rapidly', async () => { - render(); + it('reverts to the previous state on a network error', async () => { + (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); - fireEvent.click(screen.getByRole('button', { name: 'Next Pick' })); - fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); - fireEvent.click(screen.getByRole('button', { name: 'Off' })); + render(); + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); await waitFor(() => { - expect(global.fetch).toHaveBeenCalledTimes(1); + expect(screen.getByRole('button', { name: 'Off' }).className).toContain( + 'border-l-muted-foreground' + ); }); }); }); + + // ─── API payload ────────────────────────────────────────────────────────── + + describe('API payload', () => { + it('posts correct formData for Next in Queue (next_pick, queueOnly=true)', async () => { + render(); + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + + await waitFor(() => expect(global.fetch).toHaveBeenCalled()); + + const body = (global.fetch as any).mock.calls[0][1].body as FormData; + expect(body.get('seasonId')).toBe('season-123'); + expect(body.get('teamId')).toBe('team-456'); + expect(body.get('isEnabled')).toBe('true'); + expect(body.get('mode')).toBe('next_pick'); + expect(body.get('queueOnly')).toBe('true'); + }); + + it('posts correct formData for All in Queue (while_on, queueOnly=true)', async () => { + render(); + fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); + + await waitFor(() => expect(global.fetch).toHaveBeenCalled()); + + const body = (global.fetch as any).mock.calls[0][1].body as FormData; + expect(body.get('isEnabled')).toBe('true'); + expect(body.get('mode')).toBe('while_on'); + expect(body.get('queueOnly')).toBe('true'); + }); + + it('posts correct formData for All Picks (while_on, queueOnly=false)', async () => { + render(); + fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); + + await waitFor(() => expect(global.fetch).toHaveBeenCalled()); + + const body = (global.fetch as any).mock.calls[0][1].body as FormData; + expect(body.get('isEnabled')).toBe('true'); + expect(body.get('mode')).toBe('while_on'); + expect(body.get('queueOnly')).toBe('false'); + }); + + it('posts isEnabled=false for Off', async () => { + render(); + fireEvent.click(screen.getByRole('button', { name: 'Off' })); + + await waitFor(() => expect(global.fetch).toHaveBeenCalled()); + + const body = (global.fetch as any).mock.calls[0][1].body as FormData; + expect(body.get('isEnabled')).toBe('false'); + }); + + it('passes an AbortSignal to fetch', async () => { + render(); + fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + + await waitFor(() => expect(global.fetch).toHaveBeenCalled()); + + const options = (global.fetch as any).mock.calls[0][1]; + expect(options.signal).toBeInstanceOf(AbortSignal); + }); + }); }); diff --git a/app/routes/leagues/$leagueId.draft.$seasonId.tsx b/app/routes/leagues/$leagueId.draft.$seasonId.tsx index 68beb71..8016602 100644 --- a/app/routes/leagues/$leagueId.draft.$seasonId.tsx +++ b/app/routes/leagues/$leagueId.draft.$seasonId.tsx @@ -698,7 +698,7 @@ export default function DraftRoom() { if (userTeam && data.teamId === userTeam.id) { // Detect server-side auto-disable: was enabled with queueOnly, now disabled const prev = userAutodraftRef.current; - if (!data.isEnabled && prev.isEnabled && prev.queueOnly) { + if (!data.isEnabled && prev.isEnabled && prev.queueOnly && prev.mode === "while_on") { toast.info("Autodraft disabled — your queue is empty"); }