Merge pull request #285 from AutoMaker-Org/adding-make-button

adding button to make when creating a new feature
This commit is contained in:
Web Dev Cody
2025-12-27 15:49:37 -05:00
committed by GitHub
2 changed files with 110 additions and 53 deletions

View File

@@ -60,9 +60,6 @@ import {
// Stable empty array to avoid infinite loop in selector // Stable empty array to avoid infinite loop in selector
const EMPTY_WORKTREES: ReturnType<ReturnType<typeof useAppStore.getState>['getWorktrees']> = []; const EMPTY_WORKTREES: ReturnType<ReturnType<typeof useAppStore.getState>['getWorktrees']> = [];
/** Delay before starting a newly created feature to allow state to settle */
const FEATURE_CREATION_SETTLE_DELAY_MS = 500;
export function BoardView() { export function BoardView() {
const { const {
currentProject, currentProject,
@@ -461,23 +458,22 @@ export function BoardView() {
requirePlanApproval: false, requirePlanApproval: false,
}; };
// Capture existing feature IDs before adding
const featuresBeforeIds = new Set(useAppStore.getState().features.map((f) => f.id));
await handleAddFeature(featureData); await handleAddFeature(featureData);
// Find the newly created feature and start it // Find the newly created feature by looking for an ID that wasn't in the original set
// We need to wait a moment for the feature to be created const latestFeatures = useAppStore.getState().features;
setTimeout(async () => { const newFeature = latestFeatures.find((f) => !featuresBeforeIds.has(f.id));
const latestFeatures = useAppStore.getState().features;
const newFeature = latestFeatures.find(
(f) =>
f.branchName === worktree.branch &&
f.status === 'backlog' &&
f.description.includes(`PR #${prNumber}`)
);
if (newFeature) { if (newFeature) {
await handleStartImplementation(newFeature); await handleStartImplementation(newFeature);
} } else {
}, FEATURE_CREATION_SETTLE_DELAY_MS); console.error('Could not find newly created feature to start it automatically.');
toast.error('Failed to auto-start feature', {
description: 'The feature was created but could not be started automatically.',
});
}
}, },
[handleAddFeature, handleStartImplementation, defaultSkipTests] [handleAddFeature, handleStartImplementation, defaultSkipTests]
); );
@@ -503,26 +499,49 @@ export function BoardView() {
requirePlanApproval: false, requirePlanApproval: false,
}; };
// Capture existing feature IDs before adding
const featuresBeforeIds = new Set(useAppStore.getState().features.map((f) => f.id));
await handleAddFeature(featureData); await handleAddFeature(featureData);
// Find the newly created feature and start it // Find the newly created feature by looking for an ID that wasn't in the original set
setTimeout(async () => { const latestFeatures = useAppStore.getState().features;
const latestFeatures = useAppStore.getState().features; const newFeature = latestFeatures.find((f) => !featuresBeforeIds.has(f.id));
const newFeature = latestFeatures.find(
(f) =>
f.branchName === worktree.branch &&
f.status === 'backlog' &&
f.description.includes('Pull latest from origin/main')
);
if (newFeature) { if (newFeature) {
await handleStartImplementation(newFeature); await handleStartImplementation(newFeature);
} } else {
}, FEATURE_CREATION_SETTLE_DELAY_MS); console.error('Could not find newly created feature to start it automatically.');
toast.error('Failed to auto-start feature', {
description: 'The feature was created but could not be started automatically.',
});
}
}, },
[handleAddFeature, handleStartImplementation, defaultSkipTests] [handleAddFeature, handleStartImplementation, defaultSkipTests]
); );
// Handler for "Make" button - creates a feature and immediately starts it
const handleAddAndStartFeature = useCallback(
async (featureData: Parameters<typeof handleAddFeature>[0]) => {
// Capture existing feature IDs before adding
const featuresBeforeIds = new Set(useAppStore.getState().features.map((f) => f.id));
await handleAddFeature(featureData);
// Find the newly created feature by looking for an ID that wasn't in the original set
const latestFeatures = useAppStore.getState().features;
const newFeature = latestFeatures.find((f) => !featuresBeforeIds.has(f.id));
if (newFeature) {
await handleStartImplementation(newFeature);
} else {
console.error('Could not find newly created feature to start it automatically.');
toast.error('Failed to auto-start feature', {
description: 'The feature was created but could not be started automatically.',
});
}
},
[handleAddFeature, handleStartImplementation]
);
// Client-side auto mode: periodically check for backlog items and move them to in-progress // Client-side auto mode: periodically check for backlog items and move them to in-progress
// Use a ref to track the latest auto mode state so async operations always check the current value // Use a ref to track the latest auto mode state so async operations always check the current value
const autoModeRunningRef = useRef(autoMode.isRunning); const autoModeRunningRef = useRef(autoMode.isRunning);
@@ -1137,6 +1156,7 @@ export function BoardView() {
} }
}} }}
onAdd={handleAddFeature} onAdd={handleAddFeature}
onAddAndStart={handleAddAndStartFeature}
categorySuggestions={categorySuggestions} categorySuggestions={categorySuggestions}
branchSuggestions={branchSuggestions} branchSuggestions={branchSuggestions}
branchCardCounts={branchCardCounts} branchCardCounts={branchCardCounts}

View File

@@ -19,7 +19,14 @@ import {
FeatureTextFilePath as DescriptionTextFilePath, FeatureTextFilePath as DescriptionTextFilePath,
ImagePreviewMap, ImagePreviewMap,
} from '@/components/ui/description-image-dropzone'; } from '@/components/ui/description-image-dropzone';
import { MessageSquare, Settings2, SlidersHorizontal, Sparkles, ChevronDown } from 'lucide-react'; import {
MessageSquare,
Settings2,
SlidersHorizontal,
Sparkles,
ChevronDown,
Play,
} from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { getElectronAPI } from '@/lib/electron'; import { getElectronAPI } from '@/lib/electron';
import { modelSupportsThinking } from '@/lib/utils'; import { modelSupportsThinking } from '@/lib/utils';
@@ -55,25 +62,28 @@ import {
type AncestorContext, type AncestorContext,
} from '@automaker/dependency-resolver'; } from '@automaker/dependency-resolver';
type FeatureData = {
title: string;
category: string;
description: string;
images: FeatureImage[];
imagePaths: DescriptionImagePath[];
textFilePaths: DescriptionTextFilePath[];
skipTests: boolean;
model: AgentModel;
thinkingLevel: ThinkingLevel;
branchName: string; // Can be empty string to use current branch
priority: number;
planningMode: PlanningMode;
requirePlanApproval: boolean;
dependencies?: string[];
};
interface AddFeatureDialogProps { interface AddFeatureDialogProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
onAdd: (feature: { onAdd: (feature: FeatureData) => void;
title: string; onAddAndStart?: (feature: FeatureData) => void;
category: string;
description: string;
images: FeatureImage[];
imagePaths: DescriptionImagePath[];
textFilePaths: DescriptionTextFilePath[];
skipTests: boolean;
model: AgentModel;
thinkingLevel: ThinkingLevel;
branchName: string; // Can be empty string to use current branch
priority: number;
planningMode: PlanningMode;
requirePlanApproval: boolean;
dependencies?: string[];
}) => void;
categorySuggestions: string[]; categorySuggestions: string[];
branchSuggestions: string[]; branchSuggestions: string[];
branchCardCounts?: Record<string, number>; // Map of branch name to unarchived card count branchCardCounts?: Record<string, number>; // Map of branch name to unarchived card count
@@ -92,6 +102,7 @@ export function AddFeatureDialog({
open, open,
onOpenChange, onOpenChange,
onAdd, onAdd,
onAddAndStart,
categorySuggestions, categorySuggestions,
branchSuggestions, branchSuggestions,
branchCardCounts, branchCardCounts,
@@ -188,16 +199,16 @@ export function AddFeatureDialog({
allFeatures, allFeatures,
]); ]);
const handleAdd = () => { const buildFeatureData = (): FeatureData | null => {
if (!newFeature.description.trim()) { if (!newFeature.description.trim()) {
setDescriptionError(true); setDescriptionError(true);
return; return null;
} }
// Validate branch selection when "other branch" is selected // Validate branch selection when "other branch" is selected
if (useWorktrees && !useCurrentBranch && !newFeature.branchName.trim()) { if (useWorktrees && !useCurrentBranch && !newFeature.branchName.trim()) {
toast.error('Please select a branch name'); toast.error('Please select a branch name');
return; return null;
} }
const category = newFeature.category || 'Uncategorized'; const category = newFeature.category || 'Uncategorized';
@@ -235,7 +246,7 @@ export function AddFeatureDialog({
} }
} }
onAdd({ return {
title: newFeature.title, title: newFeature.title,
category, category,
description: finalDescription, description: finalDescription,
@@ -251,9 +262,10 @@ export function AddFeatureDialog({
requirePlanApproval, requirePlanApproval,
// In spawn mode, automatically add parent as dependency // In spawn mode, automatically add parent as dependency
dependencies: isSpawnMode && parentFeature ? [parentFeature.id] : undefined, dependencies: isSpawnMode && parentFeature ? [parentFeature.id] : undefined,
}); };
};
// Reset form const resetForm = () => {
setNewFeature({ setNewFeature({
title: '', title: '',
category: '', category: '',
@@ -276,6 +288,20 @@ export function AddFeatureDialog({
onOpenChange(false); onOpenChange(false);
}; };
const handleAction = (actionFn?: (data: FeatureData) => void) => {
if (!actionFn) return;
const featureData = buildFeatureData();
if (!featureData) return;
actionFn(featureData);
resetForm();
};
const handleAdd = () => handleAction(onAdd);
const handleAddAndStart = () => handleAction(onAddAndStart);
const handleDialogClose = (open: boolean) => { const handleDialogClose = (open: boolean) => {
onOpenChange(open); onOpenChange(open);
if (!open) { if (!open) {
@@ -575,6 +601,17 @@ export function AddFeatureDialog({
<Button variant="ghost" onClick={() => onOpenChange(false)}> <Button variant="ghost" onClick={() => onOpenChange(false)}>
Cancel Cancel
</Button> </Button>
{onAddAndStart && (
<Button
onClick={handleAddAndStart}
variant="secondary"
data-testid="confirm-add-and-start-feature"
disabled={useWorktrees && !useCurrentBranch && !newFeature.branchName.trim()}
>
<Play className="w-4 h-4 mr-2" />
Make
</Button>
)}
<HotkeyButton <HotkeyButton
onClick={handleAdd} onClick={handleAdd}
hotkey={{ key: 'Enter', cmdCtrl: true }} hotkey={{ key: 'Enter', cmdCtrl: true }}