Dev#25
Conversation
- Implemented a WebGLErrorBoundary to catch WebGL context creation failures. - Added a function to check WebGL availability and display a fallback UI if unavailable. - Updated MainCanvas to utilize the new error boundary and fallback mechanism for improved user experience.
There was a problem hiding this comment.
Code Review
This pull request introduces WebGL availability checks and a new WebGLErrorBoundary component to handle WebGL context creation failures gracefully in the 3D wall editor. Feedback focuses on optimizing performance and robustness: caching the WebGL check globally to prevent repeated context creation and ensure SSR safety, logging caught WebGL errors to PostHog for production monitoring, and optimizing the fallback UI rendering by memoizing or moving the JSX definition outside of the component body.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function isWebGLAvailable(): boolean { | ||
| try { | ||
| const canvas = document.createElement("canvas"); | ||
| const ctx = | ||
| canvas.getContext("webgl2", { failIfMajorPerformanceCaveat: false }) || | ||
| canvas.getContext("webgl", { failIfMajorPerformanceCaveat: false }) || | ||
| canvas.getContext("experimental-webgl", { failIfMajorPerformanceCaveat: false }); | ||
| return !!ctx; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
The isWebGLAvailable function is called on every mount of MainCanvas via the useState initializer. In single-page applications, repeated creation of WebGL contexts (even if garbage collected) can lead to WebGL context exhaustion or browser warnings. Additionally, if this component is ever rendered in a Server-Side Rendering (SSR) environment, accessing document directly will throw a ReferenceError.
Caching the WebGL support check globally and adding a safe check for document avoids these issues.
let cachedWebGLAvailable: boolean | null = null;
function isWebGLAvailable(): boolean {
if (typeof window === "undefined" || typeof document === "undefined") {
return false;
}
if (cachedWebGLAvailable !== null) {
return cachedWebGLAvailable;
}
try {
const canvas = document.createElement("canvas");
const ctx =
canvas.getContext("webgl2", { failIfMajorPerformanceCaveat: false }) ||
canvas.getContext("webgl", { failIfMajorPerformanceCaveat: false }) ||
canvas.getContext("experimental-webgl", { failIfMajorPerformanceCaveat: false });
cachedWebGLAvailable = !!ctx;
return cachedWebGLAvailable;
} catch {
cachedWebGLAvailable = false;
return false;
}
}
| class WebGLErrorBoundary extends Component< | ||
| { children: React.ReactNode; fallback: React.ReactNode }, | ||
| WebGLErrorBoundaryState | ||
| > { | ||
| constructor(props: { children: React.ReactNode; fallback: React.ReactNode }) { | ||
| super(props); | ||
| this.state = { hasError: false, errorMessage: "" }; | ||
| } | ||
|
|
||
| static getDerivedStateFromError(error: Error): WebGLErrorBoundaryState { | ||
| return { hasError: true, errorMessage: error.message }; | ||
| } | ||
|
|
||
| render() { | ||
| if (this.state.hasError) { | ||
| return this.props.fallback; | ||
| } | ||
| return this.props.children; | ||
| } | ||
| } |
There was a problem hiding this comment.
The WebGLErrorBoundary catches WebGL context creation and rendering errors, but it currently does not log or report them. Since WebGL failures are critical to the core functionality of a 3D editor, capturing these errors in your analytics/monitoring platform (like PostHog, which is already imported) is highly recommended for production monitoring.
class WebGLErrorBoundary extends Component<
{ children: React.ReactNode; fallback: React.ReactNode },
WebGLErrorBoundaryState
> {
constructor(props: { children: React.ReactNode; fallback: React.ReactNode }) {
super(props);
this.state = { hasError: false, errorMessage: "" };
}
static getDerivedStateFromError(error: Error): WebGLErrorBoundaryState {
return { hasError: true, errorMessage: error.message };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("WebGL Error caught by boundary:", error, errorInfo);
posthog.capture("webgl_error_boundary_triggered", {
message: error.message,
stack: error.stack,
});
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
| const webGLFallback = ( | ||
| <div className="relative w-full h-full flex items-center justify-center bg-surface"> | ||
| <div className="text-center max-w-md px-8 py-10 rounded-2xl bg-surface-low shadow-[0_8px_32px_0_rgba(0,0,0,0.4)]"> | ||
| <span className="material-symbols-outlined text-5xl text-on-surface-variant mb-4 block">broken_image</span> | ||
| <h2 className="text-lg font-semibold text-on-surface mb-2">3D viewer unavailable</h2> | ||
| <p className="text-sm text-on-surface-variant mb-4"> | ||
| Your browser could not create a WebGL context, which is required to display the 3D wall editor. | ||
| This is usually caused by disabled hardware acceleration or an incompatible graphics driver. | ||
| </p> | ||
| <p className="text-xs text-on-surface-variant opacity-60 mb-6"> | ||
| Try enabling hardware acceleration in your browser settings | ||
| (<strong>Settings → System → Use hardware acceleration when available</strong>) | ||
| and reload the page. | ||
| </p> | ||
| <button | ||
| onClick={() => window.location.reload()} | ||
| className="px-4 py-2 rounded-lg bg-surface-high text-on-surface text-sm hover:opacity-80 transition-opacity" | ||
| > | ||
| Reload page | ||
| </button> | ||
| </div> | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
The webGLFallback JSX element is defined directly inside the MainCanvas component body. Since MainCanvas re-renders frequently (e.g., during dragging or state updates), this large JSX tree is unnecessarily re-created on every render. Wrapping it in useMemo avoids this overhead.
const webGLFallback = useMemo(() => (
<div className="relative w-full h-full flex items-center justify-center bg-surface">
<div className="text-center max-w-md px-8 py-10 rounded-2xl bg-surface-low shadow-[0_8px_32px_0_rgba(0,0,0,0.4)]">
<span className="material-symbols-outlined text-5xl text-on-surface-variant mb-4 block">broken_image</span>
<h2 className="text-lg font-semibold text-on-surface mb-2">3D viewer unavailable</h2>
<p className="text-sm text-on-surface-variant mb-4">
Your browser could not create a WebGL context, which is required to display the 3D wall editor.
This is usually caused by disabled hardware acceleration or an incompatible graphics driver.
</p>
<p className="text-xs text-on-surface-variant opacity-60 mb-6">
Try enabling hardware acceleration in your browser settings
(<strong>Settings → System → Use hardware acceleration when available</strong>)
and reload the page.
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 rounded-lg bg-surface-high text-on-surface text-sm hover:opacity-80 transition-opacity"
>
Reload page
</button>
</div>
</div>
), []);
There was a problem hiding this comment.
Pull request overview
This PR improves resilience of the 3D editor canvas by adding proactive WebGL capability detection and a UI fallback, and updates the frontend dependency lockfile (transitive and minor/patch bumps within existing semver ranges).
Changes:
- Added a WebGL availability check plus a fallback UI when WebGL can’t be initialized.
- Wrapped the React Three Fiber
<Canvas>in an error boundary to prevent WebGL/Three initialization failures from breaking the rest of the editor UI. - Updated
frontend/package-lock.jsonto newer resolved versions of dependencies.
Reviewed changes
Copilot reviewed 1 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
frontend/src/features/editor/components/MainCanvas.tsx |
Adds WebGL detection + error boundary + fallback UI around the 3D canvas. |
frontend/package-lock.json |
Refreshes resolved dependency versions in the lockfile. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { Canvas, useThree } from "@react-three/fiber"; | ||
| import { OrbitControls } from "@react-three/drei"; | ||
| import { Suspense } from "react"; | ||
| import { Suspense, Component } from "react"; |
| interface WebGLErrorBoundaryState { | ||
| hasError: boolean; | ||
| errorMessage: string; | ||
| } | ||
|
|
||
| /** | ||
| * Error boundary that catches WebGL context creation failures thrown by | ||
| * Three.js / React Three Fiber so the rest of the UI stays functional. | ||
| */ | ||
| class WebGLErrorBoundary extends Component< | ||
| { children: React.ReactNode; fallback: React.ReactNode }, | ||
| WebGLErrorBoundaryState | ||
| > { | ||
| constructor(props: { children: React.ReactNode; fallback: React.ReactNode }) { | ||
| super(props); | ||
| this.state = { hasError: false, errorMessage: "" }; | ||
| } | ||
|
|
||
| static getDerivedStateFromError(error: Error): WebGLErrorBoundaryState { | ||
| return { hasError: true, errorMessage: error.message }; | ||
| } |
| <span className="material-symbols-outlined text-5xl text-on-surface-variant mb-4 block">broken_image</span> | ||
| <h2 className="text-lg font-semibold text-on-surface mb-2">3D viewer unavailable</h2> | ||
| <p className="text-sm text-on-surface-variant mb-4"> | ||
| Your browser could not create a WebGL context, which is required to display the 3D wall editor. | ||
| This is usually caused by disabled hardware acceleration or an incompatible graphics driver. | ||
| </p> |
| <p className="text-sm text-on-surface-variant mb-4"> | ||
| Your browser could not create a WebGL context, which is required to display the 3D wall editor. | ||
| This is usually caused by disabled hardware acceleration or an incompatible graphics driver. | ||
| </p> |
No description provided.