diff --git a/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx
index 412876367..0e1310a47 100644
--- a/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx
+++ b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx
@@ -19,10 +19,10 @@
// SOFTWARE.
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
-import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
import { useTranslation } from "react-i18next";
import { graphql, useFragment } from "react-relay";
+import { InlineErrorCard } from "#/components/errors/InlineErrorCard";
import { HomeSection } from "#/components/HomeSection/HomeSection";
import type { ComplianceFrameworksSection_trustCenter$key } from "./__generated__/ComplianceFrameworksSection_trustCenter.graphql";
@@ -60,11 +60,7 @@ export function ComplianceFrameworksSection({ trustCenterKey }: ComplianceFramew
// The data comes from the preloaded HomePageQuery, so there is no local
// refetch to clear a field error — reload the page to recover.
- window.location.reload()}
- />
+ window.location.reload()} />
)}
>
diff --git a/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx b/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx
index 77bfa3807..8b5b6b701 100644
--- a/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx
+++ b/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx
@@ -20,10 +20,10 @@
import { Link } from "@probo/ui/src/v2/Button/Link";
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
-import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
import { useTranslation } from "react-i18next";
import { graphql, useFragment } from "react-relay";
+import { InlineErrorCard } from "#/components/errors/InlineErrorCard";
import { HomeSection } from "#/components/HomeSection/HomeSection";
import { MailingListUpdateListItem } from "#/components/MailingListUpdateListItem/MailingListUpdateListItem";
import { dotPatternStyle } from "#/components/MediaTile/variants";
@@ -60,11 +60,7 @@ export function RecentUpdatesSection({ trustCenterKey }: RecentUpdatesSectionPro
// The data comes from the preloaded HomePageQuery, so there is no local
// refetch to clear a field error — reload the page to recover.
- window.location.reload()}
- />
+ window.location.reload()} />
)}
>
diff --git a/apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx b/apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx
index 323520dd5..6d718d4c4 100644
--- a/apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx
+++ b/apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx
@@ -19,10 +19,10 @@
// SOFTWARE.
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
-import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
import { useTranslation } from "react-i18next";
import { graphql, useFragment } from "react-relay";
+import { InlineErrorCard } from "#/components/errors/InlineErrorCard";
import { HomeSection } from "#/components/HomeSection/HomeSection";
import type { TrustedBySection_trustCenter$key } from "./__generated__/TrustedBySection_trustCenter.graphql";
@@ -58,11 +58,7 @@ export function TrustedBySection({ trustCenterKey }: TrustedBySectionProps) {
// The data comes from the preloaded HomePageQuery, so there is no local
// refetch to clear a field error — reload the page to recover.
- window.location.reload()}
- />
+ window.location.reload()} />
)}
>
diff --git a/apps/compliance-portal/src/components/errors/InlineErrorCard.tsx b/apps/compliance-portal/src/components/errors/InlineErrorCard.tsx
new file mode 100644
index 000000000..0f3cfe791
--- /dev/null
+++ b/apps/compliance-portal/src/components/errors/InlineErrorCard.tsx
@@ -0,0 +1,39 @@
+// Copyright (c) 2026 Probo Inc .
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+import { Card } from "@probo/ui/src/v2/Card/Card";
+import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
+import { useTranslation } from "react-i18next";
+
+interface InlineErrorCardProps {
+ // Retry handler. When omitted, the retry action is hidden.
+ onRetry?: () => void;
+}
+
+// The standard section/list error fallback: the vertical InlineError on a soft
+// card surface, so a failed region reads as intentional content rather than
+// floating text. Row-level failures use the bare horizontal InlineError.
+export function InlineErrorCard({ onRetry }: InlineErrorCardProps) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ );
+}
diff --git a/apps/compliance-portal/src/components/errors/ListErrorBoundary.tsx b/apps/compliance-portal/src/components/errors/ListErrorBoundary.tsx
index 60cbde989..50096cbfc 100644
--- a/apps/compliance-portal/src/components/errors/ListErrorBoundary.tsx
+++ b/apps/compliance-portal/src/components/errors/ListErrorBoundary.tsx
@@ -25,11 +25,12 @@ interface ListErrorBoundaryProps {
children: ReactNode;
}
-// Contains a list/section field error to an inline fallback with a working
-// retry. The boundary only resets *after* the caller's refetch settles (via the
-// `done` callback bumping its key), so remounting reads the refreshed store
-// instead of racing the in-flight request back into the same error. See
-// contrib/claude/error-handling.md.
+// Contains a list field error to an inline fallback with a working retry. A
+// standalone list page has no section framing, so the fallback is a bare
+// InlineError (no card). The boundary only resets *after* the caller's refetch
+// settles (via the `done` callback bumping its key), so remounting reads the
+// refreshed store instead of racing the in-flight request back into the same
+// error. See contrib/claude/error-handling.md.
export function ListErrorBoundary({ onRetry, children }: ListErrorBoundaryProps) {
const { t } = useTranslation();
const [resetToken, setResetToken] = useState(0);
diff --git a/apps/compliance-portal/src/lib/relay/fetch.ts b/apps/compliance-portal/src/lib/relay/fetch.ts
index 302c45ef2..35010fef4 100644
--- a/apps/compliance-portal/src/lib/relay/fetch.ts
+++ b/apps/compliance-portal/src/lib/relay/fetch.ts
@@ -26,6 +26,8 @@ import { type FetchFunction, type GraphQLResponse } from "relay-runtime";
const isRequestLevel = (error: GraphQLError) =>
error.path === undefined || error.path === null || error.path.length === 0;
+type ErrorResponse = GraphQLResponse & { errors?: GraphQLError[] };
+
// The portal fetch only throws for request-level failures. Everything else
// (field-level errors) flows through to Relay untouched.
export const makeFetchQuery = (endpoint: string): FetchFunction => {
@@ -82,9 +84,7 @@ export const makeFetchQuery = (endpoint: string): FetchFunction => {
throw new InternalServerError();
}
- const json = (await response.json()) as GraphQLResponse & {
- errors?: GraphQLError[];
- };
+ const json = (await response.json()) as ErrorResponse;
if (json.errors) {
// An unauthenticated session is always a global concern: the backend