import { useState, useEffect, useCallback } from 'react' import { useQueryClient, useQuery } from '@tanstack/react-query' import { useProjects, useFeatures, useAgentStatus, useSettings } from './hooks/useProjects' import { useProjectWebSocket } from './hooks/useWebSocket' import { useFeatureSound } from './hooks/useFeatureSound' import { useCelebration } from './hooks/useCelebration' import { ProjectSelector } from './components/ProjectSelector' import { KanbanBoard } from './components/KanbanBoard' import { AgentControl } from './components/AgentControl' import { ProgressDashboard } from './components/ProgressDashboard' import { SetupWizard } from './components/SetupWizard' import { AddFeatureForm } from './components/AddFeatureForm' import { FeatureModal } from './components/FeatureModal' import { DebugLogViewer, type TabType } from './components/DebugLogViewer' import { AgentThought } from './components/AgentThought' import { AgentMissionControl } from './components/AgentMissionControl' import { CelebrationOverlay } from './components/CelebrationOverlay' import { AssistantFAB } from './components/AssistantFAB' import { AssistantPanel } from './components/AssistantPanel' import { ExpandProjectModal } from './components/ExpandProjectModal' import { SettingsModal } from './components/SettingsModal' import { DevServerControl } from './components/DevServerControl' import { ViewToggle, type ViewMode } from './components/ViewToggle' import { DependencyGraph } from './components/DependencyGraph' import { KeyboardShortcutsHelp } from './components/KeyboardShortcutsHelp' import { getDependencyGraph } from './lib/api' import { Loader2, Settings, Moon, Sun } from 'lucide-react' import type { Feature } from './lib/types' const STORAGE_KEY = 'autocoder-selected-project' const DARK_MODE_KEY = 'autocoder-dark-mode' const VIEW_MODE_KEY = 'autocoder-view-mode' function App() { // Initialize selected project from localStorage const [selectedProject, setSelectedProject] = useState(() => { try { return localStorage.getItem(STORAGE_KEY) } catch { return null } }) const [showAddFeature, setShowAddFeature] = useState(false) const [showExpandProject, setShowExpandProject] = useState(false) const [selectedFeature, setSelectedFeature] = useState(null) const [setupComplete, setSetupComplete] = useState(true) // Start optimistic const [debugOpen, setDebugOpen] = useState(false) const [debugPanelHeight, setDebugPanelHeight] = useState(288) // Default height const [debugActiveTab, setDebugActiveTab] = useState('agent') const [assistantOpen, setAssistantOpen] = useState(false) const [showSettings, setShowSettings] = useState(false) const [showKeyboardHelp, setShowKeyboardHelp] = useState(false) const [isSpecCreating, setIsSpecCreating] = useState(false) const [darkMode, setDarkMode] = useState(() => { try { return localStorage.getItem(DARK_MODE_KEY) === 'true' } catch { return false } }) const [viewMode, setViewMode] = useState(() => { try { const stored = localStorage.getItem(VIEW_MODE_KEY) return (stored === 'graph' ? 'graph' : 'kanban') as ViewMode } catch { return 'kanban' } }) const queryClient = useQueryClient() const { data: projects, isLoading: projectsLoading } = useProjects() const { data: features } = useFeatures(selectedProject) const { data: settings } = useSettings() useAgentStatus(selectedProject) // Keep polling for status updates const wsState = useProjectWebSocket(selectedProject) // Fetch graph data when in graph view const { data: graphData } = useQuery({ queryKey: ['dependencyGraph', selectedProject], queryFn: () => getDependencyGraph(selectedProject!), enabled: !!selectedProject && viewMode === 'graph', refetchInterval: 5000, // Refresh every 5 seconds }) // Apply dark mode class to document useEffect(() => { if (darkMode) { document.documentElement.classList.add('dark') } else { document.documentElement.classList.remove('dark') } try { localStorage.setItem(DARK_MODE_KEY, String(darkMode)) } catch { // localStorage not available } }, [darkMode]) // Persist view mode to localStorage useEffect(() => { try { localStorage.setItem(VIEW_MODE_KEY, viewMode) } catch { // localStorage not available } }, [viewMode]) // Play sounds when features move between columns useFeatureSound(features) // Celebrate when all features are complete useCelebration(features, selectedProject) // Persist selected project to localStorage const handleSelectProject = useCallback((project: string | null) => { setSelectedProject(project) try { if (project) { localStorage.setItem(STORAGE_KEY, project) } else { localStorage.removeItem(STORAGE_KEY) } } catch { // localStorage not available } }, []) // Handle graph node click - memoized to prevent DependencyGraph re-renders const handleGraphNodeClick = useCallback((nodeId: number) => { const allFeatures = [ ...(features?.pending ?? []), ...(features?.in_progress ?? []), ...(features?.done ?? []) ] const feature = allFeatures.find(f => f.id === nodeId) if (feature) setSelectedFeature(feature) }, [features]) // Validate stored project exists (clear if project was deleted) useEffect(() => { if (selectedProject && projects && !projects.some(p => p.name === selectedProject)) { handleSelectProject(null) } }, [selectedProject, projects, handleSelectProject]) // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Ignore if user is typing in an input if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { return } // D : Toggle debug window if (e.key === 'd' || e.key === 'D') { e.preventDefault() setDebugOpen(prev => !prev) } // T : Toggle terminal tab in debug panel if (e.key === 't' || e.key === 'T') { e.preventDefault() if (!debugOpen) { // If panel is closed, open it and switch to terminal tab setDebugOpen(true) setDebugActiveTab('terminal') } else if (debugActiveTab === 'terminal') { // If already on terminal tab, close the panel setDebugOpen(false) } else { // If open but on different tab, switch to terminal setDebugActiveTab('terminal') } } // N : Add new feature (when project selected) if ((e.key === 'n' || e.key === 'N') && selectedProject) { e.preventDefault() setShowAddFeature(true) } // E : Expand project with AI (when project selected and has features) if ((e.key === 'e' || e.key === 'E') && selectedProject && features && (features.pending.length + features.in_progress.length + features.done.length) > 0) { e.preventDefault() setShowExpandProject(true) } // A : Toggle assistant panel (when project selected and not in spec creation) if ((e.key === 'a' || e.key === 'A') && selectedProject && !isSpecCreating) { e.preventDefault() setAssistantOpen(prev => !prev) } // , : Open settings if (e.key === ',') { e.preventDefault() setShowSettings(true) } // G : Toggle between Kanban and Graph view (when project selected) if ((e.key === 'g' || e.key === 'G') && selectedProject) { e.preventDefault() setViewMode(prev => prev === 'kanban' ? 'graph' : 'kanban') } // ? : Show keyboard shortcuts help if (e.key === '?') { e.preventDefault() setShowKeyboardHelp(true) } // Escape : Close modals if (e.key === 'Escape') { if (showKeyboardHelp) { setShowKeyboardHelp(false) } else if (showExpandProject) { setShowExpandProject(false) } else if (showSettings) { setShowSettings(false) } else if (assistantOpen) { setAssistantOpen(false) } else if (showAddFeature) { setShowAddFeature(false) } else if (selectedFeature) { setSelectedFeature(null) } else if (debugOpen) { setDebugOpen(false) } } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [selectedProject, showAddFeature, showExpandProject, selectedFeature, debugOpen, debugActiveTab, assistantOpen, features, showSettings, showKeyboardHelp, isSpecCreating, viewMode]) // Combine WebSocket progress with feature data const progress = wsState.progress.total > 0 ? wsState.progress : { passing: features?.done.length ?? 0, total: (features?.pending.length ?? 0) + (features?.in_progress.length ?? 0) + (features?.done.length ?? 0), percentage: 0, } if (progress.total > 0 && progress.percentage === 0) { progress.percentage = Math.round((progress.passing / progress.total) * 100 * 10) / 10 } if (!setupComplete) { return setSetupComplete(true)} /> } return (
{/* Header */}
{/* Logo and Title */}

AutoCoder

{/* Controls */}
{selectedProject && ( <> {/* GLM Mode Badge */} {settings?.glm_mode && ( GLM )} )} {/* Dark mode toggle - always visible */}
{/* Main Content */}
{!selectedProject ? (

Welcome to AutoCoder

Select a project from the dropdown above or create a new one to get started.

) : (
{/* Progress Dashboard */} {/* Agent Mission Control - shows active agents in parallel mode */} {wsState.activeAgents.length > 0 && ( )} {/* Agent Thought - shows latest agent narrative (single agent mode) */} {wsState.activeAgents.length === 0 && ( )} {/* Initializing Features State - show when agent is running but no features yet */} {features && features.pending.length === 0 && features.in_progress.length === 0 && features.done.length === 0 && wsState.agentStatus === 'running' && (

Initializing Features...

The agent is reading your spec and creating features. This may take a moment.

)} {/* View Toggle - only show when there are features */} {features && (features.pending.length + features.in_progress.length + features.done.length) > 0 && (
)} {/* Kanban Board or Dependency Graph based on view mode */} {viewMode === 'kanban' ? ( setShowAddFeature(true)} onExpandProject={() => setShowExpandProject(true)} activeAgents={wsState.activeAgents} /> ) : (
{graphData ? ( ) : (
)}
)}
)}
{/* Add Feature Modal */} {showAddFeature && selectedProject && ( setShowAddFeature(false)} /> )} {/* Feature Detail Modal */} {selectedFeature && selectedProject && ( setSelectedFeature(null)} /> )} {/* Expand Project Modal - AI-powered bulk feature creation */} {showExpandProject && selectedProject && ( setShowExpandProject(false)} onFeaturesAdded={() => { // Invalidate features query to refresh the kanban board queryClient.invalidateQueries({ queryKey: ['features', selectedProject] }) }} /> )} {/* Debug Log Viewer - fixed to bottom */} {selectedProject && ( setDebugOpen(!debugOpen)} onClear={wsState.clearLogs} onClearDevLogs={wsState.clearDevLogs} onHeightChange={setDebugPanelHeight} projectName={selectedProject} activeTab={debugActiveTab} onTabChange={setDebugActiveTab} /> )} {/* Assistant FAB and Panel - hide when expand modal or spec creation is open */} {selectedProject && !showExpandProject && !isSpecCreating && ( <> setAssistantOpen(!assistantOpen)} isOpen={assistantOpen} /> setAssistantOpen(false)} /> )} {/* Settings Modal */} {showSettings && ( setShowSettings(false)} /> )} {/* Keyboard Shortcuts Help */} {showKeyboardHelp && ( setShowKeyboardHelp(false)} /> )} {/* Celebration Overlay - shows when a feature is completed by an agent */} {wsState.celebration && ( )}
) } export default App