-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(ui): persist staged task state across app restarts #800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
4d66739
0ba1933
7dd3f67
f5d37e8
15543d0
6255baf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,10 +19,18 @@ import { | |
| sortableKeyboardCoordinates, | ||
| verticalListSortingStrategy | ||
| } from '@dnd-kit/sortable'; | ||
| import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, RefreshCw } from 'lucide-react'; | ||
| import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, RefreshCw, Trash2, FolderCheck } from 'lucide-react'; | ||
| import { ScrollArea } from './ui/scroll-area'; | ||
| import { Button } from './ui/button'; | ||
| import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; | ||
| import { | ||
| AlertDialog, | ||
| AlertDialogContent, | ||
| AlertDialogDescription, | ||
| AlertDialogFooter, | ||
| AlertDialogHeader, | ||
| AlertDialogTitle, | ||
| } from './ui/alert-dialog'; | ||
| import { TaskCard } from './TaskCard'; | ||
| import { SortableTaskCard } from './SortableTaskCard'; | ||
| import { TASK_STATUS_COLUMNS, TASK_STATUS_LABELS } from '../../shared/constants'; | ||
|
|
@@ -336,6 +344,11 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR | |
| const [overColumnId, setOverColumnId] = useState<string | null>(null); | ||
| const { showArchived, toggleShowArchived } = useViewState(); | ||
|
|
||
| // Worktree cleanup dialog state | ||
| const [worktreeDialogOpen, setWorktreeDialogOpen] = useState(false); | ||
| const [pendingDoneTask, setPendingDoneTask] = useState<Task | null>(null); | ||
| const [isCleaningUp, setIsCleaningUp] = useState(false); | ||
|
|
||
| // Calculate archived count for Done column button | ||
| const archivedCount = useMemo(() => | ||
| tasks.filter(t => t.metadata?.archivedAt).length, | ||
|
|
@@ -439,7 +452,39 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR | |
| } | ||
| }; | ||
|
|
||
| const handleDragEnd = (event: DragEndEvent) => { | ||
| // Check if a task has a worktree (async check) | ||
| const checkTaskHasWorktree = async (taskId: string): Promise<boolean> => { | ||
| try { | ||
| const result = await window.electronAPI.getWorktreeStatus(taskId); | ||
| return result.success && result.data?.exists === true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| }; | ||
|
|
||
| // Handle moving task to done with worktree cleanup option | ||
| const handleMoveToDone = async (task: Task, deleteWorktree: boolean) => { | ||
| setIsCleaningUp(true); | ||
| try { | ||
| if (deleteWorktree) { | ||
| // Delete worktree first, skip automatic status change to backlog | ||
| // since we're about to set status to 'done' | ||
| const result = await window.electronAPI.discardWorktree(task.id, true); | ||
| if (!result.success) { | ||
| console.error('Failed to delete worktree:', result.error); | ||
| // Continue anyway - user can clean up manually | ||
| } | ||
| } | ||
| // Mark as done | ||
| await persistTaskStatus(task.id, 'done'); | ||
| } finally { | ||
| setIsCleaningUp(false); | ||
| setWorktreeDialogOpen(false); | ||
| setPendingDoneTask(null); | ||
| } | ||
| }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignoring status persist failure after worktree deletionMedium Severity In
Comment on lines
+466
to
+485
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider improving error handling clarity. The error handling in Consider adding a toast notification or updating the UI to show that worktree deletion failed but the task was still marked as done. This provides better user feedback about the partial success. Example approach: if (!result.success) {
console.error('Failed to delete worktree:', result.error);
// Show toast or set error state to inform user
toast.warning('Task marked as done, but worktree deletion failed. You may need to clean it up manually.');
}🤖 Prompt for AI Agents |
||
|
|
||
| const handleDragEnd = async (event: DragEndEvent) => { | ||
| const { active, over } = event; | ||
| setActiveTask(null); | ||
| setOverColumnId(null); | ||
|
|
@@ -449,27 +494,38 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR | |
| const activeTaskId = active.id as string; | ||
| const overId = over.id as string; | ||
|
|
||
| // Check if dropped on a column | ||
| if (isValidDropColumn(overId)) { | ||
| const newStatus = overId; | ||
| const task = tasks.find((t) => t.id === activeTaskId); | ||
| // Determine target status | ||
| let targetStatus: TaskStatus | null = null; | ||
|
|
||
| if (task && task.status !== newStatus) { | ||
| // Persist status change to file and update local state | ||
| persistTaskStatus(activeTaskId, newStatus); | ||
| if (isValidDropColumn(overId)) { | ||
| targetStatus = overId; | ||
| } else { | ||
| // Dropped on another task - get its column | ||
| const overTask = tasks.find((t) => t.id === overId); | ||
| if (overTask) { | ||
| targetStatus = overTask.status; | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| // Check if dropped on another task - move to that task's column | ||
| const overTask = tasks.find((t) => t.id === overId); | ||
| if (overTask) { | ||
| const task = tasks.find((t) => t.id === activeTaskId); | ||
| if (task && task.status !== overTask.status) { | ||
| // Persist status change to file and update local state | ||
| persistTaskStatus(activeTaskId, overTask.status); | ||
| if (!targetStatus) return; | ||
|
|
||
| const task = tasks.find((t) => t.id === activeTaskId); | ||
| if (!task || task.status === targetStatus) return; | ||
|
|
||
| // Special handling for moving to "done" - check for worktree | ||
| if (targetStatus === 'done') { | ||
| const hasWorktree = await checkTaskHasWorktree(task.id); | ||
|
|
||
| if (hasWorktree) { | ||
| // Show dialog asking about worktree cleanup | ||
| setPendingDoneTask(task); | ||
| setWorktreeDialogOpen(true); | ||
| return; | ||
| } | ||
|
Comment on lines
+516
to
524
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Potential UX issue: drag appears to complete but task stays in place. When moving to "done" triggers the worktree dialog, the function returns early at line 513 without updating the task status. The user sees the drag complete visually (card snaps back), then the dialog appears. This could be confusing. Consider either:
This is minor since the dialog explains the situation, but worth noting for UX polish. 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // Normal status change | ||
| persistTaskStatus(activeTaskId, targetStatus); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rapid drags to done column can lose tasksLow Severity Making |
||
| }; | ||
|
|
||
| return ( | ||
|
|
@@ -524,6 +580,73 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR | |
| ) : null} | ||
| </DragOverlay> | ||
| </DndContext> | ||
|
|
||
| {/* Worktree cleanup confirmation dialog */} | ||
| <AlertDialog open={worktreeDialogOpen} onOpenChange={setWorktreeDialogOpen}> | ||
| <AlertDialogContent className="max-w-md"> | ||
| <AlertDialogHeader> | ||
| <AlertDialogTitle className="flex items-center gap-2"> | ||
| <FolderCheck className="h-5 w-5 text-primary" /> | ||
| {t('kanban.worktreeCleanupTitle', 'Worktree Cleanup')} | ||
| </AlertDialogTitle> | ||
| <AlertDialogDescription asChild> | ||
| <div className="text-left space-y-2"> | ||
| {pendingDoneTask?.stagedInMainProject ? ( | ||
| <p> | ||
| {t('kanban.worktreeCleanupStaged', 'This task has been staged and has a worktree. Would you like to clean up the worktree?')} | ||
| </p> | ||
| ) : ( | ||
| <p> | ||
| {t('kanban.worktreeCleanupNotStaged', 'This task has a worktree with changes that have not been merged. Delete the worktree to mark as done, or cancel to review the changes first.')} | ||
| </p> | ||
| )} | ||
| {pendingDoneTask && ( | ||
| <p className="text-sm font-medium text-foreground/80 bg-muted/50 rounded px-2 py-1.5"> | ||
| {pendingDoneTask.title} | ||
| </p> | ||
| )} | ||
| </div> | ||
| </AlertDialogDescription> | ||
| </AlertDialogHeader> | ||
| <AlertDialogFooter className="flex-col sm:flex-row gap-2"> | ||
| <Button | ||
| variant="outline" | ||
| onClick={() => { | ||
| setWorktreeDialogOpen(false); | ||
| setPendingDoneTask(null); | ||
| }} | ||
| disabled={isCleaningUp} | ||
| > | ||
| {t('common:cancel', 'Cancel')} | ||
| </Button> | ||
| {/* Only show "Keep Worktree" option if task is staged */} | ||
| {pendingDoneTask?.stagedInMainProject && ( | ||
| <Button | ||
| variant="secondary" | ||
| onClick={() => pendingDoneTask && handleMoveToDone(pendingDoneTask, false)} | ||
| disabled={isCleaningUp} | ||
| className="gap-2" | ||
| > | ||
| <FolderCheck className="h-4 w-4" /> | ||
| {t('kanban.keepWorktree', 'Keep Worktree')} | ||
| </Button> | ||
| )} | ||
| <Button | ||
| variant={pendingDoneTask?.stagedInMainProject ? 'default' : 'destructive'} | ||
| onClick={() => pendingDoneTask && handleMoveToDone(pendingDoneTask, true)} | ||
| disabled={isCleaningUp} | ||
| className="gap-2" | ||
| > | ||
| {isCleaningUp ? ( | ||
| <Loader2 className="h-4 w-4 animate-spin" /> | ||
| ) : ( | ||
| <Trash2 className="h-4 w-4" /> | ||
| )} | ||
| {t('kanban.deleteWorktree', 'Delete Worktree & Mark Done')} | ||
|
Comment on lines
+590
to
+645
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's find the translation files in the repository
find . -type f -name "*.json" | grep -E "(locale|i18n|translation|lang)" | head -20Repository: AndyMik90/Auto-Claude Length of output: 1198 🏁 Script executed: # Let's search for kanban keys in the translation files
echo "=== Searching for kanban keys in translation files ==="
rg "kanban|worktreeCleanup|keepWorktree|deleteWorktree" apps/frontend/src/shared/i18n/locales/ --type json
echo ""
echo "=== Checking if kanban.json file exists ==="
ls -la apps/frontend/src/shared/i18n/locales/en/ | grep -i kanban
ls -la apps/frontend/src/shared/i18n/locales/fr/ | grep -i kanban
echo ""
echo "=== All JSON files in en directory ==="
ls apps/frontend/src/shared/i18n/locales/en/
echo ""
echo "=== Checking common.json for cancel key ==="
rg "cancel" apps/frontend/src/shared/i18n/locales/en/common.json -C2Repository: AndyMik90/Auto-Claude Length of output: 1029 🏁 Script executed: # Let's examine the kanban object in tasks.json to see what keys are defined
echo "=== Content of kanban object in en/tasks.json ==="
cat apps/frontend/src/shared/i18n/locales/en/tasks.json | jq '.kanban' 2>/dev/null || cat apps/frontend/src/shared/i18n/locales/en/tasks.json | grep -A 50 '"kanban"'
echo ""
echo "=== Content of kanban object in fr/tasks.json ==="
cat apps/frontend/src/shared/i18n/locales/fr/tasks.json | jq '.kanban' 2>/dev/null || cat apps/frontend/src/shared/i18n/locales/fr/tasks.json | grep -A 50 '"kanban"'Repository: AndyMik90/Auto-Claude Length of output: 1797 🏁 Script executed: # Let's verify that these keys are definitely missing and count the files that need updates
echo "=== Verifying missing keys in en/tasks.json ==="
grep -c "worktreeCleanupTitle\|worktreeCleanupStaged\|worktreeCleanupNotStaged\|keepWorktree\|deleteWorktree" apps/frontend/src/shared/i18n/locales/en/tasks.json || echo "No matches found (confirmed missing)"
echo ""
echo "=== Verifying missing keys in fr/tasks.json ==="
grep -c "worktreeCleanupTitle\|worktreeCleanupStaged\|worktreeCleanupNotStaged\|keepWorktree\|deleteWorktree" apps/frontend/src/shared/i18n/locales/fr/tasks.json || echo "No matches found (confirmed missing)"
echo ""
echo "=== Confirming common:cancel key exists ==="
grep -c "\"cancel\"" apps/frontend/src/shared/i18n/locales/en/common.jsonRepository: AndyMik90/Auto-Claude Length of output: 286 Add missing kanban i18n translation keys to both en and fr language files. The code uses translation keys ( 🤖 Prompt for AI Agents |
||
| </Button> | ||
| </AlertDialogFooter> | ||
| </AlertDialogContent> | ||
| </AlertDialog> | ||
|
Comment on lines
+583
to
+649
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, find the locale file structure
find apps/frontend/src/shared/i18n/locales -type f -name "*.json" | head -20Repository: AndyMik90/Auto-Claude Length of output: 1158 🏁 Script executed: # Search for worktreeCleanup keys in the codebase
rg "worktreeCleanup|keepWorktree|deleteWorktree" apps/frontend/src/shared/i18n/locales/Repository: AndyMik90/Auto-Claude Length of output: 47 🏁 Script executed: # Check the structure of en locale files
ls -la apps/frontend/src/shared/i18n/locales/en/Repository: AndyMik90/Auto-Claude Length of output: 815 🏁 Script executed: # Search for any kanban keys in existing locale files
rg "kanban" apps/frontend/src/shared/i18n/locales/en/Repository: AndyMik90/Auto-Claude Length of output: 217 🏁 Script executed: # Check tasks.json and dialogs.json for structure
cat apps/frontend/src/shared/i18n/locales/en/tasks.json | head -40Repository: AndyMik90/Auto-Claude Length of output: 1012 🏁 Script executed: # Get the full kanban section from tasks.json
rg -A 50 '"kanban"' apps/frontend/src/shared/i18n/locales/en/tasks.jsonRepository: AndyMik90/Auto-Claude Length of output: 1672 🏁 Script executed: # Check fr locale for kanban keys
rg -A 50 '"kanban"' apps/frontend/src/shared/i18n/locales/fr/tasks.jsonRepository: AndyMik90/Auto-Claude Length of output: 1875 Add translation keys for the worktree cleanup dialog to locale files. The code introduces five new user-facing strings without adding translation keys to the locale files:
These keys must be added to both 🤖 Prompt for AI Agents |
||
| </div> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.