fix(ui): surface real error instead of generic toast + catch async errors

The global Vue errorHandler swallowed every crash into "Something went wrong.
Please refresh the page." — which hides exactly what we need to diagnose the
companion-app (Android WebView) post-login crash. Now:
- the toast shows the real (truncated) error message;
- a 25-entry ring buffer is kept on window.__archyErrors for retrieval where
  there's no console (companion WebView via chrome://inspect, or a debug view);
- window 'error' and 'unhandledrejection' listeners catch async/non-Vue errors
  that Vue's errorHandler misses (e.g. a JS API absent in an older WebView).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-06-20 08:05:51 -04:00
parent f92e442bfc
commit b3633ec525

View File

@ -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')