From 2d8ade629b34f80b350ef3d4de5bcefb26fc6a3e Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 24 Jun 2026 05:55:49 -0400 Subject: [PATCH] fix(ui): log global errors silently instead of popping a toast + overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global error handler (Vue errorHandler + window error + unhandledrejection) fired a red 'Something went wrong: ' toast AND an auto on-device overlay on every caught error — deliberately loud for bug-bash, but it surfaces benign, non-actionable noise (e.g. a transient RPC rejection during a ws reconnect, or the service worker failing to register over a self-signed cert) right in the user's face. Demote the catch-all to SILENT capture: keep console.error + the window.__archyErrors ring buffer, and expose the screenshot-able overlay on-demand via window.__archyShowErrors() — but never auto-pop. Components that need to report a specific, actionable failure still call toast.error() directly. Also filter known-benign environmental noise (PWA service-worker registration failing over a self-signed cert — needs a trusted cert, #56) so it doesn't even occupy a ring-buffer slot and push out real errors. Co-Authored-By: Claude Opus 4.8 (1M context) --- neode-ui/src/main.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/neode-ui/src/main.ts b/neode-ui/src/main.ts index b34b39eb..9e984a56 100644 --- a/neode-ui/src/main.ts +++ b/neode-ui/src/main.ts @@ -23,8 +23,6 @@ if (!navigator.clipboard) { }, }) } -import { useToast } from '@/composables/useToast' - const app = createApp(App) const pinia = createPinia() @@ -97,14 +95,20 @@ function recordError(source: string, err: unknown, info?: string) { const entry: ArchyErrorEntry = { when: new Date().toISOString(), source, message, info, stack: e?.stack } errorLog.push(entry) if (errorLog.length > 25) errorLog.shift() + // Log SILENTLY: a global handler error is almost always something we should + // fix at the source, not interrupt the user for. Keep the full record on the + // console + the window.__archyErrors ring buffer, and make the screenshot-able + // overlay available ON DEMAND (window.__archyShowErrors(), or the debug view) + // — but do NOT auto-pop a red toast / overlay over the UI. Components that + // need to tell the user about a *specific, actionable* failure still call + // toast.error() directly; this catch-all stays out of the way. console.error(`[${source}]`, err, info ?? '') - // Surface the real message (truncated) instead of a generic toast — this is a - // test/bug-bash build, and "Something went wrong" hides exactly what we need. - const short = message.length > 140 ? `${message.slice(0, 140)}…` : message - try { - useToast().error(`Something went wrong: ${short}`) - } catch { /* toast itself failed — the console + ring buffer still have it */ } - // Always show the on-device overlay so the error is visible without a console. +} + +// Expose the on-demand error overlay + ring buffer so a crash that only repros +// in a runtime without a console (Android companion WebView) is still +// retrievable: call `window.__archyShowErrors()` to screenshot/Copy them. +;(window as unknown as { __archyShowErrors?: () => void }).__archyShowErrors = () => { try { showErrorOverlay() } catch { /* overlay is best-effort */ } } @@ -133,15 +137,28 @@ function reloadOnceForStaleChunk(err: unknown): boolean { return true } +// Known-benign environmental noise — expected on some deployments and not +// actionable by the user or us, so it shouldn't even occupy a ring-buffer slot +// (which would push out real errors). The PWA service worker can't register +// over a self-signed cert (it needs a trusted cert or localhost); on those +// nodes the SW/offline cache simply doesn't run, which is fine. Logged at debug +// only. (A trusted cert is the real fix — tracked separately, #56.) +function isBenignEnvironmentError(err: unknown): boolean { + const msg = (err as { message?: string })?.message ?? String(err ?? '') + return /Failed to register a ServiceWorker|ServiceWorker.*(SSL|certificate|SecurityError)|An SSL certificate error occurred when fetching the script/i.test(msg) +} + // Vue's errorHandler only catches errors raised synchronously inside Vue's // lifecycle/reactivity. Async rejections and plain runtime errors (e.g. a JS // API missing in an older WebView) slip past it, so catch those too. window.addEventListener('error', (ev) => { if (reloadOnceForStaleChunk(ev.error ?? ev.message)) return + if (isBenignEnvironmentError(ev.error ?? ev.message)) { console.debug('[benign]', ev.message); return } recordError('window.error', ev.error ?? ev.message) }) window.addEventListener('unhandledrejection', (ev) => { if (reloadOnceForStaleChunk(ev.reason)) return + if (isBenignEnvironmentError(ev.reason)) { console.debug('[benign]', ev.reason); return } recordError('unhandledrejection', ev.reason) })