import { useEffect, useRef } from 'react'; import { getElectronAPI } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; interface UseBoardEffectsProps { currentProject: { path: string; id: string } | null; specCreatingForProject: string | null; setSpecCreatingForProject: (path: string | null) => void; setSuggestionsCount: (count: number) => void; setFeatureSuggestions: (suggestions: any[]) => void; setIsGeneratingSuggestions: (generating: boolean) => void; checkContextExists: (featureId: string) => Promise; features: any[]; isLoading: boolean; featuresWithContext: Set; setFeaturesWithContext: (set: Set) => void; } export function useBoardEffects({ currentProject, specCreatingForProject, setSpecCreatingForProject, setSuggestionsCount, setFeatureSuggestions, setIsGeneratingSuggestions, checkContextExists, features, isLoading, featuresWithContext, setFeaturesWithContext, }: UseBoardEffectsProps) { // Keep a ref to the current featuresWithContext for use in event handlers const featuresWithContextRef = useRef(featuresWithContext); useEffect(() => { featuresWithContextRef.current = featuresWithContext; }, [featuresWithContext]); // Make current project available globally for modal useEffect(() => { if (currentProject) { (window as any).__currentProject = currentProject; } return () => { (window as any).__currentProject = null; }; }, [currentProject]); // Listen for suggestions events to update count (persists even when dialog is closed) useEffect(() => { const api = getElectronAPI(); if (!api?.suggestions) return; const unsubscribe = api.suggestions.onEvent((event) => { if (event.type === 'suggestions_complete' && event.suggestions) { setSuggestionsCount(event.suggestions.length); setFeatureSuggestions(event.suggestions); setIsGeneratingSuggestions(false); } else if (event.type === 'suggestions_error') { setIsGeneratingSuggestions(false); } }); return () => { unsubscribe(); }; }, [setSuggestionsCount, setFeatureSuggestions, setIsGeneratingSuggestions]); // Subscribe to spec regeneration events to clear creating state on completion useEffect(() => { const api = getElectronAPI(); if (!api.specRegeneration) return; const unsubscribe = api.specRegeneration.onEvent((event) => { console.log( '[BoardView] Spec regeneration event:', event.type, 'for project:', event.projectPath ); if (event.projectPath !== specCreatingForProject) { return; } if (event.type === 'spec_regeneration_complete') { setSpecCreatingForProject(null); } else if (event.type === 'spec_regeneration_error') { setSpecCreatingForProject(null); } }); return () => { unsubscribe(); }; }, [specCreatingForProject, setSpecCreatingForProject]); // Sync running tasks from electron backend on mount useEffect(() => { if (!currentProject) return; const syncRunningTasks = async () => { try { const api = getElectronAPI(); if (!api?.autoMode?.status) return; const status = await api.autoMode.status(currentProject.path); if (status.success) { const projectId = currentProject.id; const { clearRunningTasks, addRunningTask } = useAppStore.getState(); if (status.runningFeatures) { console.log('[Board] Syncing running tasks from backend:', status.runningFeatures); clearRunningTasks(projectId); status.runningFeatures.forEach((featureId: string) => { addRunningTask(projectId, featureId); }); } } } catch (error) { console.error('[Board] Failed to sync running tasks:', error); } }; syncRunningTasks(); }, [currentProject]); // Check which features have context files useEffect(() => { const checkAllContexts = async () => { const featuresWithPotentialContext = features.filter( (f) => f.status === 'in_progress' || f.status === 'waiting_approval' || f.status === 'verified' ); const contextChecks = await Promise.all( featuresWithPotentialContext.map(async (f) => ({ id: f.id, hasContext: await checkContextExists(f.id), })) ); const newSet = new Set(); contextChecks.forEach(({ id, hasContext }) => { if (hasContext) { newSet.add(id); } }); setFeaturesWithContext(newSet); }; if (features.length > 0 && !isLoading) { checkAllContexts(); } }, [features, isLoading, checkContextExists, setFeaturesWithContext]); // Re-check context when a feature stops, completes, or errors // This ensures hasContext is updated even if the features array doesn't change useEffect(() => { const api = getElectronAPI(); if (!api?.autoMode) return; const unsubscribe = api.autoMode.onEvent(async (event) => { // When a feature stops (error/abort) or completes, re-check its context if ( (event.type === 'auto_mode_error' || event.type === 'auto_mode_feature_complete') && event.featureId ) { const hasContext = await checkContextExists(event.featureId); if (hasContext) { const newSet = new Set(featuresWithContextRef.current); newSet.add(event.featureId); setFeaturesWithContext(newSet); } } }); return () => { unsubscribe(); }; }, [checkContextExists, setFeaturesWithContext]); }