mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
- Fix 75 ESLint errors by updating eslint.config.mjs: - Add missing browser globals (MouseEvent, AbortController, Response, etc.) - Add Vite define global (__APP_VERSION__) - Configure @ts-nocheck to require descriptions - Add no-unused-vars rule for .mjs scripts - Fix runtime bug in agent-output-modal.tsx (setOutput -> setStreamedContent) - Remove ~120 unused variable warnings across 97 files: - Remove unused imports (React hooks, lucide icons, types) - Remove unused constants and variables - Remove unused function definitions - Prefix intentionally unused parameters with underscore - Add descriptions to all @ts-nocheck comments (25 files) - Clean up misc issues: - Remove invalid deprecation plugin comments - Fix eslint-disable comment placement - Add missing RefreshCw import in code-view.tsx Reduces lint warnings from ~300 to 67 (all remaining are no-explicit-any) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
226 lines
8.4 KiB
TypeScript
226 lines
8.4 KiB
TypeScript
// @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<string, Feature[]> = {
|
|
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,
|
|
};
|
|
}
|