Add error boundary around object viewers

Wrap the resolved viewer in an error boundary so a render failure (e.g.
WebGL context loss or a model/texture load error) is contained to the
inspector pane instead of crashing the page. The fallback shows the
error message and stack trace.
This commit is contained in:
2026-08-08 12:58:28 +08:00
parent 001d5eeb54
commit 7d1f05902d
2 changed files with 66 additions and 9 deletions
+54
View File
@@ -0,0 +1,54 @@
import { Component, type ErrorInfo, type ReactNode } from 'react';
interface Props {
children: ReactNode;
/** Optional fallback rendered in place of `children` when an error is caught. */
fallback?: (error: Error) => ReactNode;
}
interface State {
error: Error | null;
}
/**
* Catches render errors in its subtree and renders a fallback instead of
* letting them crash the whole page. Used to isolate failures in object
* viewers (e.g. WebGL context loss, model/texture load errors).
*/
export default class ErrorBoundary extends Component<Props, State> {
override state: State = { error: null };
static getDerivedStateFromError(error: Error): State {
return { error };
}
override componentDidCatch(error: Error, info: ErrorInfo) {
// Surface the error for debugging without breaking the UI.
console.error('Viewer error:', error, info.componentStack);
}
override render() {
if (this.state.error) {
return this.props.fallback
? this.props.fallback(this.state.error)
: <DefaultFallback error={this.state.error} />;
}
return this.props.children;
}
}
function DefaultFallback({ error }: { error: Error }) {
return (
<div className="flex h-80 flex-col items-center justify-center gap-2 overflow-auto rounded-lg border border-zinc-800 bg-zinc-900 p-4 text-center">
<p className="text-sm font-medium text-zinc-200">Couldn't render this object</p>
<p className="max-w-sm wrap-break-word font-mono text-xs text-zinc-400">
{error.message}
</p>
{error.stack && (
<pre className="mt-2 max-h-40 w-full overflow-auto whitespace-pre-wrap text-left font-mono text-[10px] leading-relaxed text-zinc-600">
{error.stack}
</pre>
)}
</div>
);
}