// @ts-nocheck - column filtering logic with dependency resolution and status mapping import { useMemo, useCallback } from 'react'; import { Feature, useAppStore } from '@/store/app-store'; import { createFeatureMap, getBlockingDependenciesFromMap, resolveDependencies, } from '@automaker/dependency-resolver'; type ColumnId = Feature['status']; interface UseBoardColumnFeaturesProps { features: Feature[]; runningAutoTasks: string[]; searchQuery: string; currentWorktreePath: string | null; // Currently selected worktree path currentWorktreeBranch: string | null; // Branch name of the selected worktree (null = main) projectPath: string | null; // Main project path (for main worktree) } export function useBoardColumnFeatures({ features, runningAutoTasks, searchQuery, currentWorktreePath, currentWorktreeBranch, projectPath, }: UseBoardColumnFeaturesProps) { // Memoize column features to prevent unnecessary re-renders const columnFeaturesMap = useMemo(() => { // Use a more flexible type to support dynamic pipeline statuses const map: Record = { backlog: [], in_progress: [], waiting_approval: [], verified: [], completed: [], // Completed features are shown in the archive modal, not as a column }; const featureMap = createFeatureMap(features); const runningTaskIds = new Set(runningAutoTasks); // Filter features by search query (case-insensitive) const normalizedQuery = searchQuery.toLowerCase().trim(); const filteredFeatures = normalizedQuery ? features.filter( (f) => f.description.toLowerCase().includes(normalizedQuery) || f.category?.toLowerCase().includes(normalizedQuery) ) : features; // Determine the effective worktree path and branch for filtering // If currentWorktreePath is null, we're on the main worktree // Use the branch name from the selected worktree // If we're selecting main (currentWorktreePath is null), currentWorktreeBranch // should contain the main branch's actual name, defaulting to "main" // If we're selecting a non-main worktree but can't find it, currentWorktreeBranch is null // In that case, we can't do branch-based filtering, so we'll handle it specially below const effectiveBranch = currentWorktreeBranch; filteredFeatures.forEach((f) => { // If feature has a running agent, always show it in "in_progress" const isRunning = runningTaskIds.has(f.id); // Check if feature matches the current worktree by branchName // Features without branchName are considered unassigned (show only on primary worktree) const featureBranch = f.branchName; let matchesWorktree: boolean; if (!featureBranch) { // No branch assigned - show only on primary worktree const isViewingPrimary = currentWorktreePath === null; matchesWorktree = isViewingPrimary; } else if (effectiveBranch === null) { // We're viewing main but branch hasn't been initialized yet // (worktrees disabled or haven't loaded yet). // Show features assigned to primary worktree's branch. if (projectPath) { const worktrees = useAppStore.getState().worktreesByProject[projectPath] ?? []; if (worktrees.length === 0) { // Worktrees not loaded yet - fallback to showing features on common default branches // This prevents features from disappearing during initial load matchesWorktree = featureBranch === 'main' || featureBranch === 'master' || featureBranch === 'develop'; } else { matchesWorktree = useAppStore .getState() .isPrimaryWorktreeBranch(projectPath, featureBranch); } } else { matchesWorktree = false; } } else { // Match by branch name matchesWorktree = featureBranch === effectiveBranch; } // Use the feature's status (fallback to backlog for unknown statuses) const status = f.status || 'backlog'; // IMPORTANT: // Historically, we forced "running" features into in_progress so they never disappeared // during stale reload windows. With pipelines, a feature can legitimately be running while // its status is `pipeline_*`, so we must respect that status to render it in the right column. // NOTE: runningAutoTasks is already worktree-scoped, so if a feature is in runningAutoTasks, // it's already running for the current worktree. However, we still need to check matchesWorktree // to ensure the feature's branchName matches the current worktree's branch. if (isRunning) { // If feature is running but doesn't match worktree, it might be a timing issue where // the feature was started for a different worktree. Still show it if it's running to // prevent disappearing features, but log a warning. if (!matchesWorktree) { // This can happen if: // 1. Feature was started for a different worktree (bug) // 2. Timing issue where branchName hasn't been set yet // 3. User switched worktrees while feature was starting // Still show it in in_progress to prevent it from disappearing console.debug( `Feature ${f.id} is running but branchName (${featureBranch}) doesn't match current worktree branch (${effectiveBranch}) - showing anyway to prevent disappearing` ); map.in_progress.push(f); return; } if (status.startsWith('pipeline_')) { if (!map[status]) map[status] = []; map[status].push(f); return; } // If it's running and has a known non-backlog status, keep it in that status. // Otherwise, fallback to in_progress as the "active work" column. if (status !== 'backlog' && map[status]) { map[status].push(f); } else { map.in_progress.push(f); } return; } // Not running: place by status (and worktree filter) // Filter all items by worktree, including backlog // This ensures backlog items with a branch assigned only show in that branch if (status === 'backlog') { if (matchesWorktree) { map.backlog.push(f); } } else if (map[status]) { // Only show if matches current worktree or has no worktree assigned if (matchesWorktree) { map[status].push(f); } } else if (status.startsWith('pipeline_')) { // Handle pipeline statuses - initialize array if needed if (matchesWorktree) { if (!map[status]) { map[status] = []; } map[status].push(f); } } else { // Unknown status, default to backlog if (matchesWorktree) { map.backlog.push(f); } } }); // Apply dependency-aware sorting to backlog // This ensures features appear in dependency order (dependencies before dependents) // Within the same dependency level, features are sorted by priority if (map.backlog.length > 0) { const { orderedFeatures } = resolveDependencies(map.backlog); // Get all features to check blocking dependencies against const enableDependencyBlocking = useAppStore.getState().enableDependencyBlocking; // Sort blocked features to the end of the backlog // This keeps the dependency order within each group (unblocked/blocked) if (enableDependencyBlocking) { const unblocked: Feature[] = []; const blocked: Feature[] = []; for (const f of orderedFeatures) { if (getBlockingDependenciesFromMap(f, featureMap).length > 0) { blocked.push(f); } else { unblocked.push(f); } } map.backlog = [...unblocked, ...blocked]; } else { map.backlog = orderedFeatures; } } return map; }, [ features, runningAutoTasks, searchQuery, currentWorktreePath, currentWorktreeBranch, projectPath, ]); const getColumnFeatures = useCallback( (columnId: ColumnId) => { return columnFeaturesMap[columnId] || []; }, [columnFeaturesMap] ); // Memoize completed features for the archive modal const completedFeatures = useMemo(() => { return features.filter((f) => f.status === 'completed'); }, [features]); return { columnFeaturesMap, getColumnFeatures, completedFeatures, }; }