Stu Mason
Stu Mason

Activity

Pull Request Merged

PR #119 merged: fix(ux): emit missing token utilities by hand

What broke

Phases 1–7 shipped components using class names like text-strong, bg-panel-raised, border-default, border-hairline, text-faint, bg-hover. Prod looked identical to pre-overhaul because the corresponding CSS rules either didn't exist OR resolved to empty values.

Root cause (refined after local Playwright verification)

Two separate problems compounding:

  1. Missing utility classes. Tailwind v4's @theme generates utilities from --color-X using the FULL suffix. So --color-text-strong produced .bg-text-strong / .text-text-strong (double prefix), never .text-strong. Same for every role-prefixed token.

  2. Unresolved var chain. Phase 1 declared tokens like --color-text-strong: var(--color-zinc-100). Tailwind v4 does not emit its default palette as standalone :root custom properties — palette colours only ship as inline fallbacks on the utility rules that consume them directly (e.g. .text-zinc-100 { color: var(--color-zinc-100, oklch(96.7% .001 286.375)) }). So var(--color-zinc-100) inside a custom :root declaration resolved to empty. Every consumer of --color-text-strong got nothing.

Both bugs together meant the new aesthetic was a no-op on prod.

The fix

  1. app.css @theme — replace every var(--color-zinc-X) / color-mix() chain with the literal oklch() value pulled from Tailwind v4's default palette. Now --color-text-strong, --color-bg-panel-raised, --color-border-default, etc. resolve to real colours on :root.
  2. app.css @layer utilities — emit the role-prefixed class names by hand (.text-strong, .bg-panel-raised, .border-default, .border-hairline, .text-faint, .text-default, .bg-page, .bg-panel, .bg-hover, hover/group-hover variants, .decoration-border-default). These reference the now-resolving @theme vars.
  3. tokens.css — drop the parallel :root block that re-declared the same tokens with the broken var() chain. It was being cascaded AFTER @theme and undoing the fix. File now only documents the design system + ships motion/radii/font-family vars that don't collide with @theme.

Local verification (Playwright, dev DB)

  • --color-text-strong resolves to oklch(96.7% .001 286.375)
  • --color-bg-panel-raised resolves to oklch(21% .006 285.885/.6)
  • --color-border-default resolves to oklch(27.4% .006 286.033)
  • Cards render with zinc-900-at-60% transparent fill + zinc-800 hairline border ✓
  • Breadcrumb (Phase 5) renders Reports / The Prediction Scorecard
  • TimestampLine (Phase 3) renders "Updated 08:32 UTC · Next in 29m" ✓
  • Contextual EmptyState (Phase 3) shows "⏱ First hits land once resolve runs..." ✓
  • Phase 6 categories (LIVE PREDICTION / PREDICTION SCORECARD / BACKTEST / MINDSHARE / COMPETITIVE INTEL) all render in emerald ✓
  • Mobile (390px) — single column, no horizontal scroll, type hierarchy correct ✓

Screenshots in docs/ux-overhaul/screenshots/.

Test plan

  • npm run build clean
  • php artisan test --compact — 188 pass + 2 pre-existing LinkedIn failures (unrelated)
  • Playwright verification at 1280×800 + 390×844 (screenshots committed)
+145
additions
-148
deletions
6
files changed