Claude commited on
Commit
986fe1a
·
unverified ·
1 Parent(s): 955ac16

fix(frontend): add ErrorBoundary to catch React render errors (BUG-010)

Browse files

Add ErrorBoundary component that catches unhandled React errors
(e.g., WebGL failures in NiiVueViewer) and displays a user-friendly
error message instead of crashing to a white screen.

- New ErrorBoundary component with retry button
- Wrap AppContent in ErrorBoundary in App.tsx

frontend/src/App.tsx CHANGED
@@ -4,9 +4,10 @@ import { CaseSelector } from './components/CaseSelector'
4
  import { NiiVueViewer } from './components/NiiVueViewer'
5
  import { MetricsPanel } from './components/MetricsPanel'
6
  import { ProgressIndicator } from './components/ProgressIndicator'
 
7
  import { useSegmentation } from './hooks/useSegmentation'
8
 
9
- export default function App() {
10
  const [selectedCase, setSelectedCase] = useState<string | null>(null)
11
  const {
12
  result,
@@ -106,3 +107,18 @@ export default function App() {
106
  </Layout>
107
  )
108
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import { NiiVueViewer } from './components/NiiVueViewer'
5
  import { MetricsPanel } from './components/MetricsPanel'
6
  import { ProgressIndicator } from './components/ProgressIndicator'
7
+ import { ErrorBoundary } from './components/ErrorBoundary'
8
  import { useSegmentation } from './hooks/useSegmentation'
9
 
10
+ function AppContent() {
11
  const [selectedCase, setSelectedCase] = useState<string | null>(null)
12
  const {
13
  result,
 
107
  </Layout>
108
  )
109
  }
110
+
111
+ /**
112
+ * Main App component wrapped in ErrorBoundary.
113
+ *
114
+ * The ErrorBoundary catches React rendering errors (e.g., WebGL failures
115
+ * in NiiVueViewer) and displays a user-friendly error message instead
116
+ * of crashing to a white screen.
117
+ */
118
+ export default function App() {
119
+ return (
120
+ <ErrorBoundary>
121
+ <AppContent />
122
+ </ErrorBoundary>
123
+ )
124
+ }
frontend/src/components/ErrorBoundary.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, type ReactNode } from 'react'
2
+
3
+ interface Props {
4
+ children: ReactNode
5
+ fallback?: ReactNode
6
+ }
7
+
8
+ interface State {
9
+ hasError: boolean
10
+ error: Error | null
11
+ }
12
+
13
+ /**
14
+ * Error Boundary component to catch React rendering errors.
15
+ *
16
+ * Prevents the entire app from crashing when a component throws.
17
+ * Displays a user-friendly error message instead of a white screen.
18
+ *
19
+ * Usage:
20
+ * <ErrorBoundary>
21
+ * <ComponentThatMightThrow />
22
+ * </ErrorBoundary>
23
+ */
24
+ export class ErrorBoundary extends Component<Props, State> {
25
+ constructor(props: Props) {
26
+ super(props)
27
+ this.state = { hasError: false, error: null }
28
+ }
29
+
30
+ static getDerivedStateFromError(error: Error): State {
31
+ return { hasError: true, error }
32
+ }
33
+
34
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
35
+ // Log error for debugging
36
+ console.error('ErrorBoundary caught error:', error, errorInfo)
37
+ }
38
+
39
+ handleRetry = (): void => {
40
+ this.setState({ hasError: false, error: null })
41
+ }
42
+
43
+ render(): ReactNode {
44
+ if (this.state.hasError) {
45
+ if (this.props.fallback) {
46
+ return this.props.fallback
47
+ }
48
+
49
+ return (
50
+ <div className="min-h-screen bg-gray-900 flex items-center justify-center p-4">
51
+ <div className="bg-gray-800 rounded-lg p-6 max-w-md w-full text-center space-y-4">
52
+ <div className="text-red-400 text-4xl">Something went wrong</div>
53
+ <p className="text-gray-300">
54
+ An unexpected error occurred while rendering the application.
55
+ </p>
56
+ {this.state.error && (
57
+ <details className="text-left">
58
+ <summary className="text-gray-400 cursor-pointer hover:text-gray-300">
59
+ Error details
60
+ </summary>
61
+ <pre className="mt-2 p-2 bg-gray-900 rounded text-xs text-red-300 overflow-auto max-h-32">
62
+ {this.state.error.message}
63
+ </pre>
64
+ </details>
65
+ )}
66
+ <button
67
+ onClick={this.handleRetry}
68
+ className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"
69
+ >
70
+ Try Again
71
+ </button>
72
+ </div>
73
+ </div>
74
+ )
75
+ }
76
+
77
+ return this.props.children
78
+ }
79
+ }