fix(ui): catch render throws with an error boundary around routed content
A purchase in a currency with no configured exchange rate makes src/domain/fx.ts throw MissingRateError (correct behavior — never silently convert at a wrong rate). Nothing caught it: 5 of 6 routes call monthView/yearView/summaryView and threw during render, and with no error boundary anywhere in src/, the throw unmounted the entire React tree including the nav bar. Since the catch-all route redirects to the current month on first load, one un-rated foreign-currency purchase white-screened the app immediately on landing, with the only way out (Settings, where the rate is added) unreachable because the nav was gone too. Adds RouteErrorBoundary (a class component, as required for getDerivedStateFromError/componentDidCatch) that renders the thrown message plus a link to Settings and a "Try again" reset, and RouteErrorBoundaryReset, a wrapper that keys the boundary on the current route so it clears on navigation. Wraps only <Routes> in App.tsx, not <nav>, so navigation stays usable when a route throws. Also guards the happy-dom per-file GlobalRegistrator.register() call in registerSw.test.ts and the new ErrorBoundary.test.tsx with isRegistered, since Bun can run both files in one process. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6eb77c8733
commit
da0d1ac35c
4 files changed
+211
-10
No files matched your search
+12
-9
@@ -8,6 +8,7 @@ import { PostYearRoute } from "./routes/PostYearRoute.tsx";
|
||||
import { SummaryRoute } from "./routes/SummaryRoute.tsx";
|
||||
import { SettingsRoute } from "./routes/SettingsRoute.tsx";
|
||||
import { UpdatePrompt } from "./components/UpdatePrompt.tsx";
|
||||
import { RouteErrorBoundaryReset } from "./components/ErrorBoundary.tsx";
|
||||
|
||||
export function AppRoutes() {
|
||||
const year = currentMonth.slice(0, 4);
|
||||
@@ -28,15 +29,17 @@ export function AppRoutes() {
|
||||
</NavLink>
|
||||
</nav>
|
||||
<main className="p-6">
|
||||
<Routes>
|
||||
<Route path="/month/:monthId" element={<MonthRoute />} />
|
||||
<Route path="/year/:year" element={<YearRoute />} />
|
||||
<Route path="/post/:postId/month/:monthId" element={<PostMonthRoute />} />
|
||||
<Route path="/post/:postId/year/:year" element={<PostYearRoute />} />
|
||||
<Route path="/summary" element={<SummaryRoute />} />
|
||||
<Route path="/settings" element={<SettingsRoute />} />
|
||||
<Route path="*" element={<Navigate to={`/month/${currentMonth}`} replace />} />
|
||||
</Routes>
|
||||
<RouteErrorBoundaryReset>
|
||||
<Routes>
|
||||
<Route path="/month/:monthId" element={<MonthRoute />} />
|
||||
<Route path="/year/:year" element={<YearRoute />} />
|
||||
<Route path="/post/:postId/month/:monthId" element={<PostMonthRoute />} />
|
||||
<Route path="/post/:postId/year/:year" element={<PostYearRoute />} />
|
||||
<Route path="/summary" element={<SummaryRoute />} />
|
||||
<Route path="/settings" element={<SettingsRoute />} />
|
||||
<Route path="*" element={<Navigate to={`/month/${currentMonth}`} replace />} />
|
||||
</Routes>
|
||||
</RouteErrorBoundaryReset>
|
||||
</main>
|
||||
<UpdatePrompt />
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user