From f3adb247673b066f8fc7ab77dc4cad4c5b59e357 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Wed, 19 Nov 2025 14:12:26 +0200 Subject: [PATCH] Enhance error handling in root.tsx ErrorBoundary with user-facing error mapping and deduplication of error descriptions --- apps/cyberstorm-remix/app/root.tsx | 73 +++++++++++++++++++++++------ apps/cyberstorm-remix/app/styles.ts | 4 ++ 2 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 apps/cyberstorm-remix/app/styles.ts diff --git a/apps/cyberstorm-remix/app/root.tsx b/apps/cyberstorm-remix/app/root.tsx index 89897a7f1..34af8c075 100644 --- a/apps/cyberstorm-remix/app/root.tsx +++ b/apps/cyberstorm-remix/app/root.tsx @@ -1,5 +1,7 @@ -// The styles need to be imported at the beginning, so that the layers are correctly set up -// eslint-disable-next-line prettier/prettier +// sort-imports-ignore +import "./styles"; +// NOTE: The sort-imports-ignore is needed here to prevent css layers from not being loaded in the correct order, feel free to remove the ignore momentarily to sort imports + // import { LinksFunction } from "@remix-run/react/dist/routeModules"; import { Provider as RadixTooltip } from "@radix-ui/react-tooltip"; import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix"; @@ -8,6 +10,11 @@ import { type publicEnvVariablesType, } from "cyberstorm/security/publicEnvVariables"; import { LinkLibrary } from "cyberstorm/utils/LinkLibrary"; +import { NimbusAwaitErrorElement } from "cyberstorm/utils/errors/NimbusErrorBoundary"; +import { + type UserFacingErrorPayload, + parseUserFacingErrorPayload, +} from "cyberstorm/utils/errors/userFacingErrorResponse"; import { type ReactNode, Suspense, memo, useEffect, useRef } from "react"; import { Await, @@ -35,8 +42,6 @@ import { isRecord, } from "@thunderstore/cyberstorm"; import { Toast } from "@thunderstore/cyberstorm"; -import "@thunderstore/cyberstorm-theme/css"; -import "@thunderstore/cyberstorm/css"; import { type CurrentUser } from "@thunderstore/dapper"; import { DapperTs } from "@thunderstore/dapper-ts"; import { type RequestConfig } from "@thunderstore/thunderstore-api"; @@ -50,10 +55,7 @@ import { import type { Route } from "./+types/root"; import { Footer } from "./commonComponents/Footer/Footer"; -// Annoying prettier issue, where it wants to insert styles import here -// eslint-disable-next-line prettier/prettier import { NavigationWrapper } from "./commonComponents/Navigation/NavigationWrapper"; -import "./styles/index.css"; // REMIX TODO: https://remix.run/docs/en/main/route/links // export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }]; @@ -621,18 +623,36 @@ export function ErrorBoundary() { console.log(error); } const isResponseError = isRouteErrorResponse(error); + let payload: UserFacingErrorPayload | null = null; + + if (isResponseError) { + payload = parseUserFacingErrorPayload(error.data); + } + + const statusCode = payload?.status ?? (isResponseError ? error.status : 500); + const headline = + payload?.headline ?? + (isResponseError + ? error.statusText || `Error ${error.status}` + : "Internal server error"); + + const fallbackDescription = + isResponseError && typeof error.data === "string" + ? dedupeDescription(headline, error.data) + : undefined; + + const description = payload?.description ?? fallbackDescription; + const showDefaultFlavor = !payload && !isResponseError; return (
-
- {isResponseError ? error.status : 500} +
+ {statusCode}
- {isResponseError ? error.data : "Internal server error"} + {headline} + {description ?
{description}
: null}
- {!isResponseError && ( + {showDefaultFlavor && (
Beep boop. Server something error happens.
@@ -641,6 +661,26 @@ export function ErrorBoundary() { ); } +function dedupeDescription( + headline: string, + description: string | undefined +): string | undefined { + if (!description) { + return undefined; + } + + const trimmedDescription = description.trim(); + if (!trimmedDescription) { + return undefined; + } + + if (trimmedDescription.toLowerCase() === headline.trim().toLowerCase()) { + return undefined; + } + + return trimmedDescription; +} + // Temporary solution for implementing ads // REMIX TODO: Move to dynamic html function AdsInit() { @@ -739,7 +779,10 @@ function getCommunityBreadcrumb( } > - + } + > {(resolvedValue) => { let label = undefined; let icon = undefined; diff --git a/apps/cyberstorm-remix/app/styles.ts b/apps/cyberstorm-remix/app/styles.ts new file mode 100644 index 000000000..6f537c02c --- /dev/null +++ b/apps/cyberstorm-remix/app/styles.ts @@ -0,0 +1,4 @@ +// sort-imports-ignore +import "./styles/index.css"; +import "@thunderstore/cyberstorm-theme/css"; +import "@thunderstore/cyberstorm/css";