Merge branch 'AutoMaker-Org:main' into claude/task-dependency-graph-iPz1k

This commit is contained in:
James Botwina
2025-12-22 14:23:18 -05:00
committed by GitHub
44 changed files with 2928 additions and 446 deletions

View File

@@ -128,116 +128,130 @@ export function AgentInfoPanel({
// Agent Info Panel for non-backlog cards
if (showAgentInfo && feature.status !== 'backlog' && agentInfo) {
return (
<div className="mb-3 space-y-2 overflow-hidden">
{/* Model & Phase */}
<div className="flex items-center gap-2 text-[11px] flex-wrap">
<div className="flex items-center gap-1 text-[var(--status-info)]">
<Cpu className="w-3 h-3" />
<span className="font-medium">{formatModelName(feature.model ?? DEFAULT_MODEL)}</span>
</div>
{agentInfo.currentPhase && (
<div
className={cn(
'px-1.5 py-0.5 rounded-md text-[10px] font-medium',
agentInfo.currentPhase === 'planning' &&
'bg-[var(--status-info-bg)] text-[var(--status-info)]',
agentInfo.currentPhase === 'action' &&
'bg-[var(--status-warning-bg)] text-[var(--status-warning)]',
agentInfo.currentPhase === 'verification' &&
'bg-[var(--status-success-bg)] text-[var(--status-success)]'
)}
>
{agentInfo.currentPhase}
<>
<div className="mb-3 space-y-2 overflow-hidden">
{/* Model & Phase */}
<div className="flex items-center gap-2 text-[11px] flex-wrap">
<div className="flex items-center gap-1 text-[var(--status-info)]">
<Cpu className="w-3 h-3" />
<span className="font-medium">{formatModelName(feature.model ?? DEFAULT_MODEL)}</span>
</div>
)}
</div>
{/* Task List Progress */}
{agentInfo.todos.length > 0 && (
<div className="space-y-1">
<div className="flex items-center gap-1 text-[10px] text-muted-foreground/70">
<ListTodo className="w-3 h-3" />
<span>
{agentInfo.todos.filter((t) => t.status === 'completed').length}/
{agentInfo.todos.length} tasks
</span>
</div>
<div className="space-y-0.5 max-h-16 overflow-y-auto">
{agentInfo.todos.slice(0, 3).map((todo, idx) => (
<div key={idx} className="flex items-center gap-1.5 text-[10px]">
{todo.status === 'completed' ? (
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" />
) : todo.status === 'in_progress' ? (
<Loader2 className="w-2.5 h-2.5 text-[var(--status-warning)] animate-spin shrink-0" />
) : (
<Circle className="w-2.5 h-2.5 text-muted-foreground/50 shrink-0" />
)}
<span
className={cn(
'break-words hyphens-auto line-clamp-2 leading-relaxed',
todo.status === 'completed' && 'text-muted-foreground/60 line-through',
todo.status === 'in_progress' && 'text-[var(--status-warning)]',
todo.status === 'pending' && 'text-muted-foreground/80'
)}
>
{todo.content}
</span>
</div>
))}
{agentInfo.todos.length > 3 && (
<p className="text-[10px] text-muted-foreground/60 pl-4">
+{agentInfo.todos.length - 3} more
</p>
)}
</div>
</div>
)}
{/* Summary for waiting_approval and verified */}
{(feature.status === 'waiting_approval' || feature.status === 'verified') && (
<>
{(feature.summary || summary || agentInfo.summary) && (
<div className="space-y-1.5 pt-2 border-t border-border/30 overflow-hidden">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1 text-[10px] text-[var(--status-success)] min-w-0">
<Sparkles className="w-3 h-3 shrink-0" />
<span className="truncate font-medium">Summary</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setIsSummaryDialogOpen(true);
}}
onPointerDown={(e) => e.stopPropagation()}
className="p-0.5 rounded-md hover:bg-muted/80 transition-colors text-muted-foreground/60 hover:text-muted-foreground shrink-0"
title="View full summary"
data-testid={`expand-summary-${feature.id}`}
>
<Expand className="w-3 h-3" />
</button>
</div>
<p className="text-[10px] text-muted-foreground/70 line-clamp-3 break-words hyphens-auto leading-relaxed overflow-hidden">
{feature.summary || summary || agentInfo.summary}
</p>
{agentInfo.currentPhase && (
<div
className={cn(
'px-1.5 py-0.5 rounded-md text-[10px] font-medium',
agentInfo.currentPhase === 'planning' &&
'bg-[var(--status-info-bg)] text-[var(--status-info)]',
agentInfo.currentPhase === 'action' &&
'bg-[var(--status-warning-bg)] text-[var(--status-warning)]',
agentInfo.currentPhase === 'verification' &&
'bg-[var(--status-success-bg)] text-[var(--status-success)]'
)}
>
{agentInfo.currentPhase}
</div>
)}
{!feature.summary && !summary && !agentInfo.summary && agentInfo.toolCallCount > 0 && (
<div className="flex items-center gap-2 text-[10px] text-muted-foreground/60 pt-2 border-t border-border/30">
<span className="flex items-center gap-1">
<Wrench className="w-2.5 h-2.5" />
{agentInfo.toolCallCount} tool calls
</div>
{/* Task List Progress */}
{agentInfo.todos.length > 0 && (
<div className="space-y-1">
<div className="flex items-center gap-1 text-[10px] text-muted-foreground/70">
<ListTodo className="w-3 h-3" />
<span>
{agentInfo.todos.filter((t) => t.status === 'completed').length}/
{agentInfo.todos.length} tasks
</span>
{agentInfo.todos.length > 0 && (
<span className="flex items-center gap-1">
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)]" />
{agentInfo.todos.filter((t) => t.status === 'completed').length} tasks done
</span>
</div>
<div className="space-y-0.5 max-h-16 overflow-y-auto">
{agentInfo.todos.slice(0, 3).map((todo, idx) => (
<div key={idx} className="flex items-center gap-1.5 text-[10px]">
{todo.status === 'completed' ? (
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" />
) : todo.status === 'in_progress' ? (
<Loader2 className="w-2.5 h-2.5 text-[var(--status-warning)] animate-spin shrink-0" />
) : (
<Circle className="w-2.5 h-2.5 text-muted-foreground/50 shrink-0" />
)}
<span
className={cn(
'break-words hyphens-auto line-clamp-2 leading-relaxed',
todo.status === 'completed' && 'text-muted-foreground/60 line-through',
todo.status === 'in_progress' && 'text-[var(--status-warning)]',
todo.status === 'pending' && 'text-muted-foreground/80'
)}
>
{todo.content}
</span>
</div>
))}
{agentInfo.todos.length > 3 && (
<p className="text-[10px] text-muted-foreground/60 pl-4">
+{agentInfo.todos.length - 3} more
</p>
)}
</div>
)}
</>
)}
</div>
</div>
)}
{/* Summary for waiting_approval and verified */}
{(feature.status === 'waiting_approval' || feature.status === 'verified') && (
<>
{(feature.summary || summary || agentInfo.summary) && (
<div className="space-y-1.5 pt-2 border-t border-border/30 overflow-hidden">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1 text-[10px] text-[var(--status-success)] min-w-0">
<Sparkles className="w-3 h-3 shrink-0" />
<span className="truncate font-medium">Summary</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setIsSummaryDialogOpen(true);
}}
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
className="p-0.5 rounded-md hover:bg-muted/80 transition-colors text-muted-foreground/60 hover:text-muted-foreground shrink-0"
title="View full summary"
data-testid={`expand-summary-${feature.id}`}
>
<Expand className="w-3 h-3" />
</button>
</div>
<p className="text-[10px] text-muted-foreground/70 line-clamp-3 break-words hyphens-auto leading-relaxed overflow-hidden">
{feature.summary || summary || agentInfo.summary}
</p>
</div>
)}
{!feature.summary &&
!summary &&
!agentInfo.summary &&
agentInfo.toolCallCount > 0 && (
<div className="flex items-center gap-2 text-[10px] text-muted-foreground/60 pt-2 border-t border-border/30">
<span className="flex items-center gap-1">
<Wrench className="w-2.5 h-2.5" />
{agentInfo.toolCallCount} tool calls
</span>
{agentInfo.todos.length > 0 && (
<span className="flex items-center gap-1">
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)]" />
{agentInfo.todos.filter((t) => t.status === 'completed').length} tasks done
</span>
)}
</div>
)}
</>
)}
</div>
{/* SummaryDialog must be rendered alongside the expand button */}
<SummaryDialog
feature={feature}
agentInfo={agentInfo}
summary={summary}
isOpen={isSummaryDialogOpen}
onOpenChange={setIsSummaryDialogOpen}
/>
</>
);
}

View File

@@ -1,17 +1,12 @@
import { Feature } from '@/store/app-store';
import { GitBranch, GitPullRequest, ExternalLink, CheckCircle2, Circle } from 'lucide-react';
import { GitBranch, GitPullRequest, ExternalLink } from 'lucide-react';
interface CardContentSectionsProps {
feature: Feature;
useWorktrees: boolean;
showSteps: boolean;
}
export function CardContentSections({
feature,
useWorktrees,
showSteps,
}: CardContentSectionsProps) {
export function CardContentSections({ feature, useWorktrees }: CardContentSectionsProps) {
return (
<>
{/* Target Branch Display */}
@@ -50,30 +45,6 @@ export function CardContentSections({
</div>
);
})()}
{/* Steps Preview */}
{showSteps && feature.steps && feature.steps.length > 0 && (
<div className="mb-3 space-y-1.5">
{feature.steps.slice(0, 3).map((step, index) => (
<div
key={index}
className="flex items-start gap-2 text-[11px] text-muted-foreground/80"
>
{feature.status === 'verified' ? (
<CheckCircle2 className="w-3 h-3 mt-0.5 text-[var(--status-success)] shrink-0" />
) : (
<Circle className="w-3 h-3 mt-0.5 shrink-0 text-muted-foreground/50" />
)}
<span className="break-words hyphens-auto line-clamp-2 leading-relaxed">{step}</span>
</div>
))}
{feature.steps.length > 3 && (
<p className="text-[10px] text-muted-foreground/60 pl-5">
+{feature.steps.length - 3} more
</p>
)}
</div>
)}
</>
);
}

View File

@@ -61,9 +61,7 @@ export const KanbanCard = memo(function KanbanCard({
cardBorderEnabled = true,
cardBorderOpacity = 100,
}: KanbanCardProps) {
const { kanbanCardDetailLevel, useWorktrees } = useAppStore();
const showSteps = kanbanCardDetailLevel === 'standard' || kanbanCardDetailLevel === 'detailed';
const { useWorktrees } = useAppStore();
const isDraggable =
feature.status === 'backlog' ||
@@ -152,7 +150,7 @@ export const KanbanCard = memo(function KanbanCard({
<CardContent className="px-3 pt-0 pb-0">
{/* Content Sections */}
<CardContentSections feature={feature} useWorktrees={useWorktrees} showSteps={showSteps} />
<CardContentSections feature={feature} useWorktrees={useWorktrees} />
{/* Agent Info Panel */}
<AgentInfoPanel

View File

@@ -62,7 +62,6 @@ interface AddFeatureDialogProps {
title: string;
category: string;
description: string;
steps: string[];
images: FeatureImage[];
imagePaths: DescriptionImagePath[];
textFilePaths: DescriptionTextFilePath[];
@@ -105,7 +104,6 @@ export function AddFeatureDialog({
title: '',
category: '',
description: '',
steps: [''],
images: [] as FeatureImage[],
imagePaths: [] as DescriptionImagePath[],
textFilePaths: [] as DescriptionTextFilePath[],
@@ -193,7 +191,6 @@ export function AddFeatureDialog({
title: newFeature.title,
category,
description: newFeature.description,
steps: newFeature.steps.filter((s) => s.trim()),
images: newFeature.images,
imagePaths: newFeature.imagePaths,
textFilePaths: newFeature.textFilePaths,
@@ -211,7 +208,6 @@ export function AddFeatureDialog({
title: '',
category: '',
description: '',
steps: [''],
images: [],
imagePaths: [],
textFilePaths: [],
@@ -502,8 +498,6 @@ export function AddFeatureDialog({
<TestingTabContent
skipTests={newFeature.skipTests}
onSkipTestsChange={(skipTests) => setNewFeature({ ...newFeature, skipTests })}
steps={newFeature.steps}
onStepsChange={(steps) => setNewFeature({ ...newFeature, steps })}
/>
</TabsContent>
</Tabs>

View File

@@ -64,7 +64,6 @@ interface EditFeatureDialogProps {
title: string;
category: string;
description: string;
steps: string[];
skipTests: boolean;
model: AgentModel;
thinkingLevel: ThinkingLevel;
@@ -165,7 +164,6 @@ export function EditFeatureDialog({
title: editingFeature.title ?? '',
category: editingFeature.category,
description: editingFeature.description,
steps: editingFeature.steps,
skipTests: editingFeature.skipTests ?? false,
model: selectedModel,
thinkingLevel: normalizedThinking,
@@ -491,8 +489,6 @@ export function EditFeatureDialog({
<TestingTabContent
skipTests={editingFeature.skipTests ?? false}
onSkipTestsChange={(skipTests) => setEditingFeature({ ...editingFeature, skipTests })}
steps={editingFeature.steps}
onStepsChange={(steps) => setEditingFeature({ ...editingFeature, steps })}
testIdPrefix="edit"
/>
</TabsContent>

View File

@@ -245,7 +245,6 @@ export function FeatureSuggestionsDialog({
id: `feature-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
category: s.category,
description: s.description,
steps: s.steps,
status: 'backlog' as const,
skipTests: true, // As specified, testing mode true
priority: s.priority, // Preserve priority from suggestion
@@ -453,23 +452,9 @@ export function FeatureSuggestionsDialog({
{suggestion.description}
</Label>
{isExpanded && (
<div className="mt-3 space-y-2 text-sm">
{suggestion.reasoning && (
<p className="text-muted-foreground italic">{suggestion.reasoning}</p>
)}
{suggestion.steps.length > 0 && (
<div>
<p className="text-xs font-medium text-muted-foreground mb-1">
Implementation Steps:
</p>
<ul className="list-disc list-inside text-xs text-muted-foreground space-y-0.5">
{suggestion.steps.map((step, i) => (
<li key={i}>{step}</li>
))}
</ul>
</div>
)}
{isExpanded && suggestion.reasoning && (
<div className="mt-3 text-sm">
<p className="text-muted-foreground italic">{suggestion.reasoning}</p>
</div>
)}
</div>

View File

@@ -89,7 +89,6 @@ export function useBoardActions({
title: string;
category: string;
description: string;
steps: string[];
images: FeatureImage[];
imagePaths: DescriptionImagePath[];
skipTests: boolean;
@@ -208,7 +207,6 @@ export function useBoardActions({
title: string;
category: string;
description: string;
steps: string[];
skipTests: boolean;
model: AgentModel;
thinkingLevel: ThinkingLevel;

View File

@@ -1,36 +1,20 @@
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { FlaskConical, Plus } from 'lucide-react';
import { FlaskConical } from 'lucide-react';
interface TestingTabContentProps {
skipTests: boolean;
onSkipTestsChange: (skipTests: boolean) => void;
steps: string[];
onStepsChange: (steps: string[]) => void;
testIdPrefix?: string;
}
export function TestingTabContent({
skipTests,
onSkipTestsChange,
steps,
onStepsChange,
testIdPrefix = '',
}: TestingTabContentProps) {
const checkboxId = testIdPrefix ? `${testIdPrefix}-skip-tests` : 'skip-tests';
const handleStepChange = (index: number, value: string) => {
const newSteps = [...steps];
newSteps[index] = value;
onStepsChange(newSteps);
};
const handleAddStep = () => {
onStepsChange([...steps, '']);
};
return (
<div className="space-y-4">
<div className="flex items-center space-x-2">
@@ -48,37 +32,9 @@ export function TestingTabContent({
</div>
</div>
<p className="text-xs text-muted-foreground">
When enabled, this feature will use automated TDD. When disabled, it will require manual
verification.
When enabled, the agent will use Playwright to verify the feature works correctly before
marking it as verified. When disabled, manual verification will be required.
</p>
{/* Verification Steps - Only shown when skipTests is enabled */}
{skipTests && (
<div className="space-y-2 pt-2 border-t border-border">
<Label>Verification Steps</Label>
<p className="text-xs text-muted-foreground mb-2">
Add manual steps to verify this feature works correctly.
</p>
{steps.map((step, index) => (
<Input
key={index}
value={step}
placeholder={`Verification step ${index + 1}`}
onChange={(e) => handleStepChange(index, e.target.value)}
data-testid={`${testIdPrefix ? testIdPrefix + '-' : ''}feature-step-${index}${testIdPrefix ? '' : '-input'}`}
/>
))}
<Button
variant="outline"
size="sm"
onClick={handleAddStep}
data-testid={`${testIdPrefix ? testIdPrefix + '-' : ''}add-step-button`}
>
<Plus className="w-4 h-4 mr-2" />
Add Verification Step
</Button>
</div>
)}
</div>
);
}