mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
Added UI features back for priority, added/fixed category generation. Added dependency trees for stories, see PR for rest
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,9 @@
|
|||||||
#added by trueheads > will remove once supercombo adds multi-os support
|
#added by trueheads > will remove once supercombo adds multi-os support
|
||||||
launch.sh
|
launch.sh
|
||||||
|
|
||||||
|
# Claude Code settings
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ export function BoardView() {
|
|||||||
isMaximized={isMaximized}
|
isMaximized={isMaximized}
|
||||||
showProfilesOnly={showProfilesOnly}
|
showProfilesOnly={showProfilesOnly}
|
||||||
aiProfiles={aiProfiles}
|
aiProfiles={aiProfiles}
|
||||||
|
allFeatures={hookFeatures}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Agent Output Modal */}
|
{/* Agent Output Modal */}
|
||||||
|
|||||||
@@ -330,6 +330,49 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Priority badge */}
|
||||||
|
{feature.priority && (
|
||||||
|
<TooltipProvider delayDuration={200}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute px-2 py-1 text-sm font-bold rounded-md flex items-center justify-center z-10",
|
||||||
|
"top-2 left-2 min-w-[36px]",
|
||||||
|
feature.priority === 1 &&
|
||||||
|
"bg-red-500/20 text-red-500 border-2 border-red-500/50",
|
||||||
|
feature.priority === 2 &&
|
||||||
|
"bg-yellow-500/20 text-yellow-500 border-2 border-yellow-500/50",
|
||||||
|
feature.priority === 3 &&
|
||||||
|
"bg-blue-500/20 text-blue-500 border-2 border-blue-500/50"
|
||||||
|
)}
|
||||||
|
data-testid={`priority-badge-${feature.id}`}
|
||||||
|
>
|
||||||
|
P{feature.priority}
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right" className="text-xs">
|
||||||
|
<p>
|
||||||
|
{feature.priority === 1
|
||||||
|
? "High Priority"
|
||||||
|
: feature.priority === 2
|
||||||
|
? "Medium Priority"
|
||||||
|
: "Low Priority"}
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Category text next to priority badge */}
|
||||||
|
{feature.priority && (
|
||||||
|
<div className="absolute top-2 left-[54px] right-12 z-10 flex items-center h-[32px]">
|
||||||
|
<span className="text-[11px] text-muted-foreground/70 font-medium truncate">
|
||||||
|
{feature.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Skip Tests (Manual) indicator badge */}
|
{/* Skip Tests (Manual) indicator badge */}
|
||||||
{feature.skipTests && !feature.error && (
|
{feature.skipTests && !feature.error && (
|
||||||
<TooltipProvider delayDuration={200}>
|
<TooltipProvider delayDuration={200}>
|
||||||
@@ -338,7 +381,7 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10",
|
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10",
|
||||||
"top-2 left-2",
|
feature.priority ? "top-11 left-2" : "top-2 left-2",
|
||||||
"bg-[var(--status-warning-bg)] border border-[var(--status-warning)]/40 text-[var(--status-warning)]"
|
"bg-[var(--status-warning-bg)] border border-[var(--status-warning)]/40 text-[var(--status-warning)]"
|
||||||
)}
|
)}
|
||||||
data-testid={`skip-tests-badge-${feature.id}`}
|
data-testid={`skip-tests-badge-${feature.id}`}
|
||||||
@@ -361,7 +404,7 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10",
|
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10",
|
||||||
"top-2 left-2",
|
feature.priority ? "top-11 left-2" : "top-2 left-2",
|
||||||
"bg-[var(--status-error-bg)] border border-[var(--status-error)]/40 text-[var(--status-error)]"
|
"bg-[var(--status-error-bg)] border border-[var(--status-error)]/40 text-[var(--status-error)]"
|
||||||
)}
|
)}
|
||||||
data-testid={`error-badge-${feature.id}`}
|
data-testid={`error-badge-${feature.id}`}
|
||||||
@@ -381,7 +424,11 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10",
|
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10",
|
||||||
feature.skipTests ? "top-8 left-2" : "top-2 left-2",
|
feature.priority
|
||||||
|
? "top-11 left-2"
|
||||||
|
: feature.skipTests
|
||||||
|
? "top-8 left-2"
|
||||||
|
: "top-2 left-2",
|
||||||
"bg-[var(--status-success-bg)] border border-[var(--status-success)]/40 text-[var(--status-success)]",
|
"bg-[var(--status-success-bg)] border border-[var(--status-success)]/40 text-[var(--status-success)]",
|
||||||
"animate-pulse"
|
"animate-pulse"
|
||||||
)}
|
)}
|
||||||
@@ -401,9 +448,11 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10 cursor-default",
|
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded-md flex items-center gap-1 z-10 cursor-default",
|
||||||
"bg-[var(--status-info-bg)] border border-[var(--status-info)]/40 text-[var(--status-info)]",
|
"bg-[var(--status-info-bg)] border border-[var(--status-info)]/40 text-[var(--status-info)]",
|
||||||
feature.error || feature.skipTests || isJustFinished
|
feature.priority
|
||||||
? "top-8 left-2"
|
? "top-11 left-2"
|
||||||
: "top-2 left-2"
|
: feature.error || feature.skipTests || isJustFinished
|
||||||
|
? "top-8 left-2"
|
||||||
|
: "top-2 left-2"
|
||||||
)}
|
)}
|
||||||
data-testid={`branch-badge-${feature.id}`}
|
data-testid={`branch-badge-${feature.id}`}
|
||||||
>
|
>
|
||||||
@@ -422,7 +471,10 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
<CardHeader
|
<CardHeader
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-3 pb-2 block",
|
"p-3 pb-2 block",
|
||||||
(feature.skipTests || feature.error || isJustFinished) && "pt-10",
|
feature.priority && "pt-12",
|
||||||
|
!feature.priority &&
|
||||||
|
(feature.skipTests || feature.error || isJustFinished) &&
|
||||||
|
"pt-10",
|
||||||
hasWorktree &&
|
hasWorktree &&
|
||||||
(feature.skipTests || feature.error || isJustFinished) &&
|
(feature.skipTests || feature.error || isJustFinished) &&
|
||||||
"pt-14"
|
"pt-14"
|
||||||
@@ -613,9 +665,11 @@ export const KanbanCard = memo(function KanbanCard({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<CardDescription className="text-[11px] mt-1.5 truncate text-muted-foreground/70">
|
{!feature.priority && (
|
||||||
{feature.category}
|
<CardDescription className="text-[11px] mt-1.5 truncate text-muted-foreground/70">
|
||||||
</CardDescription>
|
{feature.category}
|
||||||
|
</CardDescription>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ interface AddFeatureDialogProps {
|
|||||||
skipTests: boolean;
|
skipTests: boolean;
|
||||||
model: AgentModel;
|
model: AgentModel;
|
||||||
thinkingLevel: ThinkingLevel;
|
thinkingLevel: ThinkingLevel;
|
||||||
|
priority: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
categorySuggestions: string[];
|
categorySuggestions: string[];
|
||||||
defaultSkipTests: boolean;
|
defaultSkipTests: boolean;
|
||||||
@@ -74,6 +75,7 @@ export function AddFeatureDialog({
|
|||||||
skipTests: false,
|
skipTests: false,
|
||||||
model: "opus" as AgentModel,
|
model: "opus" as AgentModel,
|
||||||
thinkingLevel: "none" as ThinkingLevel,
|
thinkingLevel: "none" as ThinkingLevel,
|
||||||
|
priority: 2 as number, // Default to medium priority
|
||||||
});
|
});
|
||||||
const [newFeaturePreviewMap, setNewFeaturePreviewMap] =
|
const [newFeaturePreviewMap, setNewFeaturePreviewMap] =
|
||||||
useState<ImagePreviewMap>(() => new Map());
|
useState<ImagePreviewMap>(() => new Map());
|
||||||
@@ -111,6 +113,7 @@ export function AddFeatureDialog({
|
|||||||
skipTests: newFeature.skipTests,
|
skipTests: newFeature.skipTests,
|
||||||
model: selectedModel,
|
model: selectedModel,
|
||||||
thinkingLevel: normalizedThinking,
|
thinkingLevel: normalizedThinking,
|
||||||
|
priority: newFeature.priority,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset form
|
// Reset form
|
||||||
@@ -122,6 +125,7 @@ export function AddFeatureDialog({
|
|||||||
imagePaths: [],
|
imagePaths: [],
|
||||||
skipTests: defaultSkipTests,
|
skipTests: defaultSkipTests,
|
||||||
model: "opus",
|
model: "opus",
|
||||||
|
priority: 2,
|
||||||
thinkingLevel: "none",
|
thinkingLevel: "none",
|
||||||
});
|
});
|
||||||
setNewFeaturePreviewMap(new Map());
|
setNewFeaturePreviewMap(new Map());
|
||||||
@@ -237,6 +241,55 @@ export function AddFeatureDialog({
|
|||||||
data-testid="feature-category-input"
|
data-testid="feature-category-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Priority Selector */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Priority</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setNewFeature({ ...newFeature, priority: 1 })
|
||||||
|
}
|
||||||
|
className={`flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
newFeature.priority === 1
|
||||||
|
? "bg-red-500/20 text-red-500 border-2 border-red-500/50"
|
||||||
|
: "bg-muted/50 text-muted-foreground border border-border hover:bg-muted"
|
||||||
|
}`}
|
||||||
|
data-testid="priority-high-button"
|
||||||
|
>
|
||||||
|
High
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setNewFeature({ ...newFeature, priority: 2 })
|
||||||
|
}
|
||||||
|
className={`flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
newFeature.priority === 2
|
||||||
|
? "bg-yellow-500/20 text-yellow-500 border-2 border-yellow-500/50"
|
||||||
|
: "bg-muted/50 text-muted-foreground border border-border hover:bg-muted"
|
||||||
|
}`}
|
||||||
|
data-testid="priority-medium-button"
|
||||||
|
>
|
||||||
|
Medium
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setNewFeature({ ...newFeature, priority: 3 })
|
||||||
|
}
|
||||||
|
className={`flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
newFeature.priority === 3
|
||||||
|
? "bg-blue-500/20 text-blue-500 border-2 border-blue-500/50"
|
||||||
|
: "bg-muted/50 text-muted-foreground border border-border hover:bg-muted"
|
||||||
|
}`}
|
||||||
|
data-testid="priority-low-button"
|
||||||
|
>
|
||||||
|
Low
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Model Tab */}
|
{/* Model Tab */}
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Feature } from "@/store/app-store";
|
||||||
|
import { AlertCircle, CheckCircle2, Circle } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface DependencyTreeDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
feature: Feature | null;
|
||||||
|
allFeatures: Feature[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DependencyTreeDialog({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
feature,
|
||||||
|
allFeatures,
|
||||||
|
}: DependencyTreeDialogProps) {
|
||||||
|
const [dependencyTree, setDependencyTree] = useState<{
|
||||||
|
dependencies: Feature[];
|
||||||
|
dependents: Feature[];
|
||||||
|
}>({ dependencies: [], dependents: [] });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!feature) return;
|
||||||
|
|
||||||
|
// Find features this depends on
|
||||||
|
const dependencies = (feature.dependencies || [])
|
||||||
|
.map((depId) => allFeatures.find((f) => f.id === depId))
|
||||||
|
.filter((f): f is Feature => f !== undefined);
|
||||||
|
|
||||||
|
// Find features that depend on this one
|
||||||
|
const dependents = allFeatures.filter((f) =>
|
||||||
|
f.dependencies?.includes(feature.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
setDependencyTree({ dependencies, dependents });
|
||||||
|
}, [feature, allFeatures]);
|
||||||
|
|
||||||
|
if (!feature) return null;
|
||||||
|
|
||||||
|
const getStatusIcon = (status: Feature["status"]) => {
|
||||||
|
switch (status) {
|
||||||
|
case "completed":
|
||||||
|
case "verified":
|
||||||
|
return <CheckCircle2 className="w-4 h-4 text-green-500" />;
|
||||||
|
case "in_progress":
|
||||||
|
case "waiting_approval":
|
||||||
|
return <Circle className="w-4 h-4 text-blue-500 fill-blue-500/20" />;
|
||||||
|
default:
|
||||||
|
return <Circle className="w-4 h-4 text-muted-foreground/50" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPriorityBadge = (priority?: number) => {
|
||||||
|
if (!priority) return null;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-xs px-1.5 py-0.5 rounded font-medium",
|
||||||
|
priority === 1 && "bg-red-500/20 text-red-500",
|
||||||
|
priority === 2 && "bg-yellow-500/20 text-yellow-500",
|
||||||
|
priority === 3 && "bg-blue-500/20 text-blue-500"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
P{priority}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onClose}>
|
||||||
|
<DialogContent className="max-w-xl max-h-[80vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Dependency Tree</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-6 mt-4">
|
||||||
|
{/* Current Feature */}
|
||||||
|
<div className="border-2 border-primary rounded-lg p-4 bg-primary/5">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
{getStatusIcon(feature.status)}
|
||||||
|
<h3 className="font-semibold text-sm">Current Feature</h3>
|
||||||
|
{getPriorityBadge(feature.priority)}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">{feature.description}</p>
|
||||||
|
<p className="text-xs text-muted-foreground/70 mt-2">
|
||||||
|
Category: {feature.category}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dependencies (what this feature needs) */}
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<h3 className="font-semibold text-sm">
|
||||||
|
Dependencies ({dependencyTree.dependencies.length})
|
||||||
|
</h3>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
This feature requires:
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{dependencyTree.dependencies.length === 0 ? (
|
||||||
|
<div className="text-sm text-muted-foreground/70 italic border border-dashed rounded-lg p-4 text-center">
|
||||||
|
No dependencies - this feature can be started independently
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{dependencyTree.dependencies.map((dep) => (
|
||||||
|
<div
|
||||||
|
key={dep.id}
|
||||||
|
className={cn(
|
||||||
|
"border rounded-lg p-3 transition-colors",
|
||||||
|
dep.status === "completed" || dep.status === "verified"
|
||||||
|
? "bg-green-500/5 border-green-500/20"
|
||||||
|
: "bg-muted/30 border-border"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 mb-1">
|
||||||
|
{getStatusIcon(dep.status)}
|
||||||
|
<span className="text-sm font-medium flex-1">
|
||||||
|
{dep.description.slice(0, 100)}
|
||||||
|
{dep.description.length > 100 && "..."}
|
||||||
|
</span>
|
||||||
|
{getPriorityBadge(dep.priority)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 ml-7">
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{dep.category}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-xs px-2 py-0.5 rounded-full",
|
||||||
|
dep.status === "completed" || dep.status === "verified"
|
||||||
|
? "bg-green-500/20 text-green-600"
|
||||||
|
: dep.status === "in_progress"
|
||||||
|
? "bg-blue-500/20 text-blue-600"
|
||||||
|
: "bg-muted text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{dep.status.replace(/_/g, " ")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dependents (what depends on this feature) */}
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<h3 className="font-semibold text-sm">
|
||||||
|
Dependents ({dependencyTree.dependents.length})
|
||||||
|
</h3>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Features blocked by this:
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{dependencyTree.dependents.length === 0 ? (
|
||||||
|
<div className="text-sm text-muted-foreground/70 italic border border-dashed rounded-lg p-4 text-center">
|
||||||
|
No dependents - no other features are waiting on this one
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{dependencyTree.dependents.map((dependent) => (
|
||||||
|
<div
|
||||||
|
key={dependent.id}
|
||||||
|
className="border rounded-lg p-3 bg-muted/30"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 mb-1">
|
||||||
|
{getStatusIcon(dependent.status)}
|
||||||
|
<span className="text-sm font-medium flex-1">
|
||||||
|
{dependent.description.slice(0, 100)}
|
||||||
|
{dependent.description.length > 100 && "..."}
|
||||||
|
</span>
|
||||||
|
{getPriorityBadge(dependent.priority)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 ml-7">
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{dependent.category}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-xs px-2 py-0.5 rounded-full",
|
||||||
|
dependent.status === "completed" ||
|
||||||
|
dependent.status === "verified"
|
||||||
|
? "bg-green-500/20 text-green-600"
|
||||||
|
: dependent.status === "in_progress"
|
||||||
|
? "bg-blue-500/20 text-blue-600"
|
||||||
|
: "bg-muted text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{dependent.status.replace(/_/g, " ")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Warning for incomplete dependencies */}
|
||||||
|
{dependencyTree.dependencies.some(
|
||||||
|
(d) => d.status !== "completed" && d.status !== "verified"
|
||||||
|
) && (
|
||||||
|
<div className="flex items-start gap-3 p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
|
||||||
|
<AlertCircle className="w-5 h-5 text-yellow-600 shrink-0 mt-0.5" />
|
||||||
|
<div className="text-sm">
|
||||||
|
<p className="font-medium text-yellow-700 dark:text-yellow-500">
|
||||||
|
Incomplete Dependencies
|
||||||
|
</p>
|
||||||
|
<p className="text-yellow-600 dark:text-yellow-400 mt-1">
|
||||||
|
This feature has dependencies that aren't completed yet.
|
||||||
|
Consider completing them first for a smoother implementation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
FeatureImagePath as DescriptionImagePath,
|
FeatureImagePath as DescriptionImagePath,
|
||||||
ImagePreviewMap,
|
ImagePreviewMap,
|
||||||
} from "@/components/ui/description-image-dropzone";
|
} from "@/components/ui/description-image-dropzone";
|
||||||
import { MessageSquare, Settings2, FlaskConical } from "lucide-react";
|
import { MessageSquare, Settings2, FlaskConical, GitBranch } from "lucide-react";
|
||||||
import { modelSupportsThinking } from "@/lib/utils";
|
import { modelSupportsThinking } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Feature,
|
Feature,
|
||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
ProfileQuickSelect,
|
ProfileQuickSelect,
|
||||||
TestingTabContent,
|
TestingTabContent,
|
||||||
} from "../shared";
|
} from "../shared";
|
||||||
|
import { DependencyTreeDialog } from "./dependency-tree-dialog";
|
||||||
|
|
||||||
interface EditFeatureDialogProps {
|
interface EditFeatureDialogProps {
|
||||||
feature: Feature | null;
|
feature: Feature | null;
|
||||||
@@ -47,12 +48,14 @@ interface EditFeatureDialogProps {
|
|||||||
model: AgentModel;
|
model: AgentModel;
|
||||||
thinkingLevel: ThinkingLevel;
|
thinkingLevel: ThinkingLevel;
|
||||||
imagePaths: DescriptionImagePath[];
|
imagePaths: DescriptionImagePath[];
|
||||||
|
priority: number;
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
categorySuggestions: string[];
|
categorySuggestions: string[];
|
||||||
isMaximized: boolean;
|
isMaximized: boolean;
|
||||||
showProfilesOnly: boolean;
|
showProfilesOnly: boolean;
|
||||||
aiProfiles: AIProfile[];
|
aiProfiles: AIProfile[];
|
||||||
|
allFeatures: Feature[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditFeatureDialog({
|
export function EditFeatureDialog({
|
||||||
@@ -63,11 +66,13 @@ export function EditFeatureDialog({
|
|||||||
isMaximized,
|
isMaximized,
|
||||||
showProfilesOnly,
|
showProfilesOnly,
|
||||||
aiProfiles,
|
aiProfiles,
|
||||||
|
allFeatures,
|
||||||
}: EditFeatureDialogProps) {
|
}: EditFeatureDialogProps) {
|
||||||
const [editingFeature, setEditingFeature] = useState<Feature | null>(feature);
|
const [editingFeature, setEditingFeature] = useState<Feature | null>(feature);
|
||||||
const [editFeaturePreviewMap, setEditFeaturePreviewMap] =
|
const [editFeaturePreviewMap, setEditFeaturePreviewMap] =
|
||||||
useState<ImagePreviewMap>(() => new Map());
|
useState<ImagePreviewMap>(() => new Map());
|
||||||
const [showEditAdvancedOptions, setShowEditAdvancedOptions] = useState(false);
|
const [showEditAdvancedOptions, setShowEditAdvancedOptions] = useState(false);
|
||||||
|
const [showDependencyTree, setShowDependencyTree] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditingFeature(feature);
|
setEditingFeature(feature);
|
||||||
@@ -93,6 +98,7 @@ export function EditFeatureDialog({
|
|||||||
model: selectedModel,
|
model: selectedModel,
|
||||||
thinkingLevel: normalizedThinking,
|
thinkingLevel: normalizedThinking,
|
||||||
imagePaths: editingFeature.imagePaths ?? [],
|
imagePaths: editingFeature.imagePaths ?? [],
|
||||||
|
priority: editingFeature.priority ?? 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
onUpdate(editingFeature.id, updates);
|
onUpdate(editingFeature.id, updates);
|
||||||
@@ -214,6 +220,64 @@ export function EditFeatureDialog({
|
|||||||
data-testid="edit-feature-category"
|
data-testid="edit-feature-category"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Priority Selector */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Priority</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setEditingFeature({
|
||||||
|
...editingFeature,
|
||||||
|
priority: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={`flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
(editingFeature.priority ?? 2) === 1
|
||||||
|
? "bg-red-500/20 text-red-500 border-2 border-red-500/50"
|
||||||
|
: "bg-muted/50 text-muted-foreground border border-border hover:bg-muted"
|
||||||
|
}`}
|
||||||
|
data-testid="edit-priority-high-button"
|
||||||
|
>
|
||||||
|
High
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setEditingFeature({
|
||||||
|
...editingFeature,
|
||||||
|
priority: 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={`flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
(editingFeature.priority ?? 2) === 2
|
||||||
|
? "bg-yellow-500/20 text-yellow-500 border-2 border-yellow-500/50"
|
||||||
|
: "bg-muted/50 text-muted-foreground border border-border hover:bg-muted"
|
||||||
|
}`}
|
||||||
|
data-testid="edit-priority-medium-button"
|
||||||
|
>
|
||||||
|
Medium
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setEditingFeature({
|
||||||
|
...editingFeature,
|
||||||
|
priority: 3,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={`flex-1 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
(editingFeature.priority ?? 2) === 3
|
||||||
|
? "bg-blue-500/20 text-blue-500 border-2 border-blue-500/50"
|
||||||
|
: "bg-muted/50 text-muted-foreground border border-border hover:bg-muted"
|
||||||
|
}`}
|
||||||
|
data-testid="edit-priority-low-button"
|
||||||
|
>
|
||||||
|
Low
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Model Tab */}
|
{/* Model Tab */}
|
||||||
@@ -297,20 +361,37 @@ export function EditFeatureDialog({
|
|||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<DialogFooter>
|
<DialogFooter className="sm:!justify-between">
|
||||||
<Button variant="ghost" onClick={onClose}>
|
<Button
|
||||||
Cancel
|
variant="outline"
|
||||||
</Button>
|
onClick={() => setShowDependencyTree(true)}
|
||||||
<HotkeyButton
|
className="gap-2 h-10"
|
||||||
onClick={handleUpdate}
|
|
||||||
hotkey={{ key: "Enter", cmdCtrl: true }}
|
|
||||||
hotkeyActive={!!editingFeature}
|
|
||||||
data-testid="confirm-edit-feature"
|
|
||||||
>
|
>
|
||||||
Save Changes
|
<GitBranch className="w-4 h-4" />
|
||||||
</HotkeyButton>
|
View Dependency Tree
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="ghost" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<HotkeyButton
|
||||||
|
onClick={handleUpdate}
|
||||||
|
hotkey={{ key: "Enter", cmdCtrl: true }}
|
||||||
|
hotkeyActive={!!editingFeature}
|
||||||
|
data-testid="confirm-edit-feature"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</HotkeyButton>
|
||||||
|
</div>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
|
<DependencyTreeDialog
|
||||||
|
open={showDependencyTree}
|
||||||
|
onClose={() => setShowDependencyTree(false)}
|
||||||
|
feature={editingFeature}
|
||||||
|
allFeatures={allFeatures}
|
||||||
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ export function FeatureSuggestionsDialog({
|
|||||||
steps: s.steps,
|
steps: s.steps,
|
||||||
status: "backlog" as const,
|
status: "backlog" as const,
|
||||||
skipTests: true, // As specified, testing mode true
|
skipTests: true, // As specified, testing mode true
|
||||||
|
priority: s.priority, // Preserve priority from suggestion
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Create each new feature using the features API
|
// Create each new feature using the features API
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export function useBoardActions({
|
|||||||
skipTests: boolean;
|
skipTests: boolean;
|
||||||
model: AgentModel;
|
model: AgentModel;
|
||||||
thinkingLevel: ThinkingLevel;
|
thinkingLevel: ThinkingLevel;
|
||||||
|
priority: number;
|
||||||
}) => {
|
}) => {
|
||||||
const newFeatureData = {
|
const newFeatureData = {
|
||||||
...featureData,
|
...featureData,
|
||||||
@@ -89,6 +90,7 @@ export function useBoardActions({
|
|||||||
model: AgentModel;
|
model: AgentModel;
|
||||||
thinkingLevel: ThinkingLevel;
|
thinkingLevel: ThinkingLevel;
|
||||||
imagePaths: DescriptionImagePath[];
|
imagePaths: DescriptionImagePath[];
|
||||||
|
priority: number;
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
updateFeature(featureId, updates);
|
updateFeature(featureId, updates);
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ export interface Feature {
|
|||||||
thinkingLevel?: ThinkingLevel; // Thinking level for extended thinking (defaults to none)
|
thinkingLevel?: ThinkingLevel; // Thinking level for extended thinking (defaults to none)
|
||||||
error?: string; // Error message if the agent errored during processing
|
error?: string; // Error message if the agent errored during processing
|
||||||
priority?: number; // Priority: 1 = high, 2 = medium, 3 = low
|
priority?: number; // Priority: 1 = high, 2 = medium, 3 = low
|
||||||
|
dependencies?: string[]; // Array of feature IDs this feature depends on
|
||||||
// Worktree info - set when a feature is being worked on in an isolated git worktree
|
// Worktree info - set when a feature is being worked on in an isolated git worktree
|
||||||
worktreePath?: string; // Path to the worktree directory
|
worktreePath?: string; // Path to the worktree directory
|
||||||
branchName?: string; // Name of the feature branch
|
branchName?: string; // Name of the feature branch
|
||||||
|
|||||||
@@ -56,17 +56,19 @@ ${spec}
|
|||||||
Generate a prioritized list of implementable features. For each feature provide:
|
Generate a prioritized list of implementable features. For each feature provide:
|
||||||
|
|
||||||
1. **id**: A unique lowercase-hyphenated identifier
|
1. **id**: A unique lowercase-hyphenated identifier
|
||||||
2. **title**: Short descriptive title
|
2. **category**: Functional category (e.g., "Core", "UI", "API", "Authentication", "Database")
|
||||||
3. **description**: What this feature does (2-3 sentences)
|
3. **title**: Short descriptive title
|
||||||
4. **priority**: 1 (high), 2 (medium), or 3 (low)
|
4. **description**: What this feature does (2-3 sentences)
|
||||||
5. **complexity**: "simple", "moderate", or "complex"
|
5. **priority**: 1 (high), 2 (medium), or 3 (low)
|
||||||
6. **dependencies**: Array of feature IDs this depends on (can be empty)
|
6. **complexity**: "simple", "moderate", or "complex"
|
||||||
|
7. **dependencies**: Array of feature IDs this depends on (can be empty)
|
||||||
|
|
||||||
Format as JSON:
|
Format as JSON:
|
||||||
{
|
{
|
||||||
"features": [
|
"features": [
|
||||||
{
|
{
|
||||||
"id": "feature-id",
|
"id": "feature-id",
|
||||||
|
"category": "Feature Category",
|
||||||
"title": "Feature Title",
|
"title": "Feature Title",
|
||||||
"description": "What it does",
|
"description": "What it does",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export async function parseAndCreateFeatures(
|
|||||||
|
|
||||||
const featureData = {
|
const featureData = {
|
||||||
id: feature.id,
|
id: feature.id,
|
||||||
|
category: feature.category || "Uncategorized",
|
||||||
title: feature.title,
|
title: feature.title,
|
||||||
description: feature.description,
|
description: feature.description,
|
||||||
status: "backlog", // Features go to backlog - user must manually start them
|
status: "backlog", // Features go to backlog - user must manually start them
|
||||||
|
|||||||
Reference in New Issue
Block a user