diff --git a/webclient-vue/src/app/App.vue b/webclient-vue/src/app/App.vue index 18da5ed..798c710 100644 --- a/webclient-vue/src/app/App.vue +++ b/webclient-vue/src/app/App.vue @@ -1,10 +1,13 @@ diff --git a/webclient-vue/src/app/main.js b/webclient-vue/src/app/main.js index e9359f1..17f597f 100644 --- a/webclient-vue/src/app/main.js +++ b/webclient-vue/src/app/main.js @@ -2,11 +2,13 @@ import { createPinia } from "pinia"; import App from "./App.vue"; import { router } from "../router"; +import { useGlobalErrorHandler } from "../composables/useGlobalErrorHandler"; import "@phosphor-icons/web/regular"; import "@phosphor-icons/web/fill"; import "../styles/main.css"; -createApp(App) - .use(createPinia()) - .use(router) - .mount("#app"); +const app = createApp(App); + +useGlobalErrorHandler(app); + +app.use(createPinia()).use(router).mount("#app"); diff --git a/webclient-vue/src/components/feedback/AppErrorBoundary.vue b/webclient-vue/src/components/feedback/AppErrorBoundary.vue new file mode 100644 index 0000000..d2397f8 --- /dev/null +++ b/webclient-vue/src/components/feedback/AppErrorBoundary.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/webclient-vue/src/composables/useGlobalErrorHandler.js b/webclient-vue/src/composables/useGlobalErrorHandler.js new file mode 100644 index 0000000..94a2dd4 --- /dev/null +++ b/webclient-vue/src/composables/useGlobalErrorHandler.js @@ -0,0 +1,44 @@ +/** + * Composable that wires up global error-catching surfaces. + * Should be called once during app bootstrap (main.js). + */ +export function useGlobalErrorHandler(app) { + /** + * 1. Vue runtime errors — render, watcher, lifecycle hooks, event handlers. + * This does NOT catch errors inside async functions (those are promises). + */ + app.config.errorHandler = (err, instance, info) => { + const message = err?.message || String(err); + console.error(`[Vue errorHandler] ${info}`, message, err); + // In Phase 3 this is where we would push to a toast/notification service. + }; + + /** + * 2. Unhandled promise rejections (fetch without catch, async function + * that throws and is not awaited, etc.) + */ + window.addEventListener("unhandledrejection", (event) => { + const reason = event.reason; + const message = reason?.message || reason?.error?.message || String(reason); + console.error("[Unhandled rejection]", message, reason); + // Prevent the default browser console noise (redundant with our log). + event.preventDefault(); + }); + + /** + * 3. Global synchronous JS errors (script parse errors, runtime throws + * outside of Vue, etc.) + */ + window.addEventListener("error", (event) => { + const error = event.error; + const message = error?.message || event.message || "Unknown error"; + console.error( + `[Global error] ${message}`, + "at", + event.filename, + event.lineno, + event.colno, + error + ); + }); +}