import React from 'react'; import { LogUIError } from '../../wailsjs/go/main/App'; // A render error used to empty the window and leave nothing behind: no line in // opslog.log, no dialog, no way for the operator to say more than "white screen". // Two separate reports — one after logging a 10 GHz QSO, one on starting a DVK // auto-call — stayed undiagnosed for weeks for exactly that reason. // // So this does two things, in order of importance: // // 1. Writes the error and its stack to the app log through LogUIError, so the // next report arrives WITH the cause instead of a description of a blank // screen. // 2. Shows it, with the text selectable and a button to copy it, because the // person who can act on it is the one looking at the screen. // // It deliberately does NOT try to recover the tree. React cannot guarantee the // state is sane after a render throw, and a half-working logger is worse than // one that says plainly what happened and offers a reload. type Props = { children: React.ReactNode }; type State = { error: Error | null; info: string }; export class ErrorBoundary extends React.Component { state: State = { error: null, info: '' }; static getDerivedStateFromError(error: Error): Partial { return { error }; } componentDidCatch(error: Error, info: React.ErrorInfo) { const stack = (info?.componentStack || error.stack || '').trim(); this.setState({ info: stack }); // Never let the reporting path throw: it runs while the app is already broken. try { LogUIError('render crash', `${error.name}: ${error.message}`, stack); } catch { /* the backend may be gone too — the on-screen copy still works */ } } render() { const { error, info } = this.state; if (!error) return this.props.children; const report = `${error.name}: ${error.message}\n\n${info}`; return (

OpsLog hit an error and stopped drawing

Your log is safe — this is the window, not the database. The details below are already in the app log; send them with your report.

{report}
); } } // installGlobalErrorLogging catches what a boundary cannot: an error thrown from // a timer, an event handler or a rejected promise. Those do not unmount the tree, // so they leave no trace at all — and an auto-call loop lives entirely in // callbacks, which is precisely where a white screen was reported. export function installGlobalErrorLogging() { const report = (kind: string, message: string, stack?: string) => { try { LogUIError(kind, message, stack || ''); } catch { /* nothing more we can do from here */ } }; window.addEventListener('error', (e: ErrorEvent) => { const src = e.filename ? ` (${e.filename}:${e.lineno}:${e.colno})` : ''; report('uncaught error', (e.message || 'unknown error') + src, e.error?.stack); }); window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => { const r: any = e.reason; report('unhandled rejection', String(r?.message ?? r ?? 'unknown'), r?.stack); }); }