diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 6dd539ba..7fcff59c 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -22,6 +22,7 @@ import { CreateGroupModal } from './components/CreateGroupModal'; import { RenameSessionModal } from './components/RenameSessionModal'; import { RenameGroupModal } from './components/RenameGroupModal'; import { ConfirmModal } from './components/ConfirmModal'; +import { ErrorBoundary } from './components/ErrorBoundary'; // Import custom hooks import { useSettings, useSessionManager, useFileExplorer } from './hooks'; @@ -1468,42 +1469,44 @@ export default function MaestroConsole() { )} {/* --- LEFT SIDEBAR --- */} - + + + {/* --- CENTER WORKSPACE --- */} {!activeSession ? ( @@ -1522,11 +1525,12 @@ export default function MaestroConsole() { ) : ( <> -
setActiveFocus('main')} - > + +
setActiveFocus('main')} + > {/* Top Bar */}
@@ -1643,38 +1647,41 @@ export default function MaestroConsole() { /> )}
+ {/* --- RIGHT PANEL --- */} - + + + )} diff --git a/src/renderer/components/ErrorBoundary.tsx b/src/renderer/components/ErrorBoundary.tsx new file mode 100644 index 00000000..5338b050 --- /dev/null +++ b/src/renderer/components/ErrorBoundary.tsx @@ -0,0 +1,148 @@ +import React, { Component, ReactNode } from 'react'; +import { AlertTriangle, RefreshCw, Home } from 'lucide-react'; + +interface Props { + children: ReactNode; + fallbackComponent?: ReactNode; + onReset?: () => void; +} + +interface State { + hasError: boolean; + error: Error | null; + errorInfo: React.ErrorInfo | null; +} + +/** + * ErrorBoundary component catches JavaScript errors anywhere in the child component tree, + * logs those errors, and displays a fallback UI instead of crashing the entire app. + * + * Usage: + * ```tsx + * + * + * + * ``` + */ +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { + hasError: false, + error: null, + errorInfo: null, + }; + } + + static getDerivedStateFromError(error: Error): State { + // Update state so the next render will show the fallback UI + return { + hasError: true, + error, + errorInfo: null, + }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + // Log error details to console for debugging + console.error('ErrorBoundary caught an error:', error, errorInfo); + + // Update state with error info + this.setState({ + error, + errorInfo, + }); + } + + handleReset = () => { + // Reset error state + this.setState({ + hasError: false, + error: null, + errorInfo: null, + }); + + // Call optional reset handler + if (this.props.onReset) { + this.props.onReset(); + } + }; + + handleReload = () => { + // Reload the entire app + window.location.reload(); + }; + + render() { + if (this.state.hasError) { + // If a custom fallback is provided, use it + if (this.props.fallbackComponent) { + return this.props.fallbackComponent; + } + + // Default error UI + return ( +
+
+
+
+ +
+
+

+ Something went wrong +

+

+ An unexpected error occurred in the application. You can try to recover or reload the app. +

+
+
+ + {this.state.error && ( +
+

Error Details:

+
+
+                    {this.state.error.toString()}
+                  
+
+
+ )} + + {this.state.errorInfo && ( +
+ + Component Stack Trace + +
+
+                    {this.state.errorInfo.componentStack}
+                  
+
+
+ )} + +
+ + +
+
+
+ ); + } + + return this.props.children; + } +} diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx index 95ae1424..34f6b33c 100644 --- a/src/renderer/main.tsx +++ b/src/renderer/main.tsx @@ -1,10 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import MaestroConsole from './App'; +import { ErrorBoundary } from './components/ErrorBoundary'; import './index.css'; ReactDOM.createRoot(document.getElementById('root')!).render( - + + + );