+
+
+
+ Review-path tools outage
+
+ {outage.window_start && (
+
+ since {formatAbsoluteTimestamp(outage.window_start)}
+
+ )}
+
+ evidence and git reads may fail — reviews resume automatically on
+ recovery
+
+
+
+ );
+}
diff --git a/panel/src/hooks/use-review-path-outage.ts b/panel/src/hooks/use-review-path-outage.ts
new file mode 100644
index 000000000..9bfce4df3
--- /dev/null
+++ b/panel/src/hooks/use-review-path-outage.ts
@@ -0,0 +1,29 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import {
+ systemHealthApi,
+ type ReviewPathOutageStatus,
+} from "@/lib/api/system-health";
+
+export const systemHealthKeys = {
+ all: ["system-health"] as const,
+ reviewPathOutage: () => [...systemHealthKeys.all, "review-path-outage"] as const,
+};
+
+/**
+ * Polls GET /api/system-health/review-path every 30s — the same cadence the
+ * maintenance-pause and rate-limit banners already use. An active review-path
+ * outage is unmissable, so the banner rides a background poll rather than a
+ * bootstrap payload: it self-clears on recovery without a reload. Polling is
+ * also the delivery choice that survives the backend slice landing later —
+ * until then the query resolves to null (no surface yet) and nothing renders.
+ */
+export function useReviewPathOutage(): ReviewPathOutageStatus | null | undefined {
+ const query = useQuery({
+ queryKey: systemHealthKeys.reviewPathOutage(),
+ queryFn: () => systemHealthApi.getReviewPathOutage(),
+ refetchInterval: 30000,
+ });
+ return query.data;
+}
diff --git a/panel/src/lib/api/__tests__/system-health.test.ts b/panel/src/lib/api/__tests__/system-health.test.ts
new file mode 100644
index 000000000..d5b41bcac
--- /dev/null
+++ b/panel/src/lib/api/__tests__/system-health.test.ts
@@ -0,0 +1,50 @@
+import { describe, it, expect, vi, afterEach } from "vitest";
+import { systemHealthApi } from "../system-health";
+
+// The real api client would hit the network; the backend slice may not be
+// merged, so axios is mocked and the 404 → null degradation is pinned here.
+vi.mock("../client", () => ({
+ default: Object.assign(vi.fn(), {
+ isAxiosError: (e: unknown) =>
+ !!e && typeof e === "object" && "isAxiosError" in e,
+ get: vi.fn(),
+ }),
+}));
+
+import api from "../client";
+
+const mockedGet = api.get as unknown as ReturnType