-
Notifications
You must be signed in to change notification settings - Fork 397
feat: add auto-generated titles for features #184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- 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
WalkthroughThis PR introduces a title generation feature for features, allowing automatic or manual title creation via Claude Haiku. It adds a new server route endpoint, UI components for managing titles, Electron/HTTP API integrations, and conflict resolution handling with running agents count display. Changes
Sequence DiagramsequenceDiagram
participant UI as AddFeatureDialog
participant Hook as use-board-actions
participant Electron as Electron API
participant HttpClient as HTTP Client
participant Server as Server Handler
participant Claude as Claude API
participant Store as App Store
UI->>Hook: onAdd(feature with description)
Hook->>Store: Create feature with titleGenerating=true
alt Title provided by user
Hook->>Store: Use provided title
else No title provided
Hook->>Electron: generateTitle(description)
Electron->>HttpClient: POST /features/generate-title
HttpClient->>Server: Send description payload
Server->>Claude: Stream request for title
Claude-->>Server: Streaming response
Server->>Server: Parse & extract title
Server-->>HttpClient: Return title JSON
HttpClient-->>Electron: Title response
Electron-->>Hook: { success, title }
end
Hook->>Store: Update feature with title, titleGenerating=false
Store->>UI: Feature updated, display title in KanbanCard
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Summary of ChangesHello @webdevcody, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a significant enhancement by integrating AI-powered title generation for features, leveraging the Claude Haiku model. This allows for more descriptive and concise feature titles to be automatically created from their descriptions, improving clarity and organization. Alongside this core functionality, the PR also adds a convenient "Pull & Resolve Conflicts" action for worktrees and enhances the board header with a real-time display of running agents, contributing to a more efficient and informative user experience. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces automatic title generation for features using Claude Haiku, which is a great enhancement for user experience. It adds a new API endpoint and updates the UI to display titles and their generation status. The changes are well-implemented, with non-blocking title generation and graceful error handling. Additionally, the PR includes some nice UI improvements like showing the running agent count and adding a 'Pull & Resolve Conflicts' action.
I've identified a critical issue where some new function calls are missing a required property, which will lead to runtime errors. I've also suggested a refactoring to improve code robustness by removing a fragile setTimeout pattern, and another to improve maintainability by reducing code duplication.
| const handleResolveConflicts = useCallback( | ||
| async (worktree: WorktreeInfo) => { | ||
| const description = `Pull latest from origin/main and resolve conflicts. Merge origin/main into the current branch (${worktree.branch}), resolving any merge conflicts that arise. After resolving conflicts, ensure the code compiles and tests pass.`; | ||
|
|
||
| // Create the feature | ||
| const featureData = { | ||
| category: "Maintenance", | ||
| description, | ||
| steps: [], | ||
| images: [], | ||
| imagePaths: [], | ||
| skipTests: defaultSkipTests, | ||
| model: "opus" as const, | ||
| thinkingLevel: "none" as const, | ||
| branchName: worktree.branch, | ||
| priority: 1, // High priority for conflict resolution | ||
| planningMode: "skip" as const, | ||
| requirePlanApproval: false, | ||
| }; | ||
|
|
||
| await handleAddFeature(featureData); | ||
|
|
||
| // Find the newly created feature and start it | ||
| setTimeout(async () => { | ||
| const latestFeatures = useAppStore.getState().features; | ||
| const newFeature = latestFeatures.find( | ||
| (f) => | ||
| f.branchName === worktree.branch && | ||
| f.status === "backlog" && | ||
| f.description.includes("Pull latest from origin/main") | ||
| ); | ||
|
|
||
| if (newFeature) { | ||
| await handleStartImplementation(newFeature); | ||
| } | ||
| }, FEATURE_CREATION_SETTLE_DELAY_MS); | ||
| }, | ||
| [handleAddFeature, handleStartImplementation, defaultSkipTests] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This handler has a couple of critical issues:
- Missing Property:
featureDatais missing the newtitleproperty, which is now required byhandleAddFeature. This will cause a runtime error. - Fragile Logic: Using
setTimeoutandfindto retrieve the newly created feature is unreliable and depends on timing.
A more robust approach would be to modify handleAddFeature in use-board-actions.ts to return the newly created feature, and then use that return value here.
Recommended Fix:
-
In
use-board-actions.ts, changehandleAddFeatureto return thecreatedFeature:const handleAddFeature = useCallback( async (featureData: { ... }): Promise<Feature> => { // Add return type // ... const createdFeature = addFeature(newFeatureData); await persistFeatureCreate(createdFeature); // ... title generation logic return createdFeature; // Return the created feature }, // ... );
-
Here in
board-view.tsx, updatehandleResolveConflictsto use the returned feature.
Note: The handleAddressPRComments function has the same issues and should be updated similarly.
const handleResolveConflicts = useCallback(
async (worktree: WorktreeInfo) => {
const description = `Pull latest from origin/main and resolve conflicts. Merge origin/main into the current branch (${worktree.branch}), resolving any merge conflicts that arise. After resolving conflicts, ensure the code compiles and tests pass.`;
// Create the feature
const featureData = {
title: "", // Pass an empty title to trigger auto-generation
category: "Maintenance",
description,
steps: [],
images: [],
imagePaths: [],
skipTests: defaultSkipTests,
model: "opus" as const,
thinkingLevel: "none" as const,
branchName: worktree.branch,
priority: 1, // High priority for conflict resolution
planningMode: "skip" as const,
requirePlanApproval: false,
};
// This assumes handleAddFeature is modified to return the created feature
const newFeature = await handleAddFeature(featureData);
if (newFeature) {
await handleStartImplementation(newFeature);
}
},
[handleAddFeature, handleStartImplementation, defaultSkipTests]
);
| // 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); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for updating the feature after title generation has some code duplication in the .then() and .catch() blocks. This can be refactored for better maintainability by using an async IIFE and a finally block to handle updating the feature state, regardless of whether the title generation succeeds or fails.
// Generate title in the background if needed (non-blocking)
if (needsTitleGeneration) {
const api = getElectronAPI();
if (api?.features?.generateTitle) {
(async () => {
const titleUpdates: Partial<Feature> = { titleGenerating: false };
try {
const result = await api.features.generateTitle(featureData.description);
if (result.success && result.title) {
titleUpdates.title = result.title;
}
} catch (error) {
console.error("[Board] Error generating title:", error);
} finally {
updateFeature(createdFeature.id, titleUpdates);
persistFeatureUpdate(createdFeature.id, titleUpdates);
}
})();
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (5)
apps/ui/src/lib/electron.ts (1)
613-619: Consider improving mock title generation logic.The current mock implementation has a few inconsistencies:
- It takes the first 6 words, then checks if the result is > 40 characters and truncates. This could create awkward titles like "Add Fix the issue with the login..."
- It always prefixes with "Add" but the real system prompt uses various verbs (Fix, Update, Implement, Create, etc.)
- The logic doesn't simulate the variety and quality of Claude-generated titles
💡 Suggested improvement for more realistic mock behavior
generateTitle: async (description: string) => { console.log("[Mock] Generating title for:", description.substring(0, 50)); - // Mock title generation - just take first few words - const words = description.split(/\s+/).slice(0, 6).join(" "); - const title = words.length > 40 ? words.substring(0, 40) + "..." : words; - return { success: true, title: `Add ${title}` }; + // Mock title generation - create action-oriented title + const words = description.split(/\s+/); + const verbs = ["Add", "Fix", "Update", "Implement", "Create"]; + const verb = verbs[Math.floor(Math.random() * verbs.length)]; + + // Take 5-9 words after the verb (to simulate 5-10 word titles) + const titleWords = words.slice(0, Math.min(8, words.length)).join(" "); + const title = titleWords.length > 50 ? titleWords.substring(0, 50) + "..." : titleWords; + return { success: true, title: `${verb} ${title}` }; },This better simulates the variety of Claude-generated titles while staying within the 5-10 word guideline.
apps/server/src/routes/features/routes/generate-title.ts (2)
37-62: Consider more robust stream parsing.The current implementation assumes specific message structure from the Claude SDK. Consider adding defensive checks to handle unexpected message formats gracefully.
🔎 Suggested improvement for robustness
async function extractTextFromStream( stream: AsyncIterable<{ type: string; subtype?: string; result?: string; message?: { content?: Array<{ type: string; text?: string }>; }; }> ): Promise<string> { let responseText = ""; for await (const msg of stream) { + if (!msg || typeof msg !== 'object') continue; + if (msg.type === "assistant" && msg.message?.content) { for (const block of msg.message.content) { - if (block.type === "text" && block.text) { + if (block && block.type === "text" && typeof block.text === "string") { responseText += block.text; } } } else if (msg.type === "result" && msg.subtype === "success") { - responseText = msg.result || responseText; + if (typeof msg.result === "string") { + responseText = msg.result; + } } } return responseText; }
95-116: Add timeout and validate generated title.The title generation call to Claude has two gaps:
- No timeout handling: If Claude takes too long or hangs, the request will wait indefinitely. This could impact user experience.
- No validation of generated title: The system prompt requests 5-10 words, but there's no verification that the result matches this constraint.
🔎 Suggested improvements
Add timeout handling:
// At the top of the file, add a constant const TITLE_GENERATION_TIMEOUT_MS = 30000; // 30 seconds // In the handler, wrap the query call const timeoutPromise = new Promise<never>((_, reject) => { setTimeout(() => reject(new Error("Title generation timed out")), TITLE_GENERATION_TIMEOUT_MS); }); const titlePromise = (async () => { const stream = query({ prompt: userPrompt, options: { model: CLAUDE_MODEL_MAP.haiku, systemPrompt: SYSTEM_PROMPT, maxTurns: 1, allowedTools: [], permissionMode: "acceptEdits", }, }); return await extractTextFromStream(stream); })(); const title = await Promise.race([titlePromise, timeoutPromise]);Add title validation:
const trimmedTitle = title.trim(); if (!trimmedTitle) { logger.warn("Received empty response from Claude"); const response: GenerateTitleErrorResponse = { success: false, error: "Failed to generate title - empty response", }; res.status(500).json(response); return; } // Validate word count (5-10 words as specified in system prompt) const wordCount = trimmedTitle.split(/\s+/).length; if (wordCount > 15) { logger.warn(`Generated title too long: ${wordCount} words`); // Truncate to approximately 10 words const truncated = trimmedTitle.split(/\s+/).slice(0, 10).join(" "); logger.info(`Truncated title: ${truncated}`); return res.json({ success: true, title: truncated }); }apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx (1)
317-331: Consider adding title generation on edit when title is cleared.The title input is properly integrated, but unlike the add feature flow, clearing the title in edit mode won't trigger auto-generation. This asymmetry might confuse users who expect the same behavior in both dialogs.
If auto-generation on edit is desired, you could add logic similar to
handleAddFeatureinuse-board-actions.tsto thehandleUpdateFeaturefunction to check if the title is empty and description exists, then trigger generation.apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (1)
358-368: Add data-testid for consistency and testability.The title input in the edit dialog has
data-testid="edit-feature-title"(line 329 in edit-feature-dialog.tsx), but this input lacks a corresponding test identifier. This inconsistency makes automated testing harder.🔧 Add test identifier
<Input id="title" value={newFeature.title} onChange={(e) => setNewFeature({ ...newFeature, title: e.target.value }) } placeholder="Leave blank to auto-generate" + data-testid="add-feature-title" />
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
apps/server/src/routes/features/index.ts(2 hunks)apps/server/src/routes/features/routes/generate-title.ts(1 hunks)apps/server/src/services/feature-loader.ts(1 hunks)apps/ui/src/components/views/board-view.tsx(3 hunks)apps/ui/src/components/views/board-view/board-header.tsx(4 hunks)apps/ui/src/components/views/board-view/components/kanban-card.tsx(1 hunks)apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx(6 hunks)apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx(4 hunks)apps/ui/src/components/views/board-view/hooks/use-board-actions.ts(3 hunks)apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx(4 hunks)apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx(3 hunks)apps/ui/src/components/views/board-view/worktree-panel/types.ts(1 hunks)apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx(2 hunks)apps/ui/src/lib/electron.ts(2 hunks)apps/ui/src/lib/http-api-client.ts(1 hunks)apps/ui/src/store/app-store.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (2)
apps/ui/src/lib/electron.ts (1)
WorktreeInfo(52-52)apps/ui/src/types/electron.d.ts (1)
WorktreeInfo(578-583)
apps/server/src/routes/features/routes/generate-title.ts (2)
apps/server/src/lib/logger.ts (1)
createLogger(32-60)apps/server/src/lib/model-resolver.ts (1)
CLAUDE_MODEL_MAP(13-17)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
WorktreeInfo(9-18)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx (3)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
WorktreeInfo(9-18)apps/ui/src/lib/electron.ts (1)
WorktreeInfo(52-52)apps/ui/src/types/electron.d.ts (1)
WorktreeInfo(578-583)
apps/ui/src/components/views/board-view.tsx (3)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
WorktreeInfo(9-18)apps/ui/src/lib/electron.ts (1)
WorktreeInfo(52-52)apps/ui/src/types/electron.d.ts (1)
WorktreeInfo(578-583)
apps/ui/src/components/views/board-view/hooks/use-board-actions.ts (1)
apps/ui/src/lib/electron.ts (1)
getElectronAPI(555-564)
apps/server/src/routes/features/index.ts (1)
apps/server/src/routes/features/routes/generate-title.ts (1)
createGenerateTitleHandler(64-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: e2e
🔇 Additional comments (19)
apps/server/src/services/feature-loader.ts (1)
17-18: LGTM! Clean interface extension.The addition of optional
titleandtitleGeneratingfields to the Feature interface is well-designed and supports the new title generation workflow without breaking existing features.apps/ui/src/lib/http-api-client.ts (1)
515-516: LGTM! Consistent API pattern.The
generateTitlemethod follows the established pattern for feature API methods and properly integrates with the HTTP client architecture.apps/ui/src/lib/electron.ts (1)
206-208: LGTM! Interface definition is correct.The
generateTitlemethod signature properly matches the expected API contract.apps/server/src/routes/features/routes/generate-title.ts (1)
28-35: LGTM! Clear and specific system prompt.The system prompt provides excellent guidance for title generation with concrete examples and clear constraints.
apps/server/src/routes/features/index.ts (2)
13-13: LGTM! Proper import.The import follows the established pattern for route handlers in this file.
24-24: LGTM! Consistent route registration.The route registration follows the same pattern as other feature routes.
apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx (1)
24-24: LGTM! Clean prop threading.The
onResolveConflictscallback is properly threaded through the component hierarchy to enable the conflict resolution UI flow.Also applies to: 152-152
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
70-70: LGTM! Proper callback type definition.The
onResolveConflictscallback is properly typed and consistent with other worktree action callbacks in the interface.apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx (1)
48-48: LGTM! Proper callback propagation.The
onResolveConflictscallback is correctly threaded through WorktreeTab to WorktreeActionsDropdown, following the established pattern for other action callbacks.Also applies to: 88-88, 348-348
apps/ui/src/store/app-store.ts (1)
283-284: LGTM! Clean interface extension for title support.The new optional
titleandtitleGeneratingfields are appropriately typed and follow the existing pattern for optional Feature properties. These fields integrate well with the persistence layer sincefeaturesis already included inpartialize.apps/ui/src/components/views/board-view/components/kanban-card.tsx (1)
650-667: LGTM! Clean conditional rendering for title states.The three-way conditional (generating → title → null) handles all states correctly. The loading indicator with spinner provides good UX feedback during async title generation. The styling hierarchy with
CardTitlefor the title andCardDescriptionfor the description is appropriate.apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)
166-174: LGTM! Well-integrated conflict resolution action.The new "Pull & Resolve Conflicts" menu item is appropriately:
- Conditionally rendered only for non-main worktrees
- Styled distinctively with purple to indicate it's a special/advanced action
- Placed logically after pull/push operations in the menu flow
apps/ui/src/components/views/board-view/board-header.tsx (1)
57-62: LGTM! Improved agents display with running count.The "X / Y" format showing running agents vs. max concurrency is a good UX improvement. The
min-w-[5ch]should handle typical values, though for edge cases like "10 / 10" you might considermin-w-[7ch]to prevent layout shift.apps/ui/src/components/views/board-view.tsx (3)
469-508: LGTM! Conflict resolution handler follows established pattern.The implementation mirrors
handleAddressPRCommentswith appropriate differences (category: "Maintenance", conflict-specific description). ThesetTimeout+ feature lookup pattern is consistent with existing code.One consideration: the feature lookup using
.includes("Pull latest from origin/main")could theoretically match user-created features with similar text. This is the same pattern used inhandleAddressPRComments, so it's consistent, but if you want more robust matching, consider adding a transient marker field or using a more unique identifier in the lookup.
930-930: LGTM! Correct wiring of running agents count.The
runningAutoTasks.lengthcorrectly reflects the number of currently running agent tasks, which aligns with whatBoardHeaderexpects for therunningAgentsCountprop.
971-971: LGTM! Handler correctly wired to WorktreePanel.The
onResolveConflictsprop is properly connected, enabling the conflict resolution flow from the worktree dropdown menu.apps/ui/src/components/views/board-view/hooks/use-board-actions.ts (2)
145-146: LGTM!The condition properly checks for both an empty title and a non-empty description before triggering generation. The
trim()calls ensure whitespace-only values are handled correctly.
187-187: No action required. BothpersistFeatureUpdateandupdateFeatureare already stable references.updateFeatureis a Zustand store action, which by design are static and never change.persistFeatureUpdateis properly memoized withuseCallback, with appropriate dependencies. Including them in the dependency array is correct and poses no performance risk.apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (1)
104-104: LGTM!The title field is properly initialized to an empty string and correctly reset after form submission, maintaining consistent state management throughout the component lifecycle.
Also applies to: 209-209
| // 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); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Silent error handling may confuse users.
When title generation fails (lines 171-174, 177-183), the titleGenerating flag is cleared silently without notifying the user. This could leave users wondering why no title appeared, especially if the generation takes several seconds before failing.
💡 Suggested improvement
Consider adding a subtle toast notification on failure:
.catch((error) => {
console.error("[Board] Error generating title:", error);
+ toast.error("Title generation failed", {
+ description: "You can manually enter a title or try again."
+ });
// Clear generating flag on error
const titleUpdates = { titleGenerating: false };Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/ui/src/components/views/board-view/hooks/use-board-actions.ts around
lines 160 to 185, the title-generation failure branches clear the
titleGenerating flag silently; update those failure and catch branches to also
surface a subtle user-facing notification (e.g., call the app's
toast/notification helper with a brief non-blocking message like "Title
generation failed" and optional retry hint) while still clearing titleGenerating
and persisting the update so the UI is consistent; ensure the notification call
is guarded (check for the toast API or import the app's notification helper) and
does not block the existing promise flow.
| 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); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition with concurrent feature updates.
If a user edits the feature (e.g., updates the description or other fields) while title generation is in progress, the generated title update (lines 168-169) could overwrite or conflict with the user's changes. The updates are applied independently without coordination.
Consider one of these approaches:
- Check if the feature's
titleGeneratingflag is stilltruebefore applying the update (to detect if user cleared it manually) - Use a versioning or timestamp mechanism to detect stale updates
- Queue updates instead of applying them immediately
This is a low-probability edge case but could affect UX in slower network conditions or with slower AI responses.
🤖 Prompt for AI Agents
In apps/ui/src/components/views/board-view/hooks/use-board-actions.ts around
lines 164-183, the title-generation promise unconditionally applies updates
which can overwrite concurrent user edits; change the flow to avoid races by
first fetching the current feature state (or using the latest local feature
object) and verifying that titleGenerating is still true and the feature's
updatedAt/version hasn't changed before applying the generated title, and if it
has changed skip or merge the update (or prompt re-check); ensure both
updateFeature and persistFeatureUpdate are only called when the checks pass,
otherwise just clear the titleGenerating flag conditionally or abort the title
write so user edits are not lost.
Summary
This PR adds automatic title generation for features using Claude Haiku.
Changes
/features/generate-titlethat generates concise titles (5-10 words) from feature descriptionstitleandtitleGeneratingfieldsTechnical Details
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.