fix(GRO-2094): instrument bootstrap with global error + ErrorBoundary
The bundle at /login was executing but the React tree never painted —
no console errors, no fallback UI, just an empty <div id='root'>.
Add three layers of defense so a future failure of this shape is
captured instead of being silently swallowed:
1. window 'error' and 'unhandledrejection' listeners in main.tsx,
printing structured context to console.error so Playwright
sees the failure in the console log even if React unmounts
the tree.
2. A top-level <ErrorBoundary> in main.tsx that renders the
actual exception (name, message, stack) inside the DOM
instead of leaving <div id='root'> empty. The boundary also
logs to console.error via componentDidCatch.
3. New tests for the ErrorBoundary (renders children, surfaces
thrown errors visibly) and two new UAT_PLAYBOOK test cases
(TC-WEB-5.1.6 / 5.1.7) that explicitly assert the
'never-blank-root' invariant on UAT.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
+37
-3
@@ -2,9 +2,41 @@ import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { App } from "./App.js";
|
||||
import { ErrorBoundary } from "./ErrorBoundary.js";
|
||||
import { installDevFetchInterceptor } from "./lib/devFetch.js";
|
||||
import "./index.css";
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Global error capture (GRO-2094).
|
||||
//
|
||||
// Symptom: React root stays empty at /login — bundle parses, no console
|
||||
// errors, no error boundary fallback. Some failure is being swallowed
|
||||
// before it reaches React's commit phase. These listeners make sure any
|
||||
// thrown error or unhandled promise rejection is at least visible in
|
||||
// the console (and in the Playwright network/console log) instead of
|
||||
// vanishing into the void.
|
||||
// --------------------------------------------------------------------
|
||||
function reportGlobalError(kind: string, payload: unknown): void {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[${kind}]`, payload);
|
||||
}
|
||||
|
||||
window.addEventListener("error", (event) => {
|
||||
reportGlobalError("window.error", {
|
||||
message: event.message,
|
||||
filename: event.filename,
|
||||
lineno: event.lineno,
|
||||
colno: event.colno,
|
||||
error: event.error,
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
reportGlobalError("unhandledrejection", {
|
||||
reason: event.reason,
|
||||
});
|
||||
});
|
||||
|
||||
installDevFetchInterceptor();
|
||||
|
||||
const root = document.getElementById("root");
|
||||
@@ -12,8 +44,10 @@ if (!root) throw new Error("Root element not found");
|
||||
|
||||
createRoot(root).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<ErrorBoundary>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ErrorBoundary>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user