apply the patches

This commit is contained in:
webdevcody
2026-01-20 10:24:38 -05:00
parent 179c5ae9c2
commit 76eb3a2ac2
42 changed files with 2679 additions and 757 deletions

View File

@@ -92,6 +92,7 @@ export function useBoardActions({
skipVerificationInAutoMode,
isPrimaryWorktreeBranch,
getPrimaryWorktreeBranch,
getAutoModeState,
} = useAppStore();
const autoMode = useAutoMode();
@@ -485,10 +486,22 @@ export function useBoardActions({
const handleStartImplementation = useCallback(
async (feature: Feature) => {
if (!autoMode.canStartNewTask) {
// Check capacity for the feature's specific worktree, not the current view
const featureBranchName = feature.branchName ?? null;
const featureWorktreeState = currentProject
? getAutoModeState(currentProject.id, featureBranchName)
: null;
const featureMaxConcurrency = featureWorktreeState?.maxConcurrency ?? autoMode.maxConcurrency;
const featureRunningCount = featureWorktreeState?.runningTasks?.length ?? 0;
const canStartInWorktree = featureRunningCount < featureMaxConcurrency;
if (!canStartInWorktree) {
const worktreeDesc = featureBranchName
? `worktree "${featureBranchName}"`
: 'main worktree';
toast.error('Concurrency limit reached', {
description: `You can only have ${autoMode.maxConcurrency} task${
autoMode.maxConcurrency > 1 ? 's' : ''
description: `${worktreeDesc} can only have ${featureMaxConcurrency} task${
featureMaxConcurrency > 1 ? 's' : ''
} running at a time. Wait for a task to complete or increase the limit.`,
});
return false;
@@ -552,6 +565,8 @@ export function useBoardActions({
updateFeature,
persistFeatureUpdate,
handleRunFeature,
currentProject,
getAutoModeState,
]
);

View File

@@ -8,6 +8,11 @@ import { COLUMNS, ColumnId } from '../constants';
const logger = createLogger('BoardDragDrop');
export interface PendingDependencyLink {
draggedFeature: Feature;
targetFeature: Feature;
}
interface UseBoardDragDropProps {
features: Feature[];
currentProject: { path: string; id: string } | null;
@@ -24,7 +29,10 @@ export function useBoardDragDrop({
handleStartImplementation,
}: UseBoardDragDropProps) {
const [activeFeature, setActiveFeature] = useState<Feature | null>(null);
const { moveFeature } = useAppStore();
const [pendingDependencyLink, setPendingDependencyLink] = useState<PendingDependencyLink | null>(
null
);
const { moveFeature, updateFeature } = useAppStore();
// Note: getOrCreateWorktreeForFeature removed - worktrees are now created server-side
// at execution time based on feature.branchName
@@ -40,6 +48,11 @@ export function useBoardDragDrop({
[features]
);
// Clear pending dependency link
const clearPendingDependencyLink = useCallback(() => {
setPendingDependencyLink(null);
}, []);
const handleDragEnd = useCallback(
async (event: DragEndEvent) => {
const { active, over } = event;
@@ -57,6 +70,85 @@ export function useBoardDragDrop({
// Check if this is a running task (non-skipTests, TDD)
const isRunningTask = runningAutoTasks.includes(featureId);
// Check if dropped on another card (for creating dependency links)
if (overId.startsWith('card-drop-')) {
const cardData = over.data.current as {
type: string;
featureId: string;
};
if (cardData?.type === 'card') {
const targetFeatureId = cardData.featureId;
// Don't link to self
if (targetFeatureId === featureId) {
return;
}
const targetFeature = features.find((f) => f.id === targetFeatureId);
if (!targetFeature) return;
// Only allow linking backlog features (both must be in backlog)
if (draggedFeature.status !== 'backlog' || targetFeature.status !== 'backlog') {
toast.error('Cannot link features', {
description: 'Both features must be in the backlog to create a dependency link.',
});
return;
}
// Set pending dependency link to trigger dialog
setPendingDependencyLink({
draggedFeature,
targetFeature,
});
return;
}
}
// Check if dropped on a worktree tab
if (overId.startsWith('worktree-drop-')) {
// Handle dropping on a worktree - change the feature's branchName
const worktreeData = over.data.current as {
type: string;
branch: string;
path: string;
isMain: boolean;
};
if (worktreeData?.type === 'worktree') {
// Don't allow moving running tasks to a different worktree
if (isRunningTask) {
logger.debug('Cannot move running feature to different worktree');
toast.error('Cannot move feature', {
description: 'This feature is currently running and cannot be moved.',
});
return;
}
const targetBranch = worktreeData.branch;
const currentBranch = draggedFeature.branchName;
// If already on the same branch, nothing to do
if (currentBranch === targetBranch) {
return;
}
// For main worktree, set branchName to undefined/null to indicate it should use main
// For other worktrees, set branchName to the target branch
const newBranchName = worktreeData.isMain ? undefined : targetBranch;
// Update feature's branchName
updateFeature(featureId, { branchName: newBranchName });
await persistFeatureUpdate(featureId, { branchName: newBranchName });
const branchDisplay = worktreeData.isMain ? targetBranch : targetBranch;
toast.success('Feature moved to branch', {
description: `Moved to ${branchDisplay}: ${draggedFeature.description.slice(0, 40)}${draggedFeature.description.length > 40 ? '...' : ''}`,
});
return;
}
}
// Determine if dragging is allowed based on status and skipTests
// - Backlog items can always be dragged
// - waiting_approval items can always be dragged (to allow manual verification via drag)
@@ -205,12 +297,21 @@ export function useBoardDragDrop({
}
}
},
[features, runningAutoTasks, moveFeature, persistFeatureUpdate, handleStartImplementation]
[
features,
runningAutoTasks,
moveFeature,
updateFeature,
persistFeatureUpdate,
handleStartImplementation,
]
);
return {
activeFeature,
handleDragStart,
handleDragEnd,
pendingDependencyLink,
clearPendingDependencyLink,
};
}

View File

@@ -1,6 +1,5 @@
import { useEffect, useRef } from 'react';
import { getElectronAPI } from '@/lib/electron';
import { useAppStore } from '@/store/app-store';
import { createLogger } from '@automaker/utils/logger';
const logger = createLogger('BoardEffects');
@@ -65,37 +64,8 @@ export function useBoardEffects({
};
}, [specCreatingForProject, setSpecCreatingForProject]);
// Sync running tasks from electron backend on mount
useEffect(() => {
if (!currentProject) return;
const syncRunningTasks = async () => {
try {
const api = getElectronAPI();
if (!api?.autoMode?.status) return;
const status = await api.autoMode.status(currentProject.path);
if (status.success) {
const projectId = currentProject.id;
const { clearRunningTasks, addRunningTask } = useAppStore.getState();
if (status.runningFeatures) {
logger.info('Syncing running tasks from backend:', status.runningFeatures);
clearRunningTasks(projectId);
status.runningFeatures.forEach((featureId: string) => {
addRunningTask(projectId, featureId);
});
}
}
} catch (error) {
logger.error('Failed to sync running tasks:', error);
}
};
syncRunningTasks();
}, [currentProject]);
// Note: Running tasks sync is now handled by useAutoMode hook in BoardView
// which correctly handles worktree/branch scoping.
// Check which features have context files
useEffect(() => {

View File

@@ -123,7 +123,9 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) {
} else if (event.type === 'auto_mode_error') {
// Remove from running tasks
if (event.featureId) {
removeRunningTask(eventProjectId, event.featureId);
const eventBranchName =
'branchName' in event && event.branchName !== undefined ? event.branchName : null;
removeRunningTask(eventProjectId, eventBranchName, event.featureId);
}
// Show error toast