mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
Merge branch 'main' of https://github.com/webdevcody/automaker
# Conflicts: # app/src/app/page.tsx
This commit is contained in:
@@ -154,29 +154,31 @@ export function AgentOutputModal({
|
||||
case "auto_mode_ultrathink_preparation":
|
||||
// Format thinking level preparation information
|
||||
let prepContent = `\n🧠 Ultrathink Preparation\n`;
|
||||
|
||||
|
||||
if (event.warnings && event.warnings.length > 0) {
|
||||
prepContent += `\n⚠️ Warnings:\n`;
|
||||
event.warnings.forEach((warning: string) => {
|
||||
prepContent += ` • ${warning}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (event.recommendations && event.recommendations.length > 0) {
|
||||
prepContent += `\n💡 Recommendations:\n`;
|
||||
event.recommendations.forEach((rec: string) => {
|
||||
prepContent += ` • ${rec}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (event.estimatedCost !== undefined) {
|
||||
prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed(2)} per execution\n`;
|
||||
prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed(
|
||||
2
|
||||
)} per execution\n`;
|
||||
}
|
||||
|
||||
|
||||
if (event.estimatedTime) {
|
||||
prepContent += `\n⏱️ Estimated Time: ${event.estimatedTime}\n`;
|
||||
}
|
||||
|
||||
|
||||
newContent = prepContent;
|
||||
break;
|
||||
case "auto_mode_feature_complete":
|
||||
@@ -299,7 +301,7 @@ export function AgentOutputModal({
|
||||
</DialogHeader>
|
||||
|
||||
{viewMode === "changes" ? (
|
||||
<div className="flex-1 overflow-y-auto min-h-[400px] max-h-[60vh]">
|
||||
<div className="flex-1 min-h-[400px] max-h-[60vh] overflow-y-auto scrollbar-visible">
|
||||
{projectPath ? (
|
||||
<GitDiffPanel
|
||||
projectPath={projectPath}
|
||||
@@ -320,7 +322,7 @@ export function AgentOutputModal({
|
||||
<div
|
||||
ref={scrollRef}
|
||||
onScroll={handleScroll}
|
||||
className="flex-1 overflow-y-auto bg-zinc-950 rounded-lg p-4 font-mono text-xs min-h-[400px] max-h-[60vh]"
|
||||
className="flex-1 overflow-y-auto bg-zinc-950 rounded-lg p-4 font-mono text-xs min-h-[400px] max-h-[60vh] scrollbar-visible"
|
||||
>
|
||||
{isLoading && !output ? (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
|
||||
@@ -268,6 +268,8 @@ export function BoardView() {
|
||||
// Track previous project to detect switches
|
||||
const prevProjectPathRef = useRef<string | null>(null);
|
||||
const isSwitchingProjectRef = useRef<boolean>(false);
|
||||
// Track if this is the initial load (to avoid showing loading spinner on subsequent reloads)
|
||||
const isInitialLoadRef = useRef<boolean>(true);
|
||||
|
||||
// Auto mode hook
|
||||
const autoMode = useAutoMode();
|
||||
@@ -367,11 +369,13 @@ export function BoardView() {
|
||||
const previousPath = prevProjectPathRef.current;
|
||||
|
||||
// If project switched, clear features first to prevent cross-contamination
|
||||
// Also treat this as an initial load for the new project
|
||||
if (previousPath !== null && currentPath !== previousPath) {
|
||||
console.log(
|
||||
`[BoardView] Project switch detected: ${previousPath} -> ${currentPath}, clearing features`
|
||||
);
|
||||
isSwitchingProjectRef.current = true;
|
||||
isInitialLoadRef.current = true;
|
||||
setFeatures([]);
|
||||
setPersistedCategories([]); // Also clear categories
|
||||
}
|
||||
@@ -379,7 +383,11 @@ export function BoardView() {
|
||||
// Update the ref to track current project
|
||||
prevProjectPathRef.current = currentPath;
|
||||
|
||||
setIsLoading(true);
|
||||
// Only show loading spinner on initial load to prevent board flash during reloads
|
||||
if (isInitialLoadRef.current) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.readFile(
|
||||
@@ -403,6 +411,7 @@ export function BoardView() {
|
||||
console.error("Failed to load features:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
isInitialLoadRef.current = false;
|
||||
isSwitchingProjectRef.current = false;
|
||||
}
|
||||
}, [currentProject, setFeatures]);
|
||||
@@ -1270,17 +1279,35 @@ export function BoardView() {
|
||||
}
|
||||
};
|
||||
|
||||
const getColumnFeatures = (columnId: ColumnId) => {
|
||||
return features.filter((f) => {
|
||||
// Memoize column features to prevent unnecessary re-renders
|
||||
const columnFeaturesMap = useMemo(() => {
|
||||
const map: Record<ColumnId, Feature[]> = {
|
||||
backlog: [],
|
||||
in_progress: [],
|
||||
waiting_approval: [],
|
||||
verified: [],
|
||||
};
|
||||
|
||||
features.forEach((f) => {
|
||||
// If feature has a running agent, always show it in "in_progress"
|
||||
const isRunning = runningAutoTasks.includes(f.id);
|
||||
if (isRunning) {
|
||||
return columnId === "in_progress";
|
||||
map.in_progress.push(f);
|
||||
} else {
|
||||
// Otherwise, use the feature's status
|
||||
map[f.status].push(f);
|
||||
}
|
||||
// Otherwise, use the feature's status
|
||||
return f.status === columnId;
|
||||
});
|
||||
};
|
||||
|
||||
return map;
|
||||
}, [features, runningAutoTasks]);
|
||||
|
||||
const getColumnFeatures = useCallback(
|
||||
(columnId: ColumnId) => {
|
||||
return columnFeaturesMap[columnId];
|
||||
},
|
||||
[columnFeaturesMap]
|
||||
);
|
||||
|
||||
const handleViewOutput = (feature: Feature) => {
|
||||
setOutputFeature(feature);
|
||||
@@ -1537,7 +1564,7 @@ export function BoardView() {
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Feature
|
||||
<span
|
||||
className="ml-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-accent border border-border-glass"
|
||||
className="ml-3 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/20 border border-primary-foreground/30 text-primary-foreground inline-flex items-center justify-center"
|
||||
data-testid="shortcut-add-feature"
|
||||
>
|
||||
{ACTION_SHORTCUTS.addFeature}
|
||||
@@ -1738,6 +1765,7 @@ export function BoardView() {
|
||||
placeholder="Describe the feature..."
|
||||
previewMap={newFeaturePreviewMap}
|
||||
onPreviewMapChange={setNewFeaturePreviewMap}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -2036,10 +2064,11 @@ export function BoardView() {
|
||||
>
|
||||
Add Feature
|
||||
<span
|
||||
className="ml-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/10 border border-primary-foreground/20"
|
||||
className="ml-3 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/10 border border-primary-foreground/20 inline-flex items-center gap-1.5"
|
||||
data-testid="shortcut-confirm-add-feature"
|
||||
>
|
||||
⌘↵
|
||||
<span className="leading-none flex items-center justify-center">⌘</span>
|
||||
<span className="leading-none flex items-center justify-center">↵</span>
|
||||
</span>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, memo } from "react";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -92,7 +92,7 @@ interface KanbanCardProps {
|
||||
summary?: string;
|
||||
}
|
||||
|
||||
export function KanbanCard({
|
||||
export const KanbanCard = memo(function KanbanCard({
|
||||
feature,
|
||||
onEdit,
|
||||
onDelete,
|
||||
@@ -227,7 +227,7 @@ export function KanbanCard({
|
||||
{/* Shortcut key badge for in-progress cards */}
|
||||
{shortcutKey && (
|
||||
<div
|
||||
className="absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-muted border border-border text-muted-foreground z-10"
|
||||
className="absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-mono rounded bg-brand-500/10 border border-brand-500/30 text-brand-400/70 z-10"
|
||||
data-testid={`shortcut-key-${feature.id}`}
|
||||
>
|
||||
{shortcutKey}
|
||||
@@ -869,4 +869,4 @@ export function KanbanCard({
|
||||
</Dialog>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { ReactNode } from "react";
|
||||
@@ -13,7 +14,7 @@ interface KanbanColumnProps {
|
||||
headerAction?: ReactNode;
|
||||
}
|
||||
|
||||
export function KanbanColumn({
|
||||
export const KanbanColumn = memo(function KanbanColumn({
|
||||
id,
|
||||
title,
|
||||
color,
|
||||
@@ -48,4 +49,4 @@ export function KanbanColumn({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -548,7 +548,7 @@ export function ProfilesView() {
|
||||
<Button onClick={() => setShowAddDialog(true)} data-testid="add-profile-button" className="relative">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New Profile
|
||||
<span className="hidden lg:flex items-center justify-center ml-2 px-2 py-0.5 text-[10px] font-mono rounded bg-white/5 border border-white/10 text-zinc-500">
|
||||
<span className="hidden lg:flex items-center justify-center ml-2 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/20 border border-primary-foreground/30 text-primary-foreground">
|
||||
{ACTION_SHORTCUTS.addProfile}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user