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

@@ -87,6 +87,7 @@ export function useBoardActions({
const handleAddFeature = useCallback(
async (featureData: {
title: string;
category: string;
description: string;
steps: string[];
@@ -141,8 +142,13 @@ export function useBoardActions({
}
}
// Check if we need to generate a title
const needsTitleGeneration = !featureData.title.trim() && featureData.description.trim();
const newFeatureData = {
...featureData,
title: featureData.title,
titleGenerating: needsTitleGeneration,
status: "backlog" as const,
branchName: finalBranchName,
};
@@ -150,14 +156,42 @@ export function useBoardActions({
// Must await to ensure feature exists on server before user can drag it
await persistFeatureCreate(createdFeature);
saveCategory(featureData.category);
// Generate title in the background if needed (non-blocking)
if (needsTitleGeneration) {
const api = getElectronAPI();
if (api?.features?.generateTitle) {
api.features.generateTitle(featureData.description)
.then((result) => {
if (result.success && result.title) {
const titleUpdates = { title: result.title, titleGenerating: false };
updateFeature(createdFeature.id, titleUpdates);
persistFeatureUpdate(createdFeature.id, titleUpdates);
} else {
// Clear generating flag even if failed
const titleUpdates = { titleGenerating: false };
updateFeature(createdFeature.id, titleUpdates);
persistFeatureUpdate(createdFeature.id, titleUpdates);
}
})
.catch((error) => {
console.error("[Board] Error generating title:", error);
// Clear generating flag on error
const titleUpdates = { titleGenerating: false };
updateFeature(createdFeature.id, titleUpdates);
persistFeatureUpdate(createdFeature.id, titleUpdates);
});
}
}
},
[addFeature, persistFeatureCreate, saveCategory, useWorktrees, currentProject, onWorktreeCreated]
[addFeature, persistFeatureCreate, persistFeatureUpdate, updateFeature, saveCategory, useWorktrees, currentProject, onWorktreeCreated]
);
const handleUpdateFeature = useCallback(
async (
featureId: string,
updates: {
title: string;
category: string;
description: string;
steps: string[];
@@ -212,6 +246,7 @@ export function useBoardActions({
const finalUpdates = {
...updates,
title: updates.title,
branchName: finalBranchName,
};