From d3b729fda1f57f2155be64a204d052ffe43df30b Mon Sep 17 00:00:00 2001 From: rsnetworkinginc Date: Sun, 26 Jul 2026 04:06:13 +0300 Subject: [PATCH] fix(ui): degrade an unrecognized skip reason to the neutral info tone instead of ready skipReasonTone fell back to the green ready tone for any reason string outside the four enumerated skip reasons, so an unrecognized/legacy value from the API rendered as a healthy-looking pill in the audit feed. Mirror the established convention from contributor-quality-table-model (band: string degrades to a neutral pill): the fallback now returns info. Adds direct fallback-branch coverage and a render test asserting an unmapped reason never gets the ready tone; the four enumerated reasons keep their existing tones. --- .../src/components/site/audit-feed-model.ts | 5 ++- .../src/components/site/audit-feed.test.tsx | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) 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");