mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
- Updated navigation functions to cast route paths correctly, improving type safety. - Added error handling for the templates API in project creation hooks to ensure robustness. - Refactored task progress panel to improve type handling for feature data. - Introduced type checks and default values in various components to enhance overall stability. These changes improve the reliability and maintainability of the application, ensuring better user experience and code quality.
193 lines
6.7 KiB
TypeScript
193 lines
6.7 KiB
TypeScript
// @ts-nocheck
|
|
import { useMemo, useCallback } from 'react';
|
|
import { Feature, useAppStore } from '@/store/app-store';
|
|
import { resolveDependencies, getBlockingDependencies } 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
|
|
};
|
|
|
|
// 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
|
|
const effectiveWorktreePath = currentWorktreePath || projectPath;
|
|
// 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 = runningAutoTasks.includes(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.
|
|
matchesWorktree = projectPath
|
|
? useAppStore.getState().isPrimaryWorktreeBranch(projectPath, featureBranch)
|
|
: 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.
|
|
if (isRunning) {
|
|
if (!matchesWorktree) 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 allFeatures = features;
|
|
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 (getBlockingDependencies(f, allFeatures).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,
|
|
};
|
|
}
|