7daa9c480a
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>
54 lines
1.6 KiB
TypeScript
54 lines
1.6 KiB
TypeScript
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");
|
|
if (!root) throw new Error("Root element not found");
|
|
|
|
createRoot(root).render(
|
|
<StrictMode>
|
|
<ErrorBoundary>
|
|
<BrowserRouter>
|
|
<App />
|
|
</BrowserRouter>
|
|
</ErrorBoundary>
|
|
</StrictMode>
|
|
);
|