import { Component, type ErrorInfo, type ReactNode } from 'react'; import { isStaleChunkError } from '@/lib/staleChunk'; /** * The last line before a white page. * * React unmounts the entire tree when a render throws and nothing catches it. * With no boundary anywhere in the app, one bad field shape on one card takes * the whole console down to an empty document — no message, no route, nothing * to act on but the browser console. That is exactly how a POS-health response * typed as an array but delivered as an object presented itself: a blank screen * on sign-in, with the cause four layers down. * * So this is not decoration. It converts "the app is broken" into "this screen * is broken, and here is what it said", which is the difference between a bug * report and a guess. * * Deliberately a class: `getDerivedStateFromError` has no hook equivalent. */ interface Props { children: ReactNode; /** Names the area in the message — "the Console page", "the assistant". */ area?: string; } interface State { error: Error | null; } export class ErrorBoundary extends Component { override state: State = { error: null }; static getDerivedStateFromError(error: Error): State { return { error }; } override componentDidCatch(error: Error, info: ErrorInfo) { // Kept as console output rather than sent anywhere: there is no error // reporting endpoint, and inventing one would be a network call nobody // asked for. The component stack is the part that is not in the throw. console.error('[nearle] render failed', error, info.componentStack); } private reset = () => { this.setState({ error: null }); }; private reload = () => { window.location.reload(); }; override render() { const { error } = this.state; if (!error) return this.props.children; const area = this.props.area ?? 'this screen'; /** * A page whose code never arrived cannot be re-rendered into existence. * * "Try again" clears the error and renders the same `lazy()` component, * which requests the same missing file and throws the same error — the * button looked like a recovery and was a loop. `lib/staleChunk.ts` already * reloads once on its own; landing here means that reload has happened and * not helped, or was suppressed to avoid a boot loop, so the honest offer * is a reload the person chooses and a message that names the cause. */ const isStale = isStaleChunkError(error); return (

{isStale ? 'This page was updated while you were working' : `Something on ${area} failed to render`}

{isStale ? 'A new version of the console was deployed, so the file this tab was about to load no longer exists. Nothing is wrong with your data — reloading picks up the current version.' : 'The rest of the console is fine — this is one screen, not the whole app. The message below is what broke, and the full stack is in the browser console.'}

          {error.message || String(error)}
        
); } }