feat: add auto-generated titles for features

- Add POST /features/generate-title endpoint using Claude Haiku
- Generate concise titles (5-10 words) from feature descriptions
- Display titles in kanban cards with loading state
- Add optional title field to add/edit feature dialogs
- Auto-generate titles when description provided but title empty
- Add 'Pull & Resolve Conflicts' action to worktree dropdown
- Show running agents count in board header (X / Y format)
- Update Feature interface to include title and titleGenerating fields
This commit is contained in:
Cody Seibert
2025-12-19 23:36:29 -05:00
parent 36e007e647
commit fcb2e904eb
16 changed files with 307 additions and 8 deletions

View File

@@ -11,6 +11,7 @@ import {
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { HotkeyButton } from "@/components/ui/hotkey-button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { CategoryAutocomplete } from "@/components/ui/category-autocomplete";
import {
@@ -58,6 +59,7 @@ interface AddFeatureDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onAdd: (feature: {
title: string;
category: string;
description: string;
steps: string[];
@@ -99,6 +101,7 @@ export function AddFeatureDialog({
const navigate = useNavigate();
const [useCurrentBranch, setUseCurrentBranch] = useState(true);
const [newFeature, setNewFeature] = useState({
title: "",
category: "",
description: "",
steps: [""],
@@ -186,6 +189,7 @@ export function AddFeatureDialog({
: newFeature.branchName || "";
onAdd({
title: newFeature.title,
category,
description: newFeature.description,
steps: newFeature.steps.filter((s) => s.trim()),
@@ -202,6 +206,7 @@ export function AddFeatureDialog({
// Reset form
setNewFeature({
title: "",
category: "",
description: "",
steps: [""],
@@ -350,6 +355,17 @@ export function AddFeatureDialog({
error={descriptionError}
/>
</div>
<div className="space-y-2">
<Label htmlFor="title">Title (optional)</Label>
<Input
id="title"
value={newFeature.title}
onChange={(e) =>
setNewFeature({ ...newFeature, title: e.target.value })
}
placeholder="Leave blank to auto-generate"
/>
</div>
<div className="flex w-fit items-center gap-3 select-none cursor-default">
<DropdownMenu>
<DropdownMenuTrigger asChild>