diff --git a/apps/loopover-ui/src/components/site/audit-feed-model.ts b/apps/loopover-ui/src/components/site/audit-feed-model.ts index 4efc4721c3..e934099bf0 100644 --- a/apps/loopover-ui/src/components/site/audit-feed-model.ts +++ b/apps/loopover-ui/src/components/site/audit-feed-model.ts @@ -125,5 +125,8 @@ export function skipReasonTone(reason: string): "ready" | "info" | "warn" | "deg if (reason === "bot_author" || reason === "not_official_gittensor_miner") return "info"; if (reason === "surface_off" || reason === "maintainer_author") return "warn"; if (reason === "miner_detection_unavailable" || reason === "missing_author") return "degraded"; - return "ready"; + // `reason` is a plain string at the API boundary (same widening as contributor-quality-table-model's + // `band: string`), so an unrecognized/legacy value can reach here. Degrade it to the neutral "info" + // pill — never "ready", which would render an unclassified skip as green/healthy. + return "info"; } diff --git a/apps/loopover-ui/src/components/site/audit-feed.test.tsx b/apps/loopover-ui/src/components/site/audit-feed.test.tsx index 563d20bb0c..202777866d 100644 --- a/apps/loopover-ui/src/components/site/audit-feed.test.tsx +++ b/apps/loopover-ui/src/components/site/audit-feed.test.tsx @@ -11,6 +11,7 @@ import { normalizeSinceInput, normalizeSkippedPrAuditExport, pullRequestHref, + skipReasonTone, } from "@/components/site/audit-feed-model"; import { AuditFeed } from "@/components/site/audit-feed"; @@ -70,6 +71,21 @@ describe("audit feed helpers", () => { ); }); + it("maps the enumerated skip reasons to their tones and degrades an unrecognized reason to the neutral info tone, never ready", () => { + // The four enumerated reasons keep their existing tones. + expect(skipReasonTone("bot_author")).toBe("info"); + expect(skipReasonTone("not_official_gittensor_miner")).toBe("info"); + expect(skipReasonTone("surface_off")).toBe("warn"); + expect(skipReasonTone("maintainer_author")).toBe("warn"); + expect(skipReasonTone("miner_detection_unavailable")).toBe("degraded"); + expect(skipReasonTone("missing_author")).toBe("degraded"); + // An unrecognized/legacy reason string must degrade to the neutral pill (the + // contributor-quality-table-model `band: string` convention), not the green "ready" tone. + expect(skipReasonTone("legacy_skip_reason")).toBe("info"); + expect(skipReasonTone("legacy_skip_reason")).not.toBe("ready"); + expect(skipReasonTone("")).toBe("info"); + }); + it("normalizes since input without throwing on invalid dates", () => { expect(normalizeSinceInput("")).toBe(""); expect(normalizeSinceInput(" ")).toBe(""); @@ -121,6 +137,29 @@ describe("AuditFeed", () => { ); }); + it("renders an unrecognized skip reason as a neutral info pill, not the green ready tone", async () => { + apiFetch.mockResolvedValue({ + ok: true, + data: { + ...SAMPLE, + items: [ + { + repoFullName: "repo-owner/owned-repo", + pullNumber: 6, + reason: "legacy_skip_reason", + timestamp: "2026-05-28T00:00:04.000Z", + remediation: "Contact support to reclassify this legacy skip.", + }, + ], + }, + }); + render(); + const pill = await screen.findByText("legacy skip reason"); + // "info" tone (text-mint), never the "ready" tone (text-success) that implies a healthy state. + expect(pill.className).toContain("text-mint"); + expect(pill.className).not.toContain("text-success"); + }); + it("wraps the audit table in a keyboard-focusable, labelled scroll region with a caption and column-scoped headers (#794 a11y pattern)", async () => { render(); await screen.findByText("repo-owner/owned-repo");