From 33acc1430f24ac5ef52c2dd7e0c34379daf20824 Mon Sep 17 00:00:00 2001 From: JoshuaRileyDev <59296334+JoshuaRileyDev@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:14:28 +0000 Subject: [PATCH] fix: prevent queue settings modal from disappearing when tasks change (#1430) * fix: add missing namespace prefix to queue settings modal translations Fixed translation keys that were missing the 'tasks:' namespace prefix, which caused the queue settings modal to not display correctly. - Fixed DialogTitle translation - Fixed DialogDescription translation - Fixed Label translation - Fixed hint text translation Co-Authored-By: Claude * fix: prevent queue settings modal from disappearing when tasks change Fixed a race condition where the queue settings modal would not open reliably or would disappear when the tasks list changed. Root cause: The modal was conditionally rendered based on `projectId` which was derived from `tasks[0]?.projectId`. When there were no tasks or when tasks changed, `projectId` would become undefined, causing the modal to not render even though `showQueueSettings` was true. Solution: Store the `projectId` in a ref when the modal opens and use that stored value for rendering, ensuring the modal remains visible regardless of task state changes. Changes: - Added `queueSettingsProjectIdRef` to store projectId when modal opens - Update onQueueSettings handler to capture projectId before opening modal - Update modal rendering condition to use stored projectId from ref - Clear stored projectId when modal closes Co-Authored-By: Claude * docs: add JSDoc comments to QueueSettingsModal Added JSDoc docstrings to: - QueueSettingsModalProps interface - QueueSettingsModal component - handleSave function - handleInputChange function This improves code documentation and helps with docstring coverage. Co-Authored-By: Claude * fix: use stored projectId ref in handleSaveQueueSettings Fix a bug where the handleSaveQueueSettings function used the component-scoped projectId variable (derived from tasks[0]?.projectId) instead of the stored ref value. This caused saves to fail silently if tasks changed while the modal was open and projectId became undefined. Co-Authored-By: Claude * fix: prevent queue settings modal from opening when no projectId exists When the queue column is empty (no tasks), projectId is undefined because it's derived from tasks[0]?.projectId. The queue settings button should not open the modal in this case since there's no valid project to configure settings for. This fixes a silent failure where the button was clickable but had no effect when the queue was empty. Co-Authored-By: Claude --------- Co-authored-by: Claude --- .../src/renderer/components/KanbanBoard.tsx | 28 ++++++++++--- .../components/QueueSettingsModal.tsx | 40 ++++++++++++++++--- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/apps/frontend/src/renderer/components/KanbanBoard.tsx b/apps/frontend/src/renderer/components/KanbanBoard.tsx index 51f13dc9..7522ac2a 100644 --- a/apps/frontend/src/renderer/components/KanbanBoard.tsx +++ b/apps/frontend/src/renderer/components/KanbanBoard.tsx @@ -489,6 +489,8 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR // Queue settings modal state const [showQueueSettings, setShowQueueSettings] = useState(false); + // Store projectId when modal opens to prevent modal from disappearing if tasks change + const queueSettingsProjectIdRef = useRef(null); // Queue processing lock to prevent race conditions const isProcessingQueueRef = useRef(false); @@ -794,11 +796,15 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR /** * Save queue settings (maxParallelTasks) + * + * Uses the stored ref value to ensure the save works even if tasks + * change while the modal is open. */ const handleSaveQueueSettings = async (maxParallel: number) => { - if (!projectId) return; + const savedProjectId = queueSettingsProjectIdRef.current || projectId; + if (!savedProjectId) return; - const success = await updateProjectSettings(projectId, { maxParallelTasks: maxParallel }); + const success = await updateProjectSettings(savedProjectId, { maxParallelTasks: maxParallel }); if (success) { toast({ title: t('queue.settings.saved'), @@ -1112,7 +1118,12 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR isOver={overColumnId === status} onAddClick={status === 'backlog' ? onNewTaskClick : undefined} onQueueAll={status === 'backlog' ? handleQueueAll : undefined} - onQueueSettings={status === 'queue' ? () => setShowQueueSettings(true) : undefined} + onQueueSettings={status === 'queue' ? () => { + // Only open modal if we have a valid projectId + if (!projectId) return; + queueSettingsProjectIdRef.current = projectId; + setShowQueueSettings(true); + } : undefined} onArchiveAll={status === 'done' ? handleArchiveAll : undefined} maxParallelTasks={status === 'in_progress' ? maxParallelTasks : undefined} archivedCount={status === 'done' ? archivedCount : undefined} @@ -1181,11 +1192,16 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR /> {/* Queue Settings Modal */} - {projectId && ( + {(queueSettingsProjectIdRef.current || projectId) && ( { + setShowQueueSettings(open); + if (!open) { + queueSettingsProjectIdRef.current = null; + } + }} + projectId={queueSettingsProjectIdRef.current || projectId || ''} currentMaxParallel={maxParallelTasks} onSave={handleSaveQueueSettings} /> diff --git a/apps/frontend/src/renderer/components/QueueSettingsModal.tsx b/apps/frontend/src/renderer/components/QueueSettingsModal.tsx index 77460b3f..18342101 100644 --- a/apps/frontend/src/renderer/components/QueueSettingsModal.tsx +++ b/apps/frontend/src/renderer/components/QueueSettingsModal.tsx @@ -12,14 +12,28 @@ import { Button } from './ui/button'; import { Label } from './ui/label'; import { Input } from './ui/input'; +/** + * Props for QueueSettingsModal component + */ interface QueueSettingsModalProps { + /** Whether the modal is currently open */ open: boolean; + /** Callback to control modal open state */ onOpenChange: (open: boolean) => void; + /** The project ID to update settings for */ projectId: string; + /** Current maximum parallel tasks setting (default: 3) */ currentMaxParallel?: number; + /** Callback when user saves the new max parallel value */ onSave: (maxParallel: number) => void; } +/** + * QueueSettingsModal - Modal for configuring queue parallel task limits + * + * Allows users to adjust the maximum number of tasks that can run in parallel + * for a specific project. Validates input between 1-10 tasks. + */ export function QueueSettingsModal({ open, onOpenChange, @@ -39,14 +53,20 @@ export function QueueSettingsModal({ } }, [open, currentMaxParallel]); + /** + * Validates and saves the max parallel tasks setting + * + * Validates that the value is between 1-10, sets an error message + * if invalid, otherwise calls onSave and closes the modal. + */ const handleSave = () => { // Validate the input if (maxParallel < 1) { - setError(t('queue.settings.minValueError')); + setError(t('tasks:queue.settings.minValueError')); return; } if (maxParallel > 10) { - setError(t('queue.settings.maxValueError')); + setError(t('tasks:queue.settings.maxValueError')); return; } @@ -54,6 +74,14 @@ export function QueueSettingsModal({ onOpenChange(false); }; + /** + * Handles input field changes for the max parallel tasks value + * + * Parses the input value, validates it's a number, and updates state. + * Allows empty input for editing purposes (will fail validation on save). + * + * @param e - The input change event from the number input field + */ const handleInputChange = (e: React.ChangeEvent) => { const inputValue = e.target.value; @@ -75,16 +103,16 @@ export function QueueSettingsModal({ - {t('queue.settings.title')} + {t('tasks:queue.settings.title')} - {t('queue.settings.description')} + {t('tasks:queue.settings.description')}
{error}

)}

- {t('queue.settings.hint')} + {t('tasks:queue.settings.hint')}