diff --git a/apps/fabro-web/app/root.test.tsx b/apps/fabro-web/app/root.test.tsx
new file mode 100644
index 000000000..ab13a3457
--- /dev/null
+++ b/apps/fabro-web/app/root.test.tsx
@@ -0,0 +1,76 @@
+import { describe, expect, test } from "bun:test";
+import { renderToStaticMarkup } from "react-dom/server";
+import { createMemoryRouter, RouterProvider, type RouteObject } from "react-router";
+
+import { installRoutes } from "./install-router";
+import { routes } from "./router";
+
+async function renderPage(routeObjects: RouteObject[], path: string, navigate = false) {
+ const router = createMemoryRouter(routeObjects, {
+ initialEntries: [navigate ? "/" : path],
+ });
+ try {
+ if (navigate) await router.navigate(path);
+ return renderToStaticMarkup();
+ } finally {
+ router.dispose();
+ }
+}
+
+describe("root error boundary", () => {
+ for (const path of ["/some_random_route", "/runs/existing-run/billing"]) {
+ for (const navigate of [false, true]) {
+ test(`${path} displays 404 on ${navigate ? "navigation" : "initial load"}`, async () => {
+ const html = await renderPage(routes, path, navigate);
+
+ expect(html).toContain(">404
");
+ expect(html).toContain("Page not found");
+ expect(html).not.toContain("Something went wrong");
+ expect(html).toContain('href="/runs"');
+ expect(html).toContain("Go back");
+ });
+ }
+ }
+
+ for (const [name, root] of [["app", routes[0]], ["installer", installRoutes[0]]] as const) {
+ async function renderFailure(error: unknown) {
+ return renderPage([
+ {
+ ...root,
+ children: [{
+ path: "failure",
+ loader() { throw error; },
+ Component: () => null,
+ }],
+ },
+ ], "/failure", true);
+ }
+
+ test(`${name} displays a route's 404 response as page not found`, async () => {
+ const html = await renderFailure(new Response(null, { status: 404 }));
+
+ expect(html).toContain(">404");
+ expect(html).toContain("Page not found");
+ expect(html).not.toContain("Something went wrong");
+ });
+
+ test.each([403, 500])(`${name} preserves a route's %i error response`, async (status) => {
+ const statusText = status === 403 ? "Forbidden" : "Internal Server Error";
+ const html = await renderFailure(new Response(null, { status, statusText }));
+
+ expect(html).toContain(`>${status}`);
+ expect(html).toContain(`${status} Error`);
+ expect(html).toContain(statusText);
+ expect(html).not.toContain("Page not found");
+ });
+
+ test(`${name} keeps unexpected exceptions as 500 errors`, async () => {
+ const html = await renderFailure(new Error("Unexpected route failure"));
+
+ expect(html).toContain(">500");
+ expect(html).toContain("Something went wrong");
+ expect(html).toContain("Unexpected route failure");
+ expect(html).not.toContain("Page not found");
+ });
+ }
+});
diff --git a/apps/fabro-web/app/root.tsx b/apps/fabro-web/app/root.tsx
index 063d3579d..3ae5d252e 100644
--- a/apps/fabro-web/app/root.tsx
+++ b/apps/fabro-web/app/root.tsx
@@ -1,10 +1,11 @@
-import { isRouteErrorResponse, Outlet } from "react-router";
+import { isRouteErrorResponse, Outlet, useRouteError } from "react-router";
export default function Root() {
return ;
}
-export function ErrorBoundary({ error }: any) {
+export function ErrorBoundary() {
+ const error = useRouteError();
let status = 500;
let heading = "Something went wrong";
let message = "An unexpected error occurred. Please try again.";