{Object.entries(projectAnalysis.filesByExtension)
- .sort((a: [string, number], b: [string, number]) => b[1] - a[1])
+ .sort(
+ (a: [string, number], b: [string, number]) =>
+ b[1] - a[1]
+ )
.slice(0, 15)
.map(([ext, count]: [string, number]) => (
@@ -1096,7 +1108,9 @@ ${Object.entries(projectAnalysis.filesByExtension)
data-testid="analysis-file-tree"
>
- {projectAnalysis.fileTree.map((node: FileTreeNode) => renderNode(node))}
+ {projectAnalysis.fileTree.map((node: FileTreeNode) =>
+ renderNode(node)
+ )}
diff --git a/app/src/components/views/board-view.tsx b/app/src/components/views/board-view.tsx
index faf7f1dc..d68275bd 100644
--- a/app/src/components/views/board-view.tsx
+++ b/app/src/components/views/board-view.tsx
@@ -529,16 +529,22 @@ export function BoardView() {
const projectId = currentProject.id;
const unsubscribe = api.autoMode.onEvent((event) => {
- // Use event's projectId if available, otherwise use current project
- const eventProjectId = event.projectId || projectId;
+ // Use event's projectPath or projectId if available, otherwise use current project
+ // Board view only reacts to events for the currently selected project
+ const eventProjectId = ('projectId' in event && event.projectId) || projectId;
if (event.type === "auto_mode_feature_complete") {
// Reload features when a feature is completed
console.log("[Board] Feature completed, reloading features...");
loadFeatures();
- // Play ding sound when feature is done
- const audio = new Audio("/sounds/ding.mp3");
- audio.play().catch((err) => console.warn("Could not play ding sound:", err));
+ // Play ding sound when feature is done (unless muted)
+ const { muteDoneSound } = useAppStore.getState();
+ if (!muteDoneSound) {
+ const audio = new Audio("/sounds/ding.mp3");
+ audio
+ .play()
+ .catch((err) => console.warn("Could not play ding sound:", err));
+ }
} else if (event.type === "auto_mode_error") {
// Reload features when an error occurs (feature moved to waiting_approval)
console.log(
@@ -580,22 +586,36 @@ export function BoardView() {
const api = getElectronAPI();
if (!api?.autoMode?.status) return;
- const status = await api.autoMode.status();
- if (status.success && status.runningFeatures) {
- console.log(
- "[Board] Syncing running tasks from backend:",
- status.runningFeatures
- );
-
- // Clear existing running tasks for this project and add the actual running ones
- const { clearRunningTasks, addRunningTask } = useAppStore.getState();
+ const status = await api.autoMode.status(currentProject.path);
+ if (status.success) {
const projectId = currentProject.id;
- clearRunningTasks(projectId);
+ const { clearRunningTasks, addRunningTask, setAutoModeRunning } =
+ useAppStore.getState();
- // Add each running feature to the store
- status.runningFeatures.forEach((featureId: string) => {
- addRunningTask(projectId, featureId);
- });
+ // Sync running features if available
+ if (status.runningFeatures) {
+ console.log(
+ "[Board] Syncing running tasks from backend:",
+ status.runningFeatures
+ );
+
+ // Clear existing running tasks for this project and add the actual running ones
+ clearRunningTasks(projectId);
+
+ // Add each running feature to the store
+ status.runningFeatures.forEach((featureId: string) => {
+ addRunningTask(projectId, featureId);
+ });
+ }
+
+ // Sync auto mode running state (backend returns autoLoopRunning, mock returns isRunning)
+ const isAutoModeRunning =
+ status.autoLoopRunning ?? status.isRunning ?? false;
+ console.log(
+ "[Board] Syncing auto mode running state:",
+ isAutoModeRunning
+ );
+ setAutoModeRunning(projectId, isAutoModeRunning);
}
} catch (error) {
console.error("[Board] Failed to sync running tasks:", error);
@@ -1899,7 +1919,7 @@ export function BoardView() {
data-testid="start-next-button"
>
- Start Next
+ Pull Top
)}
diff --git a/app/src/components/views/feature-suggestions-dialog.tsx b/app/src/components/views/feature-suggestions-dialog.tsx
index d3c04185..e68ae57f 100644
--- a/app/src/components/views/feature-suggestions-dialog.tsx
+++ b/app/src/components/views/feature-suggestions-dialog.tsx
@@ -20,8 +20,11 @@ import {
StopCircle,
ChevronDown,
ChevronRight,
+ RefreshCw,
+ Shield,
+ Zap,
} from "lucide-react";
-import { getElectronAPI, FeatureSuggestion, SuggestionsEvent } from "@/lib/electron";
+import { getElectronAPI, FeatureSuggestion, SuggestionsEvent, SuggestionType } from "@/lib/electron";
import { useAppStore, Feature } from "@/store/app-store";
import { toast } from "sonner";
@@ -36,6 +39,39 @@ interface FeatureSuggestionsDialogProps {
setIsGenerating: (generating: boolean) => void;
}
+// Configuration for each suggestion type
+const suggestionTypeConfig: Record
;
+ description: string;
+ color: string;
+}> = {
+ features: {
+ label: "Feature Suggestions",
+ icon: Lightbulb,
+ description: "Discover missing features and improvements",
+ color: "text-yellow-500",
+ },
+ refactoring: {
+ label: "Refactoring Suggestions",
+ icon: RefreshCw,
+ description: "Find code smells and refactoring opportunities",
+ color: "text-blue-500",
+ },
+ security: {
+ label: "Security Suggestions",
+ icon: Shield,
+ description: "Identify security vulnerabilities and issues",
+ color: "text-red-500",
+ },
+ performance: {
+ label: "Performance Suggestions",
+ icon: Zap,
+ description: "Discover performance bottlenecks and optimizations",
+ color: "text-green-500",
+ },
+};
+
export function FeatureSuggestionsDialog({
open,
onClose,
@@ -49,6 +85,7 @@ export function FeatureSuggestionsDialog({
const [selectedIds, setSelectedIds] = useState>(new Set());
const [expandedIds, setExpandedIds] = useState>(new Set());
const [isImporting, setIsImporting] = useState(false);
+ const [currentSuggestionType, setCurrentSuggestionType] = useState(null);
const scrollRef = useRef(null);
const autoScrollRef = useRef(true);
@@ -87,7 +124,8 @@ export function FeatureSuggestionsDialog({
setSuggestions(event.suggestions);
// Select all by default
setSelectedIds(new Set(event.suggestions.map((s) => s.id)));
- toast.success(`Generated ${event.suggestions.length} feature suggestions!`);
+ const typeLabel = currentSuggestionType ? suggestionTypeConfig[currentSuggestionType].label.toLowerCase() : "suggestions";
+ toast.success(`Generated ${event.suggestions.length} ${typeLabel}!`);
} else {
toast.info("No suggestions generated. Try again.");
}
@@ -100,10 +138,10 @@ export function FeatureSuggestionsDialog({
return () => {
unsubscribe();
};
- }, [open, setSuggestions, setIsGenerating]);
+ }, [open, setSuggestions, setIsGenerating, currentSuggestionType]);
- // Start generating suggestions
- const handleGenerate = useCallback(async () => {
+ // Start generating suggestions for a specific type
+ const handleGenerate = useCallback(async (suggestionType: SuggestionType) => {
const api = getElectronAPI();
if (!api?.suggestions) {
toast.error("Suggestions API not available");
@@ -114,9 +152,10 @@ export function FeatureSuggestionsDialog({
setProgress([]);
setSuggestions([]);
setSelectedIds(new Set());
+ setCurrentSuggestionType(suggestionType);
try {
- const result = await api.suggestions.generate(projectPath);
+ const result = await api.suggestions.generate(projectPath, suggestionType);
if (!result.success) {
toast.error(result.error || "Failed to start generation");
setIsGenerating(false);
@@ -203,8 +242,10 @@ export function FeatureSuggestionsDialog({
}));
// Create each new feature using the features API
- for (const feature of newFeatures) {
- await api.features.create(projectPath, feature);
+ if (api.features) {
+ for (const feature of newFeatures) {
+ await api.features.create(projectPath, feature);
+ }
}
// Merge with existing features for store update
@@ -219,6 +260,7 @@ export function FeatureSuggestionsDialog({
setSuggestions([]);
setSelectedIds(new Set());
setProgress([]);
+ setCurrentSuggestionType(null);
onClose();
} catch (error) {
@@ -238,16 +280,17 @@ export function FeatureSuggestionsDialog({
autoScrollRef.current = isAtBottom;
};
- // Reset state when dialog closes
- useEffect(() => {
- if (!open) {
- // Don't reset immediately - allow re-open to see results
- // Only reset if explicitly closed without importing
- }
- }, [open]);
+ // Go back to type selection
+ const handleBackToSelection = useCallback(() => {
+ setSuggestions([]);
+ setSelectedIds(new Set());
+ setProgress([]);
+ setCurrentSuggestionType(null);
+ }, [setSuggestions]);
const hasStarted = progress.length > 0 || suggestions.length > 0;
const hasSuggestions = suggestions.length > 0;
+ const currentConfig = currentSuggestionType ? suggestionTypeConfig[currentSuggestionType] : null;
return (