Problem
Two prominent budget KPIs render as plain neutral text regardless of whether the value is favorable, neutral, or alarming:
- "Variance" on
/budget — currently shows -$28,743.84 in the same color as "Total Budget" and "Allocated".
- "YTD Budget Util." on the dashboard — currently shows
108% in default text style even though the value is over 100% (i.e. budget already overrun for the year).
Other parts of the app already use semantic color: the Budget Alert banner, the over-100% workspace progress bars on /claude. The dashboard tiles and the FY card on /budget should match that vocabulary so an admin scanning the page can spot trouble at a glance — currently a 108% overspend looks identical to a 50% utilisation.
Evidence
Browser smoke test on /budget and / (2026-04-28). Screenshots saved under C:\Users\stude\AppData\Local\Temp\smoke-budget.png and smoke-dashboard.png (locally — feel free to recreate via the steps in the verification section).
Implicated components (find via grep on "Variance" and "YTD Budget"):
/budget summary card — likely under src/app/budget/ or src/components/.
- Dashboard KPI tile — likely under
src/app/page.tsx or src/components/dashboard*.
Proposed approach
- Decide the thresholds — propose:
- Utilisation
< 80% → default/muted-foreground.
80%–100% → amber (text-amber-500 / shadcn warning palette).
> 100% → destructive red (text-destructive).
- Variance: positive (under-spend) → muted/neutral; negative (over-spend) → destructive.
- Extract the threshold logic into one small helper (e.g.
src/lib/budget-color.ts) that returns a Tailwind class string. Reuse it on both the dashboard KPI tile and the /budget FY card.
- Update both call sites to apply the class to the value's wrapping span.
- Add appropriate
aria-label (e.g. \"Variance: -$28,743.84 (over budget)\") so screen-reader users get the semantic info that sighted users get from color — required for a11y and the reason this issue is also tagged a11y.
- Add a Vitest unit test for the helper covering boundaries (79.9, 80, 99.9, 100, 100.1).
Acceptance criteria
Verification
- With seed data showing a 108% YTD utilisation: visit
/ → tile is red and screen-reader announces "over budget". Visit /budget → Variance row is red.
- Manually set utilisation to 75% (e.g. by inserting a smaller
billed_costs total in dev) → tile becomes neutral.
- Manually set utilisation to 90% → tile becomes amber.
Problem
Two prominent budget KPIs render as plain neutral text regardless of whether the value is favorable, neutral, or alarming:
/budget— currently shows-$28,743.84in the same color as "Total Budget" and "Allocated".108%in default text style even though the value is over 100% (i.e. budget already overrun for the year).Other parts of the app already use semantic color: the Budget Alert banner, the over-100% workspace progress bars on
/claude. The dashboard tiles and the FY card on/budgetshould match that vocabulary so an admin scanning the page can spot trouble at a glance — currently a 108% overspend looks identical to a 50% utilisation.Evidence
Browser smoke test on
/budgetand/(2026-04-28). Screenshots saved underC:\Users\stude\AppData\Local\Temp\smoke-budget.pngandsmoke-dashboard.png(locally — feel free to recreate via the steps in the verification section).Implicated components (find via grep on "Variance" and "YTD Budget"):
/budgetsummary card — likely undersrc/app/budget/orsrc/components/.src/app/page.tsxorsrc/components/dashboard*.Proposed approach
< 80%→ default/muted-foreground.80%–100%→ amber (text-amber-500/ shadcn warning palette).> 100%→ destructive red (text-destructive).src/lib/budget-color.ts) that returns a Tailwind class string. Reuse it on both the dashboard KPI tile and the/budgetFY card.aria-label(e.g.\"Variance: -$28,743.84 (over budget)\") so screen-reader users get the semantic info that sighted users get from color — required for a11y and the reason this issue is also taggeda11y.Acceptance criteria
aria-labeldescribing the status in words, not only color.pnpm lint && pnpm typecheck && pnpm testpass.Verification
/→ tile is red and screen-reader announces "over budget". Visit/budget→ Variance row is red.billed_coststotal in dev) → tile becomes neutral.