From a4b204ffe405f526d6b7bc59d9491a7392f8dd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 5 Jul 2026 21:28:09 +0200 Subject: [PATCH] refactor(ui): retire the raw dracula utilities for semantic tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate every remaining *-dracula-* utility site (127 lint warnings, 193 class occurrences) onto the semantic vocabulary. The swaps are pixel-identical in both themes — each target token aliases the same primitive the utility read, including the light-theme AA overrides: - green/orange/cyan/red → success/warning/info/destructive - purple → primary (same value in both themes) - pink → the new --brand-pink token (Coach gradient, Apple-Health badge, recording mic): dark #ff79c6 — 6.86:1 card / 5.97:1 bg; light #a3134d — 7.32:1 card / 6.83:1 bg (computed) - yellow sites resolve to their status meaning: warn-level log icon, update-available pill and offline-geo hint → warning; the admin helpful-rate ramp → success/warning/destructive; the confidence meter mirrors the coverage-meter precedent (medium + low share the caution tint, the lit count carries the distinction) - the confidence-badge low tier dropped dead *-dracula-comment/* classes (no generated utility) for border-border + bg-muted/50 Close two rule blind spots the sweep surfaced — directional borders (border-l-dracula-red) and the parenthesised CSS-var shorthand (bg-(--dracula-green)) — and flip healthlog/no-dracula-utility from warn to error so the class cannot regrow. var(--dracula-*) references in chart code stay: they are token references the light theme overrides, and the chart palette is pinned visually. --- .../__tests__/no-raw-palette-color.test.mjs | 33 +++++++++++++++++ eslint-plugins/healthlog/index.js | 6 ++-- .../healthlog/no-raw-palette-color.js | 22 ++++++------ eslint.config.mjs | 12 ++++--- src/app/coach/conversations/page.tsx | 2 +- src/app/globals.css | 35 +++++++++++++++---- .../__tests__/coach-feedback-section.test.tsx | 6 ++-- src/components/admin/_shared.tsx | 13 ++++--- src/components/admin/ai-quality-section.tsx | 8 ++--- .../admin/api-token-overview-section.tsx | 4 +-- .../admin/app-log-preview-section.tsx | 4 +-- src/components/admin/danger-zone-section.tsx | 2 +- .../admin/invite-tokens-section.tsx | 22 ++++++------ .../admin/login-overview-section.tsx | 4 +-- src/components/admin/recent-audit-preview.tsx | 2 +- .../admin/system-status-section.tsx | 16 ++++----- .../admin/system-status-summary.tsx | 8 ++--- .../admin/user-management-section.tsx | 4 +-- src/components/admin/version-tile-section.tsx | 4 +-- .../charts/medication-compliance-chart.tsx | 4 +-- .../__tests__/confidence-meter.test.tsx | 14 ++++---- .../__tests__/recommendations-grid.test.tsx | 10 +++--- .../__tests__/suggested-prompts.test.tsx | 4 +-- .../coach-panel/coach-conversation.tsx | 2 +- .../insights/coach-panel/coach-hero.tsx | 4 +-- .../insights/coach-panel/coach-input.tsx | 6 ++-- .../coach-panel/guided-dialog-bubbles.tsx | 19 +++++----- .../coach-panel/guided-questions-card.tsx | 12 +++---- .../insights/coach-panel/history-rail.tsx | 4 +-- .../insights/coach-panel/message-thread.tsx | 16 ++++----- .../coach-panel/reminder-suggestion-card.tsx | 6 ++-- .../insights/coach-panel/scope-hint-badge.tsx | 5 +-- .../coach-panel/self-context-adopt-offer.tsx | 12 +++---- .../insights/coach-panel/source-chips.tsx | 2 +- .../insights/coach-panel/sources-rail.tsx | 4 +-- .../coach-panel/suggested-action-card.tsx | 6 ++-- src/components/insights/confidence-badge.ts | 6 +++- src/components/insights/confidence-meter.tsx | 18 ++++++---- src/components/insights/correlation-card.tsx | 13 +++---- src/components/insights/hero-strip.tsx | 2 +- src/components/insights/layout-coach-fab.tsx | 6 ++-- .../insights/measurement-diversity-nudge.tsx | 2 +- .../insights/personal-record-badge.tsx | 4 +-- .../insights/recommendations-grid.tsx | 8 ++--- src/components/insights/suggested-prompts.tsx | 4 +-- src/components/insights/trend-annotation.tsx | 2 +- ...asurement-list-apple-health-badge.test.tsx | 12 +++---- .../measurements/measurement-list.tsx | 4 +-- .../card-parts/medication-compliance-bars.tsx | 2 +- .../research-mode-acknowledgment-dialog.tsx | 2 +- .../scheduling/schedule-history-timeline.tsx | 8 ++--- src/components/mood/mood-list.tsx | 4 +-- src/components/onboarding/done-screen.tsx | 2 +- src/components/settings/about-me-section.tsx | 10 +++--- .../settings/coach-reminders-section.tsx | 2 +- .../targets/__tests__/range-bar.test.tsx | 16 ++++----- src/components/targets/consistency-strip.tsx | 6 ++-- src/components/targets/range-bar.tsx | 14 ++++---- src/components/ui/checkbox.tsx | 2 +- 59 files changed, 272 insertions(+), 214 deletions(-) diff --git a/eslint-plugins/healthlog/__tests__/no-raw-palette-color.test.mjs b/eslint-plugins/healthlog/__tests__/no-raw-palette-color.test.mjs index b55629c45..5283c827a 100644 --- a/eslint-plugins/healthlog/__tests__/no-raw-palette-color.test.mjs +++ b/eslint-plugins/healthlog/__tests__/no-raw-palette-color.test.mjs @@ -159,3 +159,36 @@ ruleTester.run("no-raw-palette-color (dracula check)", rule, { }, ], }); + +ruleTester.run("no-raw-palette-color (dracula blind spots)", rule, { + valid: [ + // Semantic replacements for the directional + paren-var forms. + { + code: 'const c = "border-l-destructive/70 bg-success border-success";', + filename: APP_FILE, + options: [{ checks: ["dracula"] }], + }, + // Token references through var() stay legal. + { + code: 'const c = "shadow-[0_0_0_1px_color-mix(in_oklab,var(--success)_20%,transparent)]";', + filename: APP_FILE, + options: [{ checks: ["dracula"] }], + }, + ], + invalid: [ + // Directional border form. + { + code: 'const c = "border-l-dracula-red/70";', + filename: APP_FILE, + options: [{ checks: ["dracula"] }], + errors: [{ messageId: "draculaUtility" }], + }, + // Parenthesised CSS-var utility shorthand. + { + code: 'const c = "bg-(--dracula-green)";', + filename: APP_FILE, + options: [{ checks: ["dracula"] }], + errors: [{ messageId: "draculaUtility" }], + }, + ], +}); diff --git a/eslint-plugins/healthlog/index.js b/eslint-plugins/healthlog/index.js index fe97d8368..ca06edf90 100644 --- a/eslint-plugins/healthlog/index.js +++ b/eslint-plugins/healthlog/index.js @@ -25,9 +25,9 @@ module.exports = { "no-raw-palette-color": noRawPaletteColor, "spacing-scale": spacingScale, // Same module as `no-raw-palette-color`, registered under a second - // name so the flat config can run the `dracula` check at a different - // severity (warn) than the error-level checks. See the staged plan in - // the rule header. + // name so the flat config can run the `dracula` check as its own + // named rule (now error-level too; the staged warn phase ended with + // the semantic sweep). See the rule header. "no-dracula-utility": noRawPaletteColor, }, }; diff --git a/eslint-plugins/healthlog/no-raw-palette-color.js b/eslint-plugins/healthlog/no-raw-palette-color.js index 3dfcffc40..4550d7adb 100644 --- a/eslint-plugins/healthlog/no-raw-palette-color.js +++ b/eslint-plugins/healthlog/no-raw-palette-color.js @@ -28,14 +28,13 @@ * before any stylesheet loads, so it must stay a literal). * - `arbitrary-value` (error) — Tailwind arbitrary colour values * (`bg-[#…]`, `text-[oklch(…)]`). `…-[var(--token)]` never matches. - * - `dracula` (WARN, staged) — raw `*-dracula-*` utilities. ~250 - * legacy sites predate the token system; the light-theme overrides in - * `globals.css` defuse their AA failures, so the check ships as a - * warning first (registered separately as - * `healthlog/no-dracula-utility`). It moves to error once the - * semantic sweep (status meaning → `text-success/warning/info/ - * destructive`, brand use → `--brand-*` tokens) has retired the - * legacy sites. + * - `dracula` (error, registered separately as + * `healthlog/no-dracula-utility`) — raw `*-dracula-*` utilities. + * The semantic sweep retired every legacy site: status meaning → + * `text-success/warning/info/destructive`, purple → `primary`, + * pink → the `--brand-pink` token. `var(--dracula-*)` references in + * chart code are token references, not utilities, and never match — + * the light-theme overrides in `globals.css` keep them AA. * * The `checks` option selects which detectors run, so the flat config can * register the same module twice at different severities (see @@ -78,9 +77,12 @@ const RAW_PALETTE_RE = // Raw `dracula-*` utility: the pre-token palette accessed directly // (`text-dracula-green`, `bg-dracula-orange/15`). Same prefix set and -// modifier tolerance as the palette check. +// modifier tolerance as the palette check, plus the directional border +// forms (`border-l-dracula-red`) and the parenthesised CSS-var utility +// shorthand (`bg-(--dracula-green)`) — both bypassed the plain prefix +// match during the staged-warning phase. const DRACULA_RE = - /\b(?:text|bg|border|ring|stroke|fill|from|to|via)-dracula-[a-z]+\b/; + /\b(?:text|bg|border(?:-[lrtbxyse])?|ring|stroke|fill|from|to|via)-(?:dracula-[a-z]+\b|\(--dracula-[a-z]+\))/; // Tailwind arbitrary colour value: `bg-[#…]`, `text-[rgb(…)]`, // `border-[oklch(…)]`. Matching on the bracket-open plus a colour-literal diff --git a/eslint.config.mjs b/eslint.config.mjs index 2ef764ed1..992d0e885 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -59,11 +59,13 @@ const eslintConfig = defineConfig([ "error", { checks: ["palette", "color-props", "arbitrary-value"] }, ], - // Staged: raw `*-dracula-*` utilities warn for now — ~250 legacy - // sites predate the token system and the light-theme overrides in - // globals.css keep them readable. Moves to error once the semantic - // sweep has retired the legacy sites (see the rule header). - "healthlog/no-dracula-utility": ["warn", { checks: ["dracula"] }], + // The semantic sweep retired every raw `*-dracula-*` utility site + // (status meaning → success/warning/info/destructive, purple → + // primary, pink → the --brand-pink token), so the staged warning is + // now a hard error. `var(--dracula-*)` references in chart code are + // token references, not utilities, and stay legal — the light-theme + // overrides in globals.css keep them AA on both themes. + "healthlog/no-dracula-utility": ["error", { checks: ["dracula"] }], // The Card primitive is gap-based; pt-/pb- on CardHeader/CardContent // fights the gap and re-opens the sl-001 drift class. Density lives // on the Card itself (`gap-2 py-3 md:py-4`). See the rule header. diff --git a/src/app/coach/conversations/page.tsx b/src/app/coach/conversations/page.tsx index c2677c2d7..6110786cb 100644 --- a/src/app/coach/conversations/page.tsx +++ b/src/app/coach/conversations/page.tsx @@ -224,7 +224,7 @@ function CoachConversationsBody() { className={cn( "shrink-0", isConfirming && - "text-dracula-red hover:text-dracula-red", + "text-destructive hover:text-destructive", )} >