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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
JoshuaRileyDev
2026-01-22 13:14:28 +00:00
committed by GitHub
parent 3b87e24d7b
commit 33acc1430f
2 changed files with 56 additions and 12 deletions
@@ -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<string | null>(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) && (
<QueueSettingsModal
open={showQueueSettings}
onOpenChange={setShowQueueSettings}
projectId={projectId}
onOpenChange={(open) => {
setShowQueueSettings(open);
if (!open) {
queueSettingsProjectIdRef.current = null;
}
}}
projectId={queueSettingsProjectIdRef.current || projectId || ''}
currentMaxParallel={maxParallelTasks}
onSave={handleSaveQueueSettings}
/>
@@ -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<HTMLInputElement>) => {
const inputValue = e.target.value;
@@ -75,16 +103,16 @@ export function QueueSettingsModal({
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t('queue.settings.title')}</DialogTitle>
<DialogTitle>{t('tasks:queue.settings.title')}</DialogTitle>
<DialogDescription>
{t('queue.settings.description')}
{t('tasks:queue.settings.description')}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="maxParallel">
{t('queue.settings.maxParallelLabel')}
{t('tasks:queue.settings.maxParallelLabel')}
</Label>
<Input
id="maxParallel"
@@ -99,7 +127,7 @@ export function QueueSettingsModal({
<p className="text-sm text-destructive">{error}</p>
)}
<p className="text-sm text-muted-foreground">
{t('queue.settings.hint')}
{t('tasks:queue.settings.hint')}
</p>
</div>
</div>