mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +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
|
||||
launch.sh
|
||||
|
||||
# Claude Code settings
|
||||
.claude/settings.local.json
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
|
||||
@@ -445,6 +445,7 @@ export function BoardView() {
|
||||
isMaximized={isMaximized}
|
||||
showProfilesOnly={showProfilesOnly}
|
||||
aiProfiles={aiProfiles}
|
||||
allFeatures={hookFeatures}
|
||||
/>
|
||||
|
||||
{/* 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 */}
|
||||
{feature.skipTests && !feature.error && (
|
||||
<TooltipProvider delayDuration={200}>
|
||||
@@ -338,7 +381,7 @@ export const KanbanCard = memo(function KanbanCard({
|
||||
<div
|
||||
className={cn(
|
||||
"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)]"
|
||||
)}
|
||||
data-testid={`skip-tests-badge-${feature.id}`}
|
||||
@@ -361,7 +404,7 @@ export const KanbanCard = memo(function KanbanCard({
|
||||
<div
|
||||
className={cn(
|
||||
"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)]"
|
||||
)}
|
||||
data-testid={`error-badge-${feature.id}`}
|
||||
@@ -381,7 +424,11 @@ export const KanbanCard = memo(function KanbanCard({
|
||||
<div
|
||||
className={cn(
|
||||
"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)]",
|
||||
"animate-pulse"
|
||||
)}
|
||||
@@ -401,9 +448,11 @@ export const KanbanCard = memo(function KanbanCard({
|
||||
className={cn(
|
||||
"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)]",
|
||||
feature.error || feature.skipTests || isJustFinished
|
||||
? "top-8 left-2"
|
||||
: "top-2 left-2"
|
||||
feature.priority
|
||||
? "top-11 left-2"
|
||||
: feature.error || feature.skipTests || isJustFinished
|
||||
? "top-8 left-2"
|
||||
: "top-2 left-2"
|
||||
)}
|
||||
data-testid={`branch-badge-${feature.id}`}
|
||||
>
|
||||
@@ -422,7 +471,10 @@ export const KanbanCard = memo(function KanbanCard({
|
||||
<CardHeader
|
||||
className={cn(
|
||||
"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 &&
|
||||
(feature.skipTests || feature.error || isJustFinished) &&
|
||||
"pt-14"
|
||||
@@ -613,9 +665,11 @@ export const KanbanCard = memo(function KanbanCard({
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<CardDescription className="text-[11px] mt-1.5 truncate text-muted-foreground/70">
|
||||
{feature.category}
|
||||
</CardDescription>
|
||||
{!feature.priority && (
|
||||
<CardDescription className="text-[11px] mt-1.5 truncate text-muted-foreground/70">
|
||||
{feature.category}
|
||||
</CardDescription>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@@ -47,6 +47,7 @@ interface AddFeatureDialogProps {
|
||||
skipTests: boolean;
|
||||
model: AgentModel;
|
||||
thinkingLevel: ThinkingLevel;
|
||||
priority: number;
|
||||
}) => void;
|
||||
categorySuggestions: string[];
|
||||
defaultSkipTests: boolean;
|
||||
@@ -74,6 +75,7 @@ export function AddFeatureDialog({
|
||||
skipTests: false,
|
||||
model: "opus" as AgentModel,
|
||||
thinkingLevel: "none" as ThinkingLevel,
|
||||
priority: 2 as number, // Default to medium priority
|
||||
});
|
||||
const [newFeaturePreviewMap, setNewFeaturePreviewMap] =
|
||||
useState<ImagePreviewMap>(() => new Map());
|
||||
@@ -111,6 +113,7 @@ export function AddFeatureDialog({
|
||||
skipTests: newFeature.skipTests,
|
||||
model: selectedModel,
|
||||
thinkingLevel: normalizedThinking,
|
||||
priority: newFeature.priority,
|
||||
});
|
||||
|
||||
// Reset form
|
||||
@@ -122,6 +125,7 @@ export function AddFeatureDialog({
|
||||
imagePaths: [],
|
||||
skipTests: defaultSkipTests,
|
||||
model: "opus",
|
||||
priority: 2,
|
||||
thinkingLevel: "none",
|
||||
});
|
||||
setNewFeaturePreviewMap(new Map());
|
||||
@@ -237,6 +241,55 @@ export function AddFeatureDialog({
|
||||
data-testid="feature-category-input"
|
||||
/>
|
||||
</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>
|
||||
|
||||
{/* 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,
|
||||
ImagePreviewMap,
|
||||
} 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 {
|
||||
Feature,
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
ProfileQuickSelect,
|
||||
TestingTabContent,
|
||||
} from "../shared";
|
||||
import { DependencyTreeDialog } from "./dependency-tree-dialog";
|
||||
|
||||
interface EditFeatureDialogProps {
|
||||
feature: Feature | null;
|
||||
@@ -47,12 +48,14 @@ interface EditFeatureDialogProps {
|
||||
model: AgentModel;
|
||||
thinkingLevel: ThinkingLevel;
|
||||
imagePaths: DescriptionImagePath[];
|
||||
priority: number;
|
||||
}
|
||||
) => void;
|
||||
categorySuggestions: string[];
|
||||
isMaximized: boolean;
|
||||
showProfilesOnly: boolean;
|
||||
aiProfiles: AIProfile[];
|
||||
allFeatures: Feature[];
|
||||
}
|
||||
|
||||
export function EditFeatureDialog({
|
||||
@@ -63,11 +66,13 @@ export function EditFeatureDialog({
|
||||
isMaximized,
|
||||
showProfilesOnly,
|
||||
aiProfiles,
|
||||
allFeatures,
|
||||
}: EditFeatureDialogProps) {
|
||||
const [editingFeature, setEditingFeature] = useState<Feature | null>(feature);
|
||||
const [editFeaturePreviewMap, setEditFeaturePreviewMap] =
|
||||
useState<ImagePreviewMap>(() => new Map());
|
||||
const [showEditAdvancedOptions, setShowEditAdvancedOptions] = useState(false);
|
||||
const [showDependencyTree, setShowDependencyTree] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setEditingFeature(feature);
|
||||
@@ -93,6 +98,7 @@ export function EditFeatureDialog({
|
||||
model: selectedModel,
|
||||
thinkingLevel: normalizedThinking,
|
||||
imagePaths: editingFeature.imagePaths ?? [],
|
||||
priority: editingFeature.priority ?? 2,
|
||||
};
|
||||
|
||||
onUpdate(editingFeature.id, updates);
|
||||
@@ -214,6 +220,64 @@ export function EditFeatureDialog({
|
||||
data-testid="edit-feature-category"
|
||||
/>
|
||||
</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>
|
||||
|
||||
{/* Model Tab */}
|
||||
@@ -297,20 +361,37 @@ export function EditFeatureDialog({
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<HotkeyButton
|
||||
onClick={handleUpdate}
|
||||
hotkey={{ key: "Enter", cmdCtrl: true }}
|
||||
hotkeyActive={!!editingFeature}
|
||||
data-testid="confirm-edit-feature"
|
||||
<DialogFooter className="sm:!justify-between">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDependencyTree(true)}
|
||||
className="gap-2 h-10"
|
||||
>
|
||||
Save Changes
|
||||
</HotkeyButton>
|
||||
<GitBranch className="w-4 h-4" />
|
||||
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>
|
||||
</DialogContent>
|
||||
|
||||
<DependencyTreeDialog
|
||||
open={showDependencyTree}
|
||||
onClose={() => setShowDependencyTree(false)}
|
||||
feature={editingFeature}
|
||||
allFeatures={allFeatures}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ export function FeatureSuggestionsDialog({
|
||||
steps: s.steps,
|
||||
status: "backlog" as const,
|
||||
skipTests: true, // As specified, testing mode true
|
||||
priority: s.priority, // Preserve priority from suggestion
|
||||
}));
|
||||
|
||||
// Create each new feature using the features API
|
||||
|
||||
@@ -66,6 +66,7 @@ export function useBoardActions({
|
||||
skipTests: boolean;
|
||||
model: AgentModel;
|
||||
thinkingLevel: ThinkingLevel;
|
||||
priority: number;
|
||||
}) => {
|
||||
const newFeatureData = {
|
||||
...featureData,
|
||||
@@ -89,6 +90,7 @@ export function useBoardActions({
|
||||
model: AgentModel;
|
||||
thinkingLevel: ThinkingLevel;
|
||||
imagePaths: DescriptionImagePath[];
|
||||
priority: number;
|
||||
}
|
||||
) => {
|
||||
updateFeature(featureId, updates);
|
||||
|
||||
@@ -292,6 +292,7 @@ export interface Feature {
|
||||
thinkingLevel?: ThinkingLevel; // Thinking level for extended thinking (defaults to none)
|
||||
error?: string; // Error message if the agent errored during processing
|
||||
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
|
||||
worktreePath?: string; // Path to the worktree directory
|
||||
branchName?: string; // Name of the feature branch
|
||||
|
||||
@@ -56,17 +56,19 @@ ${spec}
|
||||
Generate a prioritized list of implementable features. For each feature provide:
|
||||
|
||||
1. **id**: A unique lowercase-hyphenated identifier
|
||||
2. **title**: Short descriptive title
|
||||
3. **description**: What this feature does (2-3 sentences)
|
||||
4. **priority**: 1 (high), 2 (medium), or 3 (low)
|
||||
5. **complexity**: "simple", "moderate", or "complex"
|
||||
6. **dependencies**: Array of feature IDs this depends on (can be empty)
|
||||
2. **category**: Functional category (e.g., "Core", "UI", "API", "Authentication", "Database")
|
||||
3. **title**: Short descriptive title
|
||||
4. **description**: What this feature does (2-3 sentences)
|
||||
5. **priority**: 1 (high), 2 (medium), or 3 (low)
|
||||
6. **complexity**: "simple", "moderate", or "complex"
|
||||
7. **dependencies**: Array of feature IDs this depends on (can be empty)
|
||||
|
||||
Format as JSON:
|
||||
{
|
||||
"features": [
|
||||
{
|
||||
"id": "feature-id",
|
||||
"category": "Feature Category",
|
||||
"title": "Feature Title",
|
||||
"description": "What it does",
|
||||
"priority": 1,
|
||||
|
||||
@@ -53,6 +53,7 @@ export async function parseAndCreateFeatures(
|
||||
|
||||
const featureData = {
|
||||
id: feature.id,
|
||||
category: feature.category || "Uncategorized",
|
||||
title: feature.title,
|
||||
description: feature.description,
|
||||
status: "backlog", // Features go to backlog - user must manually start them
|
||||
|
||||
Reference in New Issue
Block a user