diff --git a/apps/frontend/src/main/project-store.ts b/apps/frontend/src/main/project-store.ts index 420b6311..e8ca153c 100644 --- a/apps/frontend/src/main/project-store.ts +++ b/apps/frontend/src/main/project-store.ts @@ -3,7 +3,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, Dirent import path from 'path'; import { v4 as uuidv4 } from 'uuid'; import type { Project, ProjectSettings, Task, TaskStatus, TaskMetadata, ImplementationPlan, ReviewReason, PlanSubtask, KanbanPreferences, ExecutionPhase } from '../shared/types'; -import { DEFAULT_PROJECT_SETTINGS, AUTO_BUILD_PATHS, getSpecsDir, JSON_ERROR_PREFIX, JSON_ERROR_TITLE_SUFFIX } from '../shared/constants'; +import { DEFAULT_PROJECT_SETTINGS, AUTO_BUILD_PATHS, getSpecsDir, JSON_ERROR_PREFIX, JSON_ERROR_TITLE_SUFFIX, TASK_STATUS_PRIORITY } from '../shared/constants'; import { getAutoBuildPath, isInitialized } from './project-initializer'; import { getTaskWorktreeDir } from './worktree-paths'; import { isValidTaskId, findAllSpecPaths } from './utils/spec-path-helpers'; @@ -324,12 +324,39 @@ export class ProjectStore { } } - // 3. Deduplicate tasks by ID (prefer worktree version if exists in both) + // 3. Deduplicate tasks by ID + // CRITICAL FIX: Don't blindly prefer worktree - it may be stale! + // If main project task is "done", it should win over worktree's "in_progress". + // Worktrees can linger after completion, containing outdated task data. const taskMap = new Map(); for (const task of allTasks) { const existing = taskMap.get(task.id); - if (!existing || task.location === 'worktree') { + if (!existing) { + // First occurrence wins taskMap.set(task.id, task); + } else { + // PREFER MAIN PROJECT over worktree - main has current user changes + // Only use status priority when both are from same location + const existingIsMain = existing.location === 'main'; + const newIsMain = task.location === 'main'; + + if (existingIsMain && !newIsMain) { + // Main wins, keep existing + continue; + } else if (!existingIsMain && newIsMain) { + // New is main, replace existing worktree + taskMap.set(task.id, task); + } else { + // Same location - use status priority to determine which is more complete + const existingPriority = TASK_STATUS_PRIORITY[existing.status] || 0; + const newPriority = TASK_STATUS_PRIORITY[task.status] || 0; + + if (newPriority > existingPriority) { + // New version has higher priority (more complete status) + taskMap.set(task.id, task); + } + // Otherwise keep existing version + } } } diff --git a/apps/frontend/src/shared/constants/task.ts b/apps/frontend/src/shared/constants/task.ts index 4f512b40..4123256a 100644 --- a/apps/frontend/src/shared/constants/task.ts +++ b/apps/frontend/src/shared/constants/task.ts @@ -3,6 +3,8 @@ * Includes status, categories, complexity, priority, and execution phases */ +import type { TaskStatus } from '@shared/types/task'; + // ============================================ // Task Status (Kanban columns) // ============================================ @@ -47,6 +49,20 @@ export const TASK_STATUS_COLORS: Record = { + 'done': 100, // Highest priority - task is complete + 'pr_created': 90, + 'human_review': 80, + 'ai_review': 70, + 'in_progress': 50, + 'queue': 30, + 'backlog': 20, + 'error': 10 // Lowest priority +} as const; + // ============================================ // Subtask Status // ============================================