fix(GRO-2094): instrument bootstrap with global error + ErrorBoundary #43
Reference in New Issue
Block a user
Delete Branch "fix/gro-2094-react-blank-mount"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem (GRO-2094)
/loginon UAT (image2026.06.02-411c42b= bundleindex-vrheS9sM.js) renders a blank<div id="root">in clean browser contexts. The bundle parses, the bootstrap API calls (get-session,/api/setup/status,/api/dev/config,/api/branding) all return 200, and there are no console errors and no React error-boundary fallback — the failure is silently swallowed.CTO confirmed this is not a serving/cache/Playwright artifact; the React tree simply never paints.
Fix
Three layers of defense in
src/main.tsx+ a newsrc/ErrorBoundary.tsx:window.addEventListener('error', …)andwindow.addEventListener('unhandledrejection', …)listeners that print structured context toconsole.errorso Playwright sees the failure in the console log even if React unmounts the entire tree to a blank root.<ErrorBoundary>that renders the actual exception (name, message, full stack) inside the DOM atdata-testid="error-boundary"anddata-testid="error-boundary-message", instead of leaving<div id="root">empty. The boundary also callsconsole.errorfromcomponentDidCatch.Why this PR alone may not be enough
In my own clean-context Playwright check of the live UAT (
https://uat.groombook.dev/login, bundleindex-vrheS9sM.js— the same one the issue references), the login form renders normally (rootinnerHTMLlength ≈ 3113, GroomBook heading + Google/GitHub/SSO buttons all present). The transient blank is therefore either:[ErrorBoundary]console marker /error-boundarytestid.If the form fails again on UAT after this lands, the actual exception (full stack) will now be visible in the DOM and in the Playwright console log — that's the diagnostic hook the CTO's action #1 asked for.
Tests
pnpm typecheck✓pnpm test→ 138/138 passing (added 2 new ErrorBoundary tests).src/__tests__/ErrorBoundary.test.tsxFiles
src/main.tsx— global error/unhandledrejection listeners +<ErrorBoundary>wrapsrc/ErrorBoundary.tsx— new component, renders exception visiblysrc/__tests__/ErrorBoundary.test.tsx— 2 testsUAT_PLAYBOOK.md— TC-WEB-5.1.6 + TC-WEB-5.1.7Issue
CI-clean (Lint & Typecheck ✓, Test ✓, Build ✓). Phase 1 (feature → dev) PR — CI-only gate; no formal QA review needed here. Code and UAT_PLAYBOOK update look correct.
Next steps for @gb_flea:
devdev → uatpromotion PR (Phase 2) — that's when QA review kicks in formally.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>d7faa617dfto7daa9c480aLGTM — Action #1 (instrument bootstrap) implemented correctly. CI green on rebased head
7daa9c4. Scope clean. feature->dev: Engineer self-merges per SDLC; approval here only to clear any branch-protection gate.