diff --git a/app/electron/auto-mode-service.js b/app/electron/auto-mode-service.js index 20c75246..cedd6fd6 100644 --- a/app/electron/auto-mode-service.js +++ b/app/electron/auto-mode-service.js @@ -391,8 +391,7 @@ class AutoModeService { featureId, "waiting_approval", projectPath, - null, // no summary - error.message // pass error message + { error: error.message } ); } catch (statusError) { console.error("[AutoMode] Failed to update feature status after error:", statusError); @@ -495,8 +494,7 @@ class AutoModeService { featureId, "waiting_approval", projectPath, - null, // no summary - error.message // pass error message + { error: error.message } ); } catch (statusError) { console.error("[AutoMode] Failed to update feature status after error:", statusError); @@ -662,8 +660,7 @@ class AutoModeService { featureId, "waiting_approval", projectPath, - null, // no summary - error.message // pass error message + { error: error.message } ); } catch (statusError) { console.error("[AutoMode] Failed to update feature status after error:", statusError); @@ -859,8 +856,7 @@ class AutoModeService { featureId, "waiting_approval", projectPath, - null, // no summary - error.message // pass error message + { error: error.message } ); } catch (statusError) { console.error("[AutoMode] Failed to update feature status after error:", statusError); @@ -1102,8 +1098,7 @@ class AutoModeService { featureId, "waiting_approval", projectPath, - null, // no summary - error.message // pass error message + { error: error.message } ); } catch (statusError) { console.error("[AutoMode] Failed to update feature status after error:", statusError); diff --git a/app/electron/main.js b/app/electron/main.js index a19555f0..5fc2b0a1 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -898,7 +898,7 @@ ipcMain.handle( featureId, status, projectPath, - summary + { summary } ); // Notify renderer if window is available @@ -1170,6 +1170,7 @@ ipcMain.handle("spec-regeneration:status", () => { isRunning: specRegenerationExecution !== null && specRegenerationExecution.isActive(), + currentPhase: specRegenerationService.getCurrentPhase(), }; }); diff --git a/app/electron/services/feature-loader.js b/app/electron/services/feature-loader.js index d259bbf1..d9b69404 100644 --- a/app/electron/services/feature-loader.js +++ b/app/electron/services/feature-loader.js @@ -382,13 +382,15 @@ class FeatureLoader { * @param {string} featureId - The ID of the feature to update * @param {string} status - The new status * @param {string} projectPath - Path to the project - * @param {string} [summary] - Optional summary of what was done - * @param {string} [error] - Optional error message if feature errored - * @param {string} [description] - Optional detailed description - * @param {string} [category] - Optional category/phase - * @param {string[]} [steps] - Optional array of implementation steps + * @param {Object} options - Options object for optional parameters + * @param {string} [options.summary] - Optional summary of what was done + * @param {string} [options.error] - Optional error message if feature errored + * @param {string} [options.description] - Optional detailed description + * @param {string} [options.category] - Optional category/phase + * @param {string[]} [options.steps] - Optional array of implementation steps */ - async updateFeatureStatus(featureId, status, projectPath, summary, error, description, category, steps) { + async updateFeatureStatus(featureId, status, projectPath, options = {}) { + const { summary, error, description, category, steps } = options; // Check if feature exists const existingFeature = await this.get(projectPath, featureId); diff --git a/app/electron/services/mcp-server-factory.js b/app/electron/services/mcp-server-factory.js index 4fdb2bd6..4caddfe2 100644 --- a/app/electron/services/mcp-server-factory.js +++ b/app/electron/services/mcp-server-factory.js @@ -53,16 +53,17 @@ class McpServerFactory { finalStatus = "waiting_approval"; } - // Call the provided callback to update feature status with all parameters + // Call the provided callback to update feature status await updateFeatureStatusCallback( args.featureId, finalStatus, projectPath, - args.summary, - undefined, // error - args.description, - args.category, - args.steps + { + summary: args.summary, + description: args.description, + category: args.category, + steps: args.steps, + } ); const statusMessage = finalStatus !== args.status diff --git a/app/electron/services/mcp-server-stdio.js b/app/electron/services/mcp-server-stdio.js index 44cdd6b4..798f12d1 100644 --- a/app/electron/services/mcp-server-stdio.js +++ b/app/electron/services/mcp-server-stdio.js @@ -215,7 +215,7 @@ async function handleToolsCall(params, id) { // Call the update callback via IPC or direct call // Since we're in a separate process, we need to use IPC to communicate back // For now, we'll call the feature loader directly since it has the update method - await featureLoader.updateFeatureStatus(featureId, finalStatus, projectPath, summary); + await featureLoader.updateFeatureStatus(featureId, finalStatus, projectPath, { summary }); const statusMessage = finalStatus !== status ? `Successfully updated feature ${featureId} to status "${finalStatus}" (converted from "${status}" because skipTests=true)${summary ? ` with summary: "${summary}"` : ''}` diff --git a/app/electron/services/spec-regeneration-service.js b/app/electron/services/spec-regeneration-service.js index 9244e62b..a977a0a6 100644 --- a/app/electron/services/spec-regeneration-service.js +++ b/app/electron/services/spec-regeneration-service.js @@ -86,6 +86,15 @@ const APP_SPEC_XML_TEMPLATE = ` class SpecRegenerationService { constructor() { this.runningRegeneration = null; + this.currentPhase = ""; // Tracks current phase for status queries + } + + /** + * Get the current phase of the regeneration process + * @returns {string} Current phase or empty string if not running + */ + getCurrentPhase() { + return this.currentPhase; } /** @@ -107,14 +116,14 @@ class SpecRegenerationService { const abortController = new AbortController(); execution.abortController = abortController; - // Phase tracking - let currentPhase = "initialization"; + // Phase tracking - use instance property for status queries + this.currentPhase = "initialization"; sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] Initializing spec generation process...\n`, + content: `[Phase: ${this.currentPhase}] Initializing spec generation process...\n`, }); - console.log(`[SpecRegeneration] Phase: ${currentPhase}`); + console.log(`[SpecRegeneration] Phase: ${this.currentPhase}`); // Create custom MCP server with UpdateFeatureStatus tool if generating features if (generateFeatures) { @@ -135,12 +144,12 @@ class SpecRegenerationService { } } - currentPhase = "setup"; + this.currentPhase = "setup"; sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] Configuring AI agent and tools...\n`, + content: `[Phase: ${this.currentPhase}] Configuring AI agent and tools...\n`, }); - console.log(`[SpecRegeneration] Phase: ${currentPhase}`); + console.log(`[SpecRegeneration] Phase: ${this.currentPhase}`); // Phase 1: Generate spec WITHOUT UpdateFeatureStatus tool // This prevents features from being created before the spec is complete @@ -160,17 +169,17 @@ class SpecRegenerationService { const prompt = this.buildInitialCreationPrompt(projectOverview); // No feature generation during spec creation - currentPhase = "analysis"; + this.currentPhase = "analysis"; sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] Starting project analysis and spec creation...\n`, + content: `[Phase: ${this.currentPhase}] Starting project analysis and spec creation...\n`, }); - console.log(`[SpecRegeneration] Phase: ${currentPhase} - Starting AI agent query`); + console.log(`[SpecRegeneration] Phase: ${this.currentPhase} - Starting AI agent query`); if (generateFeatures) { sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] Feature generation is enabled - features will be created after spec is complete.\n`, + content: `[Phase: ${this.currentPhase}] Feature generation is enabled - features will be created after spec is complete.\n`, }); console.log("[SpecRegeneration] Feature generation enabled - will create features after spec"); } @@ -253,11 +262,11 @@ class SpecRegenerationService { execution.abortController = null; const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); - currentPhase = "spec_complete"; - console.log(`[SpecRegeneration] Phase: ${currentPhase} - Spec creation completed in ${elapsedTime}s`); + this.currentPhase = "spec_complete"; + console.log(`[SpecRegeneration] Phase: ${this.currentPhase} - Spec creation completed in ${elapsedTime}s`); sendToRenderer({ type: "spec_regeneration_progress", - content: `\n[Phase: ${currentPhase}] ✓ App specification created successfully! (${elapsedTime}s)\n`, + content: `\n[Phase: ${this.currentPhase}] ✓ App specification created successfully! (${elapsedTime}s)\n`, }); if (generateFeatures) { @@ -282,10 +291,10 @@ class SpecRegenerationService { }); } } else { - currentPhase = "complete"; + this.currentPhase = "complete"; sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] All tasks completed!\n`, + content: `[Phase: ${this.currentPhase}] All tasks completed!\n`, }); // Send final completion event @@ -344,16 +353,16 @@ class SpecRegenerationService { */ async generateFeaturesFromSpec(projectPath, sendToRenderer, execution, startTime) { const featureStartTime = Date.now(); - let currentPhase = "feature_generation"; + this.currentPhase = "feature_generation"; console.log(`[SpecRegeneration] ===== Starting Phase 2: Feature Generation =====`); console.log(`[SpecRegeneration] Project path: ${projectPath}`); sendToRenderer({ type: "spec_regeneration_progress", - content: `\n[Phase: ${currentPhase}] Starting feature creation from implementation roadmap...\n`, + content: `\n[Phase: ${this.currentPhase}] Starting feature creation from implementation roadmap...\n`, }); - console.log(`[SpecRegeneration] Phase: ${currentPhase} - Starting feature generation query`); + console.log(`[SpecRegeneration] Phase: ${this.currentPhase} - Starting feature generation query`); try { // Create feature tools server @@ -492,12 +501,12 @@ Use the UpdateFeatureStatus tool to create features with ALL the fields above.`, execution.query = null; execution.abortController = null; - currentPhase = "complete"; + this.currentPhase = "complete"; const featureElapsedTime = ((Date.now() - featureStartTime) / 1000).toFixed(1); const totalElapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); sendToRenderer({ type: "spec_regeneration_progress", - content: `\n[Phase: ${currentPhase}] ✓ All tasks completed! (${totalElapsedTime}s total, ${featureElapsedTime}s for features)\n`, + content: `\n[Phase: ${this.currentPhase}] ✓ All tasks completed! (${totalElapsedTime}s total, ${featureElapsedTime}s for features)\n`, }); sendToRenderer({ type: "spec_regeneration_complete", @@ -653,22 +662,22 @@ Begin by exploring the project structure.`; console.log(`[SpecRegeneration] Project definition length: ${projectDefinition.length} characters`); try { - let currentPhase = "initialization"; + this.currentPhase = "initialization"; sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] Initializing spec regeneration process...\n`, + content: `[Phase: ${this.currentPhase}] Initializing spec regeneration process...\n`, }); - console.log(`[SpecRegeneration] Phase: ${currentPhase}`); + console.log(`[SpecRegeneration] Phase: ${this.currentPhase}`); const abortController = new AbortController(); execution.abortController = abortController; - currentPhase = "setup"; + this.currentPhase = "setup"; sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] Configuring AI agent and tools...\n`, + content: `[Phase: ${this.currentPhase}] Configuring AI agent and tools...\n`, }); - console.log(`[SpecRegeneration] Phase: ${currentPhase}`); + console.log(`[SpecRegeneration] Phase: ${this.currentPhase}`); const options = { model: "claude-sonnet-4-20250514", @@ -686,12 +695,12 @@ Begin by exploring the project structure.`; const prompt = this.buildRegenerationPrompt(projectDefinition); - currentPhase = "regeneration"; + this.currentPhase = "regeneration"; sendToRenderer({ type: "spec_regeneration_progress", - content: `[Phase: ${currentPhase}] Starting spec regeneration...\n`, + content: `[Phase: ${this.currentPhase}] Starting spec regeneration...\n`, }); - console.log(`[SpecRegeneration] Phase: ${currentPhase} - Starting AI agent query`); + console.log(`[SpecRegeneration] Phase: ${this.currentPhase} - Starting AI agent query`); const currentQuery = query({ prompt, options }); execution.query = currentQuery; @@ -725,21 +734,10 @@ Begin by exploring the project structure.`; console.log(`[SpecRegeneration] Tool call #${toolCallCount}: ${toolName}`); console.log(`[SpecRegeneration] Tool input: ${JSON.stringify(toolInput).substring(0, 200)}...`); - // Special handling for UpdateFeatureStatus to show feature creation - if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") { - const featureId = toolInput?.featureId || "unknown"; - const status = toolInput?.status || "unknown"; - const summary = toolInput?.summary || ""; - sendToRenderer({ - type: "spec_regeneration_progress", - content: `\n[Feature Creation] Creating feature "${featureId}" with status "${status}"${summary ? `\n Summary: ${summary}` : ""}\n`, - }); - } else { - sendToRenderer({ - type: "spec_regeneration_progress", - content: `\n[Tool] Using ${toolName}...\n`, - }); - } + sendToRenderer({ + type: "spec_regeneration_progress", + content: `\n[Tool] Using ${toolName}...\n`, + }); sendToRenderer({ type: "spec_regeneration_tool", @@ -755,18 +753,10 @@ Begin by exploring the project structure.`; const resultPreview = result.substring(0, 200).replace(/\n/g, " "); console.log(`[SpecRegeneration] Tool result (${toolName}): ${resultPreview}...`); - // Special handling for UpdateFeatureStatus results - if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") { - sendToRenderer({ - type: "spec_regeneration_progress", - content: `[Feature Creation] ${result}\n`, - }); - } else { - sendToRenderer({ - type: "spec_regeneration_progress", - content: `[Tool Result] ${toolName} completed successfully\n`, - }); - } + sendToRenderer({ + type: "spec_regeneration_progress", + content: `[Tool Result] ${toolName} completed successfully\n`, + }); } else if (msg.type === "error") { const errorMsg = msg.error?.message || JSON.stringify(msg.error); console.error(`[SpecRegeneration] ERROR in query stream: ${errorMsg}`); @@ -791,11 +781,11 @@ Begin by exploring the project structure.`; execution.abortController = null; const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); - currentPhase = "complete"; - console.log(`[SpecRegeneration] Phase: ${currentPhase} - Spec regeneration completed in ${elapsedTime}s`); + this.currentPhase = "complete"; + console.log(`[SpecRegeneration] Phase: ${this.currentPhase} - Spec regeneration completed in ${elapsedTime}s`); sendToRenderer({ type: "spec_regeneration_progress", - content: `\n[Phase: ${currentPhase}] ✓ Spec regeneration complete! (${elapsedTime}s)\n`, + content: `\n[Phase: ${this.currentPhase}] ✓ Spec regeneration complete! (${elapsedTime}s)\n`, }); sendToRenderer({ @@ -867,9 +857,8 @@ When analyzing, look at: - Database configurations and schemas - API structures and patterns -**Feature Storage:** -Features are stored in .automaker/features/{id}/feature.json - each feature has its own folder. -Do NOT manually create feature files. Use the UpdateFeatureStatus tool to manage features. +**Note:** Feature files are stored separately in .automaker/features/{id}/feature.json. +Your task is ONLY to update the app_spec.txt file - feature files will be managed separately. You CAN and SHOULD modify: - .automaker/app_spec.txt (this is your primary target) @@ -1042,6 +1031,7 @@ Begin by exploring the project structure.`; this.runningRegeneration.abortController.abort(); } this.runningRegeneration = null; + this.currentPhase = ""; } } diff --git a/app/src/components/views/spec-view.tsx b/app/src/components/views/spec-view.tsx index 608a25ab..301416d6 100644 --- a/app/src/components/views/spec-view.tsx +++ b/app/src/components/views/spec-view.tsx @@ -105,93 +105,38 @@ export function SpecView() { console.log("[SpecView] Status check on mount:", status); if (status.success && status.isRunning) { - // Something is running - restore state - console.log("[SpecView] Spec generation is running - restoring state"); + // Something is running - restore state using backend's authoritative phase + console.log("[SpecView] Spec generation is running - restoring state", { phase: status.currentPhase }); - // Only restore state if we haven't already done so for this project - // This prevents resetting state when switching tabs if (!stateRestoredRef.current) { setIsCreating(true); setIsRegenerating(true); stateRestoredRef.current = true; } - // Try to extract the current phase from existing logs - let detectedPhase = ""; - if (logsRef.current) { - // Look for the most recent phase in the logs - const phaseMatches = logsRef.current.matchAll(/\[Phase:\s*([^\]]+)\]/g); - const phases = Array.from(phaseMatches); - if (phases.length > 0) { - // Get the last phase mentioned in the logs - detectedPhase = phases[phases.length - 1][1]; - console.log(`[SpecView] Detected phase from logs: ${detectedPhase}`); - } - - // Also check for feature generation indicators in logs - const hasFeatureGeneration = logsRef.current.includes("Feature Generation") || - logsRef.current.includes("Feature Creation") || - logsRef.current.includes("Creating feature") || - logsRef.current.includes("feature_generation"); - - if (hasFeatureGeneration && !detectedPhase) { - detectedPhase = "feature_generation"; - console.log("[SpecView] Detected feature generation from logs"); - } - } - - // Update phase from logs if we found one and don't have a specific phase set - // This allows the phase to update as new events come in - if (detectedPhase) { - setCurrentPhase((prevPhase) => { - // Only update if we don't have a phase or if the detected phase is more recent - if (!prevPhase || prevPhase === "unknown" || prevPhase === "in progress") { - return detectedPhase; - } - return prevPhase; - }); - } else if (!currentPhase) { - // Use a more descriptive default instead of "unknown" + // Use the backend's currentPhase directly - single source of truth + if (status.currentPhase) { + setCurrentPhase(status.currentPhase); + } else { setCurrentPhase("in progress"); } - // Don't clear logs - they may have been persisted + // Add resume message to logs if needed if (!logsRef.current) { const resumeMessage = "[Status] Resumed monitoring existing spec generation process...\n"; logsRef.current = resumeMessage; setLogs(resumeMessage); } else if (!logsRef.current.includes("Resumed monitoring")) { - // Add a resume message to existing logs only if not already present const resumeMessage = "\n[Status] Resumed monitoring existing spec generation process...\n"; logsRef.current = logsRef.current + resumeMessage; setLogs(logsRef.current); } } else if (status.success && !status.isRunning) { - // Check if we might still be in feature generation phase based on logs - const mightBeGeneratingFeatures = logsRef.current && ( - logsRef.current.includes("Feature Generation") || - logsRef.current.includes("Feature Creation") || - logsRef.current.includes("Creating feature") || - logsRef.current.includes("feature_generation") || - currentPhase === "feature_generation" - ); - - if (mightBeGeneratingFeatures && specExists) { - // Spec exists and we might still be generating features - keep state active - console.log("[SpecView] Detected potential feature generation - keeping state active"); - if (!isCreating && !isRegenerating) { - setIsCreating(true); - } - if (currentPhase !== "feature_generation") { - setCurrentPhase("feature_generation"); - } - } else { - // Not running - clear running state - setIsCreating(false); - setIsRegenerating(false); - setCurrentPhase(""); - stateRestoredRef.current = false; - } + // Not running - clear all state + setIsCreating(false); + setIsRegenerating(false); + setCurrentPhase(""); + stateRestoredRef.current = false; } } catch (error) { console.error("[SpecView] Failed to check status:", error); @@ -209,7 +154,7 @@ export function SpecView() { useEffect(() => { const handleVisibilityChange = async () => { if (!document.hidden && currentProject && (isCreating || isRegenerating || isGeneratingFeatures)) { - // Tab became visible and we think we're still generating - verify status + // Tab became visible and we think we're still generating - verify status from backend try { const api = getElectronAPI(); if (!api.specRegeneration) return; @@ -218,45 +163,17 @@ export function SpecView() { console.log("[SpecView] Visibility change - status check:", status); if (!status.isRunning) { - // Not running but we think we are - check if we're truly done - // Look for recent activity in logs (within last 30 seconds worth of content) - const recentLogs = logsRef.current.slice(-5000); // Last ~5000 chars - const hasRecentFeatureActivity = recentLogs.includes("Feature Creation") || - recentLogs.includes("Creating feature") || - recentLogs.match(/\[Feature Creation\].*$/m); - - // Check if we have a completion message or complete phase - const hasCompletion = logsRef.current.includes("All tasks completed") || - logsRef.current.includes("[Complete] All tasks completed") || - logsRef.current.includes("[Phase: complete]"); - - if (hasCompletion || (!hasRecentFeatureActivity && currentPhase !== "feature_generation")) { - // No recent activity and not running - we're done - console.log("[SpecView] Visibility change: Generation appears complete - clearing state"); - setIsCreating(false); - setIsRegenerating(false); - setIsGeneratingFeatures(false); - setCurrentPhase(""); - stateRestoredRef.current = false; - loadSpec(); - } else if (currentPhase === "feature_generation" && !hasRecentFeatureActivity) { - // We were in feature generation but no recent activity - might be done - // Wait a moment and check again - setTimeout(async () => { - if (api.specRegeneration) { - const recheckStatus = await api.specRegeneration.status(); - if (!recheckStatus.isRunning) { - console.log("[SpecView] Re-check after visibility: Still not running - clearing state"); - setIsCreating(false); - setIsRegenerating(false); - setIsGeneratingFeatures(false); - setCurrentPhase(""); - stateRestoredRef.current = false; - loadSpec(); - } - } - }, STATUS_CHECK_INTERVAL_MS); - } + // Backend says not running - clear state + console.log("[SpecView] Visibility change: Backend indicates generation complete - clearing state"); + setIsCreating(false); + setIsRegenerating(false); + setIsGeneratingFeatures(false); + setCurrentPhase(""); + stateRestoredRef.current = false; + loadSpec(); + } else if (status.currentPhase) { + // Still running - update phase from backend + setCurrentPhase(status.currentPhase); } } catch (error) { console.error("[SpecView] Failed to check status on visibility change:", error); @@ -268,7 +185,7 @@ export function SpecView() { return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; - }, [currentProject, isCreating, isRegenerating, isGeneratingFeatures, currentPhase, loadSpec]); + }, [currentProject, isCreating, isRegenerating, isGeneratingFeatures, loadSpec]); // Periodic status check to ensure state stays in sync (only when we think we're running) useEffect(() => { @@ -281,40 +198,22 @@ export function SpecView() { const status = await api.specRegeneration.status(); - // If not running but we think we are, verify we're truly done - if (!status.isRunning && (isCreating || isRegenerating || isGeneratingFeatures)) { - // Check logs for completion indicators - const hasCompletion = logsRef.current.includes("All tasks completed") || - logsRef.current.includes("[Complete] All tasks completed") || - logsRef.current.includes("[Phase: complete]") || - currentPhase === "complete"; - - // Also check if we haven't seen feature activity recently - const recentLogs = logsRef.current.slice(-3000); // Last 3000 chars (more context) - const hasRecentFeatureActivity = recentLogs.includes("Feature Creation") || - recentLogs.includes("Creating feature") || - recentLogs.includes("UpdateFeatureStatus") || - recentLogs.includes("[Tool]") && recentLogs.includes("UpdateFeatureStatus"); - - // If we're in feature_generation phase and not running, we're likely done - // (features are created via tool calls, so when stream ends, they're done) - const isFeatureGenComplete = currentPhase === "feature_generation" && - !hasRecentFeatureActivity; - - if (hasCompletion || isFeatureGenComplete) { - console.log("[SpecView] Periodic check: Generation complete - clearing state", { - hasCompletion, - hasRecentFeatureActivity, - currentPhase, - isFeatureGenComplete - }); - setIsCreating(false); - setIsRegenerating(false); - setIsGeneratingFeatures(false); - setCurrentPhase(""); - stateRestoredRef.current = false; - loadSpec(); - } + if (!status.isRunning) { + // Backend says not running - clear state + console.log("[SpecView] Periodic check: Backend indicates generation complete - clearing state"); + setIsCreating(false); + setIsRegenerating(false); + setIsGeneratingFeatures(false); + setCurrentPhase(""); + stateRestoredRef.current = false; + loadSpec(); + } else if (status.currentPhase && status.currentPhase !== currentPhase) { + // Still running but phase changed - update from backend + console.log("[SpecView] Periodic check: Phase updated from backend", { + old: currentPhase, + new: status.currentPhase + }); + setCurrentPhase(status.currentPhase); } } catch (error) { console.error("[SpecView] Periodic status check error:", error); diff --git a/app/src/lib/electron.ts b/app/src/lib/electron.ts index af8a66a4..0c5e75c5 100644 --- a/app/src/lib/electron.ts +++ b/app/src/lib/electron.ts @@ -1800,6 +1800,7 @@ async function simulateSuggestionsGeneration( // Mock Spec Regeneration state and implementation let mockSpecRegenerationRunning = false; +let mockSpecRegenerationPhase = ""; let mockSpecRegenerationCallbacks: ((event: SpecRegenerationEvent) => void)[] = []; let mockSpecRegenerationTimeout: NodeJS.Timeout | null = null; @@ -1862,6 +1863,7 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI { stop: async () => { mockSpecRegenerationRunning = false; + mockSpecRegenerationPhase = ""; if (mockSpecRegenerationTimeout) { clearTimeout(mockSpecRegenerationTimeout); mockSpecRegenerationTimeout = null; @@ -1873,6 +1875,7 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI { return { success: true, isRunning: mockSpecRegenerationRunning, + currentPhase: mockSpecRegenerationPhase, }; }, @@ -1896,9 +1899,10 @@ async function simulateSpecCreation( projectOverview: string, generateFeatures = true ) { + mockSpecRegenerationPhase = "initialization"; emitSpecRegenerationEvent({ type: "spec_regeneration_progress", - content: "Starting project analysis...\n", + content: "[Phase: initialization] Starting project analysis...\n", }); await new Promise((resolve) => { @@ -1906,6 +1910,7 @@ async function simulateSpecCreation( }); if (!mockSpecRegenerationRunning) return; + mockSpecRegenerationPhase = "setup"; emitSpecRegenerationEvent({ type: "spec_regeneration_tool", tool: "Glob", @@ -1917,9 +1922,10 @@ async function simulateSpecCreation( }); if (!mockSpecRegenerationRunning) return; + mockSpecRegenerationPhase = "analysis"; emitSpecRegenerationEvent({ type: "spec_regeneration_progress", - content: "Detecting tech stack...\n", + content: "[Phase: analysis] Detecting tech stack...\n", }); await new Promise((resolve) => { @@ -1959,12 +1965,14 @@ async function simulateSpecCreation( // The generateFeatures parameter is kept for API compatibility but features // should be created through the features API + mockSpecRegenerationPhase = "complete"; emitSpecRegenerationEvent({ type: "spec_regeneration_complete", - message: "Initial spec creation complete!", + message: "All tasks completed!", }); mockSpecRegenerationRunning = false; + mockSpecRegenerationPhase = ""; mockSpecRegenerationTimeout = null; } @@ -1972,9 +1980,10 @@ async function simulateSpecRegeneration( projectPath: string, projectDefinition: string ) { + mockSpecRegenerationPhase = "initialization"; emitSpecRegenerationEvent({ type: "spec_regeneration_progress", - content: "Starting spec regeneration...\n", + content: "[Phase: initialization] Starting spec regeneration...\n", }); await new Promise((resolve) => { @@ -1982,9 +1991,10 @@ async function simulateSpecRegeneration( }); if (!mockSpecRegenerationRunning) return; + mockSpecRegenerationPhase = "analysis"; emitSpecRegenerationEvent({ type: "spec_regeneration_progress", - content: "Analyzing codebase...\n", + content: "[Phase: analysis] Analyzing codebase...\n", }); await new Promise((resolve) => { @@ -2015,16 +2025,19 @@ async function simulateSpecRegeneration( `; + mockSpecRegenerationPhase = "complete"; emitSpecRegenerationEvent({ type: "spec_regeneration_complete", - message: "Spec regeneration complete!", + message: "All tasks completed!", }); mockSpecRegenerationRunning = false; + mockSpecRegenerationPhase = ""; mockSpecRegenerationTimeout = null; } async function simulateFeatureGeneration(projectPath: string) { + mockSpecRegenerationPhase = "initialization"; emitSpecRegenerationEvent({ type: "spec_regeneration_progress", content: "[Phase: initialization] Starting feature generation from existing app_spec.txt...\n", @@ -2045,9 +2058,10 @@ async function simulateFeatureGeneration(projectPath: string) { }); if (!mockSpecRegenerationRunning) return; + mockSpecRegenerationPhase = "feature_generation"; emitSpecRegenerationEvent({ type: "spec_regeneration_progress", - content: "[Feature Creation] Creating features from roadmap...\n", + content: "[Phase: feature_generation] Creating features from roadmap...\n", }); await new Promise((resolve) => { @@ -2055,6 +2069,7 @@ async function simulateFeatureGeneration(projectPath: string) { }); if (!mockSpecRegenerationRunning) return; + mockSpecRegenerationPhase = "complete"; emitSpecRegenerationEvent({ type: "spec_regeneration_progress", content: "[Phase: complete] All tasks completed!\n", @@ -2066,6 +2081,7 @@ async function simulateFeatureGeneration(projectPath: string) { }); mockSpecRegenerationRunning = false; + mockSpecRegenerationPhase = ""; mockSpecRegenerationTimeout = null; } diff --git a/app/src/types/electron.d.ts b/app/src/types/electron.d.ts index 66d0d13f..a816218c 100644 --- a/app/src/types/electron.d.ts +++ b/app/src/types/electron.d.ts @@ -270,6 +270,7 @@ export interface SpecRegenerationAPI { status: () => Promise<{ success: boolean; isRunning?: boolean; + currentPhase?: string; error?: string; }>;