diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index ab53a579..5ff95f39 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -218,7 +218,7 @@ app.use('/api/context', createContextRoutes(settingsService)); app.use('/api/backlog-plan', createBacklogPlanRoutes(events, settingsService)); app.use('/api/mcp', createMCPRoutes(mcpTestService)); app.use('/api/pipeline', createPipelineRoutes(pipelineService)); -app.use('/api/ideation', createIdeationRoutes(ideationService, featureLoader)); +app.use('/api/ideation', createIdeationRoutes(events, ideationService, featureLoader)); // Create HTTP server const server = createServer(app); diff --git a/apps/server/src/routes/ideation/index.ts b/apps/server/src/routes/ideation/index.ts index cd64c739..95fe128b 100644 --- a/apps/server/src/routes/ideation/index.ts +++ b/apps/server/src/routes/ideation/index.ts @@ -3,6 +3,7 @@ */ import { Router } from 'express'; +import type { EventEmitter } from '../../lib/events.js'; import { validatePathParams } from '../../middleware/validate-paths.js'; import type { IdeationService } from '../../services/ideation-service.js'; import type { FeatureLoader } from '../../services/feature-loader.js'; @@ -19,10 +20,12 @@ import { createIdeasUpdateHandler } from './routes/ideas-update.js'; import { createIdeasDeleteHandler } from './routes/ideas-delete.js'; import { createAnalyzeHandler, createGetAnalysisHandler } from './routes/analyze.js'; import { createConvertHandler } from './routes/convert.js'; +import { createAddSuggestionHandler } from './routes/add-suggestion.js'; import { createPromptsHandler, createPromptsByCategoryHandler } from './routes/prompts.js'; import { createSuggestionsGenerateHandler } from './routes/suggestions-generate.js'; export function createIdeationRoutes( + events: EventEmitter, ideationService: IdeationService, featureLoader: FeatureLoader ): Router { @@ -35,7 +38,7 @@ export function createIdeationRoutes( createSessionStartHandler(ideationService) ); router.post('/session/message', createSessionMessageHandler(ideationService)); - router.post('/session/stop', createSessionStopHandler(ideationService)); + router.post('/session/stop', createSessionStopHandler(events, ideationService)); router.post( '/session/get', validatePathParams('projectPath'), @@ -51,7 +54,7 @@ export function createIdeationRoutes( router.post( '/ideas/create', validatePathParams('projectPath'), - createIdeasCreateHandler(ideationService) + createIdeasCreateHandler(events, ideationService) ); router.post( '/ideas/get', @@ -61,12 +64,12 @@ export function createIdeationRoutes( router.post( '/ideas/update', validatePathParams('projectPath'), - createIdeasUpdateHandler(ideationService) + createIdeasUpdateHandler(events, ideationService) ); router.post( '/ideas/delete', validatePathParams('projectPath'), - createIdeasDeleteHandler(ideationService) + createIdeasDeleteHandler(events, ideationService) ); // Project analysis @@ -81,7 +84,14 @@ export function createIdeationRoutes( router.post( '/convert', validatePathParams('projectPath'), - createConvertHandler(ideationService, featureLoader) + createConvertHandler(events, ideationService, featureLoader) + ); + + // Add suggestion to board as a feature + router.post( + '/add-suggestion', + validatePathParams('projectPath'), + createAddSuggestionHandler(ideationService, featureLoader) ); // Guided prompts (no validation needed - static data) diff --git a/apps/server/src/routes/ideation/routes/add-suggestion.ts b/apps/server/src/routes/ideation/routes/add-suggestion.ts new file mode 100644 index 00000000..3326bfc3 --- /dev/null +++ b/apps/server/src/routes/ideation/routes/add-suggestion.ts @@ -0,0 +1,70 @@ +/** + * POST /add-suggestion - Add an analysis suggestion to the board as a feature + * + * This endpoint converts an AnalysisSuggestion to a Feature using the + * IdeationService's mapIdeaCategoryToFeatureCategory for consistent category mapping. + * This ensures a single source of truth for the conversion logic. + */ + +import type { Request, Response } from 'express'; +import type { IdeationService } from '../../../services/ideation-service.js'; +import type { FeatureLoader } from '../../../services/feature-loader.js'; +import type { AnalysisSuggestion } from '@automaker/types'; +import { getErrorMessage, logError } from '../common.js'; + +export function createAddSuggestionHandler( + ideationService: IdeationService, + featureLoader: FeatureLoader +) { + return async (req: Request, res: Response): Promise => { + try { + const { projectPath, suggestion } = req.body as { + projectPath: string; + suggestion: AnalysisSuggestion; + }; + + if (!projectPath) { + res.status(400).json({ success: false, error: 'projectPath is required' }); + return; + } + + if (!suggestion) { + res.status(400).json({ success: false, error: 'suggestion is required' }); + return; + } + + if (!suggestion.title) { + res.status(400).json({ success: false, error: 'suggestion.title is required' }); + return; + } + + if (!suggestion.category) { + res.status(400).json({ success: false, error: 'suggestion.category is required' }); + return; + } + + // Build description with rationale if provided + const description = suggestion.rationale + ? `${suggestion.description}\n\n**Rationale:** ${suggestion.rationale}` + : suggestion.description; + + // Use the service's category mapping for consistency + const featureCategory = ideationService.mapSuggestionCategoryToFeatureCategory( + suggestion.category + ); + + // Create the feature + const feature = await featureLoader.create(projectPath, { + title: suggestion.title, + description, + category: featureCategory, + status: 'backlog', + }); + + res.json({ success: true, featureId: feature.id }); + } catch (error) { + logError(error, 'Add suggestion to board failed'); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/ideation/routes/convert.ts b/apps/server/src/routes/ideation/routes/convert.ts index ab83164d..e1939bb4 100644 --- a/apps/server/src/routes/ideation/routes/convert.ts +++ b/apps/server/src/routes/ideation/routes/convert.ts @@ -3,12 +3,14 @@ */ import type { Request, Response } from 'express'; +import type { EventEmitter } from '../../../lib/events.js'; import type { IdeationService } from '../../../services/ideation-service.js'; import type { FeatureLoader } from '../../../services/feature-loader.js'; import type { ConvertToFeatureOptions } from '@automaker/types'; import { getErrorMessage, logError } from '../common.js'; export function createConvertHandler( + events: EventEmitter, ideationService: IdeationService, featureLoader: FeatureLoader ) { @@ -49,8 +51,22 @@ export function createConvertHandler( // Delete the idea unless keepIdea is explicitly true if (!keepIdea) { await ideationService.deleteIdea(projectPath, ideaId); + + // Emit idea deleted event + events.emit('ideation:idea-deleted', { + projectPath, + ideaId, + }); } + // Emit idea converted event to notify frontend + events.emit('ideation:idea-converted', { + projectPath, + ideaId, + featureId: feature.id, + keepIdea: !!keepIdea, + }); + // Return featureId as expected by the frontend API interface res.json({ success: true, featureId: feature.id }); } catch (error) { diff --git a/apps/server/src/routes/ideation/routes/ideas-create.ts b/apps/server/src/routes/ideation/routes/ideas-create.ts index d854622e..bf368fd9 100644 --- a/apps/server/src/routes/ideation/routes/ideas-create.ts +++ b/apps/server/src/routes/ideation/routes/ideas-create.ts @@ -3,11 +3,12 @@ */ import type { Request, Response } from 'express'; +import type { EventEmitter } from '../../../lib/events.js'; import type { IdeationService } from '../../../services/ideation-service.js'; import type { CreateIdeaInput } from '@automaker/types'; import { getErrorMessage, logError } from '../common.js'; -export function createIdeasCreateHandler(ideationService: IdeationService) { +export function createIdeasCreateHandler(events: EventEmitter, ideationService: IdeationService) { return async (req: Request, res: Response): Promise => { try { const { projectPath, idea } = req.body as { @@ -34,6 +35,13 @@ export function createIdeasCreateHandler(ideationService: IdeationService) { } const created = await ideationService.createIdea(projectPath, idea); + + // Emit idea created event for frontend notification + events.emit('ideation:idea-created', { + projectPath, + idea: created, + }); + res.json({ success: true, idea: created }); } catch (error) { logError(error, 'Create idea failed'); diff --git a/apps/server/src/routes/ideation/routes/ideas-delete.ts b/apps/server/src/routes/ideation/routes/ideas-delete.ts index 931ae32a..b1bcf006 100644 --- a/apps/server/src/routes/ideation/routes/ideas-delete.ts +++ b/apps/server/src/routes/ideation/routes/ideas-delete.ts @@ -3,10 +3,11 @@ */ import type { Request, Response } from 'express'; +import type { EventEmitter } from '../../../lib/events.js'; import type { IdeationService } from '../../../services/ideation-service.js'; import { getErrorMessage, logError } from '../common.js'; -export function createIdeasDeleteHandler(ideationService: IdeationService) { +export function createIdeasDeleteHandler(events: EventEmitter, ideationService: IdeationService) { return async (req: Request, res: Response): Promise => { try { const { projectPath, ideaId } = req.body as { @@ -25,6 +26,13 @@ export function createIdeasDeleteHandler(ideationService: IdeationService) { } await ideationService.deleteIdea(projectPath, ideaId); + + // Emit idea deleted event for frontend notification + events.emit('ideation:idea-deleted', { + projectPath, + ideaId, + }); + res.json({ success: true }); } catch (error) { logError(error, 'Delete idea failed'); diff --git a/apps/server/src/routes/ideation/routes/ideas-update.ts b/apps/server/src/routes/ideation/routes/ideas-update.ts index c2434ce4..fbf0d8b6 100644 --- a/apps/server/src/routes/ideation/routes/ideas-update.ts +++ b/apps/server/src/routes/ideation/routes/ideas-update.ts @@ -3,11 +3,12 @@ */ import type { Request, Response } from 'express'; +import type { EventEmitter } from '../../../lib/events.js'; import type { IdeationService } from '../../../services/ideation-service.js'; import type { UpdateIdeaInput } from '@automaker/types'; import { getErrorMessage, logError } from '../common.js'; -export function createIdeasUpdateHandler(ideationService: IdeationService) { +export function createIdeasUpdateHandler(events: EventEmitter, ideationService: IdeationService) { return async (req: Request, res: Response): Promise => { try { const { projectPath, ideaId, updates } = req.body as { @@ -37,6 +38,13 @@ export function createIdeasUpdateHandler(ideationService: IdeationService) { return; } + // Emit idea updated event for frontend notification + events.emit('ideation:idea-updated', { + projectPath, + ideaId, + idea, + }); + res.json({ success: true, idea }); } catch (error) { logError(error, 'Update idea failed'); diff --git a/apps/server/src/routes/ideation/routes/prompts.ts b/apps/server/src/routes/ideation/routes/prompts.ts index fb54e1dd..8d686bbb 100644 --- a/apps/server/src/routes/ideation/routes/prompts.ts +++ b/apps/server/src/routes/ideation/routes/prompts.ts @@ -26,7 +26,7 @@ export function createPromptsByCategoryHandler(ideationService: IdeationService) try { const { category } = req.params as { category: string }; - const validCategories: IdeaCategory[] = ['feature', 'ux-ui', 'dx', 'growth', 'technical']; + const validCategories = ideationService.getPromptCategories().map((c) => c.id); if (!validCategories.includes(category as IdeaCategory)) { res.status(400).json({ success: false, error: 'Invalid category' }); return; diff --git a/apps/server/src/routes/ideation/routes/session-stop.ts b/apps/server/src/routes/ideation/routes/session-stop.ts index 858d7b7b..c0d59e3b 100644 --- a/apps/server/src/routes/ideation/routes/session-stop.ts +++ b/apps/server/src/routes/ideation/routes/session-stop.ts @@ -3,13 +3,17 @@ */ import type { Request, Response } from 'express'; +import type { EventEmitter } from '../../../lib/events.js'; import type { IdeationService } from '../../../services/ideation-service.js'; import { getErrorMessage, logError } from '../common.js'; -export function createSessionStopHandler(ideationService: IdeationService) { +export function createSessionStopHandler(events: EventEmitter, ideationService: IdeationService) { return async (req: Request, res: Response): Promise => { try { - const { sessionId } = req.body as { sessionId: string }; + const { sessionId, projectPath } = req.body as { + sessionId: string; + projectPath?: string; + }; if (!sessionId) { res.status(400).json({ success: false, error: 'sessionId is required' }); @@ -17,6 +21,15 @@ export function createSessionStopHandler(ideationService: IdeationService) { } await ideationService.stopSession(sessionId); + + // Emit session stopped event for frontend notification + // Note: The service also emits 'ideation:session-ended' internally, + // but we emit here as well for route-level consistency with other routes + events.emit('ideation:session-ended', { + sessionId, + projectPath, + }); + res.json({ success: true }); } catch (error) { logError(error, 'Stop session failed'); diff --git a/apps/server/src/routes/ideation/routes/suggestions-generate.ts b/apps/server/src/routes/ideation/routes/suggestions-generate.ts index 6907b1af..8add2af5 100644 --- a/apps/server/src/routes/ideation/routes/suggestions-generate.ts +++ b/apps/server/src/routes/ideation/routes/suggestions-generate.ts @@ -5,6 +5,7 @@ import type { Request, Response } from 'express'; import type { IdeationService } from '../../../services/ideation-service.js'; import { createLogger } from '@automaker/utils'; +import { getErrorMessage, logError } from '../common.js'; const logger = createLogger('ideation:suggestions-generate'); @@ -45,10 +46,10 @@ export function createSuggestionsGenerateHandler(ideationService: IdeationServic suggestions, }); } catch (error) { - logger.error('Failed to generate suggestions:', error); + logError(error, 'Failed to generate suggestions'); res.status(500).json({ success: false, - error: (error as Error).message, + error: getErrorMessage(error), }); } }; diff --git a/apps/server/src/services/ideation-service.ts b/apps/server/src/services/ideation-service.ts index 3528c858..7973db05 100644 --- a/apps/server/src/services/ideation-service.ts +++ b/apps/server/src/services/ideation-service.ts @@ -39,6 +39,7 @@ import { ProviderFactory } from '../providers/provider-factory.js'; import type { SettingsService } from './settings-service.js'; import type { FeatureLoader } from './feature-loader.js'; import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js'; +import { resolveModelString } from '@automaker/model-resolver'; const logger = createLogger('IdeationService'); @@ -200,20 +201,22 @@ export class IdeationService { existingWorkContext ); + // Resolve model alias to canonical identifier + const modelId = resolveModelString(options?.model ?? 'sonnet'); + // Create SDK options const sdkOptions = createChatOptions({ cwd: projectPath, - model: options?.model || 'sonnet', + model: modelId, systemPrompt, abortController: activeSession.abortController!, }); - const effectiveModel = sdkOptions.model!; - const provider = ProviderFactory.getProviderForModel(effectiveModel); + const provider = ProviderFactory.getProviderForModel(modelId); const executeOptions: ExecuteOptions = { prompt: message, - model: effectiveModel, + model: modelId, cwd: projectPath, systemPrompt: sdkOptions.systemPrompt, maxTurns: 1, // Single turn for ideation @@ -645,20 +648,22 @@ export class IdeationService { existingWorkContext ); + // Resolve model alias to canonical identifier + const modelId = resolveModelString('sonnet'); + // Create SDK options const sdkOptions = createChatOptions({ cwd: projectPath, - model: 'sonnet', + model: modelId, systemPrompt, abortController: new AbortController(), }); - const effectiveModel = sdkOptions.model!; - const provider = ProviderFactory.getProviderForModel(effectiveModel); + const provider = ProviderFactory.getProviderForModel(modelId); const executeOptions: ExecuteOptions = { prompt: prompt.prompt, - model: effectiveModel, + model: modelId, cwd: projectPath, systemPrompt: sdkOptions.systemPrompt, maxTurns: 1, @@ -892,6 +897,30 @@ ${contextSection}${existingWorkSection}`; icon: 'Cpu', description: 'Architecture and infrastructure', }, + { + id: 'security', + name: 'Security', + icon: 'Shield', + description: 'Security improvements and vulnerability fixes', + }, + { + id: 'performance', + name: 'Performance', + icon: 'Gauge', + description: 'Performance optimization and speed improvements', + }, + { + id: 'accessibility', + name: 'Accessibility', + icon: 'Accessibility', + description: 'Accessibility features and inclusive design', + }, + { + id: 'analytics', + name: 'Analytics', + icon: 'BarChart', + description: 'Analytics, monitoring, and insights features', + }, ]; } @@ -905,7 +934,8 @@ ${contextSection}${existingWorkSection}`; /** * Get all guided prompts - * NOTE: Keep in sync with apps/ui/src/components/views/ideation-view/data/guided-prompts.ts + * This is the single source of truth for guided prompts data. + * Frontend fetches this data via /api/ideation/prompts endpoint. */ getAllPrompts(): IdeationPrompt[] { return [ @@ -1629,7 +1659,20 @@ Focus on practical, implementable suggestions that would genuinely improve the p return `${summary}. Found ${suggestions.length} improvement opportunities${highPriority > 0 ? ` (${highPriority} high priority)` : ''}.`; } + /** + * Map idea category to feature category + * Used internally for idea-to-feature conversion + */ private mapIdeaCategoryToFeatureCategory(category: IdeaCategory): string { + return this.mapSuggestionCategoryToFeatureCategory(category); + } + + /** + * Map suggestion/idea category to feature category + * This is the single source of truth for category mapping. + * Used by both idea-to-feature conversion and suggestion-to-feature conversion. + */ + mapSuggestionCategoryToFeatureCategory(category: IdeaCategory): string { const mapping: Record = { feature: 'ui', 'ux-ui': 'enhancement', diff --git a/apps/ui/src/components/views/ideation-view/components/prompt-category-grid.tsx b/apps/ui/src/components/views/ideation-view/components/prompt-category-grid.tsx index abf29c83..ccf0de83 100644 --- a/apps/ui/src/components/views/ideation-view/components/prompt-category-grid.tsx +++ b/apps/ui/src/components/views/ideation-view/components/prompt-category-grid.tsx @@ -13,9 +13,10 @@ import { Gauge, Accessibility, BarChart3, + Loader2, } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; -import { PROMPT_CATEGORIES } from '../data/guided-prompts'; +import { useGuidedPrompts } from '@/hooks/use-guided-prompts'; import type { IdeaCategory } from '@automaker/types'; interface PromptCategoryGridProps { @@ -36,6 +37,8 @@ const iconMap: Record = { }; export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps) { + const { categories, isLoading, error } = useGuidedPrompts(); + return (
@@ -48,30 +51,43 @@ export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps Back -
- {PROMPT_CATEGORIES.map((category) => { - const Icon = iconMap[category.icon] || Zap; - return ( - onSelect(category.id)} - > - -
-
- + {isLoading && ( +
+ + Loading categories... +
+ )} + {error && ( +
+

Failed to load categories: {error}

+
+ )} + {!isLoading && !error && ( +
+ {categories.map((category) => { + const Icon = iconMap[category.icon] || Zap; + return ( + onSelect(category.id)} + > + +
+
+ +
+
+

{category.name}

+

{category.description}

+
-
-

{category.name}

-

{category.description}

-
-
- - - ); - })} -
+ + + ); + })} +
+ )}
); diff --git a/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx b/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx index b9fd1d31..76713350 100644 --- a/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx +++ b/apps/ui/src/components/views/ideation-view/components/prompt-list.tsx @@ -5,7 +5,7 @@ import { useState } from 'react'; import { ArrowLeft, Lightbulb, Loader2, CheckCircle2 } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; -import { getPromptsByCategory } from '../data/guided-prompts'; +import { useGuidedPrompts } from '@/hooks/use-guided-prompts'; import { useIdeationStore } from '@/store/ideation-store'; import { useAppStore } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; @@ -24,6 +24,11 @@ export function PromptList({ category, onBack }: PromptListProps) { const [loadingPromptId, setLoadingPromptId] = useState(null); const [startedPrompts, setStartedPrompts] = useState>(new Set()); const navigate = useNavigate(); + const { + getPromptsByCategory, + isLoading: isLoadingPrompts, + error: promptsError, + } = useGuidedPrompts(); const prompts = getPromptsByCategory(category); @@ -101,60 +106,73 @@ export function PromptList({ category, onBack }: PromptListProps) {
- {prompts.map((prompt) => { - const isLoading = loadingPromptId === prompt.id; - const isGenerating = generatingPromptIds.has(prompt.id); - const isStarted = startedPrompts.has(prompt.id); - const isDisabled = loadingPromptId !== null || isGenerating; + {isLoadingPrompts && ( +
+ + Loading prompts... +
+ )} + {promptsError && ( +
+

Failed to load prompts: {promptsError}

+
+ )} + {!isLoadingPrompts && + !promptsError && + prompts.map((prompt) => { + const isLoading = loadingPromptId === prompt.id; + const isGenerating = generatingPromptIds.has(prompt.id); + const isStarted = startedPrompts.has(prompt.id); + const isDisabled = loadingPromptId !== null || isGenerating; - return ( - !isDisabled && handleSelectPrompt(prompt)} - > - -
-
- {isLoading || isGenerating ? ( - - ) : isStarted ? ( - - ) : ( - - )} + return ( + !isDisabled && handleSelectPrompt(prompt)} + > + +
+
+ {isLoading || isGenerating ? ( + + ) : isStarted ? ( + + ) : ( + + )} +
+
+

{prompt.title}

+

{prompt.description}

+ {(isLoading || isGenerating) && ( +

Generating in dashboard...

+ )} + {isStarted && !isGenerating && ( +

+ Already generated - check dashboard +

+ )} +
-
-

{prompt.title}

-

{prompt.description}

- {(isLoading || isGenerating) && ( -

Generating in dashboard...

- )} - {isStarted && !isGenerating && ( -

- Already generated - check dashboard -

- )} -
-
- - - ); - })} + + + ); + })}
diff --git a/apps/ui/src/components/views/ideation-view/data/guided-prompts.ts b/apps/ui/src/components/views/ideation-view/data/guided-prompts.ts deleted file mode 100644 index 41e8f59c..00000000 --- a/apps/ui/src/components/views/ideation-view/data/guided-prompts.ts +++ /dev/null @@ -1,391 +0,0 @@ -/** - * Guided prompts for ideation sessions - * Static data that provides pre-made prompts for different categories - */ - -import type { IdeaCategory, IdeationPrompt, PromptCategory } from '@automaker/types'; - -export const PROMPT_CATEGORIES: PromptCategory[] = [ - { - id: 'feature', - name: 'Features', - icon: 'Zap', - description: 'New capabilities and functionality', - }, - { - id: 'ux-ui', - name: 'UX/UI', - icon: 'Palette', - description: 'Design and user experience improvements', - }, - { - id: 'dx', - name: 'Developer Experience', - icon: 'Code', - description: 'Developer tooling and workflows', - }, - { - id: 'growth', - name: 'Growth', - icon: 'TrendingUp', - description: 'User engagement and retention', - }, - { - id: 'technical', - name: 'Technical', - icon: 'Cpu', - description: 'Architecture and infrastructure', - }, - { - id: 'security', - name: 'Security', - icon: 'Shield', - description: 'Security and privacy improvements', - }, - { - id: 'performance', - name: 'Performance', - icon: 'Gauge', - description: 'Speed and optimization', - }, - { - id: 'accessibility', - name: 'Accessibility', - icon: 'Accessibility', - description: 'Inclusive design for all users', - }, - { - id: 'analytics', - name: 'Analytics', - icon: 'BarChart3', - description: 'Data insights and tracking', - }, -]; - -export const GUIDED_PROMPTS: IdeationPrompt[] = [ - // Feature prompts - { - id: 'feature-missing', - category: 'feature', - title: 'Missing Features', - description: 'Discover features users might expect', - prompt: - "Analyze this codebase and identify features that users of similar applications typically expect but are missing here. Consider the app's domain, target users, and common patterns in similar products.", - }, - { - id: 'feature-automation', - category: 'feature', - title: 'Automation Opportunities', - description: 'Find manual processes that could be automated', - prompt: - 'Review this codebase and identify manual processes or repetitive tasks that could be automated. Look for patterns where users might be doing things repeatedly that software could handle.', - }, - { - id: 'feature-integrations', - category: 'feature', - title: 'Integration Ideas', - description: 'Identify valuable third-party integrations', - prompt: - "Based on this codebase, what third-party services or APIs would provide value if integrated? Consider the app's domain and what complementary services users might need.", - }, - { - id: 'feature-workflow', - category: 'feature', - title: 'Workflow Improvements', - description: 'Streamline user workflows', - prompt: - 'Analyze the user workflows in this application. What steps could be combined, eliminated, or automated? Where are users likely spending too much time on repetitive tasks?', - }, - - // UX/UI prompts - { - id: 'ux-friction', - category: 'ux-ui', - title: 'Friction Points', - description: 'Identify where users might get stuck', - prompt: - 'Analyze the user flows in this codebase and identify potential friction points. Where might users get confused, stuck, or frustrated? Look at form submissions, navigation, error states, and complex interactions.', - }, - { - id: 'ux-empty-states', - category: 'ux-ui', - title: 'Empty States', - description: 'Improve empty state experiences', - prompt: - "Review the components in this codebase and identify empty states that could be improved. How can we guide users when there's no content? Consider onboarding, helpful prompts, and sample data.", - }, - { - id: 'ux-accessibility', - category: 'ux-ui', - title: 'Accessibility Improvements', - description: 'Enhance accessibility and inclusivity', - prompt: - 'Analyze this codebase for accessibility improvements. Consider keyboard navigation, screen reader support, color contrast, focus states, and ARIA labels. What specific improvements would make this more accessible?', - }, - { - id: 'ux-mobile', - category: 'ux-ui', - title: 'Mobile Experience', - description: 'Optimize for mobile users', - prompt: - 'Review this codebase from a mobile-first perspective. What improvements would enhance the mobile user experience? Consider touch targets, responsive layouts, and mobile-specific interactions.', - }, - { - id: 'ux-feedback', - category: 'ux-ui', - title: 'User Feedback', - description: 'Improve feedback and status indicators', - prompt: - 'Analyze how this application communicates with users. Where are loading states, success messages, or error handling missing or unclear? What feedback would help users understand what is happening?', - }, - - // DX prompts - { - id: 'dx-documentation', - category: 'dx', - title: 'Documentation Gaps', - description: 'Identify missing documentation', - prompt: - 'Review this codebase and identify areas lacking documentation. What would help new developers understand the architecture, APIs, and conventions? Consider inline comments, READMEs, and API docs.', - }, - { - id: 'dx-testing', - category: 'dx', - title: 'Testing Improvements', - description: 'Enhance test coverage and quality', - prompt: - 'Analyze the testing patterns in this codebase. What areas need better test coverage? What types of tests are missing? Consider unit tests, integration tests, and end-to-end tests.', - }, - { - id: 'dx-tooling', - category: 'dx', - title: 'Developer Tooling', - description: 'Improve development workflows', - prompt: - 'Review the development setup and tooling in this codebase. What improvements would speed up development? Consider build times, hot reload, debugging tools, and developer scripts.', - }, - { - id: 'dx-error-handling', - category: 'dx', - title: 'Error Handling', - description: 'Improve error messages and debugging', - prompt: - 'Analyze error handling in this codebase. Where are error messages unclear or missing? What would help developers debug issues faster? Consider logging, error boundaries, and stack traces.', - }, - - // Growth prompts - { - id: 'growth-onboarding', - category: 'growth', - title: 'Onboarding Flow', - description: 'Improve new user experience', - prompt: - "Analyze this application's onboarding experience. How can we help new users understand the value and get started quickly? Consider tutorials, progressive disclosure, and quick wins.", - }, - { - id: 'growth-engagement', - category: 'growth', - title: 'User Engagement', - description: 'Increase user retention and activity', - prompt: - 'Review this application and suggest features that would increase user engagement and retention. What would bring users back daily? Consider notifications, streaks, social features, and personalization.', - }, - { - id: 'growth-sharing', - category: 'growth', - title: 'Shareability', - description: 'Make the app more shareable', - prompt: - 'How can this application be made more shareable? What features would encourage users to invite others or share their work? Consider collaboration, public profiles, and export features.', - }, - { - id: 'growth-monetization', - category: 'growth', - title: 'Monetization Ideas', - description: 'Identify potential revenue streams', - prompt: - 'Based on this codebase, what features or tiers could support monetization? Consider premium features, usage limits, team features, and integrations that users would pay for.', - }, - - // Technical prompts - { - id: 'tech-performance', - category: 'technical', - title: 'Performance Optimization', - description: 'Identify performance bottlenecks', - prompt: - 'Analyze this codebase for performance optimization opportunities. Where are the likely bottlenecks? Consider database queries, API calls, bundle size, rendering, and caching strategies.', - }, - { - id: 'tech-architecture', - category: 'technical', - title: 'Architecture Review', - description: 'Evaluate and improve architecture', - prompt: - 'Review the architecture of this codebase. What improvements would make it more maintainable, scalable, or testable? Consider separation of concerns, dependency management, and patterns.', - }, - { - id: 'tech-debt', - category: 'technical', - title: 'Technical Debt', - description: 'Identify areas needing refactoring', - prompt: - 'Identify technical debt in this codebase. What areas are becoming hard to maintain or understand? What refactoring would have the highest impact? Consider duplicated code, complexity, and outdated patterns.', - }, - { - id: 'tech-security', - category: 'technical', - title: 'Security Review', - description: 'Identify security improvements', - prompt: - 'Review this codebase for security improvements. What best practices are missing? Consider authentication, authorization, input validation, and data protection. Note: This is for improvement suggestions, not a security audit.', - }, - - // Security prompts - { - id: 'security-auth', - category: 'security', - title: 'Authentication Security', - description: 'Review authentication mechanisms', - prompt: - 'Analyze the authentication system in this codebase. What security improvements would strengthen user authentication? Consider password policies, session management, MFA, and token handling.', - }, - { - id: 'security-data', - category: 'security', - title: 'Data Protection', - description: 'Protect sensitive user data', - prompt: - 'Review how this application handles sensitive data. What improvements would better protect user privacy? Consider encryption, data minimization, secure storage, and data retention policies.', - }, - { - id: 'security-input', - category: 'security', - title: 'Input Validation', - description: 'Prevent injection attacks', - prompt: - 'Analyze input handling in this codebase. Where could input validation be strengthened? Consider SQL injection, XSS, command injection, and file upload vulnerabilities.', - }, - { - id: 'security-api', - category: 'security', - title: 'API Security', - description: 'Secure API endpoints', - prompt: - 'Review the API security in this codebase. What improvements would make the API more secure? Consider rate limiting, authorization, CORS, and request validation.', - }, - - // Performance prompts - { - id: 'perf-frontend', - category: 'performance', - title: 'Frontend Performance', - description: 'Optimize UI rendering and loading', - prompt: - 'Analyze the frontend performance of this application. What optimizations would improve load times and responsiveness? Consider bundle splitting, lazy loading, memoization, and render optimization.', - }, - { - id: 'perf-backend', - category: 'performance', - title: 'Backend Performance', - description: 'Optimize server-side operations', - prompt: - 'Review backend performance in this codebase. What optimizations would improve response times? Consider database queries, caching strategies, async operations, and resource pooling.', - }, - { - id: 'perf-database', - category: 'performance', - title: 'Database Optimization', - description: 'Improve query performance', - prompt: - 'Analyze database interactions in this codebase. What optimizations would improve data access performance? Consider indexing, query optimization, denormalization, and connection pooling.', - }, - { - id: 'perf-caching', - category: 'performance', - title: 'Caching Strategies', - description: 'Implement effective caching', - prompt: - 'Review caching opportunities in this application. Where would caching provide the most benefit? Consider API responses, computed values, static assets, and session data.', - }, - - // Accessibility prompts - { - id: 'a11y-keyboard', - category: 'accessibility', - title: 'Keyboard Navigation', - description: 'Enable full keyboard access', - prompt: - 'Analyze keyboard accessibility in this codebase. What improvements would enable users to navigate entirely with keyboard? Consider focus management, tab order, and keyboard shortcuts.', - }, - { - id: 'a11y-screen-reader', - category: 'accessibility', - title: 'Screen Reader Support', - description: 'Improve screen reader experience', - prompt: - 'Review screen reader compatibility in this application. What improvements would help users with visual impairments? Consider ARIA labels, semantic HTML, live regions, and alt text.', - }, - { - id: 'a11y-visual', - category: 'accessibility', - title: 'Visual Accessibility', - description: 'Improve visual design for all users', - prompt: - 'Analyze visual accessibility in this codebase. What improvements would help users with visual impairments? Consider color contrast, text sizing, focus indicators, and reduced motion.', - }, - { - id: 'a11y-forms', - category: 'accessibility', - title: 'Accessible Forms', - description: 'Make forms usable for everyone', - prompt: - 'Review form accessibility in this application. What improvements would make forms more accessible? Consider labels, error messages, required field indicators, and input assistance.', - }, - - // Analytics prompts - { - id: 'analytics-tracking', - category: 'analytics', - title: 'User Tracking', - description: 'Track key user behaviors', - prompt: - 'Analyze this application for analytics opportunities. What user behaviors should be tracked to understand engagement? Consider page views, feature usage, conversion funnels, and session duration.', - }, - { - id: 'analytics-metrics', - category: 'analytics', - title: 'Key Metrics', - description: 'Define success metrics', - prompt: - 'Based on this codebase, what key metrics should be tracked? Consider user acquisition, retention, engagement, and feature adoption. What dashboards would be most valuable?', - }, - { - id: 'analytics-errors', - category: 'analytics', - title: 'Error Monitoring', - description: 'Track and analyze errors', - prompt: - 'Review error handling in this codebase for monitoring opportunities. What error tracking would help identify and fix issues faster? Consider error aggregation, alerting, and stack traces.', - }, - { - id: 'analytics-performance', - category: 'analytics', - title: 'Performance Monitoring', - description: 'Track application performance', - prompt: - 'Analyze this application for performance monitoring opportunities. What metrics would help identify bottlenecks? Consider load times, API response times, and resource usage.', - }, -]; - -export function getPromptsByCategory(category: IdeaCategory): IdeationPrompt[] { - return GUIDED_PROMPTS.filter((p) => p.category === category); -} - -export function getPromptById(id: string): IdeationPrompt | undefined { - return GUIDED_PROMPTS.find((p) => p.id === id); -} - -export function getCategoryById(id: IdeaCategory): PromptCategory | undefined { - return PROMPT_CATEGORIES.find((c) => c.id === id); -} diff --git a/apps/ui/src/components/views/ideation-view/index.tsx b/apps/ui/src/components/views/ideation-view/index.tsx index aa962ae8..fd4ad245 100644 --- a/apps/ui/src/components/views/ideation-view/index.tsx +++ b/apps/ui/src/components/views/ideation-view/index.tsx @@ -9,27 +9,12 @@ import { useAppStore } from '@/store/app-store'; import { PromptCategoryGrid } from './components/prompt-category-grid'; import { PromptList } from './components/prompt-list'; import { IdeationDashboard } from './components/ideation-dashboard'; -import { getCategoryById } from './data/guided-prompts'; +import { useGuidedPrompts } from '@/hooks/use-guided-prompts'; import { Button } from '@/components/ui/button'; import { ArrowLeft, ChevronRight, Lightbulb } from 'lucide-react'; import type { IdeaCategory } from '@automaker/types'; import type { IdeationMode } from '@/store/ideation-store'; -// Get subtitle text based on current mode -function getSubtitle(currentMode: IdeationMode, selectedCategory: IdeaCategory | null): string { - if (currentMode === 'dashboard') { - return 'Review and accept generated ideas'; - } - if (currentMode === 'prompts') { - if (selectedCategory) { - const categoryInfo = getCategoryById(selectedCategory); - return `Select a prompt from ${categoryInfo?.name || 'category'}`; - } - return 'Select a category to generate ideas'; - } - return ''; -} - // Breadcrumb component - compact inline breadcrumbs function IdeationBreadcrumbs({ currentMode, @@ -40,6 +25,7 @@ function IdeationBreadcrumbs({ selectedCategory: IdeaCategory | null; onNavigate: (mode: IdeationMode, category?: IdeaCategory | null) => void; }) { + const { getCategoryById } = useGuidedPrompts(); const categoryInfo = selectedCategory ? getCategoryById(selectedCategory) : null; // On dashboard, no breadcrumbs needed (it's the root) @@ -88,9 +74,26 @@ function IdeationHeader({ onGenerateIdeas: () => void; onBack: () => void; }) { - const subtitle = getSubtitle(currentMode, selectedCategory); + const { getCategoryById } = useGuidedPrompts(); const showBackButton = currentMode === 'prompts'; + // Get subtitle text based on current mode + const getSubtitle = (): string => { + if (currentMode === 'dashboard') { + return 'Review and accept generated ideas'; + } + if (currentMode === 'prompts') { + if (selectedCategory) { + const categoryInfo = getCategoryById(selectedCategory); + return `Select a prompt from ${categoryInfo?.name || 'category'}`; + } + return 'Select a category to generate ideas'; + } + return ''; + }; + + const subtitle = getSubtitle(); + return (
diff --git a/apps/ui/src/hooks/index.ts b/apps/ui/src/hooks/index.ts index 8f2264d6..8a354b3d 100644 --- a/apps/ui/src/hooks/index.ts +++ b/apps/ui/src/hooks/index.ts @@ -1,6 +1,7 @@ export { useAutoMode } from './use-auto-mode'; export { useBoardBackgroundSettings } from './use-board-background-settings'; export { useElectronAgent } from './use-electron-agent'; +export { useGuidedPrompts } from './use-guided-prompts'; export { useKeyboardShortcuts } from './use-keyboard-shortcuts'; export { useMessageQueue } from './use-message-queue'; export { useOSDetection, type OperatingSystem, type OSDetectionResult } from './use-os-detection'; diff --git a/apps/ui/src/hooks/use-guided-prompts.ts b/apps/ui/src/hooks/use-guided-prompts.ts new file mode 100644 index 00000000..e192d6b3 --- /dev/null +++ b/apps/ui/src/hooks/use-guided-prompts.ts @@ -0,0 +1,86 @@ +/** + * Hook for fetching guided prompts from the backend API + * + * This hook provides the single source of truth for guided prompts, + * fetched from the backend /api/ideation/prompts endpoint. + */ + +import { useState, useEffect, useCallback } from 'react'; +import type { IdeationPrompt, PromptCategory, IdeaCategory } from '@automaker/types'; +import { getElectronAPI } from '@/lib/electron'; + +interface UseGuidedPromptsReturn { + prompts: IdeationPrompt[]; + categories: PromptCategory[]; + isLoading: boolean; + error: string | null; + refetch: () => Promise; + getPromptsByCategory: (category: IdeaCategory) => IdeationPrompt[]; + getPromptById: (id: string) => IdeationPrompt | undefined; + getCategoryById: (id: IdeaCategory) => PromptCategory | undefined; +} + +export function useGuidedPrompts(): UseGuidedPromptsReturn { + const [prompts, setPrompts] = useState([]); + const [categories, setCategories] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchPrompts = useCallback(async () => { + setIsLoading(true); + setError(null); + + try { + const api = getElectronAPI(); + const result = await api.ideation?.getPrompts(); + + if (result?.success) { + setPrompts(result.prompts || []); + setCategories(result.categories || []); + } else { + setError(result?.error || 'Failed to fetch prompts'); + } + } catch (err) { + console.error('Failed to fetch guided prompts:', err); + setError(err instanceof Error ? err.message : 'Failed to fetch prompts'); + } finally { + setIsLoading(false); + } + }, []); + + useEffect(() => { + fetchPrompts(); + }, [fetchPrompts]); + + const getPromptsByCategory = useCallback( + (category: IdeaCategory): IdeationPrompt[] => { + return prompts.filter((p) => p.category === category); + }, + [prompts] + ); + + const getPromptById = useCallback( + (id: string): IdeationPrompt | undefined => { + return prompts.find((p) => p.id === id); + }, + [prompts] + ); + + const getCategoryById = useCallback( + (id: IdeaCategory): PromptCategory | undefined => { + return categories.find((c) => c.id === id); + }, + [categories] + ); + + return { + prompts, + categories, + isLoading, + error, + refetch: fetchPrompts, + getPromptsByCategory, + getPromptById, + getCategoryById, + }; +} diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index ef9c6bb9..d81b46b6 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -17,6 +17,8 @@ import type { IdeaCategory, IdeationSession, IdeationMessage, + IdeationPrompt, + PromptCategory, ProjectAnalysisResult, AnalysisSuggestion, StartSessionOptions, @@ -46,6 +48,8 @@ export type { IdeaCategory, IdeationSession, IdeationMessage, + IdeationPrompt, + PromptCategory, ProjectAnalysisResult, AnalysisSuggestion, StartSessionOptions, @@ -123,6 +127,14 @@ export interface IdeationAPI { suggestion: AnalysisSuggestion ) => Promise<{ success: boolean; featureId?: string; error?: string }>; + // Get guided prompts (single source of truth from backend) + getPrompts: () => Promise<{ + success: boolean; + prompts?: IdeationPrompt[]; + categories?: PromptCategory[]; + error?: string; + }>; + // Event subscriptions onStream: (callback: (event: any) => void) => () => void; onAnalysisEvent: (callback: (event: any) => void) => () => void; diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index 7464d55a..a76e2549 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -1690,35 +1690,13 @@ export class HttpApiClient implements ElectronAPI { convertToFeature: (projectPath: string, ideaId: string, options?: ConvertToFeatureOptions) => this.post('/api/ideation/convert', { projectPath, ideaId, ...options }), - addSuggestionToBoard: async (projectPath: string, suggestion: AnalysisSuggestion) => { - // Create a feature directly from the suggestion - const result = await this.post<{ success: boolean; feature?: Feature; error?: string }>( - '/api/features/create', - { - projectPath, - feature: { - title: suggestion.title, - description: - suggestion.description + - (suggestion.rationale ? `\n\n**Rationale:** ${suggestion.rationale}` : ''), - category: - suggestion.category === 'ux-ui' - ? 'enhancement' - : suggestion.category === 'dx' - ? 'chore' - : suggestion.category === 'technical' - ? 'refactor' - : 'feature', - status: 'backlog', - }, - } - ); - return { - success: result.success, - featureId: result.feature?.id, - error: result.error, - }; - }, + addSuggestionToBoard: ( + projectPath: string, + suggestion: AnalysisSuggestion + ): Promise<{ success: boolean; featureId?: string; error?: string }> => + this.post('/api/ideation/add-suggestion', { projectPath, suggestion }), + + getPrompts: () => this.get('/api/ideation/prompts'), onStream: (callback: (event: any) => void): (() => void) => { return this.subscribeToEvent('ideation:stream', callback as EventCallback); diff --git a/libs/types/src/event.ts b/libs/types/src/event.ts index 091b3e90..6692f0f0 100644 --- a/libs/types/src/event.ts +++ b/libs/types/src/event.ts @@ -35,6 +35,10 @@ export type EventType = | 'ideation:analysis-progress' | 'ideation:analysis-complete' | 'ideation:analysis-error' - | 'ideation:suggestions'; + | 'ideation:suggestions' + | 'ideation:idea-created' + | 'ideation:idea-updated' + | 'ideation:idea-deleted' + | 'ideation:idea-converted'; export type EventCallback = (type: EventType, payload: unknown) => void;