diff --git a/apps/server/tests/unit/lib/logger.test.ts b/apps/server/tests/unit/lib/logger.test.ts index 56d98a9f..0559d157 100644 --- a/apps/server/tests/unit/lib/logger.test.ts +++ b/apps/server/tests/unit/lib/logger.test.ts @@ -1,5 +1,12 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { LogLevel, createLogger, getLogLevel, setLogLevel } from '@automaker/utils'; +import { + LogLevel, + createLogger, + getLogLevel, + setLogLevel, + setColorsEnabled, + setTimestampsEnabled, +} from '@automaker/utils'; describe('logger.ts', () => { let consoleSpy: { @@ -11,6 +18,9 @@ describe('logger.ts', () => { beforeEach(() => { originalLogLevel = getLogLevel(); + // Disable colors and timestamps for predictable test output + setColorsEnabled(false); + setTimestampsEnabled(false); consoleSpy = { log: vi.spyOn(console, 'log').mockImplementation(() => {}), warn: vi.spyOn(console, 'warn').mockImplementation(() => {}), @@ -51,7 +61,8 @@ describe('logger.ts', () => { logger.info('test message'); - expect(consoleSpy.log).toHaveBeenCalledWith('[TestContext]', 'test message'); + // New format: 'LEVEL [Context]' as first arg, then message + expect(consoleSpy.log).toHaveBeenCalledWith('INFO [TestContext]', 'test message'); }); it('should log error at all log levels', () => { @@ -59,7 +70,7 @@ describe('logger.ts', () => { setLogLevel(LogLevel.ERROR); logger.error('error message'); - expect(consoleSpy.error).toHaveBeenCalledWith('[Test]', 'error message'); + expect(consoleSpy.error).toHaveBeenCalledWith('ERROR [Test]', 'error message'); }); it('should log warn when level is WARN or higher', () => { @@ -71,7 +82,7 @@ describe('logger.ts', () => { setLogLevel(LogLevel.WARN); logger.warn('warn message 2'); - expect(consoleSpy.warn).toHaveBeenCalledWith('[Test]', 'warn message 2'); + expect(consoleSpy.warn).toHaveBeenCalledWith('WARN [Test]', 'warn message 2'); }); it('should log info when level is INFO or higher', () => { @@ -83,7 +94,7 @@ describe('logger.ts', () => { setLogLevel(LogLevel.INFO); logger.info('info message 2'); - expect(consoleSpy.log).toHaveBeenCalledWith('[Test]', 'info message 2'); + expect(consoleSpy.log).toHaveBeenCalledWith('INFO [Test]', 'info message 2'); }); it('should log debug only when level is DEBUG', () => { @@ -95,7 +106,7 @@ describe('logger.ts', () => { setLogLevel(LogLevel.DEBUG); logger.debug('debug message 2'); - expect(consoleSpy.log).toHaveBeenCalledWith('[Test]', '[DEBUG]', 'debug message 2'); + expect(consoleSpy.log).toHaveBeenCalledWith('DEBUG [Test]', 'debug message 2'); }); it('should pass multiple arguments to log functions', () => { @@ -103,7 +114,27 @@ describe('logger.ts', () => { const logger = createLogger('Multi'); logger.info('message', { data: 'value' }, 123); - expect(consoleSpy.log).toHaveBeenCalledWith('[Multi]', 'message', { data: 'value' }, 123); + expect(consoleSpy.log).toHaveBeenCalledWith( + 'INFO [Multi]', + 'message', + { data: 'value' }, + 123 + ); + }); + + it('should include timestamps when enabled', () => { + setTimestampsEnabled(true); + setLogLevel(LogLevel.INFO); + const logger = createLogger('Timestamp'); + + logger.info('test'); + + // First arg should contain ISO timestamp format + const firstArg = consoleSpy.log.mock.calls[0][0]; + expect(firstArg).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z INFO \[Timestamp\]$/); + expect(consoleSpy.log.mock.calls[0][1]).toBe('test'); + + setTimestampsEnabled(false); }); }); }); diff --git a/apps/server/tests/unit/providers/claude-provider.test.ts b/apps/server/tests/unit/providers/claude-provider.test.ts index 90057c3f..228e2f93 100644 --- a/apps/server/tests/unit/providers/claude-provider.test.ts +++ b/apps/server/tests/unit/providers/claude-provider.test.ts @@ -248,9 +248,9 @@ describe('claude-provider.ts', () => { await expect(collectAsyncGenerator(generator)).rejects.toThrow('SDK execution failed'); // Should log error with classification info (via logger) - // Logger format: [Context] message, data + // Logger format: 'ERROR [Context]' message, data const errorCall = consoleErrorSpy.mock.calls[0]; - expect(errorCall[0]).toBe('[ClaudeProvider]'); + expect(errorCall[0]).toMatch(/ERROR.*\[ClaudeProvider\]/); expect(errorCall[1]).toBe('executeQuery() error during execution:'); expect(errorCall[2]).toMatchObject({ type: expect.any(String), diff --git a/apps/server/tests/unit/services/feature-loader.test.ts b/apps/server/tests/unit/services/feature-loader.test.ts index f5f54e81..a86bf0e5 100644 --- a/apps/server/tests/unit/services/feature-loader.test.ts +++ b/apps/server/tests/unit/services/feature-loader.test.ts @@ -144,7 +144,7 @@ describe('feature-loader.ts', () => { expect(result).toHaveLength(1); expect(result[0].id).toBe('feature-2'); expect(consoleSpy).toHaveBeenCalledWith( - '[FeatureLoader]', + expect.stringMatching(/WARN.*\[FeatureLoader\]/), expect.stringContaining("missing required 'id' field") ); @@ -191,7 +191,7 @@ describe('feature-loader.ts', () => { expect(result).toEqual([]); expect(consoleSpy).toHaveBeenCalledWith( - '[FeatureLoader]', + expect.stringMatching(/WARN.*\[FeatureLoader\]/), expect.stringContaining('Failed to parse feature.json') ); @@ -363,7 +363,7 @@ describe('feature-loader.ts', () => { expect(result).toBe(false); expect(consoleSpy).toHaveBeenCalledWith( - '[FeatureLoader]', + expect.stringMatching(/ERROR.*\[FeatureLoader\]/), expect.stringContaining('Failed to delete feature'), expect.objectContaining({ message: 'Permission denied' }) ); diff --git a/apps/ui/package.json b/apps/ui/package.json index c4613dba..af500a71 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -31,6 +31,7 @@ "postinstall": "electron-builder install-app-deps", "preview": "vite preview", "lint": "npx eslint", + "typecheck": "tsc --noEmit", "pretest": "node scripts/kill-test-servers.mjs && node scripts/setup-e2e-fixtures.mjs", "test": "playwright test", "test:headed": "playwright test --headed", diff --git a/apps/ui/src/app.tsx b/apps/ui/src/app.tsx index 479e5ca2..a76d2fc5 100644 --- a/apps/ui/src/app.tsx +++ b/apps/ui/src/app.tsx @@ -1,5 +1,6 @@ import { useState, useCallback } from 'react'; import { RouterProvider } from '@tanstack/react-router'; +import { createLogger } from '@automaker/utils/logger'; import { router } from './utils/router'; import { SplashScreen } from './components/splash-screen'; import { useSettingsMigration } from './hooks/use-settings-migration'; @@ -7,6 +8,8 @@ import { useCursorStatusInit } from './hooks/use-cursor-status-init'; import './styles/global.css'; import './styles/theme-imports'; +const logger = createLogger('App'); + export default function App() { const [showSplash, setShowSplash] = useState(() => { // Only show splash once per session @@ -19,7 +22,7 @@ export default function App() { // Run settings migration on startup (localStorage -> file storage) const migrationState = useSettingsMigration(); if (migrationState.migrated) { - console.log('[App] Settings migrated to file storage'); + logger.info('Settings migrated to file storage'); } // Initialize Cursor CLI status at startup diff --git a/apps/ui/src/components/dialogs/board-background-modal.tsx b/apps/ui/src/components/dialogs/board-background-modal.tsx index c1acdfd9..fef64087 100644 --- a/apps/ui/src/components/dialogs/board-background-modal.tsx +++ b/apps/ui/src/components/dialogs/board-background-modal.tsx @@ -1,5 +1,8 @@ import { useState, useRef, useCallback, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { ImageIcon, Upload, Loader2, Trash2 } from 'lucide-react'; + +const logger = createLogger('BoardBackgroundModal'); import { Sheet, SheetContent, @@ -113,7 +116,7 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa setPreviewImage(null); } } catch (error) { - console.error('Failed to process image:', error); + logger.error('Failed to process image:', error); toast.error('Failed to process image'); setPreviewImage(null); } finally { @@ -185,7 +188,7 @@ export function BoardBackgroundModal({ open, onOpenChange }: BoardBackgroundModa toast.error(result.error || 'Failed to clear background image'); } } catch (error) { - console.error('Failed to clear background:', error); + logger.error('Failed to clear background:', error); toast.error('Failed to clear background'); } finally { setIsProcessing(false); diff --git a/apps/ui/src/components/dialogs/new-project-modal.tsx b/apps/ui/src/components/dialogs/new-project-modal.tsx index 229b3055..dd114bf9 100644 --- a/apps/ui/src/components/dialogs/new-project-modal.tsx +++ b/apps/ui/src/components/dialogs/new-project-modal.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, @@ -29,6 +30,8 @@ import { cn } from '@/lib/utils'; import { useFileBrowser } from '@/contexts/file-browser-context'; import { getDefaultWorkspaceDirectory, saveLastProjectDirectory } from '@/lib/workspace-config'; +const logger = createLogger('NewProjectModal'); + interface ValidationErrors { projectName?: boolean; workspaceDir?: boolean; @@ -78,7 +81,7 @@ export function NewProjectModal({ } }) .catch((error) => { - console.error('Failed to get default workspace directory:', error); + logger.error('Failed to get default workspace directory:', error); }) .finally(() => { setIsLoadingWorkspace(false); diff --git a/apps/ui/src/components/dialogs/sandbox-rejection-screen.tsx b/apps/ui/src/components/dialogs/sandbox-rejection-screen.tsx index 32be56d4..2e830f15 100644 --- a/apps/ui/src/components/dialogs/sandbox-rejection-screen.tsx +++ b/apps/ui/src/components/dialogs/sandbox-rejection-screen.tsx @@ -6,7 +6,10 @@ */ import { useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { ShieldX, RefreshCw, Container, Copy, Check } from 'lucide-react'; + +const logger = createLogger('SandboxRejectionScreen'); import { Button } from '@/components/ui/button'; const DOCKER_COMMAND = 'npm run dev:docker'; @@ -26,7 +29,7 @@ export function SandboxRejectionScreen() { setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { - console.error('Failed to copy:', err); + logger.error('Failed to copy:', err); } }; diff --git a/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx b/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx index 94940257..7b6eab90 100644 --- a/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx +++ b/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx @@ -6,7 +6,10 @@ */ import { useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { ShieldAlert, Copy, Check } from 'lucide-react'; + +const logger = createLogger('SandboxRiskDialog'); import { Dialog, DialogContent, @@ -43,7 +46,7 @@ export function SandboxRiskDialog({ open, onConfirm, onDeny }: SandboxRiskDialog setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { - console.error('Failed to copy:', err); + logger.error('Failed to copy:', err); } }; diff --git a/apps/ui/src/components/layout/sidebar.tsx b/apps/ui/src/components/layout/sidebar.tsx index 0ad8804d..a1d03e87 100644 --- a/apps/ui/src/components/layout/sidebar.tsx +++ b/apps/ui/src/components/layout/sidebar.tsx @@ -1,5 +1,8 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useNavigate, useLocation } from '@tanstack/react-router'; + +const logger = createLogger('Sidebar'); import { cn } from '@/lib/utils'; import { useAppStore, type ThemeMode } from '@/store/app-store'; import { useKeyboardShortcuts, useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts'; @@ -215,7 +218,7 @@ export function Sidebar() { }); } } catch (error) { - console.error('[Sidebar] Failed to open project:', error); + logger.error('Failed to open project:', error); toast.error('Failed to open project', { description: error instanceof Error ? error.message : 'Unknown error', }); diff --git a/apps/ui/src/components/layout/sidebar/hooks/use-project-creation.ts b/apps/ui/src/components/layout/sidebar/hooks/use-project-creation.ts index 6345cf3c..4d713906 100644 --- a/apps/ui/src/components/layout/sidebar/hooks/use-project-creation.ts +++ b/apps/ui/src/components/layout/sidebar/hooks/use-project-creation.ts @@ -1,5 +1,8 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; + +const logger = createLogger('ProjectCreation'); import { initializeProject } from '@/lib/project-init'; import { toast } from 'sonner'; import type { StarterTemplate } from '@/lib/templates'; @@ -82,7 +85,7 @@ export function useProjectCreation({ toast.success('Project created successfully'); } catch (error) { - console.error('[ProjectCreation] Failed to finalize project:', error); + logger.error('Failed to finalize project:', error); toast.error('Failed to initialize project', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -108,7 +111,7 @@ export function useProjectCreation({ // Finalize project setup await finalizeProjectCreation(projectPath, projectName); } catch (error) { - console.error('[ProjectCreation] Failed to create blank project:', error); + logger.error('Failed to create blank project:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -180,7 +183,7 @@ export function useProjectCreation({ description: `Created ${projectName} from ${template.name}`, }); } catch (error) { - console.error('[ProjectCreation] Failed to create from template:', error); + logger.error('Failed to create from template:', error); toast.error('Failed to create project from template', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -252,7 +255,7 @@ export function useProjectCreation({ description: `Created ${projectName} from ${repoUrl}`, }); } catch (error) { - console.error('[ProjectCreation] Failed to create from custom URL:', error); + logger.error('Failed to create from custom URL:', error); toast.error('Failed to create project from URL', { description: error instanceof Error ? error.message : 'Unknown error', }); diff --git a/apps/ui/src/components/layout/sidebar/hooks/use-running-agents.ts b/apps/ui/src/components/layout/sidebar/hooks/use-running-agents.ts index 7431e934..2e88fec5 100644 --- a/apps/ui/src/components/layout/sidebar/hooks/use-running-agents.ts +++ b/apps/ui/src/components/layout/sidebar/hooks/use-running-agents.ts @@ -1,6 +1,9 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; +const logger = createLogger('RunningAgents'); + export function useRunningAgents() { const [runningAgentsCount, setRunningAgentsCount] = useState(0); @@ -15,7 +18,7 @@ export function useRunningAgents() { } } } catch (error) { - console.error('[Sidebar] Error fetching running agents count:', error); + logger.error('Error fetching running agents count:', error); } }, []); diff --git a/apps/ui/src/components/layout/sidebar/hooks/use-setup-dialog.ts b/apps/ui/src/components/layout/sidebar/hooks/use-setup-dialog.ts index 8a94fd18..a9fde54f 100644 --- a/apps/ui/src/components/layout/sidebar/hooks/use-setup-dialog.ts +++ b/apps/ui/src/components/layout/sidebar/hooks/use-setup-dialog.ts @@ -1,5 +1,8 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; + +const logger = createLogger('SetupDialog'); import { toast } from 'sonner'; import type { FeatureCount } from '@/components/views/spec-view/types'; @@ -53,7 +56,7 @@ export function useSetupDialog({ ); if (!result.success) { - console.error('[SetupDialog] Failed to start spec creation:', result.error); + logger.error('Failed to start spec creation:', result.error); setSpecCreatingForProject(null); toast.error('Failed to create specification', { description: result.error, @@ -66,7 +69,7 @@ export function useSetupDialog({ } // If successful, we'll wait for the events to update the state } catch (error) { - console.error('[SetupDialog] Failed to create spec:', error); + logger.error('Failed to create spec:', error); setSpecCreatingForProject(null); toast.error('Failed to create specification', { description: error instanceof Error ? error.message : 'Unknown error', diff --git a/apps/ui/src/components/layout/sidebar/hooks/use-spec-regeneration.ts b/apps/ui/src/components/layout/sidebar/hooks/use-spec-regeneration.ts index 5337a603..88348655 100644 --- a/apps/ui/src/components/layout/sidebar/hooks/use-spec-regeneration.ts +++ b/apps/ui/src/components/layout/sidebar/hooks/use-spec-regeneration.ts @@ -1,5 +1,8 @@ import { useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { toast } from 'sonner'; + +const logger = createLogger('SpecRegeneration'); import { getElectronAPI } from '@/lib/electron'; import type { SpecRegenerationEvent } from '@/types/electron'; @@ -30,16 +33,11 @@ export function useSpecRegeneration({ if (!api.specRegeneration) return; const unsubscribe = api.specRegeneration.onEvent((event: SpecRegenerationEvent) => { - console.log( - '[Sidebar] Spec regeneration event:', - event.type, - 'for project:', - event.projectPath - ); + logger.debug('Spec regeneration event:', event.type, 'for project:', event.projectPath); // Only handle events for the project we're currently setting up if (event.projectPath !== creatingSpecProjectPath && event.projectPath !== setupProjectPath) { - console.log('[Sidebar] Ignoring event - not for project being set up'); + logger.debug('Ignoring event - not for project being set up'); return; } diff --git a/apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts b/apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts index 2112bc37..d539ddbe 100644 --- a/apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts +++ b/apps/ui/src/components/layout/sidebar/hooks/use-trash-operations.ts @@ -1,5 +1,8 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { toast } from 'sonner'; + +const logger = createLogger('TrashOperations'); import { getElectronAPI, type TrashedProject } from '@/lib/electron'; interface UseTrashOperationsProps { @@ -24,7 +27,7 @@ export function useTrashOperations({ description: 'Added back to your project list.', }); } catch (error) { - console.error('[Sidebar] Failed to restore project:', error); + logger.error('Failed to restore project:', error); toast.error('Failed to restore project', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -52,7 +55,7 @@ export function useTrashOperations({ description: trashedProject.path, }); } catch (error) { - console.error('[Sidebar] Failed to delete project from disk:', error); + logger.error('Failed to delete project from disk:', error); toast.error('Failed to delete project folder', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -69,7 +72,7 @@ export function useTrashOperations({ emptyTrash(); toast.success('Recycle bin cleared'); } catch (error) { - console.error('[Sidebar] Failed to empty trash:', error); + logger.error('Failed to empty trash:', error); toast.error('Failed to clear recycle bin', { description: error instanceof Error ? error.message : 'Unknown error', }); diff --git a/apps/ui/src/components/layout/sidebar/hooks/use-unviewed-validations.ts b/apps/ui/src/components/layout/sidebar/hooks/use-unviewed-validations.ts index ac5add46..e7020d9a 100644 --- a/apps/ui/src/components/layout/sidebar/hooks/use-unviewed-validations.ts +++ b/apps/ui/src/components/layout/sidebar/hooks/use-unviewed-validations.ts @@ -1,5 +1,8 @@ import { useState, useEffect, useCallback, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; + +const logger = createLogger('UnviewedValidations'); import type { Project, StoredValidation } from '@/lib/electron'; /** @@ -38,7 +41,7 @@ export function useUnviewedValidations(currentProject: Project | null) { } } } catch (err) { - console.error('[useUnviewedValidations] Failed to load count:', err); + logger.error('Failed to load count:', err); } }, []); diff --git a/apps/ui/src/components/session-manager.tsx b/apps/ui/src/components/session-manager.tsx index f8452aa1..88c31acc 100644 --- a/apps/ui/src/components/session-manager.tsx +++ b/apps/ui/src/components/session-manager.tsx @@ -1,5 +1,8 @@ import { useState, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +const logger = createLogger('SessionManager'); import { Button } from '@/components/ui/button'; import { HotkeyButton } from '@/components/ui/hotkey-button'; import { Input } from '@/components/ui/input'; @@ -126,7 +129,7 @@ export function SessionManager({ } } catch (err) { // Ignore errors for individual session checks - console.warn(`[SessionManager] Failed to check running state for ${session.id}:`, err); + logger.warn(`Failed to check running state for ${session.id}:`, err); } } @@ -227,7 +230,7 @@ export function SessionManager({ const handleArchiveSession = async (sessionId: string) => { const api = getElectronAPI(); if (!api?.sessions) { - console.error('[SessionManager] Sessions API not available'); + logger.error('[SessionManager] Sessions API not available'); return; } @@ -240,10 +243,10 @@ export function SessionManager({ } await loadSessions(); } else { - console.error('[SessionManager] Archive failed:', result.error); + logger.error('[SessionManager] Archive failed:', result.error); } } catch (error) { - console.error('[SessionManager] Archive error:', error); + logger.error('[SessionManager] Archive error:', error); } }; @@ -251,7 +254,7 @@ export function SessionManager({ const handleUnarchiveSession = async (sessionId: string) => { const api = getElectronAPI(); if (!api?.sessions) { - console.error('[SessionManager] Sessions API not available'); + logger.error('[SessionManager] Sessions API not available'); return; } @@ -260,10 +263,10 @@ export function SessionManager({ if (result.success) { await loadSessions(); } else { - console.error('[SessionManager] Unarchive failed:', result.error); + logger.error('[SessionManager] Unarchive failed:', result.error); } } catch (error) { - console.error('[SessionManager] Unarchive error:', error); + logger.error('[SessionManager] Unarchive error:', error); } }; diff --git a/apps/ui/src/components/ui/description-image-dropzone.tsx b/apps/ui/src/components/ui/description-image-dropzone.tsx index 9df5e0e6..2512c80e 100644 --- a/apps/ui/src/components/ui/description-image-dropzone.tsx +++ b/apps/ui/src/components/ui/description-image-dropzone.tsx @@ -1,5 +1,8 @@ import React, { useState, useRef, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { cn } from '@/lib/utils'; + +const logger = createLogger('DescriptionImageDropZone'); import { ImageIcon, X, Loader2, FileText } from 'lucide-react'; import { Textarea } from '@/components/ui/textarea'; import { getElectronAPI } from '@/lib/electron'; @@ -108,7 +111,7 @@ export function DescriptionImageDropZone({ // Check if saveImageToTemp method exists if (!api.saveImageToTemp) { // Fallback path when saveImageToTemp is not available - console.log('[DescriptionImageDropZone] Using fallback path for image'); + logger.info('Using fallback path for image'); return `.automaker/images/${Date.now()}_${filename}`; } @@ -118,10 +121,10 @@ export function DescriptionImageDropZone({ if (result.success && result.path) { return result.path; } - console.error('[DescriptionImageDropZone] Failed to save image:', result.error); + logger.error('Failed to save image:', result.error); return null; } catch (error) { - console.error('[DescriptionImageDropZone] Error saving image:', error); + logger.error('Error saving image:', error); return null; } }, @@ -216,7 +219,7 @@ export function DescriptionImageDropZone({ } if (errors.length > 0) { - console.warn('File upload errors:', errors); + logger.warn('File upload errors:', errors); } if (newImages.length > 0) { diff --git a/apps/ui/src/components/ui/feature-image-upload.tsx b/apps/ui/src/components/ui/feature-image-upload.tsx index 4722502e..ec4ef205 100644 --- a/apps/ui/src/components/ui/feature-image-upload.tsx +++ b/apps/ui/src/components/ui/feature-image-upload.tsx @@ -1,5 +1,8 @@ import React, { useState, useRef, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { cn } from '@/lib/utils'; + +const logger = createLogger('FeatureImageUpload'); import { ImageIcon, X, Upload } from 'lucide-react'; import { fileToBase64, @@ -77,7 +80,7 @@ export function FeatureImageUpload({ } if (errors.length > 0) { - console.warn('Image upload errors:', errors); + logger.warn('Image upload errors:', errors); } if (newImages.length > 0) { diff --git a/apps/ui/src/components/ui/image-drop-zone.tsx b/apps/ui/src/components/ui/image-drop-zone.tsx index 2f8f5c43..cdd7b396 100644 --- a/apps/ui/src/components/ui/image-drop-zone.tsx +++ b/apps/ui/src/components/ui/image-drop-zone.tsx @@ -1,5 +1,8 @@ import React, { useState, useRef, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { cn } from '@/lib/utils'; + +const logger = createLogger('ImageDropZone'); import { ImageIcon, X, Upload } from 'lucide-react'; import type { ImageAttachment } from '@/store/app-store'; import { @@ -88,7 +91,7 @@ export function ImageDropZone({ } if (errors.length > 0) { - console.warn('Image upload errors:', errors); + logger.warn('Image upload errors:', errors); } if (newImages.length > 0) { diff --git a/apps/ui/src/components/ui/task-progress-panel.tsx b/apps/ui/src/components/ui/task-progress-panel.tsx index 12ebe69e..985fff0d 100644 --- a/apps/ui/src/components/ui/task-progress-panel.tsx +++ b/apps/ui/src/components/ui/task-progress-panel.tsx @@ -1,7 +1,10 @@ 'use client'; import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { cn } from '@/lib/utils'; + +const logger = createLogger('TaskProgressPanel'); import { Check, Loader2, Circle, ChevronDown, ChevronRight, FileCode } from 'lucide-react'; import { getElectronAPI } from '@/lib/electron'; import type { AutoModeEvent } from '@/types/electron'; @@ -72,7 +75,7 @@ export function TaskProgressPanel({ setCurrentTaskId(currentId || null); } } catch (error) { - console.error('Failed to load initial tasks:', error); + logger.error('Failed to load initial tasks:', error); } finally { setIsLoading(false); } diff --git a/apps/ui/src/components/views/agent-tools-view.tsx b/apps/ui/src/components/views/agent-tools-view.tsx index 468f7640..4485f165 100644 --- a/apps/ui/src/components/views/agent-tools-view.tsx +++ b/apps/ui/src/components/views/agent-tools-view.tsx @@ -1,4 +1,5 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; @@ -19,6 +20,8 @@ import { import { cn } from '@/lib/utils'; import { getElectronAPI } from '@/lib/electron'; +const logger = createLogger('AgentToolsView'); + interface ToolResult { success: boolean; output?: string; @@ -62,7 +65,7 @@ export function AgentToolsView() { try { // Simulate agent requesting file read - console.log(`[Agent Tool] Requesting to read file: ${readFilePath}`); + logger.info(`[Agent Tool] Requesting to read file: ${readFilePath}`); const result = await api.readFile(readFilePath); @@ -72,14 +75,14 @@ export function AgentToolsView() { output: result.content, timestamp: new Date(), }); - console.log(`[Agent Tool] File read successful: ${readFilePath}`); + logger.info(`[Agent Tool] File read successful: ${readFilePath}`); } else { setReadFileResult({ success: false, error: result.error || 'Failed to read file', timestamp: new Date(), }); - console.log(`[Agent Tool] File read failed: ${result.error}`); + logger.info(`[Agent Tool] File read failed: ${result.error}`); } } catch (error) { setReadFileResult({ @@ -101,7 +104,7 @@ export function AgentToolsView() { try { // Simulate agent requesting file write - console.log(`[Agent Tool] Requesting to write file: ${writeFilePath}`); + logger.info(`[Agent Tool] Requesting to write file: ${writeFilePath}`); const result = await api.writeFile(writeFilePath, writeFileContent); @@ -111,14 +114,14 @@ export function AgentToolsView() { output: `File written successfully: ${writeFilePath}`, timestamp: new Date(), }); - console.log(`[Agent Tool] File write successful: ${writeFilePath}`); + logger.info(`[Agent Tool] File write successful: ${writeFilePath}`); } else { setWriteFileResult({ success: false, error: result.error || 'Failed to write file', timestamp: new Date(), }); - console.log(`[Agent Tool] File write failed: ${result.error}`); + logger.info(`[Agent Tool] File write failed: ${result.error}`); } } catch (error) { setWriteFileResult({ @@ -140,7 +143,7 @@ export function AgentToolsView() { try { // Terminal command simulation for demonstration purposes - console.log(`[Agent Tool] Simulating command: ${terminalCommand}`); + logger.info(`[Agent Tool] Simulating command: ${terminalCommand}`); // Simulated outputs for common commands (preview mode) // In production, the agent executes commands via Claude SDK @@ -165,7 +168,7 @@ export function AgentToolsView() { output: output, timestamp: new Date(), }); - console.log(`[Agent Tool] Command executed successfully: ${terminalCommand}`); + logger.info(`[Agent Tool] Command executed successfully: ${terminalCommand}`); } catch (error) { setTerminalResult({ success: false, diff --git a/apps/ui/src/components/views/agent-view/hooks/use-agent-session.ts b/apps/ui/src/components/views/agent-view/hooks/use-agent-session.ts index 1dca6ec3..fbf773e3 100644 --- a/apps/ui/src/components/views/agent-view/hooks/use-agent-session.ts +++ b/apps/ui/src/components/views/agent-view/hooks/use-agent-session.ts @@ -1,6 +1,9 @@ import { useState, useCallback, useEffect, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; +const logger = createLogger('AgentSession'); + interface UseAgentSessionOptions { projectPath: string | undefined; } @@ -44,7 +47,7 @@ export function useAgentSession({ projectPath }: UseAgentSessionOptions): UseAge const lastSessionId = getLastSelectedSession(projectPath); if (lastSessionId) { - console.log('[AgentView] Restoring last selected session:', lastSessionId); + logger.info('Restoring last selected session:', lastSessionId); setCurrentSessionId(lastSessionId); } }, [projectPath, getLastSelectedSession]); diff --git a/apps/ui/src/components/views/agent-view/hooks/use-file-attachments.ts b/apps/ui/src/components/views/agent-view/hooks/use-file-attachments.ts index 1f7be03c..ee37d749 100644 --- a/apps/ui/src/components/views/agent-view/hooks/use-file-attachments.ts +++ b/apps/ui/src/components/views/agent-view/hooks/use-file-attachments.ts @@ -1,5 +1,8 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import type { ImageAttachment, TextFileAttachment } from '@/store/app-store'; + +const logger = createLogger('FileAttachments'); import { fileToBase64, generateImageId, @@ -138,7 +141,7 @@ export function useFileAttachments({ } if (errors.length > 0) { - console.warn('File upload errors:', errors); + logger.warn('File upload errors:', errors); } if (newImages.length > 0) { diff --git a/apps/ui/src/components/views/analysis-view.tsx b/apps/ui/src/components/views/analysis-view.tsx index 10a976d9..771321d7 100644 --- a/apps/ui/src/components/views/analysis-view.tsx +++ b/apps/ui/src/components/views/analysis-view.tsx @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore, FileTreeNode, ProjectAnalysis } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; @@ -21,6 +22,8 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; +const logger = createLogger('AnalysisView'); + const IGNORE_PATTERNS = [ 'node_modules', '.git', @@ -109,7 +112,7 @@ export function AnalysisView() { return nodes; } catch (error) { - console.error('Failed to scan directory:', path, error); + logger.error('Failed to scan directory:', path, error); return []; } }, @@ -165,7 +168,7 @@ export function AnalysisView() { setProjectAnalysis(analysis); } catch (error) { - console.error('Analysis failed:', error); + logger.error('Analysis failed:', error); } finally { setIsAnalyzing(false); } @@ -373,7 +376,7 @@ ${Object.entries(projectAnalysis.filesByExtension) setSpecError(writeResult.error || 'Failed to write spec file'); } } catch (error) { - console.error('Failed to generate spec:', error); + logger.error('Failed to generate spec:', error); setSpecError(error instanceof Error ? error.message : 'Failed to generate spec'); } finally { setIsGeneratingSpec(false); @@ -644,7 +647,7 @@ ${Object.entries(projectAnalysis.filesByExtension) setFeatureListGenerated(true); } catch (error) { - console.error('Failed to generate feature list:', error); + logger.error('Failed to generate feature list:', error); setFeatureListError( error instanceof Error ? error.message : 'Failed to generate feature list' ); diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 9274c6cf..389c479e 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, useCallback, useMemo, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { PointerSensor, useSensor, @@ -62,6 +63,8 @@ import { // Stable empty array to avoid infinite loop in selector const EMPTY_WORKTREES: ReturnType['getWorktrees']> = []; +const logger = createLogger('Board'); + export function BoardView() { const { currentProject, @@ -188,7 +191,7 @@ export function BoardView() { return result.success && result.exists === true; } catch (error) { - console.error('[Board] Error checking context:', error); + logger.error('Error checking context:', error); return false; } }, @@ -222,7 +225,7 @@ export function BoardView() { setPipelineConfig(currentProject.path, result.config); } } catch (error) { - console.error('[Board] Failed to load pipeline config:', error); + logger.error('Failed to load pipeline config:', error); } }; @@ -288,7 +291,7 @@ export function BoardView() { setBranchSuggestions(localBranches); } } catch (error) { - console.error('[BoardView] Error fetching branches:', error); + logger.error('Error fetching branches:', error); setBranchSuggestions([]); } }; @@ -497,7 +500,7 @@ export function BoardView() { if (newFeature) { await handleStartImplementation(newFeature); } else { - console.error('Could not find newly created feature to start it automatically.'); + logger.error('Could not find newly created feature to start it automatically.'); toast.error('Failed to auto-start feature', { description: 'The feature was created but could not be started automatically.', }); @@ -538,7 +541,7 @@ export function BoardView() { if (newFeature) { await handleStartImplementation(newFeature); } else { - console.error('Could not find newly created feature to start it automatically.'); + logger.error('Could not find newly created feature to start it automatically.'); toast.error('Failed to auto-start feature', { description: 'The feature was created but could not be started automatically.', }); @@ -561,7 +564,7 @@ export function BoardView() { if (newFeature) { await handleStartImplementation(newFeature); } else { - console.error('Could not find newly created feature to start it automatically.'); + logger.error('Could not find newly created feature to start it automatically.'); toast.error('Failed to auto-start feature', { description: 'The feature was created but could not be started automatically.', }); @@ -889,10 +892,10 @@ export function BoardView() { // Reload features from server to ensure sync loadFeatures(); } else { - console.error('[Board] Failed to approve plan:', result.error); + logger.error('Failed to approve plan:', result.error); } } catch (error) { - console.error('[Board] Error approving plan:', error); + logger.error('Error approving plan:', error); } finally { setIsPlanApprovalLoading(false); setPendingPlanApproval(null); @@ -945,10 +948,10 @@ export function BoardView() { // Reload features from server to ensure sync loadFeatures(); } else { - console.error('[Board] Failed to reject plan:', result.error); + logger.error('Failed to reject plan:', result.error); } } catch (error) { - console.error('[Board] Error rejecting plan:', error); + logger.error('Error rejecting plan:', error); } finally { setIsPlanApprovalLoading(false); setPendingPlanApproval(null); @@ -1407,7 +1410,7 @@ export function BoardView() { // Persist changes asynchronously and in parallel Promise.all( featuresToUpdate.map((feature) => persistFeatureUpdate(feature.id, { prUrl })) - ).catch(console.error); + ).catch((err) => logger.error('Error in handleMove:', err)); } setWorktreeRefreshKey((k) => k + 1); setSelectedWorktreeForAction(null); diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index d055c731..f06fbefd 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, @@ -64,6 +65,8 @@ import { } from '@automaker/dependency-resolver'; import { isCursorModel, PROVIDER_PREFIXES } from '@automaker/types'; +const logger = createLogger('AddFeatureDialog'); + type FeatureData = { title: string; category: string; @@ -331,7 +334,7 @@ export function AddFeatureDialog({ toast.error(result?.error || 'Failed to enhance description'); } } catch (error) { - console.error('Enhancement failed:', error); + logger.error('Enhancement failed:', error); toast.error('Failed to enhance description'); } finally { setIsEnhancing(false); diff --git a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx index 9ea5ed32..f0b08dfa 100644 --- a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, @@ -29,6 +30,8 @@ interface AgentOutputModalProps { type ViewMode = 'parsed' | 'raw' | 'changes'; +const logger = createLogger('AgentOutputModal'); + export function AgentOutputModal({ open, onClose, @@ -88,7 +91,7 @@ export function AgentOutputModal({ setOutput(''); } } catch (error) { - console.error('Failed to load output:', error); + logger.error('Failed to load output:', error); setOutput(''); } finally { setIsLoading(false); @@ -105,11 +108,11 @@ export function AgentOutputModal({ const api = getElectronAPI(); if (!api?.autoMode) return; - console.log('[AgentOutputModal] Subscribing to events for featureId:', featureId); + logger.info('Subscribing to events for featureId:', featureId); const unsubscribe = api.autoMode.onEvent((event) => { - console.log( - '[AgentOutputModal] Received event:', + logger.debug( + 'Received event:', event.type, 'featureId:', 'featureId' in event ? event.featureId : 'none', @@ -119,7 +122,7 @@ export function AgentOutputModal({ // Filter events for this specific feature only (skip events without featureId) if ('featureId' in event && event.featureId !== featureId) { - console.log('[AgentOutputModal] Skipping event - featureId mismatch'); + logger.debug('Skipping event - featureId mismatch'); return; } diff --git a/apps/ui/src/components/views/board-view/dialogs/create-branch-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/create-branch-dialog.tsx index 14890233..886cf2f4 100644 --- a/apps/ui/src/components/views/board-view/dialogs/create-branch-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/create-branch-dialog.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, @@ -22,6 +23,8 @@ interface WorktreeInfo { changedFilesCount?: number; } +const logger = createLogger('CreateBranchDialog'); + interface CreateBranchDialogProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -77,7 +80,7 @@ export function CreateBranchDialog({ setError(result.error || 'Failed to create branch'); } } catch (err) { - console.error('Create branch failed:', err); + logger.error('Create branch failed:', err); setError('Failed to create branch'); } finally { setIsCreating(false); diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index ca68f4c7..6cce46b0 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, @@ -57,6 +58,8 @@ import { import { DependencyTreeDialog } from './dependency-tree-dialog'; import { isCursorModel, PROVIDER_PREFIXES } from '@automaker/types'; +const logger = createLogger('EditFeatureDialog'); + interface EditFeatureDialogProps { feature: Feature | null; onClose: () => void; @@ -248,7 +251,7 @@ export function EditFeatureDialog({ toast.error(result?.error || 'Failed to enhance description'); } } catch (error) { - console.error('Enhancement failed:', error); + logger.error('Enhancement failed:', error); toast.error('Failed to enhance description'); } finally { setIsEnhancing(false); diff --git a/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx index db6187d2..f52b7958 100644 --- a/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Dialog, DialogContent, @@ -34,6 +35,8 @@ import { useAppStore, Feature } from '@/store/app-store'; import { toast } from 'sonner'; import { LogViewer } from '@/components/ui/log-viewer'; +const logger = createLogger('FeatureSuggestions'); + interface FeatureSuggestionsDialogProps { open: boolean; onClose: () => void; @@ -176,7 +179,7 @@ export function FeatureSuggestionsDialog({ setIsGenerating(false); } } catch (error) { - console.error('Failed to generate suggestions:', error); + logger.error('Failed to generate suggestions:', error); toast.error('Failed to start generation'); setIsGenerating(false); } @@ -194,7 +197,7 @@ export function FeatureSuggestionsDialog({ setIsGenerating(false); toast.info('Generation stopped'); } catch (error) { - console.error('Failed to stop generation:', error); + logger.error('Failed to stop generation:', error); } }, [setIsGenerating]); @@ -280,7 +283,7 @@ export function FeatureSuggestionsDialog({ onClose(); } catch (error) { - console.error('Failed to import features:', error); + logger.error('Failed to import features:', error); toast.error('Failed to import features'); } finally { setIsImporting(false); diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts index 7daeb92b..a3cade8d 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts @@ -13,6 +13,9 @@ import { toast } from 'sonner'; import { useAutoMode } from '@/hooks/use-auto-mode'; import { truncateDescription } from '@/lib/utils'; import { getBlockingDependencies } from '@automaker/dependency-resolver'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('BoardActions'); interface UseBoardActionsProps { currentProject: { path: string; id: string } | null; @@ -112,8 +115,8 @@ export function useBoardActions({ if (api?.worktree?.create) { const result = await api.worktree.create(currentProject.path, finalBranchName); if (result.success && result.worktree) { - console.log( - `[Board] Worktree for branch "${finalBranchName}" ${ + logger.info( + `Worktree for branch "${finalBranchName}" ${ result.worktree?.isNew ? 'created' : 'already exists' }` ); @@ -125,8 +128,8 @@ export function useBoardActions({ // Refresh worktree list in UI onWorktreeCreated?.(); } else if (!result.success) { - console.error( - `[Board] Failed to create worktree for branch "${finalBranchName}":`, + logger.error( + `Failed to create worktree for branch "${finalBranchName}":`, result.error ); toast.error('Failed to create worktree', { @@ -135,7 +138,7 @@ export function useBoardActions({ } } } catch (error) { - console.error('[Board] Error creating worktree:', error); + logger.error('Error creating worktree:', error); toast.error('Failed to create worktree', { description: error instanceof Error ? error.message : 'An error occurred', }); @@ -180,7 +183,7 @@ export function useBoardActions({ } }) .catch((error) => { - console.error('[Board] Error generating title:', error); + logger.error('Error generating title:', error); // Clear generating flag on error const titleUpdates = { titleGenerating: false }; updateFeature(createdFeature.id, titleUpdates); @@ -229,16 +232,16 @@ export function useBoardActions({ if (api?.worktree?.create) { const result = await api.worktree.create(currentProject.path, finalBranchName); if (result.success) { - console.log( - `[Board] Worktree for branch "${finalBranchName}" ${ + logger.info( + `Worktree for branch "${finalBranchName}" ${ result.worktree?.isNew ? 'created' : 'already exists' }` ); // Refresh worktree list in UI onWorktreeCreated?.(); } else { - console.error( - `[Board] Failed to create worktree for branch "${finalBranchName}":`, + logger.error( + `Failed to create worktree for branch "${finalBranchName}":`, result.error ); toast.error('Failed to create worktree', { @@ -247,7 +250,7 @@ export function useBoardActions({ } } } catch (error) { - console.error('[Board] Error creating worktree:', error); + logger.error('Error creating worktree:', error); toast.error('Failed to create worktree', { description: error instanceof Error ? error.message : 'An error occurred', }); @@ -292,7 +295,7 @@ export function useBoardActions({ description: `Stopped and deleted: ${truncateDescription(feature.description)}`, }); } catch (error) { - console.error('[Board] Error stopping feature before delete:', error); + logger.error('Error stopping feature before delete:', error); toast.error('Failed to stop agent', { description: 'The feature will still be deleted.', }); @@ -305,13 +308,13 @@ export function useBoardActions({ for (const imagePathObj of feature.imagePaths) { try { await api.deleteFile(imagePathObj.path); - console.log(`[Board] Deleted image: ${imagePathObj.path}`); + logger.info(`Deleted image: ${imagePathObj.path}`); } catch (error) { - console.error(`[Board] Failed to delete image ${imagePathObj.path}:`, error); + logger.error(`Failed to delete image ${imagePathObj.path}:`, error); } } } catch (error) { - console.error(`[Board] Error deleting images for feature ${featureId}:`, error); + logger.error(`Error deleting images for feature ${featureId}:`, error); } } @@ -328,7 +331,7 @@ export function useBoardActions({ try { const api = getElectronAPI(); if (!api?.autoMode) { - console.error('Auto mode API not available'); + logger.error('Auto mode API not available'); return; } @@ -341,16 +344,13 @@ export function useBoardActions({ ); if (result.success) { - console.log( - '[Board] Feature run started successfully, branch:', - feature.branchName || 'default' - ); + logger.info('Feature run started successfully, branch:', feature.branchName || 'default'); } else { - console.error('[Board] Failed to run feature:', result.error); + logger.error('Failed to run feature:', result.error); await loadFeatures(); } } catch (error) { - console.error('[Board] Error running feature:', error); + logger.error('Error running feature:', error); await loadFeatures(); } }, @@ -392,7 +392,7 @@ export function useBoardActions({ updateFeature(feature.id, updates); // Must await to ensure feature status is persisted before starting agent await persistFeatureUpdate(feature.id, updates); - console.log('[Board] Feature moved to in_progress, starting agent...'); + logger.info('Feature moved to in_progress, starting agent...'); await handleRunFeature(feature); return true; }, @@ -413,20 +413,20 @@ export function useBoardActions({ try { const api = getElectronAPI(); if (!api?.autoMode) { - console.error('Auto mode API not available'); + logger.error('Auto mode API not available'); return; } const result = await api.autoMode.verifyFeature(currentProject.path, feature.id); if (result.success) { - console.log('[Board] Feature verification started successfully'); + logger.info('Feature verification started successfully'); } else { - console.error('[Board] Failed to verify feature:', result.error); + logger.error('Failed to verify feature:', result.error); await loadFeatures(); } } catch (error) { - console.error('[Board] Error verifying feature:', error); + logger.error('Error verifying feature:', error); await loadFeatures(); } }, @@ -435,20 +435,20 @@ export function useBoardActions({ const handleResumeFeature = useCallback( async (feature: Feature) => { - console.log('[Board] handleResumeFeature called for feature:', feature.id); + logger.info('handleResumeFeature called for feature:', feature.id); if (!currentProject) { - console.error('[Board] No current project'); + logger.error('No current project'); return; } try { const api = getElectronAPI(); if (!api?.autoMode) { - console.error('[Board] Auto mode API not available'); + logger.error('Auto mode API not available'); return; } - console.log('[Board] Calling resumeFeature API...', { + logger.info('Calling resumeFeature API...', { projectPath: currentProject.path, featureId: feature.id, useWorktrees, @@ -460,16 +460,16 @@ export function useBoardActions({ useWorktrees ); - console.log('[Board] resumeFeature result:', result); + logger.info('resumeFeature result:', result); if (result.success) { - console.log('[Board] Feature resume started successfully'); + logger.info('Feature resume started successfully'); } else { - console.error('[Board] Failed to resume feature:', result.error); + logger.error('Failed to resume feature:', result.error); await loadFeatures(); } } catch (error) { - console.error('[Board] Error resuming feature:', error); + logger.error('Error resuming feature:', error); await loadFeatures(); } }, @@ -523,7 +523,7 @@ export function useBoardActions({ const api = getElectronAPI(); if (!api?.autoMode?.followUpFeature) { - console.error('Follow-up feature API not available'); + logger.error('Follow-up feature API not available'); toast.error('Follow-up not available', { description: 'This feature is not available in the current version.', }); @@ -559,7 +559,7 @@ export function useBoardActions({ // No worktreePath - server derives from feature.branchName ) .catch((error) => { - console.error('[Board] Error sending follow-up:', error); + logger.error('Error sending follow-up:', error); toast.error('Failed to send follow-up', { description: error instanceof Error ? error.message : 'An error occurred', }); @@ -587,7 +587,7 @@ export function useBoardActions({ try { const api = getElectronAPI(); if (!api?.autoMode?.commitFeature) { - console.error('Commit feature API not available'); + logger.error('Commit feature API not available'); toast.error('Commit not available', { description: 'This feature is not available in the current version.', }); @@ -610,14 +610,14 @@ export function useBoardActions({ // Refresh worktree selector to update commit counts onWorktreeCreated?.(); } else { - console.error('[Board] Failed to commit feature:', result.error); + logger.error('Failed to commit feature:', result.error); toast.error('Failed to commit feature', { description: result.error || 'An error occurred', }); await loadFeatures(); } } catch (error) { - console.error('[Board] Error committing feature:', error); + logger.error('Error committing feature:', error); toast.error('Failed to commit feature', { description: error instanceof Error ? error.message : 'An error occurred', }); @@ -634,7 +634,7 @@ export function useBoardActions({ try { const api = getElectronAPI(); if (!api?.worktree?.mergeFeature) { - console.error('Worktree API not available'); + logger.error('Worktree API not available'); toast.error('Merge not available', { description: 'This feature is not available in the current version.', }); @@ -651,13 +651,13 @@ export function useBoardActions({ )}`, }); } else { - console.error('[Board] Failed to merge feature:', result.error); + logger.error('Failed to merge feature:', result.error); toast.error('Failed to merge feature', { description: result.error || 'An error occurred', }); } } catch (error) { - console.error('[Board] Error merging feature:', error); + logger.error('Error merging feature:', error); toast.error('Failed to merge feature', { description: error instanceof Error ? error.message : 'An error occurred', }); @@ -747,7 +747,7 @@ export function useBoardActions({ : `Stopped working on: ${truncateDescription(feature.description)}`, }); } catch (error) { - console.error('[Board] Error stopping feature:', error); + logger.error('Error stopping feature:', error); toast.error('Failed to stop agent', { description: error instanceof Error ? error.message : 'An error occurred', }); @@ -857,7 +857,7 @@ export function useBoardActions({ try { await autoMode.stopFeature(feature.id); } catch (error) { - console.error('[Board] Error stopping feature before archive:', error); + logger.error('Error stopping feature before archive:', error); } } // Archive the feature by setting status to completed diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-drag-drop.ts b/apps/ui/src/components/views/board-view/hooks/use-board-drag-drop.ts index 15593516..466d7cca 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-drag-drop.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-drag-drop.ts @@ -1,10 +1,13 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { DragStartEvent, DragEndEvent } from '@dnd-kit/core'; import { Feature } from '@/store/app-store'; import { useAppStore } from '@/store/app-store'; import { toast } from 'sonner'; import { COLUMNS, ColumnId } from '../constants'; +const logger = createLogger('BoardDragDrop'); + interface UseBoardDragDropProps { features: Feature[]; currentProject: { path: string; id: string } | null; @@ -63,7 +66,7 @@ export function useBoardDragDrop({ if (draggedFeature.status === 'in_progress') { // Only allow dragging in_progress if it's not currently running if (isRunningTask) { - console.log('[Board] Cannot drag feature - currently running'); + logger.debug('Cannot drag feature - currently running'); return; } } diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-effects.ts b/apps/ui/src/components/views/board-view/hooks/use-board-effects.ts index 318b326b..d9eb9119 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-effects.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-effects.ts @@ -1,6 +1,9 @@ import { useEffect, useRef } from 'react'; import { getElectronAPI } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('BoardEffects'); interface UseBoardEffectsProps { currentProject: { path: string; id: string } | null; @@ -70,12 +73,7 @@ export function useBoardEffects({ if (!api.specRegeneration) return; const unsubscribe = api.specRegeneration.onEvent((event) => { - console.log( - '[BoardView] Spec regeneration event:', - event.type, - 'for project:', - event.projectPath - ); + logger.info('Spec regeneration event:', event.type, 'for project:', event.projectPath); if (event.projectPath !== specCreatingForProject) { return; @@ -108,7 +106,7 @@ export function useBoardEffects({ const { clearRunningTasks, addRunningTask } = useAppStore.getState(); if (status.runningFeatures) { - console.log('[Board] Syncing running tasks from backend:', status.runningFeatures); + logger.info('Syncing running tasks from backend:', status.runningFeatures); clearRunningTasks(projectId); @@ -118,7 +116,7 @@ export function useBoardEffects({ } } } catch (error) { - console.error('[Board] Failed to sync running tasks:', error); + logger.error('Failed to sync running tasks:', error); } }; diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-features.ts b/apps/ui/src/components/views/board-view/hooks/use-board-features.ts index eb70dc52..3122a5ee 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-features.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-features.ts @@ -2,6 +2,9 @@ import { useState, useCallback, useEffect, useRef } from 'react'; import { useAppStore, Feature } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { toast } from 'sonner'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('BoardFeatures'); interface UseBoardFeaturesProps { currentProject: { path: string; id: string } | null; @@ -32,7 +35,7 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { // If project switched, mark it but don't clear features yet // We'll clear after successful API load to prevent data loss if (isProjectSwitch) { - console.log(`[BoardView] Project switch detected: ${previousPath} -> ${currentPath}`); + logger.info(`Project switch detected: ${previousPath} -> ${currentPath}`); isSwitchingProjectRef.current = true; isInitialLoadRef.current = true; } @@ -48,7 +51,7 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { try { const api = getElectronAPI(); if (!api.features) { - console.error('[BoardView] Features API not available'); + logger.error('Features API not available'); // Keep cached features if API is unavailable return; } @@ -73,7 +76,7 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { setPersistedCategories([]); } } else if (!result.success && result.error) { - console.error('[BoardView] API returned error:', result.error); + logger.error('API returned error:', result.error); // If it's a new project or the error indicates no features found, // that's expected - start with empty array if (isProjectSwitch) { @@ -83,7 +86,7 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { // Otherwise keep cached features } } catch (error) { - console.error('Failed to load features:', error); + logger.error('Failed to load features:', error); // On error, keep existing cached features for the current project // Only clear on project switch if we have no features from server if (isProjectSwitch && cachedFeatures.length === 0) { @@ -115,7 +118,7 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { setPersistedCategories([]); } } catch (error) { - console.error('Failed to load categories:', error); + logger.error('Failed to load categories:', error); // If file doesn't exist, ensure categories are cleared setPersistedCategories([]); } @@ -147,7 +150,7 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { setPersistedCategories(categories); } } catch (error) { - console.error('Failed to save category:', error); + logger.error('Failed to save category:', error); } }, [currentProject, persistedCategories] @@ -165,7 +168,7 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { currentProject && event.projectPath === currentProject.path ) { - console.log('[BoardView] Spec regeneration complete, refreshing features'); + logger.info('Spec regeneration complete, refreshing features'); loadFeatures(); } }); @@ -190,27 +193,27 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) { if (event.type === 'auto_mode_feature_complete') { // Reload features when a feature is completed - console.log('[Board] Feature completed, reloading features...'); + logger.info('Feature completed, reloading features...'); loadFeatures(); // Play ding sound when feature is done (unless muted) const { muteDoneSound } = useAppStore.getState(); if (!muteDoneSound) { const audio = new Audio('/sounds/ding.mp3'); - audio.play().catch((err) => console.warn('Could not play ding sound:', err)); + audio.play().catch((err) => logger.warn('Could not play ding sound:', err)); } } else if (event.type === 'plan_approval_required') { // Reload features when plan is generated and requires approval // This ensures the feature card shows the "Approve Plan" button - console.log('[Board] Plan approval required, reloading features...'); + logger.info('Plan approval required, reloading features...'); loadFeatures(); } else if (event.type === 'pipeline_step_started') { // Pipeline steps update the feature status to `pipeline_*` before the step runs. // Reload so the card moves into the correct pipeline column immediately. - console.log('[Board] Pipeline step started, reloading features...'); + logger.info('Pipeline step started, reloading features...'); loadFeatures(); } else if (event.type === 'auto_mode_error') { // Reload features when an error occurs (feature moved to waiting_approval) - console.log('[Board] Feature error, reloading features...', event.error); + logger.info('Feature error, reloading features...', event.error); // Remove from running tasks so it moves to the correct column if (event.featureId) { diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts b/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts index 477671ac..4a25de7e 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts @@ -2,6 +2,9 @@ import { useCallback } from 'react'; import { Feature } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('BoardPersistence'); interface UseBoardPersistenceProps { currentProject: { path: string; id: string } | null; @@ -18,7 +21,7 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps try { const api = getElectronAPI(); if (!api.features) { - console.error('[BoardView] Features API not available'); + logger.error('Features API not available'); return; } @@ -27,7 +30,7 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps updateFeature(result.feature.id, result.feature); } } catch (error) { - console.error('Failed to persist feature update:', error); + logger.error('Failed to persist feature update:', error); } }, [currentProject, updateFeature] @@ -41,7 +44,7 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps try { const api = getElectronAPI(); if (!api.features) { - console.error('[BoardView] Features API not available'); + logger.error('Features API not available'); return; } @@ -50,7 +53,7 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps updateFeature(result.feature.id, result.feature); } } catch (error) { - console.error('Failed to persist feature creation:', error); + logger.error('Failed to persist feature creation:', error); } }, [currentProject, updateFeature] @@ -64,13 +67,13 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps try { const api = getElectronAPI(); if (!api.features) { - console.error('[BoardView] Features API not available'); + logger.error('Features API not available'); return; } await api.features.delete(currentProject.path, featureId); } catch (error) { - console.error('Failed to persist feature deletion:', error); + logger.error('Failed to persist feature deletion:', error); } }, [currentProject] diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts index 1291dfef..1cb1cec6 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-branches.ts @@ -1,7 +1,10 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; import type { BranchInfo, GitRepoStatus } from '../types'; +const logger = createLogger('Branches'); + export function useBranches() { const [branches, setBranches] = useState([]); const [aheadCount, setAheadCount] = useState(0); @@ -26,7 +29,7 @@ export function useBranches() { try { const api = getElectronAPI(); if (!api?.worktree?.listBranches) { - console.warn('List branches API not available'); + logger.warn('List branches API not available'); return; } const result = await api.worktree.listBranches(worktreePath); @@ -45,11 +48,11 @@ export function useBranches() { setGitRepoStatus({ isGitRepo: true, hasCommits: false }); } else if (!result.success) { // Other errors - log them - console.warn('Failed to fetch branches:', result.error); + logger.warn('Failed to fetch branches:', result.error); resetBranchState(); } } catch (error) { - console.error('Failed to fetch branches:', error); + logger.error('Failed to fetch branches:', error); resetBranchState(); // Reset git status to unknown state on network/API errors setGitRepoStatus({ isGitRepo: true, hasCommits: true }); diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-default-editor.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-default-editor.ts index b1e1ef3b..9b074800 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-default-editor.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-default-editor.ts @@ -1,6 +1,9 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; +const logger = createLogger('DefaultEditor'); + export function useDefaultEditor() { const [defaultEditorName, setDefaultEditorName] = useState('Editor'); @@ -15,7 +18,7 @@ export function useDefaultEditor() { setDefaultEditorName(result.result.editorName); } } catch (error) { - console.error('Failed to fetch default editor:', error); + logger.error('Failed to fetch default editor:', error); } }, []); diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-servers.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-servers.ts index 90f7bce6..7733a5ff 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-servers.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-dev-servers.ts @@ -1,9 +1,12 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; import { normalizePath } from '@/lib/utils'; import { toast } from 'sonner'; import type { DevServerInfo, WorktreeInfo } from '../types'; +const logger = createLogger('DevServers'); + interface UseDevServersOptions { projectPath: string; } @@ -27,7 +30,7 @@ export function useDevServers({ projectPath }: UseDevServersOptions) { setRunningDevServers(serversMap); } } catch (error) { - console.error('Failed to fetch dev servers:', error); + logger.error('Failed to fetch dev servers:', error); } }, []); @@ -73,7 +76,7 @@ export function useDevServers({ projectPath }: UseDevServersOptions) { toast.error(result.error || 'Failed to start dev server'); } } catch (error) { - console.error('Start dev server failed:', error); + logger.error('Start dev server failed:', error); toast.error('Failed to start dev server'); } finally { setIsStartingDevServer(false); @@ -105,7 +108,7 @@ export function useDevServers({ projectPath }: UseDevServersOptions) { toast.error(result.error || 'Failed to stop dev server'); } } catch (error) { - console.error('Stop dev server failed:', error); + logger.error('Stop dev server failed:', error); toast.error('Failed to stop dev server'); } }, diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts index 62249f97..ac53c12b 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts @@ -1,8 +1,11 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from '@/lib/electron'; import { toast } from 'sonner'; import type { WorktreeInfo } from '../types'; +const logger = createLogger('WorktreeActions'); + // Error codes that need special user-friendly handling const GIT_STATUS_ERROR_CODES = ['NOT_GIT_REPO', 'NO_COMMITS'] as const; type GitStatusErrorCode = (typeof GIT_STATUS_ERROR_CODES)[number]; @@ -56,7 +59,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre toast.error(result.error || 'Failed to switch branch'); } } catch (error) { - console.error('Switch branch failed:', error); + logger.error('Switch branch failed:', error); toast.error('Failed to switch branch'); } finally { setIsSwitching(false); @@ -84,7 +87,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre toast.error(result.error || 'Failed to pull latest changes'); } } catch (error) { - console.error('Pull failed:', error); + logger.error('Pull failed:', error); toast.error('Failed to pull latest changes'); } finally { setIsPulling(false); @@ -113,7 +116,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre toast.error(result.error || 'Failed to push changes'); } } catch (error) { - console.error('Push failed:', error); + logger.error('Push failed:', error); toast.error('Failed to push changes'); } finally { setIsPushing(false); @@ -126,7 +129,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre try { const api = getElectronAPI(); if (!api?.worktree?.openInEditor) { - console.warn('Open in editor API not available'); + logger.warn('Open in editor API not available'); return; } const result = await api.worktree.openInEditor(worktree.path); @@ -136,7 +139,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre toast.error(result.error); } } catch (error) { - console.error('Open in editor failed:', error); + logger.error('Open in editor failed:', error); } }, []); diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktrees.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktrees.ts index 31a6675a..1575f38a 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktrees.ts +++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktrees.ts @@ -1,9 +1,12 @@ import { useState, useEffect, useCallback, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { pathsEqual } from '@/lib/utils'; import type { WorktreeInfo } from '../types'; +const logger = createLogger('Worktrees'); + interface UseWorktreesOptions { projectPath: string; refreshTrigger?: number; @@ -33,7 +36,7 @@ export function useWorktrees({ try { const api = getElectronAPI(); if (!api?.worktree?.listAll) { - console.warn('Worktree API not available'); + logger.warn('Worktree API not available'); return; } const result = await api.worktree.listAll(projectPath, true); @@ -44,7 +47,7 @@ export function useWorktrees({ // Return removed worktrees so they can be handled by the caller return result.removedWorktrees; } catch (error) { - console.error('Failed to fetch worktrees:', error); + logger.error('Failed to fetch worktrees:', error); return undefined; } finally { if (!silent) { diff --git a/apps/ui/src/components/views/code-view.tsx b/apps/ui/src/components/views/code-view.tsx index f80c0fc6..581a298b 100644 --- a/apps/ui/src/components/views/code-view.tsx +++ b/apps/ui/src/components/views/code-view.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { Card, CardContent } from '@/components/ui/card'; @@ -6,6 +7,8 @@ import { Button } from '@/components/ui/button'; import { File, Folder, FolderOpen, ChevronRight, ChevronDown, RefreshCw, Code } from 'lucide-react'; import { cn } from '@/lib/utils'; +const logger = createLogger('CodeView'); + interface FileTreeNode { name: string; path: string; @@ -60,7 +63,7 @@ export function CodeView() { setFileTree(entries); } } catch (error) { - console.error('Failed to load file tree:', error); + logger.error('Failed to load file tree:', error); } finally { setIsLoading(false); } @@ -91,7 +94,7 @@ export function CodeView() { })); } } catch (error) { - console.error('Failed to load subdirectory:', error); + logger.error('Failed to load subdirectory:', error); } return []; }; @@ -107,7 +110,7 @@ export function CodeView() { setSelectedFile(path); } } catch (error) { - console.error('Failed to load file:', error); + logger.error('Failed to load file:', error); } }; diff --git a/apps/ui/src/components/views/context-view.tsx b/apps/ui/src/components/views/context-view.tsx index d81d6210..ab33dbe8 100644 --- a/apps/ui/src/components/views/context-view.tsx +++ b/apps/ui/src/components/views/context-view.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, useCallback, useMemo, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { getHttpApiClient } from '@/lib/http-api-client'; @@ -38,6 +39,8 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils'; + +const logger = createLogger('ContextView'); import { sanitizeFilename } from '@/lib/image-utils'; import { Markdown } from '../ui/markdown'; import { @@ -160,7 +163,7 @@ export function ContextView() { const metadataPath = `${contextPath}/context-metadata.json`; await api.writeFile(metadataPath, JSON.stringify(metadata, null, 2)); } catch (error) { - console.error('Failed to save metadata:', error); + logger.error('Failed to save metadata:', error); } }, [getContextPath] @@ -202,7 +205,7 @@ export function ContextView() { setContextFiles(files); } } catch (error) { - console.error('Failed to load context files:', error); + logger.error('Failed to load context files:', error); } finally { setIsLoading(false); } @@ -223,7 +226,7 @@ export function ContextView() { setHasChanges(false); } } catch (error) { - console.error('Failed to load file content:', error); + logger.error('Failed to load file content:', error); } }, []); @@ -247,7 +250,7 @@ export function ContextView() { setSelectedFile({ ...selectedFile, content: editedContent }); setHasChanges(false); } catch (error) { - console.error('Failed to save file:', error); + logger.error('Failed to save file:', error); } finally { setIsSaving(false); } @@ -279,7 +282,7 @@ export function ContextView() { result.error || `Automaker couldn't generate a description for “${fileName}”.`; toast.error('Failed to generate description', { description: message }); } catch (error) { - console.error('Failed to generate description:', error); + logger.error('Failed to generate description:', error); const message = error instanceof Error ? error.message @@ -315,7 +318,7 @@ export function ContextView() { }); } } catch (error) { - console.error('Failed to generate description:', error); + logger.error('Failed to generate description:', error); } finally { // Remove from generating set setGeneratingDescriptions((prev) => { @@ -401,7 +404,7 @@ export function ContextView() { // For images, use the path in the images directory generateDescriptionAsync(imagePathForDescription || filePath, fileName, isImage); } catch (error) { - console.error('Failed to upload file:', error); + logger.error('Failed to upload file:', error); toast.error('Failed to upload file', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -492,7 +495,7 @@ export function ContextView() { setNewMarkdownDescription(''); setNewMarkdownContent(''); } catch (error) { - console.error('Failed to create markdown:', error); + logger.error('Failed to create markdown:', error); } }; @@ -515,7 +518,7 @@ export function ContextView() { setHasChanges(false); await loadContextFiles(); } catch (error) { - console.error('Failed to delete file:', error); + logger.error('Failed to delete file:', error); } }; @@ -537,14 +540,14 @@ export function ContextView() { // Check if file with new name already exists const exists = await api.exists(newPath); if (exists) { - console.error('A file with this name already exists'); + logger.error('A file with this name already exists'); return; } // Read current file content const result = await api.readFile(selectedFile.path); if (!result.success || result.content === undefined) { - console.error('Failed to read file for rename'); + logger.error('Failed to read file for rename'); return; } @@ -578,7 +581,7 @@ export function ContextView() { }; setSelectedFile(renamedFile); } catch (error) { - console.error('Failed to rename file:', error); + logger.error('Failed to rename file:', error); } }; @@ -603,7 +606,7 @@ export function ContextView() { setEditDescriptionValue(''); setEditDescriptionFileName(''); } catch (error) { - console.error('Failed to save description:', error); + logger.error('Failed to save description:', error); } }; @@ -634,7 +637,7 @@ export function ContextView() { await loadContextFiles(); } catch (error) { - console.error('Failed to delete file:', error); + logger.error('Failed to delete file:', error); } }; diff --git a/apps/ui/src/components/views/github-issues-view.tsx b/apps/ui/src/components/views/github-issues-view.tsx index a0cabcc7..363cefd3 100644 --- a/apps/ui/src/components/views/github-issues-view.tsx +++ b/apps/ui/src/components/views/github-issues-view.tsx @@ -1,4 +1,5 @@ import { useState, useCallback, useMemo } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { CircleDot, RefreshCw } from 'lucide-react'; import { getElectronAPI, GitHubIssue, IssueValidationResult } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; @@ -14,6 +15,8 @@ import { formatDate, getFeaturePriority } from './github-issues-view/utils'; import { useModelOverride } from '@/components/shared'; import type { ValidateIssueOptions } from './github-issues-view/types'; +const logger = createLogger('GitHubIssuesView'); + export function GitHubIssuesView() { const [selectedIssue, setSelectedIssue] = useState(null); const [validationResult, setValidationResult] = useState(null); @@ -118,7 +121,7 @@ export function GitHubIssuesView() { } } } catch (err) { - console.error('[GitHubIssuesView] Convert to task error:', err); + logger.error('Convert to task error:', err); toast.error(err instanceof Error ? err.message : 'Failed to create task'); } }, @@ -247,7 +250,7 @@ export function GitHubIssuesView() { confirmText="Re-validate" onConfirm={() => { if (selectedIssue && pendingRevalidateOptions) { - console.log('[GitHubIssuesView] Revalidating with options:', { + logger.info('Revalidating with options:', { commentsCount: pendingRevalidateOptions.comments?.length ?? 0, linkedPRsCount: pendingRevalidateOptions.linkedPRs?.length ?? 0, }); diff --git a/apps/ui/src/components/views/github-issues-view/hooks/use-github-issues.ts b/apps/ui/src/components/views/github-issues-view/hooks/use-github-issues.ts index 74b4b0b2..0083a877 100644 --- a/apps/ui/src/components/views/github-issues-view/hooks/use-github-issues.ts +++ b/apps/ui/src/components/views/github-issues-view/hooks/use-github-issues.ts @@ -1,5 +1,8 @@ import { useState, useEffect, useCallback, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI, GitHubIssue } from '@/lib/electron'; + +const logger = createLogger('GitHubIssues'); import { useAppStore } from '@/store/app-store'; export function useGithubIssues() { @@ -38,7 +41,7 @@ export function useGithubIssues() { } } catch (err) { if (isMountedRef.current) { - console.error('[GitHubIssuesView] Error fetching issues:', err); + logger.error('Error fetching issues:', err); setError(err instanceof Error ? err.message : 'Failed to fetch issues'); } } finally { diff --git a/apps/ui/src/components/views/github-issues-view/hooks/use-issue-comments.ts b/apps/ui/src/components/views/github-issues-view/hooks/use-issue-comments.ts index b5b3534f..7ae1b130 100644 --- a/apps/ui/src/components/views/github-issues-view/hooks/use-issue-comments.ts +++ b/apps/ui/src/components/views/github-issues-view/hooks/use-issue-comments.ts @@ -1,5 +1,8 @@ import { useState, useEffect, useCallback, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI, GitHubComment } from '@/lib/electron'; + +const logger = createLogger('IssueComments'); import { useAppStore } from '@/store/app-store'; interface UseIssueCommentsResult { @@ -69,7 +72,7 @@ export function useIssueComments(issueNumber: number | null): UseIssueCommentsRe } } catch (err) { if (isMountedRef.current) { - console.error('[useIssueComments] Error fetching comments:', err); + logger.error('Error fetching comments:', err); setError(err instanceof Error ? err.message : 'Failed to fetch comments'); } } finally { diff --git a/apps/ui/src/components/views/github-issues-view/hooks/use-issue-validation.ts b/apps/ui/src/components/views/github-issues-view/hooks/use-issue-validation.ts index fa7d62dc..2ec993ef 100644 --- a/apps/ui/src/components/views/github-issues-view/hooks/use-issue-validation.ts +++ b/apps/ui/src/components/views/github-issues-view/hooks/use-issue-validation.ts @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI, GitHubIssue, @@ -12,6 +13,8 @@ import { useAppStore } from '@/store/app-store'; import { toast } from 'sonner'; import { isValidationStale } from '../utils'; +const logger = createLogger('IssueValidation'); + /** * Extract model string from PhaseModelEntry or string (handles both formats) */ @@ -78,7 +81,7 @@ export function useIssueValidation({ } } catch (err) { if (isMounted) { - console.error('[GitHubIssuesView] Failed to load cached validations:', err); + logger.error('Failed to load cached validations:', err); } } }; @@ -107,7 +110,7 @@ export function useIssueValidation({ } } catch (err) { if (isMounted) { - console.error('[GitHubIssuesView] Failed to load running validations:', err); + logger.error('Failed to load running validations:', err); } } }; @@ -283,7 +286,7 @@ export function useIssueValidation({ // On success, the result will come through the event stream } } catch (err) { - console.error('[GitHubIssuesView] Validation error:', err); + logger.error('Validation error:', err); toast.error(err instanceof Error ? err.message : 'Failed to validate issue'); } }, @@ -325,7 +328,7 @@ export function useIssueValidation({ }); } } catch (err) { - console.error('[GitHubIssuesView] Failed to mark validation as viewed:', err); + logger.error('Failed to mark validation as viewed:', err); } } } diff --git a/apps/ui/src/components/views/github-prs-view.tsx b/apps/ui/src/components/views/github-prs-view.tsx index 1b4ea4ea..855d136c 100644 --- a/apps/ui/src/components/views/github-prs-view.tsx +++ b/apps/ui/src/components/views/github-prs-view.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { GitPullRequest, Loader2, RefreshCw, ExternalLink, GitMerge, X } from 'lucide-react'; import { getElectronAPI, GitHubPR } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; @@ -6,6 +7,8 @@ import { Button } from '@/components/ui/button'; import { Markdown } from '@/components/ui/markdown'; import { cn } from '@/lib/utils'; +const logger = createLogger('GitHubPRsView'); + export function GitHubPRsView() { const [openPRs, setOpenPRs] = useState([]); const [mergedPRs, setMergedPRs] = useState([]); @@ -35,7 +38,7 @@ export function GitHubPRsView() { } } } catch (err) { - console.error('[GitHubPRsView] Error fetching PRs:', err); + logger.error('Error fetching PRs:', err); setError(err instanceof Error ? err.message : 'Failed to fetch pull requests'); } finally { setLoading(false); diff --git a/apps/ui/src/components/views/interview-view.tsx b/apps/ui/src/components/views/interview-view.tsx index 71d0fa4d..2bf809a5 100644 --- a/apps/ui/src/components/views/interview-view.tsx +++ b/apps/ui/src/components/views/interview-view.tsx @@ -1,4 +1,5 @@ import { useState, useCallback, useRef, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore, Feature } from '@/store/app-store'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; @@ -12,6 +13,8 @@ import { toast } from 'sonner'; import { useNavigate } from '@tanstack/react-router'; import { getDefaultWorkspaceDirectory, saveLastProjectDirectory } from '@/lib/workspace-config'; +const logger = createLogger('InterviewView'); + interface InterviewMessage { id: string; role: 'user' | 'assistant'; @@ -97,7 +100,7 @@ export function InterviewView() { setProjectPath(defaultDir); } } catch (error) { - console.error('Failed to load default workspace directory:', error); + logger.error('Failed to load default workspace directory:', error); } }; @@ -367,7 +370,7 @@ export function InterviewView() { addProject(project); setCurrentProject(project); } catch (error) { - console.error('Failed to create project:', error); + logger.error('Failed to create project:', error); setIsGenerating(false); } }; diff --git a/apps/ui/src/components/views/running-agents-view.tsx b/apps/ui/src/components/views/running-agents-view.tsx index fb1ded96..17e14b25 100644 --- a/apps/ui/src/components/views/running-agents-view.tsx +++ b/apps/ui/src/components/views/running-agents-view.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Bot, Folder, Loader2, RefreshCw, Square, Activity, FileText } from 'lucide-react'; import { getElectronAPI, RunningAgent } from '@/lib/electron'; import { useAppStore } from '@/store/app-store'; @@ -7,6 +8,8 @@ import { cn } from '@/lib/utils'; import { useNavigate } from '@tanstack/react-router'; import { AgentOutputModal } from './board-view/dialogs/agent-output-modal'; +const logger = createLogger('RunningAgentsView'); + export function RunningAgentsView() { const [runningAgents, setRunningAgents] = useState([]); const [loading, setLoading] = useState(true); @@ -25,7 +28,7 @@ export function RunningAgentsView() { } } } catch (error) { - console.error('[RunningAgentsView] Error fetching running agents:', error); + logger.error('Error fetching running agents:', error); } finally { setLoading(false); setRefreshing(false); @@ -78,7 +81,7 @@ export function RunningAgentsView() { fetchRunningAgents(); } } catch (error) { - console.error('[RunningAgentsView] Error stopping agent:', error); + logger.error('Error stopping agent:', error); } }, [fetchRunningAgents] diff --git a/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts b/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts index 97eb157a..d2f12839 100644 --- a/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts +++ b/apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts @@ -1,5 +1,8 @@ import { useState, useEffect } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; + +const logger = createLogger('ApiKeyManagement'); import { getElectronAPI } from '@/lib/electron'; import type { ProviderConfigParams } from '@/config/api-providers'; @@ -60,7 +63,7 @@ export function useApiKeyManagement() { }); } } catch (error) { - console.error('Failed to check API key status:', error); + logger.error('Failed to check API key status:', error); } } }; diff --git a/apps/ui/src/components/views/settings-view/hooks/use-cli-status.ts b/apps/ui/src/components/views/settings-view/hooks/use-cli-status.ts index dff1dfa9..ce067e17 100644 --- a/apps/ui/src/components/views/settings-view/hooks/use-cli-status.ts +++ b/apps/ui/src/components/views/settings-view/hooks/use-cli-status.ts @@ -1,5 +1,8 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useSetupStore } from '@/store/setup-store'; + +const logger = createLogger('CliStatus'); import { getElectronAPI } from '@/lib/electron'; interface CliStatusResult { @@ -40,7 +43,7 @@ export function useCliStatus() { const status = await api.checkClaudeCli(); setClaudeCliStatus(status); } catch (error) { - console.error('Failed to check Claude CLI status:', error); + logger.error('Failed to check Claude CLI status:', error); } } @@ -84,7 +87,7 @@ export function useCliStatus() { setClaudeAuthStatus(authStatus); } } catch (error) { - console.error('Failed to check Claude auth status:', error); + logger.error('Failed to check Claude auth status:', error); } } }; @@ -102,7 +105,7 @@ export function useCliStatus() { setClaudeCliStatus(status); } } catch (error) { - console.error('Failed to refresh Claude CLI status:', error); + logger.error('Failed to refresh Claude CLI status:', error); } finally { setIsCheckingClaudeCli(false); } diff --git a/apps/ui/src/components/views/settings-view/hooks/use-cursor-permissions.ts b/apps/ui/src/components/views/settings-view/hooks/use-cursor-permissions.ts index 580320b3..a911892e 100644 --- a/apps/ui/src/components/views/settings-view/hooks/use-cursor-permissions.ts +++ b/apps/ui/src/components/views/settings-view/hooks/use-cursor-permissions.ts @@ -1,5 +1,8 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { toast } from 'sonner'; + +const logger = createLogger('CursorPermissions'); import { getHttpApiClient } from '@/lib/http-api-client'; import type { CursorPermissionProfile } from '@automaker/types'; @@ -41,7 +44,7 @@ export function useCursorPermissions(projectPath?: string) { }); } } catch (error) { - console.error('Failed to load Cursor permissions:', error); + logger.error('Failed to load Cursor permissions:', error); } finally { setIsLoadingPermissions(false); } diff --git a/apps/ui/src/components/views/settings-view/hooks/use-cursor-status.ts b/apps/ui/src/components/views/settings-view/hooks/use-cursor-status.ts index a9d20788..a082e71b 100644 --- a/apps/ui/src/components/views/settings-view/hooks/use-cursor-status.ts +++ b/apps/ui/src/components/views/settings-view/hooks/use-cursor-status.ts @@ -1,5 +1,8 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { toast } from 'sonner'; + +const logger = createLogger('CursorStatus'); import { getHttpApiClient } from '@/lib/http-api-client'; import { useSetupStore } from '@/store/setup-store'; @@ -48,7 +51,7 @@ export function useCursorStatus() { }); } } catch (error) { - console.error('Failed to load Cursor settings:', error); + logger.error('Failed to load Cursor settings:', error); toast.error('Failed to load Cursor settings'); } finally { setIsLoading(false); diff --git a/apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts b/apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts index a6cd83b4..c0f16b4d 100644 --- a/apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts +++ b/apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts @@ -1,5 +1,8 @@ import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; + +const logger = createLogger('MCPServers'); import { toast } from 'sonner'; import type { MCPServerConfig } from '@automaker/types'; import { syncSettingsToServer, loadMCPServersFromServer } from '@/hooks/use-settings-migration'; @@ -72,7 +75,7 @@ export function useMCPServers() { // Auto-load MCP servers from settings file on mount useEffect(() => { loadMCPServersFromServer().catch((error) => { - console.error('Failed to load MCP servers on mount:', error); + logger.error('Failed to load MCP servers on mount:', error); }); }, []); @@ -431,7 +434,7 @@ export function useMCPServers() { if (serverData.type === 'stdio') { if (!serverConfig.command) { - console.warn(`Skipping ${name}: no command specified`); + logger.warn(`Skipping ${name}: no command specified`); return null; } @@ -458,7 +461,7 @@ export function useMCPServers() { } } else { if (!serverConfig.url) { - console.warn(`Skipping ${name}: no url specified`); + logger.warn(`Skipping ${name}: no url specified`); return null; } serverData.url = serverConfig.url as string; @@ -491,7 +494,7 @@ export function useMCPServers() { const name = config.name as string; if (!name) { - console.warn('Skipping server: no name specified'); + logger.warn('Skipping server: no name specified'); skippedCount++; continue; } diff --git a/apps/ui/src/components/views/setup-view.tsx b/apps/ui/src/components/views/setup-view.tsx index 8764630f..6a109213 100644 --- a/apps/ui/src/components/views/setup-view.tsx +++ b/apps/ui/src/components/views/setup-view.tsx @@ -1,3 +1,4 @@ +import { createLogger } from '@automaker/utils/logger'; import { useSetupStore } from '@/store/setup-store'; import { StepIndicator } from './setup-view/components'; import { @@ -10,6 +11,8 @@ import { } from './setup-view/steps'; import { useNavigate } from '@tanstack/react-router'; +const logger = createLogger('SetupView'); + // Main Setup View export function SetupView() { const { currentStep, setCurrentStep, completeSetup, setSkipClaudeSetup } = useSetupStore(); @@ -28,33 +31,33 @@ export function SetupView() { const currentIndex = steps.indexOf(getStepName()); const handleNext = (from: string) => { - console.log('[Setup Flow] handleNext called from:', from, 'currentStep:', currentStep); + logger.debug('[Setup Flow] handleNext called from:', from, 'currentStep:', currentStep); switch (from) { case 'welcome': - console.log('[Setup Flow] Moving to theme step'); + logger.debug('[Setup Flow] Moving to theme step'); setCurrentStep('theme'); break; case 'theme': - console.log('[Setup Flow] Moving to claude_detect step'); + logger.debug('[Setup Flow] Moving to claude_detect step'); setCurrentStep('claude_detect'); break; case 'claude': - console.log('[Setup Flow] Moving to cursor step'); + logger.debug('[Setup Flow] Moving to cursor step'); setCurrentStep('cursor'); break; case 'cursor': - console.log('[Setup Flow] Moving to github step'); + logger.debug('[Setup Flow] Moving to github step'); setCurrentStep('github'); break; case 'github': - console.log('[Setup Flow] Moving to complete step'); + logger.debug('[Setup Flow] Moving to complete step'); setCurrentStep('complete'); break; } }; const handleBack = (from: string) => { - console.log('[Setup Flow] handleBack called from:', from); + logger.debug('[Setup Flow] handleBack called from:', from); switch (from) { case 'theme': setCurrentStep('welcome'); @@ -72,25 +75,25 @@ export function SetupView() { }; const handleSkipClaude = () => { - console.log('[Setup Flow] Skipping Claude setup'); + logger.debug('[Setup Flow] Skipping Claude setup'); setSkipClaudeSetup(true); setCurrentStep('cursor'); }; const handleSkipCursor = () => { - console.log('[Setup Flow] Skipping Cursor setup'); + logger.debug('[Setup Flow] Skipping Cursor setup'); setCurrentStep('github'); }; const handleSkipGithub = () => { - console.log('[Setup Flow] Skipping GitHub setup'); + logger.debug('[Setup Flow] Skipping GitHub setup'); setCurrentStep('complete'); }; const handleFinish = () => { - console.log('[Setup Flow] handleFinish called - completing setup'); + logger.debug('[Setup Flow] handleFinish called - completing setup'); completeSetup(); - console.log('[Setup Flow] Setup completed, redirecting to welcome view'); + logger.debug('[Setup Flow] Setup completed, redirecting to welcome view'); navigate({ to: '/' }); }; diff --git a/apps/ui/src/components/views/setup-view/hooks/use-cli-installation.ts b/apps/ui/src/components/views/setup-view/hooks/use-cli-installation.ts index e745ddc4..aeb57d53 100644 --- a/apps/ui/src/components/views/setup-view/hooks/use-cli-installation.ts +++ b/apps/ui/src/components/views/setup-view/hooks/use-cli-installation.ts @@ -1,5 +1,8 @@ import { useState, useCallback } from 'react'; import { toast } from 'sonner'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('CliInstallation'); interface UseCliInstallationOptions { cliType: 'claude'; @@ -82,7 +85,7 @@ export function useCliInstallation({ toast.error('Installation failed', { description: result.error }); } } catch (error) { - console.error(`Failed to install ${cliType}:`, error); + logger.error(`Failed to install ${cliType}:`, error); toast.error('Installation failed'); } finally { setIsInstalling(false); diff --git a/apps/ui/src/components/views/setup-view/hooks/use-cli-status.ts b/apps/ui/src/components/views/setup-view/hooks/use-cli-status.ts index fbe365d1..43c8a6f6 100644 --- a/apps/ui/src/components/views/setup-view/hooks/use-cli-status.ts +++ b/apps/ui/src/components/views/setup-view/hooks/use-cli-status.ts @@ -1,4 +1,5 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; interface UseCliStatusOptions { cliType: 'claude'; @@ -14,13 +15,14 @@ export function useCliStatus({ setAuthStatus, }: UseCliStatusOptions) { const [isChecking, setIsChecking] = useState(false); + const logger = createLogger('CliStatus'); const checkStatus = useCallback(async () => { - console.log(`[${cliType} Setup] Starting status check...`); + logger.info(`Starting status check for ${cliType}...`); setIsChecking(true); try { const result = await statusApi(); - console.log(`[${cliType} Setup] Raw status result:`, result); + logger.info(`Raw status result for ${cliType}:`, result); if (result.success) { const cliStatus = { @@ -29,7 +31,7 @@ export function useCliStatus({ version: result.version || null, method: result.method || 'none', }; - console.log(`[${cliType} Setup] CLI Status:`, cliStatus); + logger.info(`CLI Status for ${cliType}:`, cliStatus); setCliStatus(cliStatus); if (result.auth) { @@ -60,11 +62,11 @@ export function useCliStatus({ } } } catch (error) { - console.error(`[${cliType} Setup] Failed to check status:`, error); + logger.error(`Failed to check status for ${cliType}:`, error); } finally { setIsChecking(false); } - }, [cliType, statusApi, setCliStatus, setAuthStatus]); + }, [cliType, statusApi, setCliStatus, setAuthStatus, logger]); return { isChecking, checkStatus }; } diff --git a/apps/ui/src/components/views/setup-view/hooks/use-token-save.ts b/apps/ui/src/components/views/setup-view/hooks/use-token-save.ts index 57dcc93d..8168f445 100644 --- a/apps/ui/src/components/views/setup-view/hooks/use-token-save.ts +++ b/apps/ui/src/components/views/setup-view/hooks/use-token-save.ts @@ -1,6 +1,9 @@ import { useState, useCallback } from 'react'; import { toast } from 'sonner'; import { getElectronAPI } from '@/lib/electron'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('TokenSave'); interface UseTokenSaveOptions { provider: string; // e.g., "anthropic_oauth_token", "anthropic", "openai" @@ -24,7 +27,7 @@ export function useTokenSave({ provider, onSuccess }: UseTokenSaveOptions) { if (setupApi?.storeApiKey) { const result = await setupApi.storeApiKey(provider, tokenValue); - console.log(`[Token Save] Store result for ${provider}:`, result); + logger.info(`Store result for ${provider}:`, result); if (result.success) { const tokenType = provider.includes('oauth') ? 'subscription token' : 'API key'; @@ -42,7 +45,7 @@ export function useTokenSave({ provider, onSuccess }: UseTokenSaveOptions) { return true; } } catch (error) { - console.error(`[Token Save] Failed to save ${provider}:`, error); + logger.error(`Failed to save ${provider}:`, error); toast.error('Failed to save token'); return false; } finally { diff --git a/apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx b/apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx index ab9df67e..bb7c26bd 100644 --- a/apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx +++ b/apps/ui/src/components/views/setup-view/steps/cursor-setup-step.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; @@ -19,6 +20,8 @@ import { import { toast } from 'sonner'; import { StatusBadge } from '../components'; +const logger = createLogger('CursorSetupStep'); + interface CursorSetupStepProps { onNext: () => void; onBack: () => void; @@ -67,7 +70,7 @@ export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps } } } catch (error) { - console.error('Failed to check Cursor status:', error); + logger.error('Failed to check Cursor status:', error); } finally { setIsChecking(false); } @@ -140,7 +143,7 @@ export function CursorSetupStep({ onNext, onBack, onSkip }: CursorSetupStepProps } }, 2000); } catch (error) { - console.error('Login failed:', error); + logger.error('Login failed:', error); toast.error('Failed to start login process'); setIsLoggingIn(false); } diff --git a/apps/ui/src/components/views/setup-view/steps/github-setup-step.tsx b/apps/ui/src/components/views/setup-view/steps/github-setup-step.tsx index 05b70e53..fcccb618 100644 --- a/apps/ui/src/components/views/setup-view/steps/github-setup-step.tsx +++ b/apps/ui/src/components/views/setup-view/steps/github-setup-step.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { useSetupStore } from '@/store/setup-store'; @@ -18,6 +19,8 @@ import { import { toast } from 'sonner'; import { StatusBadge } from '../components'; +const logger = createLogger('GitHubSetupStep'); + interface GitHubSetupStepProps { onNext: () => void; onBack: () => void; @@ -46,7 +49,7 @@ export function GitHubSetupStep({ onNext, onBack, onSkip }: GitHubSetupStepProps }); } } catch (error) { - console.error('Failed to check gh status:', error); + logger.error('Failed to check gh status:', error); } finally { setIsChecking(false); } diff --git a/apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts b/apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts index 4e792853..7507ce2c 100644 --- a/apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts +++ b/apps/ui/src/components/views/spec-view/hooks/use-spec-generation.ts @@ -1,5 +1,8 @@ import { useEffect, useState, useCallback, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; + +const logger = createLogger('SpecGeneration'); import { getElectronAPI } from '@/lib/electron'; import { toast } from 'sonner'; import { CheckCircle2 } from 'lucide-react'; @@ -79,7 +82,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { } const status = await api.specRegeneration.status(); - console.log( + logger.debug( '[useSpecGeneration] Status check on mount:', status, 'for project:', @@ -87,7 +90,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { ); if (status.success && status.isRunning) { - console.log( + logger.debug( '[useSpecGeneration] Spec generation is running globally. Tentatively showing loader.' ); @@ -103,7 +106,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { clearTimeout(pendingStatusTimeoutRef.current); } pendingStatusTimeoutRef.current = setTimeout(() => { - console.log( + logger.debug( '[useSpecGeneration] No events received for current project - clearing tentative state' ); setIsCreating(false); @@ -118,7 +121,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { stateRestoredRef.current = false; } } catch (error) { - console.error('[useSpecGeneration] Failed to check status:', error); + logger.error('[useSpecGeneration] Failed to check status:', error); } finally { statusCheckRef.current = false; } @@ -141,10 +144,10 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { if (!api.specRegeneration) return; const status = await api.specRegeneration.status(); - console.log('[useSpecGeneration] Visibility change - status check:', status); + logger.debug('[useSpecGeneration] Visibility change - status check:', status); if (!status.isRunning) { - console.log( + logger.debug( '[useSpecGeneration] Visibility change: Backend indicates generation complete - clearing state' ); setIsCreating(false); @@ -157,7 +160,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { setCurrentPhase(status.currentPhase); } } catch (error) { - console.error('[useSpecGeneration] Failed to check status on visibility change:', error); + logger.error('[useSpecGeneration] Failed to check status on visibility change:', error); } } }; @@ -180,7 +183,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { const status = await api.specRegeneration.status(); if (!status.isRunning) { - console.log( + logger.debug( '[useSpecGeneration] Periodic check: Backend indicates generation complete - clearing state' ); setIsCreating(false); @@ -190,14 +193,14 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { stateRestoredRef.current = false; loadSpec(); } else if (status.currentPhase && status.currentPhase !== currentPhase) { - console.log('[useSpecGeneration] Periodic check: Phase updated from backend', { + logger.debug('[useSpecGeneration] Periodic check: Phase updated from backend', { old: currentPhase, new: status.currentPhase, }); setCurrentPhase(status.currentPhase); } } catch (error) { - console.error('[useSpecGeneration] Periodic status check error:', error); + logger.error('[useSpecGeneration] Periodic status check error:', error); } }, STATUS_CHECK_INTERVAL_MS); @@ -214,7 +217,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { if (!api.specRegeneration) return; const unsubscribe = api.specRegeneration.onEvent((event: SpecRegenerationEvent) => { - console.log( + logger.debug( '[useSpecGeneration] Regeneration event:', event.type, 'for project:', @@ -224,14 +227,14 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { ); if (event.projectPath !== currentProject?.path) { - console.log('[useSpecGeneration] Ignoring event - not for current project'); + logger.debug('[useSpecGeneration] Ignoring event - not for current project'); return; } if (pendingStatusTimeoutRef.current) { clearTimeout(pendingStatusTimeoutRef.current); pendingStatusTimeoutRef.current = null; - console.log( + logger.debug( '[useSpecGeneration] Event confirmed this is for current project - clearing timeout' ); } @@ -244,10 +247,10 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { if (phaseMatch) { const phase = phaseMatch[1]; setCurrentPhase(phase); - console.log(`[useSpecGeneration] Phase updated: ${phase}`); + logger.debug(`[useSpecGeneration] Phase updated: ${phase}`); if (phase === 'complete') { - console.log('[useSpecGeneration] Phase is complete - clearing state'); + logger.debug('[useSpecGeneration] Phase is complete - clearing state'); setIsCreating(false); setIsRegenerating(false); stateRestoredRef.current = false; @@ -261,7 +264,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { event.content.includes('All tasks completed') || event.content.includes('✓ All tasks completed') ) { - console.log( + logger.debug( '[useSpecGeneration] Detected completion in progress message - clearing state' ); setIsCreating(false); @@ -276,7 +279,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { const newLog = logsRef.current + event.content; logsRef.current = newLog; setLogs(newLog); - console.log('[useSpecGeneration] Progress:', event.content.substring(0, 100)); + logger.debug('[useSpecGeneration] Progress:', event.content.substring(0, 100)); if (errorMessage) { setErrorMessage(''); @@ -292,7 +295,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { setCurrentPhase('feature_generation'); setIsCreating(true); setIsRegenerating(true); - console.log( + logger.debug( '[useSpecGeneration] Detected feature creation tool - setting phase to feature_generation' ); } @@ -305,7 +308,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { const newLog = logsRef.current + toolLog; logsRef.current = newLog; setLogs(newLog); - console.log('[useSpecGeneration] Tool:', event.tool, event.input); + logger.debug('[useSpecGeneration] Tool:', event.tool, event.input); } else if (event.type === 'spec_regeneration_complete') { const completionLog = logsRef.current + `\n[Complete] ${event.message}\n`; logsRef.current = completionLog; @@ -328,7 +331,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { (isFinalCompletionMessage || hasCompletePhase) && !isIntermediateCompletion; if (shouldComplete) { - console.log('[useSpecGeneration] Final completion detected - clearing state', { + logger.debug('[useSpecGeneration] Final completion detected - clearing state', { isFinalCompletionMessage, hasCompletePhase, message: event.message, @@ -367,12 +370,12 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { setIsCreating(true); setIsRegenerating(true); setCurrentPhase('feature_generation'); - console.log( + logger.debug( '[useSpecGeneration] Intermediate completion, continuing with feature generation' ); } - console.log('[useSpecGeneration] Spec generation event:', event.message); + logger.debug('[useSpecGeneration] Spec generation event:', event.message); } else if (event.type === 'spec_regeneration_error') { setIsRegenerating(false); setIsCreating(false); @@ -383,7 +386,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { const errorLog = logsRef.current + `\n\n[ERROR] ${event.error}\n`; logsRef.current = errorLog; setLogs(errorLog); - console.error('[useSpecGeneration] Regeneration error:', event.error); + logger.error('[useSpecGeneration] Regeneration error:', event.error); } }); @@ -402,11 +405,11 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { setErrorMessage(''); logsRef.current = ''; setLogs(''); - console.log('[useSpecGeneration] Starting spec creation, generateFeatures:', generateFeatures); + logger.debug('[useSpecGeneration] Starting spec creation, generateFeatures:', generateFeatures); try { const api = getElectronAPI(); if (!api.specRegeneration) { - console.error('[useSpecGeneration] Spec regeneration not available'); + logger.error('[useSpecGeneration] Spec regeneration not available'); setIsCreating(false); return; } @@ -420,7 +423,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { if (!result.success) { const errorMsg = result.error || 'Unknown error'; - console.error('[useSpecGeneration] Failed to start spec creation:', errorMsg); + logger.error('[useSpecGeneration] Failed to start spec creation:', errorMsg); setIsCreating(false); setCurrentPhase('error'); setErrorMessage(errorMsg); @@ -430,7 +433,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); - console.error('[useSpecGeneration] Failed to create spec:', errorMsg); + logger.error('[useSpecGeneration] Failed to create spec:', errorMsg); setIsCreating(false); setCurrentPhase('error'); setErrorMessage(errorMsg); @@ -455,14 +458,14 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { setErrorMessage(''); logsRef.current = ''; setLogs(''); - console.log( + logger.debug( '[useSpecGeneration] Starting spec regeneration, generateFeatures:', generateFeaturesOnRegenerate ); try { const api = getElectronAPI(); if (!api.specRegeneration) { - console.error('[useSpecGeneration] Spec regeneration not available'); + logger.error('[useSpecGeneration] Spec regeneration not available'); setIsRegenerating(false); return; } @@ -476,7 +479,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { if (!result.success) { const errorMsg = result.error || 'Unknown error'; - console.error('[useSpecGeneration] Failed to start regeneration:', errorMsg); + logger.error('[useSpecGeneration] Failed to start regeneration:', errorMsg); setIsRegenerating(false); setCurrentPhase('error'); setErrorMessage(errorMsg); @@ -486,7 +489,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); - console.error('[useSpecGeneration] Failed to regenerate spec:', errorMsg); + logger.error('[useSpecGeneration] Failed to regenerate spec:', errorMsg); setIsRegenerating(false); setCurrentPhase('error'); setErrorMessage(errorMsg); @@ -511,11 +514,11 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { setErrorMessage(''); logsRef.current = ''; setLogs(''); - console.log('[useSpecGeneration] Starting feature generation from existing spec'); + logger.debug('[useSpecGeneration] Starting feature generation from existing spec'); try { const api = getElectronAPI(); if (!api.specRegeneration) { - console.error('[useSpecGeneration] Spec regeneration not available'); + logger.error('[useSpecGeneration] Spec regeneration not available'); setIsGeneratingFeatures(false); return; } @@ -523,7 +526,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { if (!result.success) { const errorMsg = result.error || 'Unknown error'; - console.error('[useSpecGeneration] Failed to start feature generation:', errorMsg); + logger.error('[useSpecGeneration] Failed to start feature generation:', errorMsg); setIsGeneratingFeatures(false); setCurrentPhase('error'); setErrorMessage(errorMsg); @@ -533,7 +536,7 @@ export function useSpecGeneration({ loadSpec }: UseSpecGenerationOptions) { } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); - console.error('[useSpecGeneration] Failed to generate features:', errorMsg); + logger.error('[useSpecGeneration] Failed to generate features:', errorMsg); setIsGeneratingFeatures(false); setCurrentPhase('error'); setErrorMessage(errorMsg); diff --git a/apps/ui/src/components/views/spec-view/hooks/use-spec-loading.ts b/apps/ui/src/components/views/spec-view/hooks/use-spec-loading.ts index e716f847..01abbfbe 100644 --- a/apps/ui/src/components/views/spec-view/hooks/use-spec-loading.ts +++ b/apps/ui/src/components/views/spec-view/hooks/use-spec-loading.ts @@ -1,5 +1,8 @@ import { useEffect, useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; + +const logger = createLogger('SpecLoading'); import { getElectronAPI } from '@/lib/electron'; export function useSpecLoading() { @@ -24,7 +27,7 @@ export function useSpecLoading() { setSpecExists(false); } } catch (error) { - console.error('Failed to load spec:', error); + logger.error('Failed to load spec:', error); setSpecExists(false); } finally { setIsLoading(false); diff --git a/apps/ui/src/components/views/spec-view/hooks/use-spec-save.ts b/apps/ui/src/components/views/spec-view/hooks/use-spec-save.ts index b8ba3351..5b0bbb47 100644 --- a/apps/ui/src/components/views/spec-view/hooks/use-spec-save.ts +++ b/apps/ui/src/components/views/spec-view/hooks/use-spec-save.ts @@ -1,5 +1,8 @@ import { useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; + +const logger = createLogger('SpecSave'); import { getElectronAPI } from '@/lib/electron'; export function useSpecSave() { @@ -16,7 +19,7 @@ export function useSpecSave() { await api.writeFile(`${currentProject.path}/.automaker/app_spec.txt`, appSpec); setHasChanges(false); } catch (error) { - console.error('Failed to save spec:', error); + logger.error('Failed to save spec:', error); } finally { setIsSaving(false); } diff --git a/apps/ui/src/components/views/terminal-view.tsx b/apps/ui/src/components/views/terminal-view.tsx index 1b0c19b7..0cad0408 100644 --- a/apps/ui/src/components/views/terminal-view.tsx +++ b/apps/ui/src/components/views/terminal-view.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Terminal as TerminalIcon, Plus, @@ -50,6 +51,8 @@ import { cn } from '@/lib/utils'; import { apiFetch, apiGet, apiPost, apiDeleteRaw, getAuthHeaders } from '@/lib/api-fetch'; import { getApiKey } from '@/lib/http-api-client'; +const logger = createLogger('Terminal'); + interface TerminalStatus { enabled: boolean; passwordRequired: boolean; @@ -301,7 +304,7 @@ export function TerminalView() { headers['X-Terminal-Token'] = terminalState.authToken; } - console.log(`[Terminal] Killing ${sessionIds.length} sessions on server`); + logger.info(`Killing ${sessionIds.length} sessions on server`); // Kill all sessions in parallel await Promise.allSettled( @@ -309,7 +312,7 @@ export function TerminalView() { try { await apiDeleteRaw(`/api/terminal/sessions/${sessionId}`, { headers }); } catch (err) { - console.error(`[Terminal] Failed to kill session ${sessionId}:`, err); + logger.error(`Failed to kill session ${sessionId}:`, err); } }) ); @@ -320,7 +323,7 @@ export function TerminalView() { const canCreateTerminal = (debounceMessage: string): boolean => { const now = Date.now(); if (now - lastCreateTimeRef.current < CREATE_COOLDOWN_MS || isCreatingRef.current) { - console.log(debounceMessage); + logger.debug(debounceMessage); return false; } lastCreateTimeRef.current = now; @@ -447,7 +450,7 @@ export function TerminalView() { } } catch (err) { setError('Failed to connect to server'); - console.error('[Terminal] Status fetch error:', err); + logger.error('Status fetch error:', err); } finally { setLoading(false); } @@ -469,7 +472,7 @@ export function TerminalView() { setServerSessionInfo({ current: data.data.currentSessions, max: data.data.maxSessions }); } } catch (err) { - console.error('[Terminal] Failed to fetch server settings:', err); + logger.error('Failed to fetch server settings:', err); } }, [terminalState.isUnlocked, terminalState.authToken]); @@ -573,7 +576,7 @@ export function TerminalView() { // If no saved layout or no tabs, we're done - terminal starts fresh for this project if (!savedLayout || savedLayout.tabs.length === 0) { - console.log('[Terminal] No saved layout for project, starting fresh'); + logger.info('No saved layout for project, starting fresh'); return; } @@ -585,7 +588,7 @@ export function TerminalView() { const restoreLayout = async () => { // Check if we're still restoring the same project (user may have switched) if (restoringProjectPathRef.current !== currentPath) { - console.log('[Terminal] Restore cancelled - project changed'); + logger.info('Restore cancelled - project changed'); return; } @@ -623,7 +626,7 @@ export function TerminalView() { ); return data.success && data.data ? data.data.id : null; } catch (err) { - console.error('[Terminal] Failed to create terminal session:', err); + logger.error('Failed to create terminal session:', err); return null; } }; @@ -691,7 +694,7 @@ export function TerminalView() { for (let tabIndex = 0; tabIndex < savedLayout.tabs.length; tabIndex++) { // Check if project changed during restore - bail out early if (restoringProjectPathRef.current !== currentPath) { - console.log('[Terminal] Restore cancelled mid-loop - project changed'); + logger.info('Restore cancelled mid-loop - project changed'); return; } @@ -730,7 +733,7 @@ export function TerminalView() { }); } } catch (err) { - console.error('[Terminal] Failed to restore terminal layout:', err); + logger.error('Failed to restore terminal layout:', err); toast.error('Failed to restore terminals', { description: 'Could not restore terminal layout. Please try creating new terminals.', duration: 5000, @@ -806,7 +809,7 @@ export function TerminalView() { } } catch (err) { setAuthError('Failed to authenticate'); - console.error('[Terminal] Auth error:', err); + logger.error('Auth error:', err); } finally { setAuthLoading(false); } @@ -851,14 +854,14 @@ export function TerminalView() { `Please close unused terminals. Limit: ${data.maxSessions || 'unknown'}`, }); } else { - console.error('[Terminal] Failed to create session:', data.error); + logger.error('Failed to create session:', data.error); toast.error('Failed to create terminal', { description: data.error || 'Unknown error', }); } } } catch (err) { - console.error('[Terminal] Create session error:', err); + logger.error('Create session error:', err); toast.error('Failed to create terminal', { description: 'Could not connect to server', }); @@ -915,7 +918,7 @@ export function TerminalView() { } } } catch (err) { - console.error('[Terminal] Create session error:', err); + logger.error('Create session error:', err); // Remove the empty tab on error const { removeTerminalTab } = useAppStore.getState(); removeTerminalTab(tabId); @@ -943,16 +946,13 @@ export function TerminalView() { if (!response.ok && response.status !== 404) { // Log non-404 errors but still proceed with UI cleanup const data = await response.json().catch(() => ({})); - console.error( - '[Terminal] Server failed to kill session:', - data.error || response.statusText - ); + logger.error('Server failed to kill session:', data.error || response.statusText); } // Refresh session count fetchServerSettings(); } catch (err) { - console.error('[Terminal] Kill session error:', err); + logger.error('Kill session error:', err); // Still remove from UI on network error - better UX than leaving broken terminal removeTerminalFromLayout(sessionId); } @@ -983,7 +983,7 @@ export function TerminalView() { try { await apiDeleteRaw(`/api/terminal/sessions/${sessionId}`, { headers }); } catch (err) { - console.error(`[Terminal] Failed to kill session ${sessionId}:`, err); + logger.error(`Failed to kill session ${sessionId}:`, err); } }) ); @@ -1210,9 +1210,7 @@ export function TerminalView() { onSessionInvalid={() => { // Auto-remove stale session when server says it doesn't exist // This handles cases like server restart where sessions are lost - console.log( - `[Terminal] Session ${content.sessionId} is invalid, removing from layout` - ); + logger.info(`Session ${content.sessionId} is invalid, removing from layout`); killTerminal(content.sessionId); }} isDragging={activeDragId === content.sessionId} @@ -1587,9 +1585,7 @@ export function TerminalView() { onNewTab={createTerminalInNewTab} onSessionInvalid={() => { const sessionId = terminalState.maximizedSessionId!; - console.log( - `[Terminal] Maximized session ${sessionId} is invalid, removing from layout` - ); + logger.info(`Maximized session ${sessionId} is invalid, removing from layout`); killTerminal(sessionId); }} isDragging={false} diff --git a/apps/ui/src/components/views/terminal-view/terminal-error-boundary.tsx b/apps/ui/src/components/views/terminal-view/terminal-error-boundary.tsx index e928036e..32f82e34 100644 --- a/apps/ui/src/components/views/terminal-view/terminal-error-boundary.tsx +++ b/apps/ui/src/components/views/terminal-view/terminal-error-boundary.tsx @@ -1,8 +1,11 @@ import React, { Component, ErrorInfo } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { AlertCircle, RefreshCw } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; +const logger = createLogger('TerminalErrorBoundary'); + interface Props { children: React.ReactNode; sessionId: string; @@ -30,7 +33,7 @@ export class TerminalErrorBoundary extends Component { } componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error('[TerminalErrorBoundary] Terminal crashed:', { + logger.error('Terminal crashed:', { sessionId: this.props.sessionId, error: error.message, stack: error.stack, diff --git a/apps/ui/src/components/views/terminal-view/terminal-panel.tsx b/apps/ui/src/components/views/terminal-view/terminal-panel.tsx index 674c87de..8e0f6b96 100644 --- a/apps/ui/src/components/views/terminal-view/terminal-panel.tsx +++ b/apps/ui/src/components/views/terminal-view/terminal-panel.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useCallback, useState } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { X, SplitSquareHorizontal, @@ -42,6 +43,8 @@ import { toast } from 'sonner'; import { getElectronAPI } from '@/lib/electron'; import { getApiKey, getSessionToken, getServerUrlSync } from '@/lib/http-api-client'; +const logger = createLogger('Terminal'); + // Font size constraints const MIN_FONT_SIZE = 8; const MAX_FONT_SIZE = 32; @@ -296,7 +299,7 @@ export function TerminalPanel({ toast.success('Copied to clipboard'); return true; } catch (err) { - console.error('[Terminal] Copy failed:', err); + logger.error('Copy failed:', err); const errorMessage = err instanceof Error ? err.message : 'Unknown error'; toast.error('Copy failed', { description: errorMessage.includes('permission') @@ -361,7 +364,7 @@ export function TerminalPanel({ await sendTextInChunks(text); } catch (err) { - console.error('[Terminal] Paste failed:', err); + logger.error('Paste failed:', err); const errorMessage = err instanceof Error ? err.message : 'Unknown error'; toast.error('Paste failed', { description: errorMessage.includes('permission') @@ -504,7 +507,7 @@ export function TerminalPanel({ }); if (!response.ok) { - console.warn('[Terminal] Failed to fetch wsToken:', response.status); + logger.warn('Failed to fetch wsToken:', response.status); return null; } @@ -515,7 +518,7 @@ export function TerminalPanel({ return null; } catch (error) { - console.error('[Terminal] Error fetching wsToken:', error); + logger.error('Error fetching wsToken:', error); return null; } }, [serverUrl]); @@ -595,7 +598,7 @@ export function TerminalPanel({ const api = getElectronAPI(); if (api?.openExternalLink) { api.openExternalLink(uri).catch((error) => { - console.error('[Terminal] Failed to open URL:', error); + logger.error('Failed to open URL:', error); // Fallback to window.open if Electron API fails window.open(uri, '_blank', 'noopener,noreferrer'); }); @@ -697,7 +700,7 @@ export function TerminalPanel({ } } catch { // If we can't get home path, just use the path as-is - console.warn('[Terminal] Could not resolve home directory path'); + logger.warn('Could not resolve home directory path'); } } else if (!clickedPath.startsWith('/') && !clickedPath.match(/^[a-zA-Z]:\\/)) { // Relative path - resolve against project path @@ -721,7 +724,7 @@ export function TerminalPanel({ toast.error('Failed to open in editor', { description: result.error }); } } catch (error) { - console.error('[Terminal] Failed to open file:', error); + logger.error('Failed to open file:', error); toast.error('Failed to open file', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -744,7 +747,7 @@ export function TerminalPanel({ }); terminal.loadAddon(webglAddon); } catch { - console.warn('[Terminal] WebGL addon not available, falling back to canvas'); + logger.warn('WebGL addon not available, falling back to canvas'); } // Fit terminal to container - wait for stable dimensions @@ -770,7 +773,7 @@ export function TerminalPanel({ try { fitAddon.fit(); } catch (err) { - console.error('[Terminal] Initial fit error:', err); + logger.error('Initial fit error:', err); } return; } @@ -1000,7 +1003,7 @@ export function TerminalPanel({ wsRef.current = ws; ws.onopen = () => { - console.log(`[Terminal] WebSocket connected for session ${sessionId}`); + logger.info(`WebSocket connected for session ${sessionId}`); setConnectionStatus('connected'); reconnectAttemptsRef.current = 0; @@ -1037,7 +1040,7 @@ export function TerminalPanel({ } break; case 'connected': { - console.log(`[Terminal] Session connected: ${msg.shell} in ${msg.cwd}`); + logger.info(`Session connected: ${msg.shell} in ${msg.cwd}`); // Detect shell type from path const shellPath = (msg.shell || '').toLowerCase(); // Windows shells use backslash paths and include powershell/pwsh/cmd @@ -1088,16 +1091,12 @@ export function TerminalPanel({ break; } } catch (err) { - console.error('[Terminal] Message parse error:', err); + logger.error('Message parse error:', err); } }; ws.onclose = (event) => { - console.log( - `[Terminal] WebSocket closed for session ${sessionId}:`, - event.code, - event.reason - ); + logger.info(`WebSocket closed for session ${sessionId}: ${event.code} ${event.reason}`); wsRef.current = null; // Clear heartbeat interval @@ -1167,8 +1166,8 @@ export function TerminalPanel({ // Attempt reconnect after exponential delay reconnectTimeoutRef.current = setTimeout(() => { if (xtermRef.current) { - console.log( - `[Terminal] Attempting reconnect for session ${sessionId} (attempt ${reconnectAttemptsRef.current}/${MAX_RECONNECT_ATTEMPTS})` + logger.info( + `Attempting reconnect for session ${sessionId} (attempt ${reconnectAttemptsRef.current}/${MAX_RECONNECT_ATTEMPTS})` ); connect(); } @@ -1176,7 +1175,7 @@ export function TerminalPanel({ }; ws.onerror = (error) => { - console.error(`[Terminal] WebSocket error for session ${sessionId}:`, error); + logger.error(`WebSocket error for session ${sessionId}:`, error); }; }; @@ -1234,7 +1233,7 @@ export function TerminalPanel({ wsRef.current.send(JSON.stringify({ type: 'resize', cols, rows })); } } catch (err) { - console.error('[Terminal] Resize error:', err); + logger.error('Resize error:', err); } }, RESIZE_DEBOUNCE_MS); }, []); @@ -1551,7 +1550,7 @@ export function TerminalPanel({ const api = getElectronAPI(); if (!api.saveImageToTemp) { // Fallback path when Electron API is not available (browser mode) - console.warn('[Terminal] saveImageToTemp not available, returning fallback path'); + logger.warn('saveImageToTemp not available, returning fallback path'); return `.automaker/images/${Date.now()}_${filename}`; } @@ -1560,10 +1559,10 @@ export function TerminalPanel({ if (result.success && result.path) { return result.path; } - console.error('[Terminal] Failed to save image:', result.error); + logger.error('Failed to save image:', result.error); return null; } catch (error) { - console.error('[Terminal] Error saving image:', error); + logger.error('Error saving image:', error); return null; } }, @@ -1662,7 +1661,7 @@ export function TerminalPanel({ toast.error(`Failed to save: ${file.name}`); } } catch (error) { - console.error('[Terminal] Error processing image:', error); + logger.error('Error processing image:', error); toast.error(`Error processing: ${file.name}`); } } diff --git a/apps/ui/src/components/views/welcome-view.tsx b/apps/ui/src/components/views/welcome-view.tsx index cc9afd60..33eb895c 100644 --- a/apps/ui/src/components/views/welcome-view.tsx +++ b/apps/ui/src/components/views/welcome-view.tsx @@ -1,4 +1,5 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Button } from '@/components/ui/button'; import { Dialog, @@ -34,6 +35,8 @@ import { getHttpApiClient } from '@/lib/http-api-client'; import type { StarterTemplate } from '@/lib/templates'; import { useNavigate } from '@tanstack/react-router'; +const logger = createLogger('WelcomeView'); + export function WelcomeView() { const { projects, @@ -65,13 +68,13 @@ export function WelcomeView() { const api = getElectronAPI(); if (!api.autoMode?.analyzeProject) { - console.log('[Welcome] Auto mode API not available, skipping analysis'); + logger.info('[Welcome] Auto mode API not available, skipping analysis'); return; } setIsAnalyzing(true); try { - console.log('[Welcome] Starting project analysis for:', projectPath); + logger.info('[Welcome] Starting project analysis for:', projectPath); const result = await api.autoMode.analyzeProject(projectPath); if (result.success) { @@ -79,10 +82,10 @@ export function WelcomeView() { description: 'AI agent has analyzed your project structure', }); } else { - console.error('[Welcome] Project analysis failed:', result.error); + logger.error('[Welcome] Project analysis failed:', result.error); } } catch (error) { - console.error('[Welcome] Failed to analyze project:', error); + logger.error('[Welcome] Failed to analyze project:', error); } finally { setIsAnalyzing(false); } @@ -125,8 +128,8 @@ export function WelcomeView() { setShowInitDialog(true); // Kick off agent to analyze the project and update app_spec.txt - console.log('[Welcome] Project initialized, created files:', initResult.createdFiles); - console.log('[Welcome] Kicking off project analysis agent...'); + logger.info('[Welcome] Project initialized, created files:', initResult.createdFiles); + logger.info('[Welcome] Kicking off project analysis agent...'); // Start analysis in background (don't await, let it run async) analyzeProject(path); @@ -139,7 +142,7 @@ export function WelcomeView() { // Navigate to the board view navigate({ to: '/board' }); } catch (error) { - console.error('[Welcome] Failed to open project:', error); + logger.error('[Welcome] Failed to open project:', error); toast.error('Failed to open project', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -179,7 +182,7 @@ export function WelcomeView() { } } } catch (error) { - console.error('[Welcome] Failed to check workspace config:', error); + logger.error('[Welcome] Failed to check workspace config:', error); // Fall back to current behavior on error const api = getElectronAPI(); const result = await api.openDirectory(); @@ -317,7 +320,7 @@ export function WelcomeView() { }); setShowInitDialog(true); } catch (error) { - console.error('Failed to create project:', error); + logger.error('Failed to create project:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -418,7 +421,7 @@ export function WelcomeView() { // Kick off project analysis analyzeProject(projectPath); } catch (error) { - console.error('Failed to create project from template:', error); + logger.error('Failed to create project from template:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); @@ -515,7 +518,7 @@ export function WelcomeView() { // Kick off project analysis analyzeProject(projectPath); } catch (error) { - console.error('Failed to create project from custom URL:', error); + logger.error('Failed to create project from custom URL:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); diff --git a/apps/ui/src/hooks/use-auto-mode.ts b/apps/ui/src/hooks/use-auto-mode.ts index a8a5808a..d1ff588c 100644 --- a/apps/ui/src/hooks/use-auto-mode.ts +++ b/apps/ui/src/hooks/use-auto-mode.ts @@ -1,9 +1,12 @@ import { useEffect, useCallback, useMemo } from 'react'; import { useShallow } from 'zustand/react/shallow'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import type { AutoModeEvent } from '@/types/electron'; +const logger = createLogger('AutoMode'); + // Type guard for plan_approval_required event function isPlanApprovalEvent( event: AutoModeEvent @@ -67,7 +70,7 @@ export function useAutoMode() { if (!api?.autoMode) return; const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => { - console.log('[AutoMode Event]', event); + logger.info('Event:', event); // Events include projectPath from backend - use it to look up project ID // Fall back to current projectId if not provided in event @@ -84,7 +87,7 @@ export function useAutoMode() { // Skip event if we couldn't determine the project if (!eventProjectId) { - console.warn('[AutoMode] Could not determine project for event:', event); + logger.warn('Could not determine project for event:', event); return; } @@ -103,7 +106,7 @@ export function useAutoMode() { case 'auto_mode_feature_complete': // Feature completed - remove from running tasks and UI will reload features on its own if (event.featureId) { - console.log('[AutoMode] Feature completed:', event.featureId, 'passes:', event.passes); + logger.info('Feature completed:', event.featureId, 'passes:', event.passes); removeRunningTask(eventProjectId, event.featureId); addAutoModeActivity({ featureId: event.featureId, @@ -121,7 +124,7 @@ export function useAutoMode() { // Check if this is a user-initiated cancellation or abort (not a real error) if (event.errorType === 'cancellation' || event.errorType === 'abort') { // User cancelled/aborted the feature - just log as info, not an error - console.log('[AutoMode] Feature cancelled/aborted:', event.error); + logger.info('Feature cancelled/aborted:', event.error); // Remove from running tasks if (eventProjectId) { removeRunningTask(eventProjectId, event.featureId); @@ -130,7 +133,7 @@ export function useAutoMode() { } // Real error - log and show to user - console.error('[AutoMode Error]', event.error); + logger.error('Error:', event.error); // Check for authentication errors and provide a more helpful message const isAuthError = @@ -182,7 +185,7 @@ export function useAutoMode() { case 'auto_mode_phase': // Log phase transitions (Planning, Action, Verification) if (event.featureId && event.phase && event.message) { - console.log(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`); + logger.debug(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`); addAutoModeActivity({ featureId: event.featureId, type: event.phase, @@ -195,7 +198,7 @@ export function useAutoMode() { case 'plan_approval_required': // Plan requires user approval before proceeding if (isPlanApprovalEvent(event)) { - console.log(`[AutoMode] Plan approval required for ${event.featureId}`); + logger.debug(`[AutoMode] Plan approval required for ${event.featureId}`); setPendingPlanApproval({ featureId: event.featureId, projectPath: event.projectPath || currentProject?.path || '', @@ -208,7 +211,7 @@ export function useAutoMode() { case 'planning_started': // Log when planning phase begins if (event.featureId && event.mode && event.message) { - console.log(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`); + logger.debug(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`); addAutoModeActivity({ featureId: event.featureId, type: 'planning', @@ -221,7 +224,7 @@ export function useAutoMode() { case 'plan_approved': // Log when plan is approved by user if (event.featureId) { - console.log(`[AutoMode] Plan approved for ${event.featureId}`); + logger.debug(`[AutoMode] Plan approved for ${event.featureId}`); addAutoModeActivity({ featureId: event.featureId, type: 'action', @@ -236,7 +239,7 @@ export function useAutoMode() { case 'plan_auto_approved': // Log when plan is auto-approved (requirePlanApproval=false) if (event.featureId) { - console.log(`[AutoMode] Plan auto-approved for ${event.featureId}`); + logger.debug(`[AutoMode] Plan auto-approved for ${event.featureId}`); addAutoModeActivity({ featureId: event.featureId, type: 'action', @@ -253,7 +256,7 @@ export function useAutoMode() { AutoModeEvent, { type: 'plan_revision_requested' } >; - console.log( + logger.debug( `[AutoMode] Plan revision requested for ${event.featureId} (v${revisionEvent.planVersion})` ); addAutoModeActivity({ @@ -269,7 +272,7 @@ export function useAutoMode() { // Task started - show which task is being worked on if (event.featureId && 'taskId' in event && 'taskDescription' in event) { const taskEvent = event as Extract; - console.log( + logger.debug( `[AutoMode] Task ${taskEvent.taskId} started for ${event.featureId}: ${taskEvent.taskDescription}` ); addAutoModeActivity({ @@ -284,7 +287,7 @@ export function useAutoMode() { // Task completed - show progress if (event.featureId && 'taskId' in event) { const taskEvent = event as Extract; - console.log( + logger.debug( `[AutoMode] Task ${taskEvent.taskId} completed for ${event.featureId} (${taskEvent.tasksCompleted}/${taskEvent.tasksTotal})` ); addAutoModeActivity({ @@ -302,7 +305,7 @@ export function useAutoMode() { AutoModeEvent, { type: 'auto_mode_phase_complete' } >; - console.log( + logger.debug( `[AutoMode] Phase ${phaseEvent.phaseNumber} completed for ${event.featureId}` ); addAutoModeActivity({ @@ -330,18 +333,18 @@ export function useAutoMode() { // Start auto mode - UI only, feature pickup is handled in board-view.tsx const start = useCallback(() => { if (!currentProject) { - console.error('No project selected'); + logger.error('No project selected'); return; } setAutoModeRunning(currentProject.id, true); - console.log(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`); + logger.debug(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`); }, [currentProject, setAutoModeRunning, maxConcurrency]); // Stop auto mode - UI only, running tasks continue until natural completion const stop = useCallback(() => { if (!currentProject) { - console.error('No project selected'); + logger.error('No project selected'); return; } @@ -350,14 +353,14 @@ export function useAutoMode() { // Stopping auto mode only turns off the toggle to prevent new features // from being picked up. Running tasks will complete naturally and be // removed via the auto_mode_feature_complete event. - console.log('[AutoMode] Stopped - running tasks will continue'); + logger.info('Stopped - running tasks will continue'); }, [currentProject, setAutoModeRunning]); // Stop a specific feature const stopFeature = useCallback( async (featureId: string) => { if (!currentProject) { - console.error('No project selected'); + logger.error('No project selected'); return; } @@ -371,7 +374,7 @@ export function useAutoMode() { if (result.success) { removeRunningTask(currentProject.id, featureId); - console.log('[AutoMode] Feature stopped successfully:', featureId); + logger.info('Feature stopped successfully:', featureId); addAutoModeActivity({ featureId, type: 'complete', @@ -379,11 +382,11 @@ export function useAutoMode() { passes: false, }); } else { - console.error('[AutoMode] Failed to stop feature:', result.error); + logger.error('Failed to stop feature:', result.error); throw new Error(result.error || 'Failed to stop feature'); } } catch (error) { - console.error('[AutoMode] Error stopping feature:', error); + logger.error('Error stopping feature:', error); throw error; } }, diff --git a/apps/ui/src/hooks/use-board-background-settings.ts b/apps/ui/src/hooks/use-board-background-settings.ts index 2e00be96..fdb09b36 100644 --- a/apps/ui/src/hooks/use-board-background-settings.ts +++ b/apps/ui/src/hooks/use-board-background-settings.ts @@ -1,8 +1,11 @@ import { useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { useAppStore } from '@/store/app-store'; import { getHttpApiClient } from '@/lib/http-api-client'; import { toast } from 'sonner'; +const logger = createLogger('BoardBackground'); + /** * Hook for managing board background settings with automatic persistence to server */ @@ -19,11 +22,11 @@ export function useBoardBackgroundSettings() { }); if (!result.success) { - console.error('Failed to persist settings:', result.error); + logger.error('Failed to persist settings:', result.error); toast.error('Failed to save settings'); } } catch (error) { - console.error('Failed to persist settings:', error); + logger.error('Failed to persist settings:', error); toast.error('Failed to save settings'); } }, diff --git a/apps/ui/src/hooks/use-electron-agent.ts b/apps/ui/src/hooks/use-electron-agent.ts index 5b0d9b43..993b14bc 100644 --- a/apps/ui/src/hooks/use-electron-agent.ts +++ b/apps/ui/src/hooks/use-electron-agent.ts @@ -4,6 +4,9 @@ import { useMessageQueue } from './use-message-queue'; import type { ImageAttachment, TextFileAttachment } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { sanitizeFilename } from '@/lib/image-utils'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('ElectronAgent'); interface UseElectronAgentOptions { sessionId: string; @@ -93,7 +96,7 @@ export function useElectronAgent({ setError(null); try { - console.log('[useElectronAgent] Sending message directly', { + logger.info('Sending message directly', { hasImages: images && images.length > 0, imageCount: images?.length || 0, hasTextFiles: textFiles && textFiles.length > 0, @@ -123,9 +126,9 @@ export function useElectronAgent({ ); if (result.success && result.path) { imagePaths.push(result.path); - console.log('[useElectronAgent] Saved image to .automaker/images:', result.path); + logger.info('Saved image to .automaker/images:', result.path); } else { - console.error('[useElectronAgent] Failed to save image:', result.error); + logger.error('Failed to save image:', result.error); } } } @@ -146,7 +149,7 @@ export function useElectronAgent({ // Note: We don't set isProcessing to false here because // it will be set by the "complete" or "error" stream event } catch (err) { - console.error('[useElectronAgent] Failed to send message:', err); + logger.error('Failed to send message:', err); setError(err instanceof Error ? err.message : 'Failed to send message'); setIsProcessing(false); throw err; @@ -191,13 +194,13 @@ export function useElectronAgent({ setError(null); try { - console.log('[useElectronAgent] Starting session:', sessionId); + logger.info('Starting session:', sessionId); const result = await api.agent!.start(sessionId, workingDirectory); if (!mounted) return; if (result.success && result.messages) { - console.log('[useElectronAgent] Loaded', result.messages.length, 'messages'); + logger.info('Loaded', result.messages.length, 'messages'); setMessages(result.messages); setIsConnected(true); @@ -205,7 +208,7 @@ export function useElectronAgent({ const historyResult = await api.agent!.getHistory(sessionId); if (mounted && historyResult.success) { const isRunning = historyResult.isRunning || false; - console.log('[useElectronAgent] Session running state:', isRunning); + logger.info('Session running state:', isRunning); setIsProcessing(isRunning); } } else { @@ -214,7 +217,7 @@ export function useElectronAgent({ } } catch (err) { if (!mounted) return; - console.error('[useElectronAgent] Failed to initialize:', err); + logger.error('Failed to initialize:', err); setError(err instanceof Error ? err.message : 'Failed to initialize'); setIsProcessing(false); } @@ -230,7 +233,7 @@ export function useElectronAgent({ // Auto-process queue when agent finishes processing useEffect(() => { if (!isProcessing && !isProcessingQueue && queuedMessages.length > 0) { - console.log('[useElectronAgent] Auto-processing next queued message'); + logger.info('Auto-processing next queued message'); processNext(); } }, [isProcessing, isProcessingQueue, queuedMessages.length, processNext]); @@ -241,21 +244,21 @@ export function useElectronAgent({ if (!api?.agent) return; if (!sessionId) return; // Don't subscribe if no session - console.log('[useElectronAgent] Subscribing to stream events for session:', sessionId); + logger.info('Subscribing to stream events for session:', sessionId); const handleStream = (event: StreamEvent) => { // CRITICAL: Only process events for our specific session if (event.sessionId !== sessionId) { - console.log('[useElectronAgent] Ignoring event for different session:', event.sessionId); + logger.info('Ignoring event for different session:', event.sessionId); return; } - console.log('[useElectronAgent] Stream event for', sessionId, ':', event.type); + logger.info('Stream event for', sessionId, ':', event.type); switch (event.type) { case 'started': // Agent started processing (including from queue) - console.log('[useElectronAgent] Agent started processing for session:', sessionId); + logger.info('Agent started processing for session:', sessionId); setIsProcessing(true); break; @@ -300,13 +303,13 @@ export function useElectronAgent({ case 'tool_use': // Tool being used - console.log('[useElectronAgent] Tool use:', event.tool.name); + logger.info('Tool use:', event.tool.name); onToolUse?.(event.tool.name, event.tool.input); break; case 'complete': // Agent finished processing for THIS session - console.log('[useElectronAgent] Processing complete for session:', sessionId); + logger.info('Processing complete for session:', sessionId); setIsProcessing(false); if (event.messageId) { setMessages((prev) => @@ -319,7 +322,7 @@ export function useElectronAgent({ case 'error': // Error occurred for THIS session - console.error('[useElectronAgent] Agent error for session:', sessionId, event.error); + logger.error('Agent error for session:', sessionId, event.error); setIsProcessing(false); setError(event.error); if (event.message) { @@ -330,13 +333,13 @@ export function useElectronAgent({ case 'queue_updated': // Server queue was updated - console.log('[useElectronAgent] Queue updated:', event.queue); + logger.info('Queue updated:', event.queue); setServerQueue(event.queue || []); break; case 'queue_error': // Error processing a queued prompt - console.error('[useElectronAgent] Queue error:', event.error); + logger.error('Queue error:', event.error); setError(event.error); break; } @@ -346,7 +349,7 @@ export function useElectronAgent({ return () => { if (unsubscribeRef.current) { - console.log('[useElectronAgent] Unsubscribing from stream events for session:', sessionId); + logger.info('Unsubscribing from stream events for session:', sessionId); unsubscribeRef.current(); unsubscribeRef.current = null; } @@ -363,7 +366,7 @@ export function useElectronAgent({ } if (isProcessing) { - console.warn('[useElectronAgent] Already processing a message'); + logger.warn('Already processing a message'); return; } @@ -371,7 +374,7 @@ export function useElectronAgent({ setError(null); try { - console.log('[useElectronAgent] Sending message', { + logger.info('Sending message', { hasImages: images && images.length > 0, imageCount: images?.length || 0, hasTextFiles: textFiles && textFiles.length > 0, @@ -401,9 +404,9 @@ export function useElectronAgent({ ); if (result.success && result.path) { imagePaths.push(result.path); - console.log('[useElectronAgent] Saved image to .automaker/images:', result.path); + logger.info('Saved image to .automaker/images:', result.path); } else { - console.error('[useElectronAgent] Failed to save image:', result.error); + logger.error('Failed to save image:', result.error); } } } @@ -424,7 +427,7 @@ export function useElectronAgent({ // Note: We don't set isProcessing to false here because // it will be set by the "complete" or "error" stream event } catch (err) { - console.error('[useElectronAgent] Failed to send message:', err); + logger.error('Failed to send message:', err); setError(err instanceof Error ? err.message : 'Failed to send message'); setIsProcessing(false); } @@ -441,7 +444,7 @@ export function useElectronAgent({ } try { - console.log('[useElectronAgent] Stopping execution'); + logger.info('Stopping execution'); const result = await api.agent!.stop(sessionId); if (!result.success) { @@ -450,7 +453,7 @@ export function useElectronAgent({ setIsProcessing(false); } } catch (err) { - console.error('[useElectronAgent] Failed to stop:', err); + logger.error('Failed to stop:', err); setError(err instanceof Error ? err.message : 'Failed to stop execution'); } }, [sessionId]); @@ -464,7 +467,7 @@ export function useElectronAgent({ } try { - console.log('[useElectronAgent] Clearing history'); + logger.info('Clearing history'); const result = await api.agent!.clear(sessionId); if (result.success) { @@ -474,7 +477,7 @@ export function useElectronAgent({ setError(result.error || 'Failed to clear history'); } } catch (err) { - console.error('[useElectronAgent] Failed to clear:', err); + logger.error('Failed to clear:', err); setError(err instanceof Error ? err.message : 'Failed to clear history'); } }, [sessionId]); @@ -516,14 +519,14 @@ export function useElectronAgent({ } } - console.log('[useElectronAgent] Adding to server queue'); + logger.info('Adding to server queue'); const result = await api.agent.queueAdd(sessionId, messageContent, imagePaths, model); if (!result.success) { setError(result.error || 'Failed to add to queue'); } } catch (err) { - console.error('[useElectronAgent] Failed to add to queue:', err); + logger.error('Failed to add to queue:', err); setError(err instanceof Error ? err.message : 'Failed to add to queue'); } }, @@ -540,14 +543,14 @@ export function useElectronAgent({ } try { - console.log('[useElectronAgent] Removing from server queue:', promptId); + logger.info('Removing from server queue:', promptId); const result = await api.agent.queueRemove(sessionId, promptId); if (!result.success) { setError(result.error || 'Failed to remove from queue'); } } catch (err) { - console.error('[useElectronAgent] Failed to remove from queue:', err); + logger.error('Failed to remove from queue:', err); setError(err instanceof Error ? err.message : 'Failed to remove from queue'); } }, @@ -563,14 +566,14 @@ export function useElectronAgent({ } try { - console.log('[useElectronAgent] Clearing server queue'); + logger.info('Clearing server queue'); const result = await api.agent.queueClear(sessionId); if (!result.success) { setError(result.error || 'Failed to clear queue'); } } catch (err) { - console.error('[useElectronAgent] Failed to clear queue:', err); + logger.error('Failed to clear queue:', err); setError(err instanceof Error ? err.message : 'Failed to clear queue'); } }, [sessionId]); diff --git a/apps/ui/src/hooks/use-message-queue.ts b/apps/ui/src/hooks/use-message-queue.ts index d84858a7..a99c1fd5 100644 --- a/apps/ui/src/hooks/use-message-queue.ts +++ b/apps/ui/src/hooks/use-message-queue.ts @@ -1,6 +1,9 @@ import { useState, useCallback } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import type { ImageAttachment, TextFileAttachment } from '@/store/app-store'; +const logger = createLogger('MessageQueue'); + export interface QueuedMessage { id: string; content: string; @@ -72,7 +75,7 @@ export function useMessageQueue({ onProcessNext }: UseMessageQueueOptions): UseM // Remove the processed message from queue setQueuedMessages((prev) => prev.slice(1)); } catch (error) { - console.error('Error processing queued message:', error); + logger.error('Error processing queued message:', error); // Keep the message in queue for retry or manual removal } finally { setIsProcessingQueue(false); diff --git a/apps/ui/src/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts index 5f60fc95..7f5bf0ed 100644 --- a/apps/ui/src/hooks/use-settings-migration.ts +++ b/apps/ui/src/hooks/use-settings-migration.ts @@ -18,11 +18,14 @@ */ import { useEffect, useState, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { getHttpApiClient, waitForApiKeyInit } from '@/lib/http-api-client'; import { isElectron } from '@/lib/electron'; import { getItem, removeItem } from '@/lib/storage'; import { useAppStore } from '@/store/app-store'; +const logger = createLogger('SettingsMigration'); + /** * State returned by useSettingsMigration hook */ @@ -109,7 +112,7 @@ export function useSettingsMigration(): MigrationState { const status = await api.settings.getStatus(); if (!status.success) { - console.error('[Settings Migration] Failed to get status:', status); + logger.error('Failed to get status:', status); setState({ checked: true, migrated: false, @@ -120,7 +123,7 @@ export function useSettingsMigration(): MigrationState { // If settings files already exist, no migration needed if (!status.needsMigration) { - console.log('[Settings Migration] Settings files exist, no migration needed'); + logger.info('Settings files exist, no migration needed'); setState({ checked: true, migrated: false, error: null }); return; } @@ -128,12 +131,12 @@ export function useSettingsMigration(): MigrationState { // Check if we have localStorage data to migrate const automakerStorage = getItem('automaker-storage'); if (!automakerStorage) { - console.log('[Settings Migration] No localStorage data to migrate'); + logger.info('No localStorage data to migrate'); setState({ checked: true, migrated: false, error: null }); return; } - console.log('[Settings Migration] Starting migration...'); + logger.info('Starting migration...'); // Collect all localStorage data const localStorageData: Record = {}; @@ -148,7 +151,7 @@ export function useSettingsMigration(): MigrationState { const result = await api.settings.migrate(localStorageData); if (result.success) { - console.log('[Settings Migration] Migration successful:', { + logger.info('Migration successful:', { globalSettings: result.migratedGlobalSettings, credentials: result.migratedCredentials, projects: result.migratedProjectCount, @@ -161,7 +164,7 @@ export function useSettingsMigration(): MigrationState { setState({ checked: true, migrated: true, error: null }); } else { - console.warn('[Settings Migration] Migration had errors:', result.errors); + logger.warn('Migration had errors:', result.errors); setState({ checked: true, migrated: false, @@ -169,7 +172,7 @@ export function useSettingsMigration(): MigrationState { }); } } catch (error) { - console.error('[Settings Migration] Migration failed:', error); + logger.error('Migration failed:', error); setState({ checked: true, migrated: false, @@ -244,7 +247,7 @@ export async function syncSettingsToServer(): Promise { const result = await api.settings.updateGlobal(updates); return result.success; } catch (error) { - console.error('[Settings Sync] Failed to sync settings:', error); + logger.error('Failed to sync settings:', error); return false; } } @@ -271,7 +274,7 @@ export async function syncCredentialsToServer(apiKeys: { const result = await api.settings.updateCredentials({ apiKeys }); return result.success; } catch (error) { - console.error('[Settings Sync] Failed to sync credentials:', error); + logger.error('Failed to sync credentials:', error); return false; } } @@ -312,7 +315,7 @@ export async function syncProjectSettingsToServer( const result = await api.settings.updateProject(projectPath, updates); return result.success; } catch (error) { - console.error('[Settings Sync] Failed to sync project settings:', error); + logger.error('Failed to sync project settings:', error); return false; } } @@ -332,7 +335,7 @@ export async function loadMCPServersFromServer(): Promise { const result = await api.settings.getGlobal(); if (!result.success || !result.settings) { - console.error('[Settings Load] Failed to load settings:', result.error); + logger.error('Failed to load settings:', result.error); return false; } @@ -344,10 +347,10 @@ export async function loadMCPServersFromServer(): Promise { // We need to update the store directly since we can't use hooks here useAppStore.setState({ mcpServers, mcpAutoApproveTools, mcpUnrestrictedTools }); - console.log(`[Settings Load] Loaded ${mcpServers.length} MCP servers from server`); + logger.info(`Loaded ${mcpServers.length} MCP servers from server`); return true; } catch (error) { - console.error('[Settings Load] Failed to load MCP servers:', error); + logger.error('Failed to load MCP servers:', error); return false; } } diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index 79208527..d81f2cfa 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -1,4 +1,5 @@ // Type definitions for Electron IPC API +import { createLogger } from '@automaker/utils/logger'; import type { SessionListItem, Message } from '@/types/electron'; import type { ClaudeUsageResponse } from '@/store/app-store'; import type { @@ -94,6 +95,8 @@ import type { ProviderStatus, } from '@/types/electron'; +const logger = createLogger('Electron'); + // Import HTTP API client (ES module) import { getHttpApiClient, getServerUrlSync } from './http-api-client'; @@ -774,8 +777,8 @@ export const getCurrentApiMode = (): 'http' => { // Debug helpers if (typeof window !== 'undefined') { (window as any).__checkApiMode = () => { - console.log('Current API mode:', getCurrentApiMode()); - console.log('isElectron():', isElectron()); + logger.info('Current API mode:', getCurrentApiMode()); + logger.info('isElectron():', isElectron()); }; } @@ -1016,7 +1019,7 @@ const getMockElectronAPI = (): ElectronAPI => { // Store the image data in mock file system for testing mockFileSystem[tempFilePath] = data; - console.log('[Mock] Saved image to temp:', tempFilePath); + logger.info('Mock saved image to temp:', tempFilePath); return { success: true, path: tempFilePath }; }, @@ -1061,7 +1064,7 @@ const getMockElectronAPI = (): ElectronAPI => { // Mock Claude API claude: { getUsage: async () => { - console.log('[Mock] Getting Claude usage'); + logger.info('Mock getting Claude usage'); return { sessionTokensUsed: 0, sessionLimit: 0, @@ -1168,7 +1171,7 @@ interface SetupAPI { function createMockSetupAPI(): SetupAPI { return { getClaudeStatus: async () => { - console.log('[Mock] Getting Claude status'); + logger.info('Mock Getting Claude status'); return { success: true, status: 'not_installed', @@ -1185,7 +1188,7 @@ function createMockSetupAPI(): SetupAPI { }, installClaude: async () => { - console.log('[Mock] Installing Claude CLI'); + logger.info('Mock Installing Claude CLI'); // Simulate installation delay await new Promise((resolve) => setTimeout(resolve, 1000)); return { @@ -1196,7 +1199,7 @@ function createMockSetupAPI(): SetupAPI { }, authClaude: async () => { - console.log('[Mock] Auth Claude CLI'); + logger.info('Mock Auth Claude CLI'); return { success: true, requiresManualAuth: true, @@ -1205,13 +1208,13 @@ function createMockSetupAPI(): SetupAPI { }, storeApiKey: async (provider: string, apiKey: string) => { - console.log('[Mock] Storing API key for:', provider); + logger.info('Mock Storing API key for:', provider); // In mock mode, we just pretend to store it (it's already in the app store) return { success: true }; }, getApiKeys: async () => { - console.log('[Mock] Getting API keys'); + logger.info('Mock Getting API keys'); return { success: true, hasAnthropicKey: false, @@ -1220,7 +1223,7 @@ function createMockSetupAPI(): SetupAPI { }, deleteApiKey: async (provider: string) => { - console.log('[Mock] Deleting API key for:', provider); + logger.info('Mock Deleting API key for:', provider); return { success: true, message: `API key for ${provider} deleted` }; }, @@ -1237,8 +1240,8 @@ function createMockSetupAPI(): SetupAPI { }, verifyClaudeAuth: async (authMethod?: 'cli' | 'api_key', apiKey?: string) => { - console.log( - '[Mock] Verifying Claude auth with method:', + logger.info( + 'Mock verifying Claude auth with method:', authMethod, apiKey ? '(with key)' : '' ); @@ -1251,7 +1254,7 @@ function createMockSetupAPI(): SetupAPI { }, getGhStatus: async () => { - console.log('[Mock] Getting GitHub CLI status'); + logger.info('Mock Getting GitHub CLI status'); return { success: true, installed: false, @@ -1278,7 +1281,7 @@ function createMockSetupAPI(): SetupAPI { function createMockWorktreeAPI(): WorktreeAPI { return { mergeFeature: async (projectPath: string, featureId: string, options?: object) => { - console.log('[Mock] Merging feature:', { + logger.info('Mock Merging feature:', { projectPath, featureId, options, @@ -1287,7 +1290,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, getInfo: async (projectPath: string, featureId: string) => { - console.log('[Mock] Getting worktree info:', { projectPath, featureId }); + logger.info('Mock Getting worktree info:', { projectPath, featureId }); return { success: true, worktreePath: `/mock/worktrees/${featureId}`, @@ -1297,7 +1300,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, getStatus: async (projectPath: string, featureId: string) => { - console.log('[Mock] Getting worktree status:', { + logger.info('Mock Getting worktree status:', { projectPath, featureId, }); @@ -1311,12 +1314,12 @@ function createMockWorktreeAPI(): WorktreeAPI { }, list: async (projectPath: string) => { - console.log('[Mock] Listing worktrees:', { projectPath }); + logger.info('Mock Listing worktrees:', { projectPath }); return { success: true, worktrees: [] }; }, listAll: async (projectPath: string, includeDetails?: boolean) => { - console.log('[Mock] Listing all worktrees:', { + logger.info('Mock Listing all worktrees:', { projectPath, includeDetails, }); @@ -1337,7 +1340,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, create: async (projectPath: string, branchName: string, baseBranch?: string) => { - console.log('[Mock] Creating worktree:', { + logger.info('Mock Creating worktree:', { projectPath, branchName, baseBranch, @@ -1353,7 +1356,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, delete: async (projectPath: string, worktreePath: string, deleteBranch?: boolean) => { - console.log('[Mock] Deleting worktree:', { + logger.info('Mock Deleting worktree:', { projectPath, worktreePath, deleteBranch, @@ -1368,7 +1371,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, commit: async (worktreePath: string, message: string) => { - console.log('[Mock] Committing changes:', { worktreePath, message }); + logger.info('Mock Committing changes:', { worktreePath, message }); return { success: true, result: { @@ -1381,7 +1384,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, push: async (worktreePath: string, force?: boolean) => { - console.log('[Mock] Pushing worktree:', { worktreePath, force }); + logger.info('Mock Pushing worktree:', { worktreePath, force }); return { success: true, result: { @@ -1393,7 +1396,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, createPR: async (worktreePath: string, options?: any) => { - console.log('[Mock] Creating PR:', { worktreePath, options }); + logger.info('Mock Creating PR:', { worktreePath, options }); return { success: true, result: { @@ -1408,7 +1411,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, getDiffs: async (projectPath: string, featureId: string) => { - console.log('[Mock] Getting file diffs:', { projectPath, featureId }); + logger.info('Mock Getting file diffs:', { projectPath, featureId }); return { success: true, diff: "diff --git a/src/feature.ts b/src/feature.ts\n+++ new file\n@@ -0,0 +1,10 @@\n+export function feature() {\n+ return 'hello';\n+}", @@ -1421,7 +1424,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, getFileDiff: async (projectPath: string, featureId: string, filePath: string) => { - console.log('[Mock] Getting file diff:', { + logger.info('Mock Getting file diff:', { projectPath, featureId, filePath, @@ -1434,7 +1437,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, pull: async (worktreePath: string) => { - console.log('[Mock] Pulling latest changes for:', worktreePath); + logger.info('Mock Pulling latest changes for:', worktreePath); return { success: true, result: { @@ -1446,7 +1449,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, checkoutBranch: async (worktreePath: string, branchName: string) => { - console.log('[Mock] Creating and checking out branch:', { + logger.info('Mock Creating and checking out branch:', { worktreePath, branchName, }); @@ -1461,7 +1464,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, listBranches: async (worktreePath: string) => { - console.log('[Mock] Listing branches for:', worktreePath); + logger.info('Mock Listing branches for:', worktreePath); return { success: true, result: { @@ -1478,7 +1481,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, switchBranch: async (worktreePath: string, branchName: string) => { - console.log('[Mock] Switching to branch:', { worktreePath, branchName }); + logger.info('Mock Switching to branch:', { worktreePath, branchName }); return { success: true, result: { @@ -1490,7 +1493,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, openInEditor: async (worktreePath: string) => { - console.log('[Mock] Opening in editor:', worktreePath); + logger.info('Mock Opening in editor:', worktreePath); return { success: true, result: { @@ -1501,7 +1504,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, getDefaultEditor: async () => { - console.log('[Mock] Getting default editor'); + logger.info('Mock Getting default editor'); return { success: true, result: { @@ -1512,7 +1515,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, initGit: async (projectPath: string) => { - console.log('[Mock] Initializing git:', projectPath); + logger.info('Mock Initializing git:', projectPath); return { success: true, result: { @@ -1523,7 +1526,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, startDevServer: async (projectPath: string, worktreePath: string) => { - console.log('[Mock] Starting dev server:', { projectPath, worktreePath }); + logger.info('Mock Starting dev server:', { projectPath, worktreePath }); return { success: true, result: { @@ -1536,7 +1539,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, stopDevServer: async (worktreePath: string) => { - console.log('[Mock] Stopping dev server:', worktreePath); + logger.info('Mock Stopping dev server:', worktreePath); return { success: true, result: { @@ -1547,7 +1550,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, listDevServers: async () => { - console.log('[Mock] Listing dev servers'); + logger.info('Mock Listing dev servers'); return { success: true, result: { @@ -1557,7 +1560,7 @@ function createMockWorktreeAPI(): WorktreeAPI { }, getPRInfo: async (worktreePath: string, branchName: string) => { - console.log('[Mock] Getting PR info:', { worktreePath, branchName }); + logger.info('Mock Getting PR info:', { worktreePath, branchName }); return { success: true, result: { @@ -1573,7 +1576,7 @@ function createMockWorktreeAPI(): WorktreeAPI { function createMockGitAPI(): GitAPI { return { getDiffs: async (projectPath: string) => { - console.log('[Mock] Getting git diffs for project:', { projectPath }); + logger.info('Mock Getting git diffs for project:', { projectPath }); return { success: true, diff: "diff --git a/src/feature.ts b/src/feature.ts\n+++ new file\n@@ -0,0 +1,10 @@\n+export function feature() {\n+ return 'hello';\n+}", @@ -1586,7 +1589,7 @@ function createMockGitAPI(): GitAPI { }, getFileDiff: async (projectPath: string, filePath: string) => { - console.log('[Mock] Getting git file diff:', { projectPath, filePath }); + logger.info('Mock Getting git file diff:', { projectPath, filePath }); return { success: true, diff: `diff --git a/${filePath} b/${filePath}\n+++ new file\n@@ -0,0 +1,5 @@\n+// New content`, @@ -1610,7 +1613,7 @@ function createMockAutoModeAPI(): AutoModeAPI { } mockAutoModeRunning = true; - console.log(`[Mock] Auto mode started with maxConcurrency: ${maxConcurrency || 3}`); + logger.info(`Mock auto mode started with maxConcurrency: ${maxConcurrency || 3}`); const featureId = 'auto-mode-0'; mockRunningFeatures.add(featureId); @@ -1679,8 +1682,8 @@ function createMockAutoModeAPI(): AutoModeAPI { }; } - console.log( - `[Mock] Running feature ${featureId} with useWorktrees: ${useWorktrees}, worktreePath: ${worktreePath}` + logger.info( + `Mock running feature ${featureId} with useWorktrees: ${useWorktrees}, worktreePath: ${worktreePath}` ); mockRunningFeatures.add(featureId); simulateAutoModeLoop(projectPath, featureId); @@ -1847,7 +1850,7 @@ function createMockAutoModeAPI(): AutoModeAPI { }; } - console.log('[Mock] Follow-up feature:', { + logger.info('Mock Follow-up feature:', { featureId, prompt, imagePaths, @@ -1864,7 +1867,7 @@ function createMockAutoModeAPI(): AutoModeAPI { }, commitFeature: async (projectPath: string, featureId: string, worktreePath?: string) => { - console.log('[Mock] Committing feature:', { + logger.info('Mock Committing feature:', { projectPath, featureId, worktreePath, @@ -1909,7 +1912,7 @@ function createMockAutoModeAPI(): AutoModeAPI { editedPlan?: string, feedback?: string ) => { - console.log('[Mock] Plan approval:', { + logger.info('Mock Plan approval:', { projectPath, featureId, approved, @@ -2070,7 +2073,7 @@ function createMockSuggestionsAPI(): SuggestionsAPI { } mockSuggestionsRunning = true; - console.log(`[Mock] Generating ${suggestionType} suggestions for: ${projectPath}`); + logger.info(`Mock generating ${suggestionType} suggestions for: ${projectPath}`); // Simulate async suggestion generation simulateSuggestionsGeneration(suggestionType); @@ -2294,8 +2297,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI { } mockSpecRegenerationRunning = true; - console.log( - `[Mock] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}` + logger.info( + `Mock creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}` ); // Simulate async spec creation @@ -2319,8 +2322,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI { } mockSpecRegenerationRunning = true; - console.log( - `[Mock] Regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}` + logger.info( + `Mock regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}` ); // Simulate async spec regeneration @@ -2338,8 +2341,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI { } mockSpecRegenerationRunning = true; - console.log( - `[Mock] Generating features from existing spec for: ${projectPath}, maxFeatures: ${maxFeatures}` + logger.info( + `Mock generating features from existing spec for: ${projectPath}, maxFeatures: ${maxFeatures}` ); // Simulate async feature generation @@ -2604,7 +2607,7 @@ function createMockFeaturesAPI(): FeaturesAPI { // Store features in mock file system using features/{id}/feature.json pattern return { getAll: async (projectPath: string) => { - console.log('[Mock] Getting all features for:', projectPath); + logger.info('Mock Getting all features for:', projectPath); // Check if test has set mock features via global variable const testFeatures = (window as any).__mockFeatures; @@ -2629,7 +2632,7 @@ function createMockFeaturesAPI(): FeaturesAPI { features.push(feature); } } catch (error) { - console.error('[Mock] Failed to parse feature:', error); + logger.error('Mock Failed to parse feature:', error); } } @@ -2642,7 +2645,7 @@ function createMockFeaturesAPI(): FeaturesAPI { }, get: async (projectPath: string, featureId: string) => { - console.log('[Mock] Getting feature:', { projectPath, featureId }); + logger.info('Mock Getting feature:', { projectPath, featureId }); const featurePath = `${projectPath}/.automaker/features/${featureId}/feature.json`; const content = mockFileSystem[featurePath]; if (content) { @@ -2652,7 +2655,7 @@ function createMockFeaturesAPI(): FeaturesAPI { }, create: async (projectPath: string, feature: Feature) => { - console.log('[Mock] Creating feature:', { + logger.info('Mock Creating feature:', { projectPath, featureId: feature.id, }); @@ -2662,7 +2665,7 @@ function createMockFeaturesAPI(): FeaturesAPI { }, update: async (projectPath: string, featureId: string, updates: Partial) => { - console.log('[Mock] Updating feature:', { + logger.info('Mock Updating feature:', { projectPath, featureId, updates, @@ -2678,7 +2681,7 @@ function createMockFeaturesAPI(): FeaturesAPI { }, delete: async (projectPath: string, featureId: string) => { - console.log('[Mock] Deleting feature:', { projectPath, featureId }); + logger.info('Mock Deleting feature:', { projectPath, featureId }); const featurePath = `${projectPath}/.automaker/features/${featureId}/feature.json`; delete mockFileSystem[featurePath]; // Also delete agent-output.md if it exists @@ -2688,14 +2691,14 @@ function createMockFeaturesAPI(): FeaturesAPI { }, getAgentOutput: async (projectPath: string, featureId: string) => { - console.log('[Mock] Getting agent output:', { projectPath, featureId }); + logger.info('Mock Getting agent output:', { projectPath, featureId }); const agentOutputPath = `${projectPath}/.automaker/features/${featureId}/agent-output.md`; const content = mockFileSystem[agentOutputPath]; return { success: true, content: content || null }; }, generateTitle: async (description: string) => { - console.log('[Mock] Generating title for:', description.substring(0, 50)); + logger.info('Mock Generating title for:', description.substring(0, 50)); // Mock title generation - just take first few words const words = description.split(/\s+/).slice(0, 6).join(' '); const title = words.length > 40 ? words.substring(0, 40) + '...' : words; @@ -2708,7 +2711,7 @@ function createMockFeaturesAPI(): FeaturesAPI { function createMockRunningAgentsAPI(): RunningAgentsAPI { return { getAll: async () => { - console.log('[Mock] Getting all running agents'); + logger.info('Mock Getting all running agents'); // Return running agents from mock auto mode state const runningAgents: RunningAgent[] = Array.from(mockRunningFeatures).map((featureId) => ({ featureId, @@ -2733,7 +2736,7 @@ let mockValidationCallbacks: ((event: IssueValidationEvent) => void)[] = []; function createMockGitHubAPI(): GitHubAPI { return { checkRemote: async (projectPath: string) => { - console.log('[Mock] Checking GitHub remote for:', projectPath); + logger.info('Mock Checking GitHub remote for:', projectPath); return { success: true, hasGitHubRemote: false, @@ -2743,7 +2746,7 @@ function createMockGitHubAPI(): GitHubAPI { }; }, listIssues: async (projectPath: string) => { - console.log('[Mock] Listing GitHub issues for:', projectPath); + logger.info('Mock Listing GitHub issues for:', projectPath); return { success: true, openIssues: [], @@ -2751,7 +2754,7 @@ function createMockGitHubAPI(): GitHubAPI { }; }, listPRs: async (projectPath: string) => { - console.log('[Mock] Listing GitHub PRs for:', projectPath); + logger.info('Mock Listing GitHub PRs for:', projectPath); return { success: true, openPRs: [], @@ -2759,7 +2762,7 @@ function createMockGitHubAPI(): GitHubAPI { }; }, validateIssue: async (projectPath: string, issue: IssueValidationInput, model?: ModelAlias) => { - console.log('[Mock] Starting async validation:', { projectPath, issue, model }); + logger.info('Mock Starting async validation:', { projectPath, issue, model }); // Simulate async validation in background setTimeout(() => { @@ -2800,7 +2803,7 @@ function createMockGitHubAPI(): GitHubAPI { }; }, getValidationStatus: async (projectPath: string, issueNumber?: number) => { - console.log('[Mock] Getting validation status:', { projectPath, issueNumber }); + logger.info('Mock Getting validation status:', { projectPath, issueNumber }); return { success: true, isRunning: false, @@ -2808,21 +2811,21 @@ function createMockGitHubAPI(): GitHubAPI { }; }, stopValidation: async (projectPath: string, issueNumber: number) => { - console.log('[Mock] Stopping validation:', { projectPath, issueNumber }); + logger.info('Mock Stopping validation:', { projectPath, issueNumber }); return { success: true, message: `Validation for issue #${issueNumber} stopped`, }; }, getValidations: async (projectPath: string, issueNumber?: number) => { - console.log('[Mock] Getting validations:', { projectPath, issueNumber }); + logger.info('Mock Getting validations:', { projectPath, issueNumber }); return { success: true, validations: [], }; }, markValidationViewed: async (projectPath: string, issueNumber: number) => { - console.log('[Mock] Marking validation as viewed:', { projectPath, issueNumber }); + logger.info('Mock Marking validation as viewed:', { projectPath, issueNumber }); return { success: true, }; @@ -2834,7 +2837,7 @@ function createMockGitHubAPI(): GitHubAPI { }; }, getIssueComments: async (projectPath: string, issueNumber: number, cursor?: string) => { - console.log('[Mock] Getting issue comments:', { projectPath, issueNumber, cursor }); + logger.info('Mock Getting issue comments:', { projectPath, issueNumber, cursor }); return { success: true, comments: [], diff --git a/apps/ui/src/lib/file-picker.ts b/apps/ui/src/lib/file-picker.ts index c2b60090..e7c4631b 100644 --- a/apps/ui/src/lib/file-picker.ts +++ b/apps/ui/src/lib/file-picker.ts @@ -10,6 +10,10 @@ * user confirmation or server-side path resolution. */ +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('FilePicker'); + /** * Directory picker result with structure information for server-side resolution */ @@ -65,18 +69,18 @@ export async function openDirectoryPicker(): Promise 0) { const absolutePath = filePath.substring(0, lastSeparator); - console.log('[FilePicker] Found absolute path:', absolutePath); + logger.info('Found absolute path:', absolutePath); // Return as directory name for now - server can validate it directly directoryName = absolutePath; } @@ -106,11 +110,11 @@ export async function openDirectoryPicker(): Promise 0) { directoryName = pathParts[0]; // Top-level directory name - console.log('[FilePicker] Extracted directory name:', directoryName); + logger.info('Extracted directory name:', directoryName); } } @@ -127,7 +131,7 @@ export async function openDirectoryPicker(): Promise { if (!resolved && !changeEventFired && (!input.files || input.files.length === 0)) { - console.log('[FilePicker] Dialog canceled (no files after focus and no change event)'); + logger.info('Dialog canceled (no files after focus and no change event)'); safeResolve(null); } }, 2000); // Increased timeout for Windows - give it time @@ -155,19 +159,19 @@ export async function openDirectoryPicker(): Promise => { if (electron?.getServerUrl) { try { cachedServerUrl = await electron.getServerUrl(); - console.log('[HTTP Client] Server URL from Electron:', cachedServerUrl); + logger.info('Server URL from Electron:', cachedServerUrl); } catch (error) { - console.warn('[HTTP Client] Failed to get server URL from Electron:', error); + logger.warn('Failed to get server URL from Electron:', error); } } }; @@ -145,16 +148,16 @@ export const initApiKey = async (): Promise => { try { cachedApiKey = await window.electronAPI.getApiKey(); if (cachedApiKey) { - console.log('[HTTP Client] Using API key from Electron'); + logger.info('Using API key from Electron'); return; } } catch (error) { - console.warn('[HTTP Client] Failed to get API key from Electron:', error); + logger.warn('Failed to get API key from Electron:', error); } } // In web mode, authentication is handled via HTTP-only cookies - console.log('[HTTP Client] Web mode - using cookie-based authentication'); + logger.info('Web mode - using cookie-based authentication'); } finally { // Mark as initialized after completion, regardless of success or failure apiKeyInitialized = true; @@ -182,7 +185,7 @@ export const checkAuthStatus = async (): Promise<{ required: data.required ?? true, }; } catch (error) { - console.error('[HTTP Client] Failed to check auth status:', error); + logger.error('Failed to check auth status:', error); return { authenticated: false, required: true }; } }; @@ -207,23 +210,23 @@ export const login = async ( // Store the session token if login succeeded if (data.success && data.token) { setSessionToken(data.token); - console.log('[HTTP Client] Session token stored after login'); + logger.info('Session token stored after login'); // Verify the session is actually working by making a request to an authenticated endpoint const verified = await verifySession(); if (!verified) { - console.error('[HTTP Client] Login appeared successful but session verification failed'); + logger.error('Login appeared successful but session verification failed'); return { success: false, error: 'Session verification failed. Please try again.', }; } - console.log('[HTTP Client] Login verified successfully'); + logger.info('Login verified successfully'); } return data; } catch (error) { - console.error('[HTTP Client] Login failed:', error); + logger.error('Login failed:', error); return { success: false, error: 'Network error' }; } }; @@ -243,20 +246,20 @@ export const fetchSessionToken = async (): Promise => { }); if (!response.ok) { - console.log('[HTTP Client] Failed to check auth status'); + logger.info('Failed to check auth status'); return false; } const data = await response.json(); if (data.success && data.authenticated) { - console.log('[HTTP Client] Session cookie is valid'); + logger.info('Session cookie is valid'); return true; } - console.log('[HTTP Client] Session cookie is not authenticated'); + logger.info('Session cookie is not authenticated'); return false; } catch (error) { - console.error('[HTTP Client] Failed to check session:', error); + logger.error('Failed to check session:', error); return false; } }; @@ -273,11 +276,11 @@ export const logout = async (): Promise<{ success: boolean }> => { // Clear the cached session token clearSessionToken(); - console.log('[HTTP Client] Session token cleared on logout'); + logger.info('Session token cleared on logout'); return await response.json(); } catch (error) { - console.error('[HTTP Client] Logout failed:', error); + logger.error('Logout failed:', error); return { success: false }; } }; @@ -310,7 +313,7 @@ export const verifySession = async (): Promise => { // Check for authentication errors if (response.status === 401 || response.status === 403) { - console.warn('[HTTP Client] Session verification failed - session expired or invalid'); + logger.warn('Session verification failed - session expired or invalid'); // Clear the session since it's no longer valid clearSessionToken(); // Try to clear the cookie via logout (fire and forget) @@ -324,14 +327,14 @@ export const verifySession = async (): Promise => { } if (!response.ok) { - console.warn('[HTTP Client] Session verification failed with status:', response.status); + logger.warn('Session verification failed with status:', response.status); return false; } - console.log('[HTTP Client] Session verified successfully'); + logger.info('Session verified successfully'); return true; } catch (error) { - console.error('[HTTP Client] Session verification error:', error); + logger.error('Session verification error:', error); return false; } }; @@ -350,14 +353,14 @@ export const checkSandboxEnvironment = async (): Promise<{ }); if (!response.ok) { - console.warn('[HTTP Client] Failed to check sandbox environment'); + logger.warn('Failed to check sandbox environment'); return { isContainerized: false, error: 'Failed to check environment' }; } const data = await response.json(); return { isContainerized: data.isContainerized ?? false }; } catch (error) { - console.error('[HTTP Client] Sandbox environment check failed:', error); + logger.error('Sandbox environment check failed:', error); return { isContainerized: false, error: 'Network error' }; } }; @@ -399,7 +402,7 @@ export class HttpApiClient implements ElectronAPI { this.connectWebSocket(); }) .catch((error) => { - console.error('[HttpApiClient] API key initialization failed:', error); + logger.error('API key initialization failed:', error); // Still attempt WebSocket connection - it may work with cookie auth this.connectWebSocket(); }); @@ -428,7 +431,7 @@ export class HttpApiClient implements ElectronAPI { }); if (!response.ok) { - console.warn('[HttpApiClient] Failed to fetch wsToken:', response.status); + logger.warn('Failed to fetch wsToken:', response.status); return null; } @@ -439,7 +442,7 @@ export class HttpApiClient implements ElectronAPI { return null; } catch (error) { - console.error('[HttpApiClient] Error fetching wsToken:', error); + logger.error('Error fetching wsToken:', error); return null; } } @@ -456,9 +459,7 @@ export class HttpApiClient implements ElectronAPI { if (isElectronMode()) { const apiKey = getApiKey(); if (!apiKey) { - console.warn( - '[HttpApiClient] Electron mode: API key not ready, delaying WebSocket connect' - ); + logger.warn('Electron mode: API key not ready, delaying WebSocket connect'); this.isConnecting = false; if (!this.reconnectTimer) { this.reconnectTimer = setTimeout(() => { @@ -482,12 +483,12 @@ export class HttpApiClient implements ElectronAPI { this.establishWebSocket(`${wsUrl}?wsToken=${encodeURIComponent(wsToken)}`); } else { // Fallback: try connecting without token (will fail if not authenticated) - console.warn('[HttpApiClient] No wsToken available, attempting connection anyway'); + logger.warn('No wsToken available, attempting connection anyway'); this.establishWebSocket(wsUrl); } }) .catch((error) => { - console.error('[HttpApiClient] Failed to prepare WebSocket connection:', error); + logger.error('Failed to prepare WebSocket connection:', error); this.isConnecting = false; }); } @@ -500,7 +501,7 @@ export class HttpApiClient implements ElectronAPI { this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { - console.log('[HttpApiClient] WebSocket connected'); + logger.info('WebSocket connected'); this.isConnecting = false; if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); @@ -511,8 +512,8 @@ export class HttpApiClient implements ElectronAPI { this.ws.onmessage = (event) => { try { const data = JSON.parse(event.data); - console.log( - '[HttpApiClient] WebSocket message:', + logger.info( + 'WebSocket message:', data.type, 'hasPayload:', !!data.payload, @@ -521,16 +522,16 @@ export class HttpApiClient implements ElectronAPI { ); const callbacks = this.eventCallbacks.get(data.type); if (callbacks) { - console.log('[HttpApiClient] Dispatching to', callbacks.size, 'callbacks'); + logger.info('Dispatching to', callbacks.size, 'callbacks'); callbacks.forEach((cb) => cb(data.payload)); } } catch (error) { - console.error('[HttpApiClient] Failed to parse WebSocket message:', error); + logger.error('Failed to parse WebSocket message:', error); } }; this.ws.onclose = () => { - console.log('[HttpApiClient] WebSocket disconnected'); + logger.info('WebSocket disconnected'); this.isConnecting = false; this.ws = null; // Attempt to reconnect after 5 seconds @@ -543,11 +544,11 @@ export class HttpApiClient implements ElectronAPI { }; this.ws.onerror = (error) => { - console.error('[HttpApiClient] WebSocket error:', error); + logger.error('WebSocket error:', error); this.isConnecting = false; }; } catch (error) { - console.error('[HttpApiClient] Failed to create WebSocket:', error); + logger.error('Failed to create WebSocket:', error); this.isConnecting = false; } } @@ -747,7 +748,7 @@ export class HttpApiClient implements ElectronAPI { const fileBrowser = getGlobalFileBrowser(); if (!fileBrowser) { - console.error('File browser not initialized'); + logger.error('File browser not initialized'); return { canceled: true, filePaths: [] }; } @@ -769,7 +770,7 @@ export class HttpApiClient implements ElectronAPI { return { canceled: false, filePaths: [result.path] }; } - console.error('Invalid directory:', result.error || 'Path not allowed'); + logger.error('Invalid directory:', result.error || 'Path not allowed'); return { canceled: true, filePaths: [] }; } @@ -777,7 +778,7 @@ export class HttpApiClient implements ElectronAPI { const fileBrowser = getGlobalFileBrowser(); if (!fileBrowser) { - console.error('File browser not initialized'); + logger.error('File browser not initialized'); return { canceled: true, filePaths: [] }; } @@ -796,7 +797,7 @@ export class HttpApiClient implements ElectronAPI { return { canceled: false, filePaths: [path] }; } - console.error('File not found'); + logger.error('File not found'); return { canceled: true, filePaths: [] }; } @@ -1910,5 +1911,5 @@ export function getHttpApiClient(): HttpApiClient { // This ensures the init promise is created early, even before React components mount // The actual async work happens in the background and won't block module loading initApiKey().catch((error) => { - console.error('[HTTP Client] Failed to initialize API key:', error); + logger.error('Failed to initialize API key:', error); }); diff --git a/apps/ui/src/lib/project-init.ts b/apps/ui/src/lib/project-init.ts index f8b39933..b4334ee9 100644 --- a/apps/ui/src/lib/project-init.ts +++ b/apps/ui/src/lib/project-init.ts @@ -5,8 +5,11 @@ * new or existing projects. */ +import { createLogger } from '@automaker/utils/logger'; import { getElectronAPI } from './electron'; +const logger = createLogger('ProjectInit'); + export interface ProjectInitResult { success: boolean; isNewProject: boolean; @@ -72,22 +75,22 @@ export async function initializeProject(projectPath: string): Promise { const fullPath = `${projectPath}/.automaker/app_spec.txt`; return await api.exists(fullPath); } catch (error) { - console.error('[project-init] Error checking app_spec.txt:', error); + logger.error('Error checking app_spec.txt:', error); return false; } } @@ -229,7 +232,7 @@ export async function hasAutomakerDir(projectPath: string): Promise { const fullPath = `${projectPath}/.automaker`; return await api.exists(fullPath); } catch (error) { - console.error('[project-init] Error checking .automaker dir:', error); + logger.error('Error checking .automaker dir:', error); return false; } } diff --git a/apps/ui/src/lib/workspace-config.ts b/apps/ui/src/lib/workspace-config.ts index 4f985c21..0726b785 100644 --- a/apps/ui/src/lib/workspace-config.ts +++ b/apps/ui/src/lib/workspace-config.ts @@ -3,11 +3,14 @@ * Centralizes the logic for determining where projects should be created/opened */ +import { createLogger } from '@automaker/utils/logger'; import { getHttpApiClient } from './http-api-client'; import { getElectronAPI } from './electron'; import { getItem, setItem } from './storage'; import path from 'path'; +const logger = createLogger('WorkspaceConfig'); + const LAST_PROJECT_DIR_KEY = 'automaker:lastProjectDir'; /** @@ -20,9 +23,7 @@ async function getDefaultDocumentsPath(): Promise { const documentsPath = await api.getPath('documents'); return path.join(documentsPath, 'Automaker'); } catch (error) { - if (typeof window !== 'undefined' && window.console) { - window.console.error('Failed to get documents path:', error); - } + logger.error('Failed to get documents path:', error); return null; } } @@ -81,9 +82,7 @@ export async function getDefaultWorkspaceDirectory(): Promise { const documentsPath = await getDefaultDocumentsPath(); return documentsPath; } catch (error) { - if (typeof window !== 'undefined' && window.console) { - window.console.error('Failed to get default workspace directory:', error); - } + logger.error('Failed to get default workspace directory:', error); // On error, try last used dir and Documents const lastUsedDir = getItem(LAST_PROJECT_DIR_KEY); diff --git a/apps/ui/src/main.ts b/apps/ui/src/main.ts index 13cb27a3..0d86830a 100644 --- a/apps/ui/src/main.ts +++ b/apps/ui/src/main.ts @@ -13,6 +13,7 @@ import crypto from 'crypto'; import http, { Server } from 'http'; import net from 'net'; import { app, BrowserWindow, ipcMain, dialog, shell, screen } from 'electron'; +import { createLogger } from '@automaker/utils/logger'; import { findNodeExecutable, buildEnhancedPath, @@ -35,6 +36,9 @@ import { systemPathExists, } from '@automaker/platform'; +const logger = createLogger('Electron'); +const serverLogger = createLogger('Server'); + // Development environment const isDev = !app.isPackaged; const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL; @@ -45,7 +49,7 @@ if (isDev) { // eslint-disable-next-line @typescript-eslint/no-require-imports require('dotenv').config({ path: path.join(__dirname, '../.env') }); } catch (error) { - console.warn('[Electron] dotenv not available:', (error as Error).message); + logger.warn('dotenv not available:', (error as Error).message); } } @@ -144,21 +148,21 @@ function ensureApiKey(): string { const key = electronUserDataReadFileSync(API_KEY_FILENAME).trim(); if (key) { apiKey = key; - console.log('[Electron] Loaded existing API key'); + logger.info('Loaded existing API key'); return apiKey; } } } catch (error) { - console.warn('[Electron] Error reading API key:', error); + logger.warn('Error reading API key:', error); } // Generate new key apiKey = crypto.randomUUID(); try { electronUserDataWriteFileSync(API_KEY_FILENAME, apiKey, { encoding: 'utf-8', mode: 0o600 }); - console.log('[Electron] Generated new API key'); + logger.info('Generated new API key'); } catch (error) { - console.error('[Electron] Failed to save API key:', error); + logger.error('Failed to save API key:', error); } return apiKey; } @@ -183,11 +187,11 @@ function getIconPath(): string | null { try { if (!electronAppExists(iconPath)) { - console.warn(`[Electron] Icon not found at: ${iconPath}`); + logger.warn('Icon not found at:', iconPath); return null; } } catch (error) { - console.warn(`[Electron] Icon check failed: ${iconPath}`, error); + logger.warn('Icon check failed:', iconPath, error); return null; } @@ -219,7 +223,7 @@ function loadWindowBounds(): WindowBounds | null { } } } catch (error) { - console.warn('[Electron] Failed to load window bounds:', (error as Error).message); + logger.warn('Failed to load window bounds:', (error as Error).message); } return null; } @@ -231,9 +235,9 @@ function loadWindowBounds(): WindowBounds | null { function saveWindowBounds(bounds: WindowBounds): void { try { electronUserDataWriteFileSync(WINDOW_BOUNDS_FILENAME, JSON.stringify(bounds, null, 2)); - console.log('[Electron] Window bounds saved'); + logger.info('Window bounds saved'); } catch (error) { - console.warn('[Electron] Failed to save window bounds:', (error as Error).message); + logger.warn('Failed to save window bounds:', (error as Error).message); } } @@ -371,7 +375,7 @@ async function startStaticServer(): Promise { return new Promise((resolve, reject) => { staticServer!.listen(staticPort, () => { - console.log(`[Electron] Static server running at http://localhost:${staticPort}`); + logger.info('Static server running at http://localhost:' + staticPort); resolve(); }); staticServer!.on('error', reject); @@ -386,7 +390,7 @@ async function startServer(): Promise { // Find Node.js executable (handles desktop launcher scenarios) const nodeResult = findNodeExecutable({ skipSearch: isDev, - logger: (msg: string) => console.log(`[Electron] ${msg}`), + logger: (msg: string) => logger.info(msg), }); const command = nodeResult.nodePath; @@ -470,7 +474,7 @@ async function startServer(): Promise { // Build enhanced PATH that includes Node.js directory (cross-platform) const enhancedPath = buildEnhancedPath(command, process.env.PATH || ''); if (enhancedPath !== process.env.PATH) { - console.log(`[Electron] Enhanced PATH with Node directory: ${path.dirname(command)}`); + logger.info('Enhanced PATH with Node directory:', path.dirname(command)); } const env = { @@ -488,12 +492,12 @@ async function startServer(): Promise { }), }; - console.log(`[Electron] Server will use port ${serverPort}`); + logger.info('Server will use port', serverPort); - console.log('[Electron] Starting backend server...'); - console.log('[Electron] Server path:', serverPath); - console.log('[Electron] Server root (cwd):', serverRoot); - console.log('[Electron] NODE_PATH:', serverNodeModules); + logger.info('Starting backend server...'); + logger.info('Server path:', serverPath); + logger.info('Server root (cwd):', serverRoot); + logger.info('NODE_PATH:', serverNodeModules); serverProcess = spawn(command, args, { cwd: serverRoot, @@ -502,20 +506,20 @@ async function startServer(): Promise { }); serverProcess.stdout?.on('data', (data) => { - console.log(`[Server] ${data.toString().trim()}`); + serverLogger.info(data.toString().trim()); }); serverProcess.stderr?.on('data', (data) => { - console.error(`[Server Error] ${data.toString().trim()}`); + serverLogger.error(data.toString().trim()); }); serverProcess.on('close', (code) => { - console.log(`[Server] Process exited with code ${code}`); + serverLogger.info('Process exited with code', code); serverProcess = null; }); serverProcess.on('error', (err) => { - console.error(`[Server] Failed to start server process:`, err); + serverLogger.error('Failed to start server process:', err); serverProcess = null; }); @@ -542,7 +546,7 @@ async function waitForServer(maxAttempts = 30): Promise { reject(new Error('Timeout')); }); }); - console.log('[Electron] Server is ready'); + logger.info('Server is ready'); return; } catch { await new Promise((r) => setTimeout(r, 500)); @@ -645,10 +649,10 @@ app.whenReady().then(async () => { const desiredUserDataPath = path.join(app.getPath('appData'), 'Automaker'); if (app.getPath('userData') !== desiredUserDataPath) { app.setPath('userData', desiredUserDataPath); - console.log('[Electron] userData path set to:', desiredUserDataPath); + logger.info('userData path set to:', desiredUserDataPath); } } catch (error) { - console.warn('[Electron] Failed to set userData path:', (error as Error).message); + logger.warn('Failed to set userData path:', (error as Error).message); } // Initialize centralized path helpers for Electron @@ -664,7 +668,7 @@ app.whenReady().then(async () => { } else { setElectronAppPaths(__dirname, process.resourcesPath); } - console.log('[Electron] Initialized path security helpers'); + logger.info('Initialized path security helpers'); // Initialize security settings for path validation // Set DATA_DIR before initializing so it's available for security checks @@ -679,7 +683,7 @@ app.whenReady().then(async () => { try { app.dock.setIcon(iconPath); } catch (error) { - console.warn('[Electron] Failed to set dock icon:', (error as Error).message); + logger.warn('Failed to set dock icon:', (error as Error).message); } } } @@ -691,16 +695,12 @@ app.whenReady().then(async () => { // Find available ports (prevents conflicts with other apps using same ports) serverPort = await findAvailablePort(DEFAULT_SERVER_PORT); if (serverPort !== DEFAULT_SERVER_PORT) { - console.log( - `[Electron] Default server port ${DEFAULT_SERVER_PORT} in use, using port ${serverPort}` - ); + logger.info('Default server port', DEFAULT_SERVER_PORT, 'in use, using port', serverPort); } staticPort = await findAvailablePort(DEFAULT_STATIC_PORT); if (staticPort !== DEFAULT_STATIC_PORT) { - console.log( - `[Electron] Default static port ${DEFAULT_STATIC_PORT} in use, using port ${staticPort}` - ); + logger.info('Default static port', DEFAULT_STATIC_PORT, 'in use, using port', staticPort); } // Start static file server in production @@ -714,7 +714,7 @@ app.whenReady().then(async () => { // Create window createWindow(); } catch (error) { - console.error('[Electron] Failed to start:', error); + logger.error('Failed to start:', error); const errorMessage = (error as Error).message; const isNodeError = errorMessage.includes('Node.js'); dialog.showErrorBox( @@ -740,12 +740,12 @@ app.on('window-all-closed', () => { // (standard macOS behavior). On other platforms, stop servers and quit. if (process.platform !== 'darwin') { if (serverProcess && serverProcess.pid) { - console.log('[Electron] All windows closed, stopping server...'); + logger.info('All windows closed, stopping server...'); if (process.platform === 'win32') { try { execSync(`taskkill /f /t /pid ${serverProcess.pid}`, { stdio: 'ignore' }); } catch (error) { - console.error('[Electron] Failed to kill server process:', (error as Error).message); + logger.error('Failed to kill server process:', (error as Error).message); } } else { serverProcess.kill('SIGTERM'); @@ -754,7 +754,7 @@ app.on('window-all-closed', () => { } if (staticServer) { - console.log('[Electron] Stopping static server...'); + logger.info('Stopping static server...'); staticServer.close(); staticServer = null; } @@ -765,7 +765,7 @@ app.on('window-all-closed', () => { app.on('before-quit', () => { if (serverProcess && serverProcess.pid) { - console.log('[Electron] Stopping server...'); + logger.info('Stopping server...'); if (process.platform === 'win32') { try { // Windows: use taskkill with /t to kill entire process tree @@ -773,7 +773,7 @@ app.on('before-quit', () => { // Using execSync to ensure process is killed before app exits execSync(`taskkill /f /t /pid ${serverProcess.pid}`, { stdio: 'ignore' }); } catch (error) { - console.error('[Electron] Failed to kill server process:', (error as Error).message); + logger.error('Failed to kill server process:', (error as Error).message); } } else { serverProcess.kill('SIGTERM'); @@ -782,7 +782,7 @@ app.on('before-quit', () => { } if (staticServer) { - console.log('[Electron] Stopping static server...'); + logger.info('Stopping static server...'); staticServer.close(); staticServer = null; } @@ -924,6 +924,6 @@ ipcMain.handle('window:updateMinWidth', (_, _sidebarExpanded: boolean) => { // Quit the application (used when user denies sandbox risk confirmation) ipcMain.handle('app:quit', () => { - console.log('[Electron] Quitting application via IPC request'); + logger.info('Quitting application via IPC request'); app.quit(); }); diff --git a/apps/ui/src/preload.ts b/apps/ui/src/preload.ts index 0955ab1b..d6598e2a 100644 --- a/apps/ui/src/preload.ts +++ b/apps/ui/src/preload.ts @@ -6,6 +6,9 @@ */ import { contextBridge, ipcRenderer, OpenDialogOptions, SaveDialogOptions } from 'electron'; +import { createLogger } from '@automaker/utils/logger'; + +const logger = createLogger('Preload'); // Expose minimal API for native features contextBridge.exposeInMainWorld('electronAPI', { @@ -55,4 +58,4 @@ contextBridge.exposeInMainWorld('electronAPI', { quit: (): Promise => ipcRenderer.invoke('app:quit'), }); -console.log('[Preload] Electron API exposed (TypeScript)'); +logger.info('Electron API exposed (TypeScript)'); diff --git a/apps/ui/src/routes/__root.tsx b/apps/ui/src/routes/__root.tsx index d486ce59..3af374f0 100644 --- a/apps/ui/src/routes/__root.tsx +++ b/apps/ui/src/routes/__root.tsx @@ -1,5 +1,6 @@ import { createRootRoute, Outlet, useLocation, useNavigate } from '@tanstack/react-router'; import { useEffect, useState, useCallback, useDeferredValue, useRef } from 'react'; +import { createLogger } from '@automaker/utils/logger'; import { Sidebar } from '@/components/layout/sidebar'; import { FileBrowserProvider, @@ -23,6 +24,8 @@ import { ThemeOption, themeOptions } from '@/config/theme-options'; import { SandboxRiskDialog } from '@/components/dialogs/sandbox-risk-dialog'; import { SandboxRejectionScreen } from '@/components/dialogs/sandbox-rejection-screen'; +const logger = createLogger('RootLayout'); + function RootLayoutContent() { const location = useLocation(); const { @@ -120,7 +123,7 @@ function RootLayoutContent() { setSandboxStatus('needs-confirmation'); } } catch (error) { - console.error('[Sandbox] Failed to check environment:', error); + logger.error('Failed to check environment:', error); // On error, assume not containerized and show warning if (skipSandboxWarning) { setSandboxStatus('confirmed'); @@ -154,10 +157,10 @@ function RootLayoutContent() { if (electronAPI?.quit) { await electronAPI.quit(); } else { - console.error('[Sandbox] quit() not available on electronAPI'); + logger.error('quit() not available on electronAPI'); } } catch (error) { - console.error('[Sandbox] Failed to quit app:', error); + logger.error('Failed to quit app:', error); } } else { // In web mode, show rejection screen @@ -202,7 +205,7 @@ function RootLayoutContent() { // Session is invalid or expired - treat as not authenticated useAuthStore.getState().setAuthState({ isAuthenticated: false, authChecked: true }); } catch (error) { - console.error('Failed to initialize auth:', error); + logger.error('Failed to initialize auth:', error); // On error, treat as not authenticated useAuthStore.getState().setAuthState({ isAuthenticated: false, authChecked: true }); } finally { @@ -282,7 +285,7 @@ function RootLayoutContent() { }); setIpcConnected(response.ok); } catch (error) { - console.error('IPC connection failed:', error); + logger.error('IPC connection failed:', error); setIpcConnected(false); } }; diff --git a/apps/ui/vite.config.mts b/apps/ui/vite.config.mts index 12243c8e..f2d2f1ea 100644 --- a/apps/ui/vite.config.mts +++ b/apps/ui/vite.config.mts @@ -69,6 +69,12 @@ export default defineConfig(({ command }) => { }, build: { outDir: 'dist', + rollupOptions: { + external: ['child_process', 'fs', 'path', 'crypto', 'http', 'net', 'os', 'util', 'stream', 'events', 'readline'], + }, + }, + optimizeDeps: { + exclude: ['@automaker/platform'], }, define: { __APP_VERSION__: JSON.stringify(appVersion), diff --git a/libs/utils/package.json b/libs/utils/package.json index 118747be..f6cc6de4 100644 --- a/libs/utils/package.json +++ b/libs/utils/package.json @@ -5,6 +5,16 @@ "description": "Shared utility functions for AutoMaker", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./logger": { + "types": "./dist/logger.d.ts", + "default": "./dist/logger.js" + } + }, "scripts": { "build": "tsc", "watch": "tsc --watch", diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index 772f8c89..11d7b4e3 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -40,7 +40,15 @@ export { } from './prompt-builder.js'; // Logger -export { createLogger, getLogLevel, setLogLevel, LogLevel } from './logger.js'; +export { + createLogger, + getLogLevel, + setLogLevel, + setColorsEnabled, + setTimestampsEnabled, + LogLevel, + type Logger, +} from './logger.js'; // File system utilities export { mkdirSafe, existsSafe } from './fs-utils.js'; diff --git a/libs/utils/src/logger.ts b/libs/utils/src/logger.ts index c79b6ae7..399a68c2 100644 --- a/libs/utils/src/logger.ts +++ b/libs/utils/src/logger.ts @@ -1,7 +1,10 @@ /** - * Simple logger utility with log levels - * Configure via LOG_LEVEL environment variable: error, warn, info, debug - * Defaults to 'info' if not set + * Enhanced logger with colors and timestamps + * + * Environment Variables: + * - LOG_LEVEL: error, warn, info, debug (default: info) + * - LOG_COLORS: true/false (default: auto-detect TTY) + * - LOG_TIMESTAMPS: true/false (default: false) */ export enum LogLevel { @@ -18,42 +21,207 @@ const LOG_LEVEL_NAMES: Record = { debug: LogLevel.DEBUG, }; +// ANSI color codes for terminal output +const ANSI = { + reset: '\x1b[0m', + bold: '\x1b[1m', + dim: '\x1b[2m', + // Foreground colors + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + gray: '\x1b[90m', +}; + +// Browser CSS styles for console output +const BROWSER_STYLES = { + timestamp: 'color: #6b7280; font-size: 11px;', + context: 'color: #3b82f6; font-weight: 600;', + reset: 'color: inherit; font-weight: inherit;', + levels: { + ERROR: + 'background: #ef4444; color: white; font-weight: bold; padding: 1px 6px; border-radius: 3px;', + WARN: 'background: #f59e0b; color: white; font-weight: bold; padding: 1px 6px; border-radius: 3px;', + INFO: 'background: #3b82f6; color: white; font-weight: bold; padding: 1px 6px; border-radius: 3px;', + DEBUG: + 'background: #8b5cf6; color: white; font-weight: bold; padding: 1px 6px; border-radius: 3px;', + }, +}; + +// Environment detection - use globalThis for cross-platform compatibility +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const isBrowser = typeof (globalThis as any).window !== 'undefined'; + +// Configuration state let currentLogLevel: LogLevel = LogLevel.INFO; +// Detect if we're in a Node.js environment with TTY support +function isTTY(): boolean { + if (isBrowser) return false; + try { + return process?.stdout?.isTTY ?? false; + } catch { + return false; + } +} + +// Get environment variable safely (works in both Node.js and browser) +function getEnvVar(name: string): string | undefined { + if (isBrowser) return undefined; + try { + return process?.env?.[name]; + } catch { + return undefined; + } +} + +// Initialize configuration from environment variables +let colorsEnabled = isTTY() && getEnvVar('LOG_COLORS') !== 'false'; +let timestampsEnabled = getEnvVar('LOG_TIMESTAMPS') === 'true'; + // Initialize log level from environment variable -const envLogLevel = process.env.LOG_LEVEL?.toLowerCase(); +const envLogLevel = getEnvVar('LOG_LEVEL')?.toLowerCase(); if (envLogLevel && LOG_LEVEL_NAMES[envLogLevel] !== undefined) { currentLogLevel = LOG_LEVEL_NAMES[envLogLevel]; } +/** + * Format ISO timestamp + */ +function formatTimestamp(): string { + return new Date().toISOString(); +} + +/** + * Format short time for browser (HH:mm:ss.SSS) + */ +function formatShortTime(): string { + return new Date().toISOString().split('T')[1].slice(0, 12); +} + +/** + * Format a log line for Node.js terminal output + */ +function formatNodeLog(level: string, context: string, levelColor: string): string { + const parts: string[] = []; + + if (timestampsEnabled) { + parts.push(colorsEnabled ? `${ANSI.gray}${formatTimestamp()}${ANSI.reset}` : formatTimestamp()); + } + + const levelPadded = level.padEnd(5); + parts.push(colorsEnabled ? `${levelColor}${levelPadded}${ANSI.reset}` : levelPadded); + parts.push(colorsEnabled ? `${ANSI.blue}[${context}]${ANSI.reset}` : `[${context}]`); + + return parts.join(' '); +} + +/** + * Logger interface returned by createLogger + */ +export interface Logger { + error: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + info: (...args: unknown[]) => void; + debug: (...args: unknown[]) => void; +} + /** * Create a logger instance with a context prefix */ -export function createLogger(context: string) { - const prefix = `[${context}]`; +export function createLogger(context: string): Logger { + if (isBrowser) { + // Browser implementation with CSS styling + return { + error: (...args: unknown[]): void => { + if (currentLogLevel >= LogLevel.ERROR) { + console.error( + `%cERROR%c %c${formatShortTime()}%c %c[${context}]%c`, + BROWSER_STYLES.levels.ERROR, + BROWSER_STYLES.reset, + BROWSER_STYLES.timestamp, + BROWSER_STYLES.reset, + BROWSER_STYLES.context, + BROWSER_STYLES.reset, + ...args + ); + } + }, + warn: (...args: unknown[]): void => { + if (currentLogLevel >= LogLevel.WARN) { + console.warn( + `%cWARN%c %c${formatShortTime()}%c %c[${context}]%c`, + BROWSER_STYLES.levels.WARN, + BROWSER_STYLES.reset, + BROWSER_STYLES.timestamp, + BROWSER_STYLES.reset, + BROWSER_STYLES.context, + BROWSER_STYLES.reset, + ...args + ); + } + }, + + info: (...args: unknown[]): void => { + if (currentLogLevel >= LogLevel.INFO) { + console.log( + `%cINFO%c %c${formatShortTime()}%c %c[${context}]%c`, + BROWSER_STYLES.levels.INFO, + BROWSER_STYLES.reset, + BROWSER_STYLES.timestamp, + BROWSER_STYLES.reset, + BROWSER_STYLES.context, + BROWSER_STYLES.reset, + ...args + ); + } + }, + + debug: (...args: unknown[]): void => { + if (currentLogLevel >= LogLevel.DEBUG) { + console.log( + `%cDEBUG%c %c${formatShortTime()}%c %c[${context}]%c`, + BROWSER_STYLES.levels.DEBUG, + BROWSER_STYLES.reset, + BROWSER_STYLES.timestamp, + BROWSER_STYLES.reset, + BROWSER_STYLES.context, + BROWSER_STYLES.reset, + ...args + ); + } + }, + }; + } + + // Node.js implementation with ANSI colors return { error: (...args: unknown[]): void => { if (currentLogLevel >= LogLevel.ERROR) { - console.error(prefix, ...args); + console.error(formatNodeLog('ERROR', context, ANSI.red), ...args); } }, warn: (...args: unknown[]): void => { if (currentLogLevel >= LogLevel.WARN) { - console.warn(prefix, ...args); + console.warn(formatNodeLog('WARN', context, ANSI.yellow), ...args); } }, info: (...args: unknown[]): void => { if (currentLogLevel >= LogLevel.INFO) { - console.log(prefix, ...args); + console.log(formatNodeLog('INFO', context, ANSI.cyan), ...args); } }, debug: (...args: unknown[]): void => { if (currentLogLevel >= LogLevel.DEBUG) { - console.log(prefix, '[DEBUG]', ...args); + console.log(formatNodeLog('DEBUG', context, ANSI.magenta), ...args); } }, }; @@ -72,3 +240,17 @@ export function getLogLevel(): LogLevel { export function setLogLevel(level: LogLevel): void { currentLogLevel = level; } + +/** + * Enable or disable colored output + */ +export function setColorsEnabled(enabled: boolean): void { + colorsEnabled = enabled; +} + +/** + * Enable or disable timestamps in output + */ +export function setTimestampsEnabled(enabled: boolean): void { + timestampsEnabled = enabled; +} diff --git a/package-lock.json b/package-lock.json index 48840b71..6039d5c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ }, "apps/server": { "name": "@automaker/server", - "version": "0.7.1", + "version": "0.7.2", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@anthropic-ai/claude-agent-sdk": "0.1.76", @@ -78,7 +78,7 @@ }, "apps/ui": { "name": "@automaker/ui", - "version": "0.7.1", + "version": "0.7.2", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE", "dependencies": { @@ -675,6 +675,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1258,6 +1259,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz", "integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1300,6 +1302,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2120,7 +2123,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -2142,7 +2144,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2159,7 +2160,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -2174,7 +2174,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2942,7 +2941,6 @@ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">=18" } @@ -3067,7 +3065,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3084,7 +3081,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3101,7 +3097,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3210,7 +3205,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3233,7 +3227,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3256,7 +3249,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3342,7 +3334,6 @@ ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/runtime": "^1.7.0" }, @@ -3365,7 +3356,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3385,7 +3375,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3785,8 +3774,7 @@ "version": "16.0.10", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz", "integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { "version": "16.0.10", @@ -3800,7 +3788,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3817,7 +3804,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3834,7 +3820,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3851,7 +3836,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3868,7 +3852,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3885,7 +3868,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3902,7 +3884,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3919,7 +3900,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -4010,6 +3990,7 @@ "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.57.0" }, @@ -5450,7 +5431,6 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -5784,6 +5764,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.6.tgz", "integrity": "sha512-qWFxi2D6eGc1L03RzUuhyEOplZ7Q6q62YOl7Of9Y0q4YjwQwxRm4zxwDVtvUIoy4RLVCpqp5UoE+Nxv2PY9trg==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.141.0", "@tanstack/react-store": "^0.8.0", @@ -6210,6 +6191,7 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -6352,6 +6334,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6362,6 +6345,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6467,6 +6451,7 @@ "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", @@ -6960,7 +6945,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@xyflow/react": { "version": "12.10.0", @@ -7058,6 +7044,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7118,6 +7105,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7716,6 +7704,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8247,8 +8236,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", @@ -8553,8 +8541,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-env": { "version": "10.1.0", @@ -8651,6 +8638,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -8952,6 +8940,7 @@ "integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.0.12", "builder-util": "26.0.11", @@ -9278,7 +9267,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -9299,7 +9287,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -9550,6 +9537,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9864,6 +9852,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -11531,7 +11520,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11553,7 +11541,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11575,7 +11562,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11597,7 +11583,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11619,7 +11604,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11641,7 +11625,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11663,7 +11646,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11685,7 +11667,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11707,7 +11688,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11729,7 +11709,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11751,7 +11730,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -14039,7 +14017,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -14056,7 +14033,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -14074,7 +14050,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -14263,6 +14238,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14272,6 +14248,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -14630,7 +14607,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -14819,6 +14795,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz", "integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -14867,7 +14844,6 @@ "hasInstallScript": true, "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", @@ -14918,7 +14894,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14941,7 +14916,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14964,7 +14938,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14981,7 +14954,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14998,7 +14970,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15015,7 +14986,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15032,7 +15002,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15049,7 +15018,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15066,7 +15034,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15083,7 +15050,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15106,7 +15072,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15129,7 +15094,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15152,7 +15116,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15175,7 +15138,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15198,7 +15160,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15667,7 +15628,6 @@ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", - "peer": true, "dependencies": { "client-only": "0.0.1" }, @@ -15837,7 +15797,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -15901,7 +15860,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -15999,6 +15957,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16203,6 +16162,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16574,6 +16534,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16663,7 +16624,8 @@ "resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", @@ -16689,6 +16651,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16731,6 +16694,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -16988,6 +16952,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -17056,6 +17021,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }