mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
Merge pull request #285 from AutoMaker-Org/adding-make-button
adding button to make when creating a new feature
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
Reference in New Issue
Block a user