From b278dcaf12d534b1828de59989fd091808d8b074 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Tue, 1 Sep 2026 20:39:52 +0200 Subject: [PATCH] fix(ui): namespace carry-meter accent token to avoid shadcn contrast regression A bare --accent in src/index.css silently overrode shadcn's own --accent/--accent-foreground pairing (near-white background paired with near-black text) with the design contract's dark teal, without touching the paired foreground. That dropped the computed contrast ratio on every outline/ghost button, select item, badge link and dialog close button that uses bg-accent/text-accent-foreground to 3.23:1 -- below WCAG AA's 4.5:1 minimum for text. Renamed the token to --budget-accent everywhere it's declared and consumed (src/index.css, src/ui/components/PostTable.tsx's carry meter), leaving shadcn's own --accent/--accent-foreground pair untouched. The other five contract tokens (--paper, --ink, --rule, --surplus, --overspend) don't collide with any shadcn token name in styles/globals.css, so they are unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- src/index.css | 14 +++++++++++--- src/ui/components/PostTable.tsx | 12 ++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/index.css b/src/index.css index a4ae3a3..258f1f5 100644 --- a/src/index.css +++ b/src/index.css @@ -4,14 +4,22 @@ * Design contract tokens (Tasks 18-23). * See .superpowers/sdd/2026-09-01-budget-app/design-contract.md * - * Deliberately unlayered (not inside @layer base) so it wins the cascade - * over the shadcn defaults imported above, which are also unlayered. + * Namespaced as --budget-accent (not --accent): shadcn already defines a + * bare --accent/--accent-foreground pair in styles/globals.css for hover + * backgrounds on outline/ghost buttons, select items, badges, and the + * dialog close button. A same-named override here would repaint that + * background without repainting its paired foreground, breaking contrast + * on every one of those controls. None of the other five tokens below + * collide with a shadcn token name (checked against styles/globals.css). + * + * Unlayered (not inside @layer base) so these predictably win the cascade + * over anything layered added later, without needing !important. */ :root { --paper: #f2f4f6; /* cool statement-paper ground, NOT cream */ --ink: #16212b; /* ink navy, primary text */ --rule: #d8dee4; /* hairlines, table borders */ - --accent: #0f6e63; /* muted teal: links, primary actions, spend fill */ + --budget-accent: #0f6e63; /* muted teal: links, primary actions, spend fill */ --surplus: #2f6f4e; /* carried-forward surplus */ --overspend: #a6402b; /* ochre-red: negative balances only */ diff --git a/src/ui/components/PostTable.tsx b/src/ui/components/PostTable.tsx index a3b41b6..871d5a0 100644 --- a/src/ui/components/PostTable.tsx +++ b/src/ui/components/PostTable.tsx @@ -20,11 +20,11 @@ interface Props { * * Segments, left to right, sized against a per-row scale of * (carried-in surplus + max(allocation, charges)): - * - --surplus : carried-in surplus (omitted when carriedIn <= 0) - * - --accent : spend within this month's allocation, filled from the - * track's left edge - * - --rule : the unspent remainder of the allocation track - * - --overspend: spend beyond the allocation, past the track's end + * - --surplus : carried-in surplus (omitted when carriedIn <= 0) + * - --budget-accent : spend within this month's allocation, filled from + * the track's left edge + * - --rule : the unspent remainder of the allocation track + * - --overspend : spend beyond the allocation, past the track's end */ function carryMeterBackground(figures: PostMonthFigures): string { const surplus = Math.max(figures.carriedIn, 0); @@ -41,7 +41,7 @@ function carryMeterBackground(figures: PostMonthFigures): string { return ( `linear-gradient(to right, ` + `var(--surplus) 0%, var(--surplus) ${pSurplus}%, ` + - `var(--accent) ${pSurplus}%, var(--accent) ${pFillEnd}%, ` + + `var(--budget-accent) ${pSurplus}%, var(--budget-accent) ${pFillEnd}%, ` + `var(--rule) ${pFillEnd}%, var(--rule) ${pTrackEnd}%, ` + `var(--overspend) ${pTrackEnd}%, var(--overspend) 100%)` );