import React, { useEffect, useState, useMemo } from 'react'; import { toast } from 'react-toastify'; import { classNames } from '~/utils/classNames'; import { logStore, type LogEntry } from '~/lib/stores/logs'; import { useStore } from '@nanostores/react'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible'; import { Progress } from '~/components/ui/Progress'; import { ScrollArea } from '~/components/ui/ScrollArea'; import { Badge } from '~/components/ui/Badge'; interface SystemInfo { os: string; arch: string; platform: string; cpus: string; memory: { total: string; free: string; used: string; percentage: number; }; node: string; browser: { name: string; version: string; language: string; userAgent: string; cookiesEnabled: boolean; online: boolean; platform: string; cores: number; }; screen: { width: number; height: number; colorDepth: number; pixelRatio: number; }; time: { timezone: string; offset: number; locale: string; }; performance: { memory: { jsHeapSizeLimit: number; totalJSHeapSize: number; usedJSHeapSize: number; usagePercentage: number; }; timing: { loadTime: number; domReadyTime: number; readyStart: number; redirectTime: number; appcacheTime: number; unloadEventTime: number; lookupDomainTime: number; connectTime: number; requestTime: number; initDomTreeTime: number; loadEventTime: number; }; navigation: { type: number; redirectCount: number; }; }; network: { downlink: number; effectiveType: string; rtt: number; saveData: boolean; type: string; }; battery?: { charging: boolean; chargingTime: number; dischargingTime: number; level: number; }; storage: { quota: number; usage: number; persistent: boolean; temporary: boolean; }; } interface GitHubRepoInfo { fullName: string; defaultBranch: string; stars: number; forks: number; openIssues?: number; } interface GitInfo { local: { commitHash: string; branch: string; commitTime: string; author: string; email: string; remoteUrl: string; repoName: string; }; github?: { currentRepo: GitHubRepoInfo; upstream?: GitHubRepoInfo; }; isForked?: boolean; } interface WebAppInfo { name: string; version: string; description: string; license: string; environment: string; timestamp: string; runtimeInfo: { nodeVersion: string; }; dependencies: { production: Array<{ name: string; version: string; type: string }>; development: Array<{ name: string; version: string; type: string }>; peer: Array<{ name: string; version: string; type: string }>; optional: Array<{ name: string; version: string; type: string }>; }; gitInfo: GitInfo; } const DependencySection = ({ title, deps, }: { title: string; deps: Array<{ name: string; version: string; type: string }>; }) => { const [isOpen, setIsOpen] = useState(false); if (deps.length === 0) { return null; } return (
{title} Dependencies ({deps.length})
{isOpen ? 'Hide' : 'Show'}
{deps.map((dep) => (
{dep.name} {dep.version}
))}
); }; export default function DebugTab() { const [systemInfo, setSystemInfo] = useState(null); const [webAppInfo, setWebAppInfo] = useState(null); const [loading, setLoading] = useState({ systemInfo: false, webAppInfo: false, errors: false, performance: false, }); const [openSections, setOpenSections] = useState({ system: false, webapp: false, errors: false, performance: false, }); // Subscribe to logStore updates const logs = useStore(logStore.logs); const errorLogs = useMemo(() => { return Object.values(logs).filter( (log): log is LogEntry => typeof log === 'object' && log !== null && 'level' in log && log.level === 'error', ); }, [logs]); // Set up error listeners when component mounts useEffect(() => { const handleError = (event: ErrorEvent) => { logStore.logError(event.message, event.error, { filename: event.filename, lineNumber: event.lineno, columnNumber: event.colno, }); }; const handleRejection = (event: PromiseRejectionEvent) => { logStore.logError('Unhandled Promise Rejection', event.reason); }; window.addEventListener('error', handleError); window.addEventListener('unhandledrejection', handleRejection); return () => { window.removeEventListener('error', handleError); window.removeEventListener('unhandledrejection', handleRejection); }; }, []); // Check for errors when the errors section is opened useEffect(() => { if (openSections.errors) { checkErrors(); } }, [openSections.errors]); // Load initial data when component mounts useEffect(() => { const loadInitialData = async () => { await Promise.all([getSystemInfo(), getWebAppInfo()]); }; loadInitialData(); }, []); // Refresh data when sections are opened useEffect(() => { if (openSections.system) { getSystemInfo(); } if (openSections.webapp) { getWebAppInfo(); } }, [openSections.system, openSections.webapp]); // Add periodic refresh of git info useEffect(() => { if (!openSections.webapp) { return undefined; } const interval = setInterval(async () => { try { const response = await fetch('/api/system/git-info'); const updatedGitInfo = (await response.json()) as GitInfo; setWebAppInfo((prev) => { if (!prev) { return null; } return { ...prev, gitInfo: updatedGitInfo, }; }); } catch (error) { console.error('Failed to refresh git info:', error); } }, 5000); const cleanup = () => { clearInterval(interval); }; return cleanup; }, [openSections.webapp]); const getSystemInfo = async () => { try { setLoading((prev) => ({ ...prev, systemInfo: true })); // Get browser info const ua = navigator.userAgent; const browserName = ua.includes('Firefox') ? 'Firefox' : ua.includes('Chrome') ? 'Chrome' : ua.includes('Safari') ? 'Safari' : ua.includes('Edge') ? 'Edge' : 'Unknown'; const browserVersion = ua.match(/(Firefox|Chrome|Safari|Edge)\/([0-9.]+)/)?.[2] || 'Unknown'; // Get performance metrics const memory = (performance as any).memory || {}; const timing = performance.timing; const navigation = performance.navigation; const connection = (navigator as any).connection; // Get battery info let batteryInfo; try { const battery = await (navigator as any).getBattery(); batteryInfo = { charging: battery.charging, chargingTime: battery.chargingTime, dischargingTime: battery.dischargingTime, level: battery.level * 100, }; } catch { console.log('Battery API not supported'); } // Get storage info let storageInfo = { quota: 0, usage: 0, persistent: false, temporary: false, }; try { const storage = await navigator.storage.estimate(); const persistent = await navigator.storage.persist(); storageInfo = { quota: storage.quota || 0, usage: storage.usage || 0, persistent, temporary: !persistent, }; } catch { console.log('Storage API not supported'); } // Get memory info from browser performance API const performanceMemory = (performance as any).memory || {}; const totalMemory = performanceMemory.jsHeapSizeLimit || 0; const usedMemory = performanceMemory.usedJSHeapSize || 0; const freeMemory = totalMemory - usedMemory; const memoryPercentage = totalMemory ? (usedMemory / totalMemory) * 100 : 0; const systemInfo: SystemInfo = { os: navigator.platform, arch: navigator.userAgent.includes('x64') ? 'x64' : navigator.userAgent.includes('arm') ? 'arm' : 'unknown', platform: navigator.platform, cpus: navigator.hardwareConcurrency + ' cores', memory: { total: formatBytes(totalMemory), free: formatBytes(freeMemory), used: formatBytes(usedMemory), percentage: Math.round(memoryPercentage), }, node: 'browser', browser: { name: browserName, version: browserVersion, language: navigator.language, userAgent: navigator.userAgent, cookiesEnabled: navigator.cookieEnabled, online: navigator.onLine, platform: navigator.platform, cores: navigator.hardwareConcurrency, }, screen: { width: window.screen.width, height: window.screen.height, colorDepth: window.screen.colorDepth, pixelRatio: window.devicePixelRatio, }, time: { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, offset: new Date().getTimezoneOffset(), locale: navigator.language, }, performance: { memory: { jsHeapSizeLimit: memory.jsHeapSizeLimit || 0, totalJSHeapSize: memory.totalJSHeapSize || 0, usedJSHeapSize: memory.usedJSHeapSize || 0, usagePercentage: memory.totalJSHeapSize ? (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100 : 0, }, timing: { loadTime: timing.loadEventEnd - timing.navigationStart, domReadyTime: timing.domContentLoadedEventEnd - timing.navigationStart, readyStart: timing.fetchStart - timing.navigationStart, redirectTime: timing.redirectEnd - timing.redirectStart, appcacheTime: timing.domainLookupStart - timing.fetchStart, unloadEventTime: timing.unloadEventEnd - timing.unloadEventStart, lookupDomainTime: timing.domainLookupEnd - timing.domainLookupStart, connectTime: timing.connectEnd - timing.connectStart, requestTime: timing.responseEnd - timing.requestStart, initDomTreeTime: timing.domInteractive - timing.responseEnd, loadEventTime: timing.loadEventEnd - timing.loadEventStart, }, navigation: { type: navigation.type, redirectCount: navigation.redirectCount, }, }, network: { downlink: connection?.downlink || 0, effectiveType: connection?.effectiveType || 'unknown', rtt: connection?.rtt || 0, saveData: connection?.saveData || false, type: connection?.type || 'unknown', }, battery: batteryInfo, storage: storageInfo, }; setSystemInfo(systemInfo); toast.success('System information updated'); } catch (error) { toast.error('Failed to get system information'); console.error('Failed to get system information:', error); } finally { setLoading((prev) => ({ ...prev, systemInfo: false })); } }; const getWebAppInfo = async () => { try { setLoading((prev) => ({ ...prev, webAppInfo: true })); const [appResponse, gitResponse] = await Promise.all([ fetch('/api/system/app-info'), fetch('/api/system/git-info'), ]); if (!appResponse.ok || !gitResponse.ok) { throw new Error('Failed to fetch webapp info'); } const appData = (await appResponse.json()) as Omit; const gitData = (await gitResponse.json()) as GitInfo; console.log('Git Info Response:', gitData); // Add logging to debug setWebAppInfo({ ...appData, gitInfo: gitData, }); toast.success('WebApp information updated'); return true; } catch (error) { console.error('Failed to fetch webapp info:', error); toast.error('Failed to fetch webapp information'); setWebAppInfo(null); return false; } finally { setLoading((prev) => ({ ...prev, webAppInfo: false })); } }; // Helper function to format bytes to human readable format const formatBytes = (bytes: number) => { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${Math.round(size)} ${units[unitIndex]}`; }; const handleLogPerformance = () => { try { setLoading((prev) => ({ ...prev, performance: true })); // Get performance metrics using modern Performance API const performanceEntries = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; const memory = (performance as any).memory; // Calculate timing metrics const timingMetrics = { loadTime: performanceEntries.loadEventEnd - performanceEntries.startTime, domReadyTime: performanceEntries.domContentLoadedEventEnd - performanceEntries.startTime, fetchTime: performanceEntries.responseEnd - performanceEntries.fetchStart, redirectTime: performanceEntries.redirectEnd - performanceEntries.redirectStart, dnsTime: performanceEntries.domainLookupEnd - performanceEntries.domainLookupStart, tcpTime: performanceEntries.connectEnd - performanceEntries.connectStart, ttfb: performanceEntries.responseStart - performanceEntries.requestStart, processingTime: performanceEntries.loadEventEnd - performanceEntries.responseEnd, }; // Get resource timing data const resourceEntries = performance.getEntriesByType('resource'); const resourceStats = { totalResources: resourceEntries.length, totalSize: resourceEntries.reduce((total, entry) => total + (entry as any).transferSize || 0, 0), totalTime: Math.max(...resourceEntries.map((entry) => entry.duration)), }; // Get memory metrics const memoryMetrics = memory ? { jsHeapSizeLimit: memory.jsHeapSizeLimit, totalJSHeapSize: memory.totalJSHeapSize, usedJSHeapSize: memory.usedJSHeapSize, heapUtilization: (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100, } : null; // Get frame rate metrics let fps = 0; if ('requestAnimationFrame' in window) { const times: number[] = []; function calculateFPS(now: number) { times.push(now); if (times.length > 10) { const fps = Math.round((1000 * 10) / (now - times[0])); times.shift(); return fps; } requestAnimationFrame(calculateFPS); return 0; } fps = calculateFPS(performance.now()); } // Log all performance metrics logStore.logSystem('Performance Metrics', { timing: timingMetrics, resources: resourceStats, memory: memoryMetrics, fps, timestamp: new Date().toISOString(), navigationEntry: { type: performanceEntries.type, redirectCount: performanceEntries.redirectCount, }, }); toast.success('Performance metrics logged'); } catch (error) { toast.error('Failed to log performance metrics'); console.error('Failed to log performance metrics:', error); } finally { setLoading((prev) => ({ ...prev, performance: false })); } }; const checkErrors = async () => { try { setLoading((prev) => ({ ...prev, errors: true })); // Get errors from log store const storedErrors = errorLogs; if (storedErrors.length === 0) { toast.success('No errors found'); } else { toast.warning(`Found ${storedErrors.length} error(s)`); } } catch (error) { toast.error('Failed to check errors'); console.error('Failed to check errors:', error); } finally { setLoading((prev) => ({ ...prev, errors: false })); } }; const exportDebugInfo = () => { try { const debugData = { timestamp: new Date().toISOString(), system: systemInfo, webApp: webAppInfo, errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), performance: { memory: (performance as any).memory || {}, timing: performance.timing, navigation: performance.navigation, }, }; const blob = new Blob([JSON.stringify(debugData, null, 2)], { type: 'application/json' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `bolt-debug-info-${new Date().toISOString()}.json`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); toast.success('Debug information exported successfully'); } catch (error) { console.error('Failed to export debug info:', error); toast.error('Failed to export debug information'); } }; return (
{/* Quick Stats Banner */}
Memory Usage
{systemInfo?.memory.percentage}%
Page Load Time
{systemInfo ? (systemInfo.performance.timing.loadTime / 1000).toFixed(2) + 's' : '-'}
DOM Ready: {systemInfo ? (systemInfo.performance.timing.domReadyTime / 1000).toFixed(2) + 's' : '-'}
Network Speed
{systemInfo?.network.downlink || '-'} Mbps
RTT: {systemInfo?.network.rtt || '-'} ms
Errors
{errorLogs.length}
{/* Action Buttons */}
{/* System Information */} setOpenSections((prev) => ({ ...prev, system: open }))} className="w-full" >

System Information

{systemInfo ? (
OS: {systemInfo.os}
Platform: {systemInfo.platform}
Architecture: {systemInfo.arch}
CPU Cores: {systemInfo.cpus}
Node Version: {systemInfo.node}
Network Type: {systemInfo.network.type} ({systemInfo.network.effectiveType})
Network Speed: {systemInfo.network.downlink}Mbps (RTT: {systemInfo.network.rtt}ms)
{systemInfo.battery && (
Battery: {systemInfo.battery.level.toFixed(1)}% {systemInfo.battery.charging ? '(Charging)' : ''}
)}
Storage: {(systemInfo.storage.usage / (1024 * 1024 * 1024)).toFixed(2)}GB /{' '} {(systemInfo.storage.quota / (1024 * 1024 * 1024)).toFixed(2)}GB
Memory Usage: {systemInfo.memory.used} / {systemInfo.memory.total} ({systemInfo.memory.percentage}%)
Browser: {systemInfo.browser.name} {systemInfo.browser.version}
Screen: {systemInfo.screen.width}x{systemInfo.screen.height} ({systemInfo.screen.pixelRatio}x)
Timezone: {systemInfo.time.timezone}
Language: {systemInfo.browser.language}
JS Heap: {(systemInfo.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1)}MB /{' '} {(systemInfo.performance.memory.totalJSHeapSize / (1024 * 1024)).toFixed(1)}MB ( {systemInfo.performance.memory.usagePercentage.toFixed(1)}%)
Page Load: {(systemInfo.performance.timing.loadTime / 1000).toFixed(2)}s
DOM Ready: {(systemInfo.performance.timing.domReadyTime / 1000).toFixed(2)}s
) : (
Loading system information...
)}
{/* Performance Metrics */} setOpenSections((prev) => ({ ...prev, performance: open }))} className="w-full" >

Performance Metrics

{systemInfo && (
Page Load Time: {(systemInfo.performance.timing.loadTime / 1000).toFixed(2)}s
DOM Ready Time: {(systemInfo.performance.timing.domReadyTime / 1000).toFixed(2)}s
Request Time: {(systemInfo.performance.timing.requestTime / 1000).toFixed(2)}s
Redirect Time: {(systemInfo.performance.timing.redirectTime / 1000).toFixed(2)}s
JS Heap Usage: {(systemInfo.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1)}MB /{' '} {(systemInfo.performance.memory.totalJSHeapSize / (1024 * 1024)).toFixed(1)}MB
Heap Utilization: {systemInfo.performance.memory.usagePercentage.toFixed(1)}%
Navigation Type: {systemInfo.performance.navigation.type === 0 ? 'Navigate' : systemInfo.performance.navigation.type === 1 ? 'Reload' : systemInfo.performance.navigation.type === 2 ? 'Back/Forward' : 'Other'}
Redirects: {systemInfo.performance.navigation.redirectCount}
)}
{/* WebApp Information */} setOpenSections((prev) => ({ ...prev, webapp: open }))} className="w-full" >

WebApp Information

{loading.webAppInfo && }
{loading.webAppInfo ? (
) : !webAppInfo ? (

Failed to load WebApp information

) : (

Basic Information

Name: {webAppInfo.name}
Version: {webAppInfo.version}
License: {webAppInfo.license}
Environment: {webAppInfo.environment}
Node Version: {webAppInfo.runtimeInfo.nodeVersion}

Git Information

Branch: {webAppInfo.gitInfo.local.branch}
Commit: {webAppInfo.gitInfo.local.commitHash}
Author: {webAppInfo.gitInfo.local.author}
Commit Time: {webAppInfo.gitInfo.local.commitTime}
{webAppInfo.gitInfo.github && ( <>
Repository: {webAppInfo.gitInfo.github.currentRepo.fullName} {webAppInfo.gitInfo.isForked && ' (fork)'}
{webAppInfo.gitInfo.github.currentRepo.stars}
{webAppInfo.gitInfo.github.currentRepo.forks}
{webAppInfo.gitInfo.github.currentRepo.openIssues}
{webAppInfo.gitInfo.github.upstream && (
Upstream: {webAppInfo.gitInfo.github.upstream.fullName}
{webAppInfo.gitInfo.github.upstream.stars}
{webAppInfo.gitInfo.github.upstream.forks}
)} )}
)} {webAppInfo && (

Dependencies

)}
{/* Error Check */} setOpenSections((prev) => ({ ...prev, errors: open }))} className="w-full" >

Error Check

{errorLogs.length > 0 && ( {errorLogs.length} Errors )}
Checks for:
  • Unhandled JavaScript errors
  • Unhandled Promise rejections
  • Runtime exceptions
  • Network errors
Status: {loading.errors ? 'Checking...' : errorLogs.length > 0 ? `${errorLogs.length} errors found` : 'No errors found'}
{errorLogs.length > 0 && (
Recent Errors:
{errorLogs.map((error) => (
{error.message}
{error.source && (
Source: {error.source} {error.details?.lineNumber && `:${error.details.lineNumber}`}
)} {error.stack && (
{error.stack}
)}
))}
)}
); }