Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion apps/frontend/src/renderer/components/KanbanBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { SortableTaskCard } from './SortableTaskCard';
import { QueueSettingsModal } from './QueueSettingsModal';
import { TASK_STATUS_COLUMNS, TASK_STATUS_LABELS } from '../../shared/constants';
import { cn } from '../lib/utils';
import { persistTaskStatus, forceCompleteTask, archiveTasks, deleteTasks, useTaskStore, isQueueAtCapacity, DEFAULT_MAX_PARALLEL_TASKS } from '../stores/task-store';
import { persistTaskStatus, forceCompleteTask, archiveTasks, unarchiveTasks, deleteTasks, useTaskStore, isQueueAtCapacity, DEFAULT_MAX_PARALLEL_TASKS } from '../stores/task-store';
import { updateProjectSettings, useProjectStore } from '../stores/project-store';
import { useKanbanSettingsStore, DEFAULT_COLUMN_WIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH, COLLAPSED_COLUMN_WIDTH_REM, MIN_COLUMN_WIDTH_REM, MAX_COLUMN_WIDTH_REM, BASE_FONT_SIZE, pxToRem } from '../stores/kanban-settings-store';
import { useToast } from '../hooks/use-toast';
Expand Down Expand Up @@ -986,6 +986,19 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
});
}
}

// Auto-unarchive: moving an archived task to another state implies the user wants it active
if (result.success && task?.metadata?.archivedAt && projectId) {
const unarchiveResult = await unarchiveTasks(projectId, [taskId]);
if (!unarchiveResult.success) {
toast({
title: t('common:errors.operationFailed'),
description: unarchiveResult.error,
variant: 'destructive',
});
}
}

// Note: queue auto-promotion when a task leaves in_progress is handled by the
// useEffect task status change listener (registerTaskStatusChangeListener), so
// no explicit processQueue() call is needed here.
Expand Down
39 changes: 36 additions & 3 deletions apps/frontend/src/renderer/components/TaskCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Play, Square, Clock, Zap, Target, Shield, Gauge, Palette, FileCode, Bug, Wrench, Loader2, AlertTriangle, RotateCcw, Archive, GitPullRequest, MoreVertical } from 'lucide-react';
import { Play, Square, Clock, Zap, Target, Shield, Gauge, Palette, FileCode, Bug, Wrench, Loader2, AlertTriangle, RotateCcw, Archive, ArchiveRestore, GitPullRequest, MoreVertical } from 'lucide-react';
import { Card, CardContent } from './ui/card';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
Expand Down Expand Up @@ -31,7 +31,7 @@ import {
JSON_ERROR_PREFIX,
JSON_ERROR_TITLE_SUFFIX
} from '../../shared/constants';
import { stopTask, checkTaskRunning, recoverStuckTask, isIncompleteHumanReview, archiveTasks, hasRecentActivity, startTaskOrQueue } from '../stores/task-store';
import { stopTask, checkTaskRunning, recoverStuckTask, isIncompleteHumanReview, archiveTasks, unarchiveTasks, hasRecentActivity, startTaskOrQueue } from '../stores/task-store';
import { useToast } from '../hooks/use-toast';
import type { Task, TaskCategory, ReviewReason, TaskStatus } from '../../shared/types';

Expand Down Expand Up @@ -265,6 +265,18 @@ export const TaskCard = memo(function TaskCard({
}
};

const handleUnarchive = async (e: React.MouseEvent) => {
e.stopPropagation();
const result = await unarchiveTasks(task.projectId, [task.id]);
if (!result.success) {
toast({
title: t('tasks:actions.unarchive'),
description: result.error,
variant: 'destructive',
});
}
};
Comment on lines +268 to +278
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Toast title on failure uses the action label instead of an error message.

handleUnarchive uses t('tasks:actions.unarchive') (which resolves to "Unarchive") as the toast title on failure. This reads oddly as a toast heading — the user sees "Unarchive" with an error description below. For consistency with the KanbanBoard auto-unarchive toast (line 995), consider using t('common:errors.operationFailed').

♻️ Suggested fix
     if (!result.success) {
       toast({
-        title: t('tasks:actions.unarchive'),
+        title: t('common:errors.operationFailed'),
         description: result.error,
         variant: 'destructive',
       });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/src/renderer/components/TaskCard.tsx` around lines 268 - 278,
The toast on failure in handleUnarchive currently uses the action label
t('tasks:actions.unarchive') as the title; change it to a generic error heading
(use t('common:errors.operationFailed')) so the toast reads like an error
(update the toast call inside handleUnarchive that follows the unarchiveTasks
call and references toast, t, and result to use the common error key instead of
the action label).


const handleViewPR = (e: React.MouseEvent) => {
e.stopPropagation();
if (task.metadata?.prUrl && window.electronAPI?.openExternal) {
Expand Down Expand Up @@ -571,7 +583,17 @@ export const TaskCard = memo(function TaskCard({
<GitPullRequest className="h-3 w-3" />
</Button>
)}
{!task.metadata?.archivedAt && (
{task.metadata?.archivedAt ? (
<Button
variant="ghost"
size="sm"
className="h-7 px-2 cursor-pointer"
onClick={handleUnarchive}
title={t('tooltips.unarchiveTask')}
>
<ArchiveRestore className="h-3 w-3" />
</Button>
) : (
<Button
variant="ghost"
size="sm"
Expand All @@ -583,6 +605,17 @@ export const TaskCard = memo(function TaskCard({
</Button>
)}
</div>
) : task.status === 'done' && task.metadata?.archivedAt ? (
<Button
variant="ghost"
size="sm"
className="h-7 px-2.5 hover:bg-muted-foreground/10"
onClick={handleUnarchive}
title={t('tooltips.unarchiveTask')}
>
<ArchiveRestore className="mr-1.5 h-3 w-3" />
{t('actions.unarchive')}
</Button>
) : task.status === 'done' && !task.metadata?.archivedAt ? (
<Button
variant="ghost"
Expand Down
30 changes: 30 additions & 0 deletions apps/frontend/src/renderer/stores/task-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,36 @@ export async function deleteTasks(
}
}

/**
* Unarchive tasks
* Removes the archivedAt timestamp from task metadata
*/
export async function unarchiveTasks(
projectId: string,
taskIds: string[]
): Promise<{ success: boolean; error?: string }> {
try {
const result = await window.electronAPI.unarchiveTasks(projectId, taskIds);

if (result.success) {
// Reload tasks to update the UI
await loadTasks(projectId);
return { success: true };
}

return {
success: false,
error: result.error || 'Failed to unarchive tasks'
};
} catch (error) {
console.error('Error unarchiving tasks:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}

/**
* Archive tasks
* Marks tasks as archived by adding archivedAt timestamp to metadata
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/shared/i18n/locales/en/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"recover": "Recover",
"resume": "Resume",
"archive": "Archive",
"unarchive": "Unarchive",
"delete": "Delete",
"view": "View Details",
"viewPR": "View PR",
Expand Down Expand Up @@ -43,6 +44,7 @@
},
"tooltips": {
"archiveTask": "Archive task",
"unarchiveTask": "Unarchive task",
"archiveAllDone": "Archive all done tasks",
"viewPR": "Open pull request in browser"
},
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/shared/i18n/locales/fr/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"recover": "Récupérer",
"resume": "Reprendre",
"archive": "Archiver",
"unarchive": "Désarchiver",
"delete": "Supprimer",
"view": "Voir les détails",
"viewPR": "Voir la PR",
Expand Down Expand Up @@ -43,6 +44,7 @@
},
"tooltips": {
"archiveTask": "Archiver la tâche",
"unarchiveTask": "Désarchiver la tâche",
"archiveAllDone": "Archiver toutes les tâches terminées",
"viewPR": "Ouvrir la pull request dans le navigateur"
},
Expand Down
Loading