diff --git a/neode-ui/src/main.ts b/neode-ui/src/main.ts index 1b3af2f8..0487a84f 100644 --- a/neode-ui/src/main.ts +++ b/neode-ui/src/main.ts @@ -37,10 +37,35 @@ app.use(i18n) // templates instead of hard-coding a `v` prefix. app.config.globalProperties.$ver = displayVersion -app.config.errorHandler = (err, _instance, info) => { - console.error('[Vue Error]', err, info) - const { error } = useToast() - error('Something went wrong. Please refresh the page.') +// Keep a small ring buffer of the most recent errors on `window.__archyErrors` +// so a crash that only reproduces inside a specific runtime (e.g. the Android +// companion WebView, where there's no easy console) can be retrieved after the +// fact — read it from chrome://inspect, or we can surface it in a debug view. +interface ArchyErrorEntry { when: string; source: string; message: string; info?: string; stack?: string } +const errorLog: ArchyErrorEntry[] = [] +;(window as unknown as { __archyErrors?: ArchyErrorEntry[] }).__archyErrors = errorLog + +function recordError(source: string, err: unknown, info?: string) { + const e = err as { message?: string; stack?: string } | undefined + const message = (e?.message ?? String(err)) || 'unknown error' + const entry: ArchyErrorEntry = { when: new Date().toISOString(), source, message, info, stack: e?.stack } + errorLog.push(entry) + if (errorLog.length > 25) errorLog.shift() + 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 */ } } +app.config.errorHandler = (err, _instance, info) => recordError('Vue Error', err, info) + +// 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) => recordError('window.error', ev.error ?? ev.message)) +window.addEventListener('unhandledrejection', (ev) => recordError('unhandledrejection', ev.reason)) + app.mount('#app')