refactor(contracts): add error boundary with retry#697
Merged
mikewheeleer merged 1 commit intoJul 25, 2026
Merged
Conversation
Wraps the contracts section in a dedicated ContractsErrorBoundary so
that an unexpected render error shows an accessible fallback with a
Retry button instead of blanking the page.
- Add ContractsErrorBoundary class component (src/components/)
* getDerivedStateFromError captures the thrown error into state
* componentDidCatch forwards to reportError('ContractsErrorBoundary')
with severity 'error' and componentStack metadata — errors are never
swallowed silently
* reset() clears error state so Retry re-mounts the children
* Default fallback: role=alert, aria-live=assertive, descriptive
message, Retry button, Go Home link
* Accepts optional fallback prop for custom error UI
- Integrate boundary into src/app/contracts/page.tsx
* ContractsPage return wrapped in <ContractsErrorBoundary>
- Comprehensive tests (22 cases, 100 % stmt/fn/line coverage)
* Normal render — children pass-through, no fallback shown
* Fallback UI content, Retry button, Go Home link href='/'
* Accessible attributes: role=alert and aria-live=assertive
* Custom fallback prop replaces default UI
* Retry re-mounts children after reset; re-throws if still broken
* Error logging via console.error (default reporter)
* Pluggable reporter receives error, context label, severity, meta
* componentStack metadata forwarded to reporter
* Multiple independent boundaries do not interfere
Closes Talenttrust#615
|
@Ade-Pheebs Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Contributor
|
looks good @Ade-Pheebs — well scoped and easy to review. merging 👍 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #615
An unexpected render error in the contracts section previously blanked the entire page with no recovery path for the user. This PR wraps the contracts section in a dedicated
ContractsErrorBoundarythat catches render failures, surfaces an accessible fallback with a Retry button, and forwards every captured error through the sharedreportErrorseam so nothing is swallowed silently.Problem
When any component inside the contracts page tree threw during rendering, React unmounted the whole page and left users with a blank screen and no way to recover. There was no error boundary specific to the contracts section, and no accessible notification to inform assistive-technology users that something had gone wrong.
Solution
1. New component —
src/components/ContractsErrorBoundary.tsxA React class component implementing the error boundary contract:
getDerivedStateFromError(error){ hasError: true, error }state so the next render shows the fallback.componentDidCatch(error, info)reportError(error, 'ContractsErrorBoundary', 'error', { componentStack })— routes through the existing pluggableerrorReporter.tsseam (same pattern asSafeBoundary,error.tsx, andglobal-error.tsx). Errors are never swallowed silently.reset()hasErroranderrortofalse/nullso clicking Retry re-mounts the children.render()fallbackprop if provided; otherwise renders the default accessible fallback.Default fallback UI
role="alert"andaria-live="assertive"so screen readers announce the failure immediately.this.reset()— resets boundary state so the child tree re-mounts on the next render cycle.href="/") gives users a non-destructive escape route.Custom fallback prop
Callers can pass
fallback={<MyFallback />}to replace the default UI entirely (e.g. for page-level boundaries that want a full-screen error state).2. Integration —
src/app/contracts/page.tsxThe
ContractsPagereturn value is now wrapped in<ContractsErrorBoundary>:This guards the empty-state path, the contract-list path, and the
ContractCreationFormmodal path with a single boundary. Any render failure in any of those sub-trees will now be caught and presented gracefully.3. Error reporting integration
ContractsErrorBoundaryuses the exact samereportErrorseam documented indocs/error-reporting.mdand already used by:src/components/SafeBoundary.tsx→reportError(error, 'SafeBoundary')src/app/error.tsx→reportError(error, 'Error Boundary')src/app/global-error.tsx→reportError(error, 'Global Error Boundary')This boundary extends the pattern with severity and metadata:
The
componentStackfield lets any injected reporter (Sentry, Rollbar, etc.) pinpoint the exact component that threw without any additional instrumentation.Tests —
src/components/ContractsErrorBoundary.test.tsx22 test cases, 100% statement / 100% function / 100% line coverage on
ContractsErrorBoundary.tsx.Edge cases covered:
fallbackprop → default UI suppressed(error, context, level, meta)signatureFull test output
The 4 pre-existing failures are in
src/app/milestones/__tests__/page.test.tsx— confirmed present on the baseline branch (55ac248) before any changes from this PR were applied.Pre-flight checklist
npm run lint— clean, exit 0npm test— 1236 / 1240 pass; 4 failures are pre-existing and unrelated to this PRnpm run build— pre-existing environment failure (@tailwindcss/oxidenative binding absent in this codespace); confirmed identical error on the baseline before this PR; TypeScripttsc --noEmitis cleanupstream/main(b38990a)role="alert",aria-live="assertive", visible focus rings on all interactive elementsreportErrorwith context label and componentStack metadataFiles changed
src/components/ContractsErrorBoundary.tsxsrc/components/ContractsErrorBoundary.test.tsxsrc/app/contracts/page.tsxRelated
SafeBoundary(src/components/SafeBoundary.tsx) and the per-section boundaries already used on the contract detail page (src/app/contracts/[id]/page.tsx).docs/error-reporting.md.Closes #615