diff --git a/auto-claude-ui/src/main/ipc-handlers/roadmap-handlers.ts b/auto-claude-ui/src/main/ipc-handlers/roadmap-handlers.ts index 30d2ee7e..154d95ac 100644 --- a/auto-claude-ui/src/main/ipc-handlers/roadmap-handlers.ts +++ b/auto-claude-ui/src/main/ipc-handlers/roadmap-handlers.ts @@ -160,6 +160,16 @@ export function registerRoadmapHandlers( } ); + // Get roadmap generation status - allows frontend to query if generation is running + ipcMain.handle( + IPC_CHANNELS.ROADMAP_GET_STATUS, + async (_, projectId: string): Promise> => { + const isRunning = agentManager.isRoadmapRunning(projectId); + debugLog('[Roadmap Handler] Get status:', { projectId, isRunning }); + return { success: true, data: { isRunning } }; + } + ); + ipcMain.on( IPC_CHANNELS.ROADMAP_GENERATE, (_, projectId: string, enableCompetitorAnalysis?: boolean) => { diff --git a/auto-claude-ui/src/preload/api/modules/roadmap-api.ts b/auto-claude-ui/src/preload/api/modules/roadmap-api.ts index 1290e1e8..5704d2b9 100644 --- a/auto-claude-ui/src/preload/api/modules/roadmap-api.ts +++ b/auto-claude-ui/src/preload/api/modules/roadmap-api.ts @@ -14,6 +14,7 @@ import { createIpcListener, invokeIpc, sendIpc, IpcListenerCleanup } from './ipc export interface RoadmapAPI { // Operations getRoadmap: (projectId: string) => Promise>; + getRoadmapStatus: (projectId: string) => Promise>; saveRoadmap: (projectId: string, roadmap: Roadmap) => Promise; generateRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean) => void; refreshRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean) => void; @@ -51,6 +52,9 @@ export const createRoadmapAPI = (): RoadmapAPI => ({ getRoadmap: (projectId: string): Promise> => invokeIpc(IPC_CHANNELS.ROADMAP_GET, projectId), + getRoadmapStatus: (projectId: string): Promise> => + invokeIpc(IPC_CHANNELS.ROADMAP_GET_STATUS, projectId), + saveRoadmap: (projectId: string, roadmap: Roadmap): Promise => invokeIpc(IPC_CHANNELS.ROADMAP_SAVE, projectId, roadmap), diff --git a/auto-claude-ui/src/renderer/components/roadmap/hooks.ts b/auto-claude-ui/src/renderer/components/roadmap/hooks.ts index 171c80f2..7315d97a 100644 --- a/auto-claude-ui/src/renderer/components/roadmap/hooks.ts +++ b/auto-claude-ui/src/renderer/components/roadmap/hooks.ts @@ -1,9 +1,18 @@ import { useEffect, useState } from 'react'; import { useRoadmapStore, loadRoadmap, generateRoadmap, refreshRoadmap, stopRoadmap } from '../../stores/roadmap-store'; +import { useTaskStore } from '../../stores/task-store'; import type { RoadmapFeature } from '../../../shared/types'; /** * Hook to manage roadmap data and loading + * + * When the projectId changes, this hook: + * 1. Loads the new project's roadmap data + * 2. Queries the backend to check if generation is running for this project + * 3. Restores the generation status UI state accordingly + * + * NOTE: Generation continues in the background when switching projects. + * The loadRoadmap function queries the backend to restore the correct UI state. */ export function useRoadmapData(projectId: string) { const roadmap = useRoadmapStore((state) => state.roadmap); @@ -11,6 +20,9 @@ export function useRoadmapData(projectId: string) { const generationStatus = useRoadmapStore((state) => state.generationStatus); useEffect(() => { + // Load roadmap data and query generation status for this project + // The loadRoadmap function handles checking if generation is running + // and restores the UI state accordingly loadRoadmap(projectId); }, [projectId]); @@ -26,6 +38,7 @@ export function useRoadmapData(projectId: string) { */ export function useFeatureActions() { const updateFeatureLinkedSpec = useRoadmapStore((state) => state.updateFeatureLinkedSpec); + const addTask = useTaskStore((state) => state.addTask); const convertFeatureToSpec = async ( projectId: string, @@ -35,6 +48,10 @@ export function useFeatureActions() { ) => { const result = await window.electronAPI.convertFeatureToSpec(projectId, feature.id); if (result.success && result.data) { + // Add the created task to the task store so it appears in the kanban immediately + addTask(result.data); + + // Update the roadmap feature with the linked spec updateFeatureLinkedSpec(feature.id, result.data.specId); if (selectedFeature?.id === feature.id) { setSelectedFeature({ diff --git a/auto-claude-ui/src/renderer/lib/browser-mock.ts b/auto-claude-ui/src/renderer/lib/browser-mock.ts index 3600a6f4..1ed95486 100644 --- a/auto-claude-ui/src/renderer/lib/browser-mock.ts +++ b/auto-claude-ui/src/renderer/lib/browser-mock.ts @@ -53,6 +53,11 @@ const browserMockAPI: ElectronAPI = { data: null }), + getRoadmapStatus: async () => ({ + success: true, + data: { isRunning: false } + }), + saveRoadmap: async () => ({ success: true }), diff --git a/auto-claude-ui/src/renderer/stores/roadmap-store.ts b/auto-claude-ui/src/renderer/stores/roadmap-store.ts index bed99ebc..822483df 100644 --- a/auto-claude-ui/src/renderer/stores/roadmap-store.ts +++ b/auto-claude-ui/src/renderer/stores/roadmap-store.ts @@ -12,11 +12,13 @@ interface RoadmapState { roadmap: Roadmap | null; competitorAnalysis: CompetitorAnalysis | null; generationStatus: RoadmapGenerationStatus; + currentProjectId: string | null; // Track which project we're viewing/generating for // Actions setRoadmap: (roadmap: Roadmap | null) => void; setCompetitorAnalysis: (analysis: CompetitorAnalysis | null) => void; setGenerationStatus: (status: RoadmapGenerationStatus) => void; + setCurrentProjectId: (projectId: string | null) => void; updateFeatureStatus: (featureId: string, status: RoadmapFeatureStatus) => void; updateFeatureLinkedSpec: (featureId: string, specId: string) => void; clearRoadmap: () => void; @@ -37,6 +39,7 @@ export const useRoadmapStore = create((set) => ({ roadmap: null, competitorAnalysis: null, generationStatus: initialGenerationStatus, + currentProjectId: null, // Actions setRoadmap: (roadmap) => set({ roadmap }), @@ -45,6 +48,8 @@ export const useRoadmapStore = create((set) => ({ setGenerationStatus: (status) => set({ generationStatus: status }), + setCurrentProjectId: (projectId) => set({ currentProjectId: projectId }), + updateFeatureStatus: (featureId, status) => set((state) => { if (!state.roadmap) return state; @@ -85,7 +90,8 @@ export const useRoadmapStore = create((set) => ({ set({ roadmap: null, competitorAnalysis: null, - generationStatus: initialGenerationStatus + generationStatus: initialGenerationStatus, + currentProjectId: null }), // Reorder features within a phase @@ -159,9 +165,34 @@ export const useRoadmapStore = create((set) => ({ // Helper functions for loading roadmap export async function loadRoadmap(projectId: string): Promise { + const store = useRoadmapStore.getState(); + + // Always set current project ID first - this ensures event handlers + // only process events for the currently viewed project + store.setCurrentProjectId(projectId); + + // Query if roadmap generation is currently running for this project + // This restores the generation status when switching back to a project + const statusResult = await window.electronAPI.getRoadmapStatus(projectId); + if (statusResult.success && statusResult.data?.isRunning) { + // Generation is running - restore the UI state to show progress + // The actual progress will be updated by incoming events + store.setGenerationStatus({ + phase: 'analyzing', + progress: 0, + message: 'Roadmap generation in progress...' + }); + } else { + // Generation is not running - reset to idle + store.setGenerationStatus({ + phase: 'idle', + progress: 0, + message: '' + }); + } + const result = await window.electronAPI.getRoadmap(projectId); if (result.success && result.data) { - const store = useRoadmapStore.getState(); store.setRoadmap(result.data); // Extract and set competitor analysis separately if present if (result.data.competitorAnalysis) { @@ -170,7 +201,6 @@ export async function loadRoadmap(projectId: string): Promise { store.setCompetitorAnalysis(null); } } else { - const store = useRoadmapStore.getState(); store.setRoadmap(null); store.setCompetitorAnalysis(null); } diff --git a/auto-claude-ui/src/shared/constants/ipc.ts b/auto-claude-ui/src/shared/constants/ipc.ts index 763152de..48c5f4f8 100644 --- a/auto-claude-ui/src/shared/constants/ipc.ts +++ b/auto-claude-ui/src/shared/constants/ipc.ts @@ -116,6 +116,7 @@ export const IPC_CHANNELS = { // Roadmap operations ROADMAP_GET: 'roadmap:get', + ROADMAP_GET_STATUS: 'roadmap:getStatus', ROADMAP_SAVE: 'roadmap:save', ROADMAP_GENERATE: 'roadmap:generate', ROADMAP_GENERATE_WITH_COMPETITOR: 'roadmap:generateWithCompetitor', diff --git a/auto-claude-ui/src/shared/types/ipc.ts b/auto-claude-ui/src/shared/types/ipc.ts index b86f3bd4..050efe6c 100644 --- a/auto-claude-ui/src/shared/types/ipc.ts +++ b/auto-claude-ui/src/shared/types/ipc.ts @@ -237,6 +237,7 @@ export interface ElectronAPI { // Roadmap operations getRoadmap: (projectId: string) => Promise>; + getRoadmapStatus: (projectId: string) => Promise>; saveRoadmap: (projectId: string, roadmap: Roadmap) => Promise; generateRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean) => void; refreshRoadmap: (projectId: string, enableCompetitorAnalysis?: boolean) => void;