From 60c0ba4483820008cb1b2c39ef05c9ec709959e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 23:46:45 +0200 Subject: [PATCH] fix(insights): point derived score-band tokens at the AA-corrected semantic colours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The band primitives (score ring, coverage dots, contributor bars, provenance chips + standard link, sparkline tint, trend arrow/delta) painted straight from the raw `--dracula-*` variables, which carry no `:root.light` override. On the white card the Alucard light theme uses, the green/orange/yellow cleared roughly 1.1-1.5:1 — under AA — and the `bg-dracula-yellow` confidence dot read near-invisible. The file comment claimed they 'track automatically'; they did not. Point BAND_NUMBER/PROGRESS/BORDER/VAR, the coverage-meter dot tint, the provenance cyan chips, the sparkline stroke, and sentimentColorClass at the semantic `--success`/`--warning`/`--destructive`/`--info` tokens instead. Those alias the same Dracula tones in dark mode (pixel-identical there, the primary target) but carry the darker AA-safe values under `:root.light`. Clamp the score-ring provisional caption to one line so a longer localised string cannot wrap over the ring arc. --- .../insights/derived/band-tokens.ts | 44 ++++++++++++------- .../insights/derived/coverage-meter.tsx | 19 +++++--- .../insights/derived/provenance-explainer.tsx | 4 +- .../insights/derived/score-ring.tsx | 2 +- .../insights/derived/sparkline-delta-tile.tsx | 6 ++- src/lib/insights/trend-sentiment.ts | 8 +++- 6 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/components/insights/derived/band-tokens.ts b/src/components/insights/derived/band-tokens.ts index 0204d56f6..381ce9fa2 100644 --- a/src/components/insights/derived/band-tokens.ts +++ b/src/components/insights/derived/band-tokens.ts @@ -5,44 +5,54 @@ * exact same band colour grammar the shipped `health-score-card` already * uses, without each component redeclaring the map (concept cohesion). * - * No new tokens — these point at the existing `--dracula-*` variables in - * `globals.css`. A green/yellow/red band is the one semantic the whole - * v1.10 score language extends. + * These point at the *semantic* feedback tokens (`--success` / `--warning` + * / `--destructive`), NOT the raw `--dracula-*` primitives. That matters + * for contrast: `:root.light` (Alucard) overrides the semantic tokens to + * AA-safe darker tones (`--success: #14720a`, `--warning: #a34d14`, + * `--destructive: #cb3a2a`) precisely because straight Dracula + * green/orange on a white `--card` clears only ~1.1–1.5:1 — under AA. + * Painting the band off the semantic token means every score surface + * tracks that light-mode correction automatically, in both themes; the + * raw `--dracula-*` primitives do NOT carry a light override. A + * green/yellow/red band is the one semantic the whole v1.10 score + * language extends. */ export type ScoreBand = "green" | "yellow" | "red"; /** Tailwind text-colour class per band (the headline number colour). */ export const BAND_NUMBER_CLASS: Record = { - green: "text-dracula-green", - yellow: "text-dracula-orange", - red: "text-dracula-red", + green: "text-success", + yellow: "text-warning", + red: "text-destructive", }; /** Tailwind background class per band (progress/fill bars). */ export const BAND_PROGRESS_CLASS: Record = { - green: "bg-dracula-green", - yellow: "bg-dracula-orange", - red: "bg-dracula-red", + green: "bg-success", + yellow: "bg-warning", + red: "bg-destructive", }; /** Tailwind border-colour class per band (card / chip outlines). */ export const BAND_BORDER_CLASS: Record = { - green: "border-dracula-green/40", - yellow: "border-dracula-orange/40", - red: "border-dracula-red/40", + green: "border-success/40", + yellow: "border-warning/40", + red: "border-destructive/40", }; /** * The raw CSS variable per band — used where a Recharts `fill` needs an * actual colour string rather than a Tailwind class (the score ring's - * ``). Reads the same `--dracula-*` tokens so dark and - * Alucard light modes track automatically. + * ``). Reads the semantic tokens so dark and Alucard light + * modes track the AA-safe light overrides automatically (the + * `--dracula-*` primitives carry no light override and would fail AA on + * the white card). */ export const BAND_VAR: Record = { - green: "var(--dracula-green)", - yellow: "var(--dracula-orange)", - red: "var(--dracula-red)", + green: "var(--success)", + yellow: "var(--warning)", + red: "var(--destructive)", }; /** diff --git a/src/components/insights/derived/coverage-meter.tsx b/src/components/insights/derived/coverage-meter.tsx index 5cf194da4..500913833 100644 --- a/src/components/insights/derived/coverage-meter.tsx +++ b/src/components/insights/derived/coverage-meter.tsx @@ -48,12 +48,21 @@ export interface CoverageMeterProps { className?: string; } -/** Dot tint per confidence band — reuses the existing dracula vocabulary. */ +/** + * Dot tint per confidence band. Points at the *semantic* feedback tokens + * (`--success` / `--warning` / `--destructive`), not the raw `--dracula-*` + * primitives, so the dots track the AA-safe `:root.light` overrides on the + * white card — straight `bg-dracula-yellow` cleared barely 1.1:1 on white + * and read near-invisible. The four bands collapse onto the three semantic + * severities (medium + low both ride the caution `--warning`); the lit-dot + * count + the percent label carry the medium↔low distinction, never colour + * alone. + */ const BAND_DOT_CLASS: Record = { - high: "bg-dracula-green", - medium: "bg-dracula-yellow", - low: "bg-dracula-orange", - draft: "bg-dracula-red", + high: "bg-success", + medium: "bg-warning", + low: "bg-warning", + draft: "bg-destructive", }; function clampInt(value: number, min: number, max: number): number { diff --git a/src/components/insights/derived/provenance-explainer.tsx b/src/components/insights/derived/provenance-explainer.tsx index 64c217beb..6112c9211 100644 --- a/src/components/insights/derived/provenance-explainer.tsx +++ b/src/components/insights/derived/provenance-explainer.tsx @@ -100,7 +100,7 @@ export function ProvenanceExplainer({
  • {input}
  • @@ -141,7 +141,7 @@ export function ProvenanceExplainer({ href={standard.url} target="_blank" rel="noopener noreferrer" - className="text-dracula-cyan hover:text-dracula-cyan/80 focus-visible:ring-ring/50 inline-flex items-center gap-1 rounded text-[11px] font-medium focus-visible:ring-2 focus-visible:outline-none" + className="text-info hover:text-info/80 focus-visible:ring-ring/50 inline-flex items-center gap-1 rounded text-[11px] font-medium focus-visible:ring-2 focus-visible:outline-none" > {t("insights.derived.provenance.standardLink", { name: standard.name, diff --git a/src/components/insights/derived/score-ring.tsx b/src/components/insights/derived/score-ring.tsx index 06514dc1e..0f75c40e7 100644 --- a/src/components/insights/derived/score-ring.tsx +++ b/src/components/insights/derived/score-ring.tsx @@ -188,7 +188,7 @@ export function ScoreRing({ {!hasScore && ( {t("insights.derived.scoreRing.provisionalCaption")} diff --git a/src/components/insights/derived/sparkline-delta-tile.tsx b/src/components/insights/derived/sparkline-delta-tile.tsx index d95a2fef6..dda91b6ca 100644 --- a/src/components/insights/derived/sparkline-delta-tile.tsx +++ b/src/components/insights/derived/sparkline-delta-tile.tsx @@ -94,11 +94,13 @@ export function SparklineDeltaTile({ // The sparkline tints with the trend sentiment so it reads "same signal" // as the arrow; neutral metrics ride the muted-foreground line. + // Semantic tokens, not raw --dracula-*, so the sparkline tint tracks the + // AA-safe :root.light overrides on the white card (the band-token fix). const strokeVar = arrowSentiment === "positive" - ? "var(--dracula-green)" + ? "var(--success)" : arrowSentiment === "negative" - ? "var(--dracula-orange)" + ? "var(--warning)" : "var(--muted-foreground)"; const fillId = `spark-${label.replace(/[^a-zA-Z0-9]/g, "")}`; diff --git a/src/lib/insights/trend-sentiment.ts b/src/lib/insights/trend-sentiment.ts index 5933f0b14..8a2fa5863 100644 --- a/src/lib/insights/trend-sentiment.ts +++ b/src/lib/insights/trend-sentiment.ts @@ -44,8 +44,12 @@ export function getTrendSentiment( export function sentimentColorClass( direction: TrendSentimentDirection, ): string { - if (direction === "positive") return "text-dracula-green"; - if (direction === "negative") return "text-dracula-orange"; + // Semantic tokens, not raw --dracula-*: `--success` / `--warning` alias + // the same Dracula green/orange in dark mode (pixel-identical there) but + // carry the AA-safe `:root.light` overrides, so the trend arrow + delta + // clear AA on the white card instead of the ~1.2:1 the raw primitives hit. + if (direction === "positive") return "text-success"; + if (direction === "negative") return "text-warning"; return "text-muted-foreground"; }