import { Component, type ErrorInfo, type ReactNode } from "react"; import { onChunkLoadReloadAbandoned, reloadForChunkLoadError } from "@/lib/chunk-load-recovery"; import { captureBrowserException } from "@/lib/sentry"; type AppErrorBoundaryState = { error: Error | null; reloading: boolean; }; /** * Last-resort boundary above the router and every provider that renders app * chrome. `RouteErrorBoundary` only guards the routed ``; a crash in * the shell around it (sidebar, providers, layout hooks) has no boundary, so * React unmounts the entire root and the user is left staring at a blank * page with no way forward but knowing to hard-refresh. This boundary trades * that blank page for a reload prompt. * * Deliberately dependency-free: no router, no toast, no query client — the * crash being handled may have originated inside any of those providers. */ export class AppErrorBoundary extends Component<{ children: ReactNode }, AppErrorBoundaryState> { override state: AppErrorBoundaryState = { error: null, reloading: false }; private stopWaitingForReload: (() => void) | null = null; static getDerivedStateFromError(error: unknown): Partial { return { error: error instanceof Error ? error : new Error(String(error)) }; } override componentDidCatch(error: unknown, info: ErrorInfo): void { this.stopWaitingForReload?.(); this.stopWaitingForReload = null; if (reloadForChunkLoadError(error)) { this.setState({ reloading: true }); this.stopWaitingForReload = onChunkLoadReloadAbandoned(() => { this.stopWaitingForReload = null; this.setState({ reloading: false }); this.report(error, info); }); return; } this.report(error, info); } override componentWillUnmount(): void { this.stopWaitingForReload?.(); this.stopWaitingForReload = null; } private report(error: unknown, info: ErrorInfo): void { console.error("App shell crashed", { error, componentStack: info.componentStack }); captureBrowserException(error, { boundary: "app", componentStack: info.componentStack }); } override render() { const { error, reloading } = this.state; if (!error) return this.props.children; if (reloading) { return (
Reloading Paperclip…
); } return (

Paperclip hit an error

Something went wrong while running the app. Reloading usually fixes this.

          {error.message}
        
); } }