Files
biggus-dickus/docs/superpowers/specs/2026-06-07-design-token-adoption-design.md
2026-06-07 14:00:42 +02:00

129 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design-Token Adoption Across Feature Screens — Design
**Date:** 2026-06-07
**Status:** Approved (brainstorming) — ready for implementation planning.
**Issue:** #49.
## Context
`web/src/index.css` defines a full semantic OKLCH token set (the shadcn defaults:
`--foreground`, `--muted-foreground`, `--primary`, `--destructive`, `--accent`, `--border`,
`--radius`, light + `.dark`) and `ui/*` components use it. But the **feature screens bypass it**:
~120 hardcoded Tailwind color utilities outside `components/ui/``text-red-600` ×27 (not
`--destructive`, a *different* red), `text-neutral-400/500/600` ×47 (three shades for "muted
text"), `bg-neutral-50/100`, plus **two competing accents** (`bg-indigo-50`/`text-indigo-600`/
`bg-indigo-600` for selection/links/chips vs the near-black `--primary` for buttons and
`bg-neutral-800` for the publish stepper). Status colors (visibility badge `amber`/`green`,
search highlight `yellow`) aren't tokens; `rounded` (0.25rem, ×23) ignores the `--radius` token;
`ui/Card` has zero usages; the uppercase caption label appears with 4 different recipes.
(Recent table/detail/toast work *did* introduce token usage — `bg-primary` ×6, `bg-destructive`,
`text-foreground` — so the codebase is now mixed; this finishes the job.)
### Decisions (from brainstorming)
1. **One brand accent: indigo `--primary`.** Set `--primary`/`--ring` to an indigo so primary
buttons, selected rows, links, and chips share one recognizable accent (the existing indigo
usages map straight onto `bg-primary`/`text-primary`).
2. **Add status tokens** (`--success`/`--warning`/`--highlight`) and route the visibility badge +
search highlight through them (via Badge variants).
3. **Enforce** with a CI/lint guard banning raw color utilities outside `components/ui/`.
4. **Dark-mode toggle stays #59.** This migration makes the `.dark` token set *work* (semantic
tokens adapt), unblocking #59; the toggle/persistence is not in scope.
5. **One milestone** (the guard can only pass once the migration is complete, so it lands last).
## Token changes (`web/src/index.css`)
- **Indigo primary** in `:root`: `--primary: oklch(0.511 0.262 276.966)` (≈ indigo-600),
`--primary-foreground: oklch(0.985 0 0)`, `--ring: oklch(0.511 0.262 276.966)`. In `.dark`: a
lighter indigo that reads on dark (≈ indigo-400, `oklch(0.673 0.182 276.966)`) +
`--primary-foreground: oklch(0.205 0 0)`, matching `--ring`. (Implementer may fine-tune to a
Tailwind indigo shade; keep `--primary-foreground` contrast AA.)
- **Status tokens** in `:root` and `.dark`, exposed in `@theme inline` as `--color-success`,
`--color-success-foreground`, `--color-warning`, `--color-warning-foreground`,
`--color-highlight`, `--color-highlight-foreground`. Light values ≈ success
`oklch(0.6 0.13 160)` / warning `oklch(0.72 0.15 75)` / highlight `oklch(0.905 0.16 100)` with
readable foregrounds; dark variants adjusted for contrast. `--destructive` unchanged.
- `--accent`/`--muted` stay the neutral shadcn grays (hover/subtle surfaces). Selected/active
states use `bg-primary/10` (a light indigo tint) rather than repurposing `--accent`.
## Component updates
- **`ui/badge.tsx`:** add `success` and `warning` variants (token-based, with dark variants);
**`VisibilityBadge`** (`web/src/objects/visibility-badge.tsx`) selects a variant — `public`
`success`, `internal``warning`, `draft` → the default/secondary — instead of patching
`bg-amber-100`/`bg-green-100`.
- **`search/highlight.tsx`:** `bg-yellow-200``bg-highlight` (token).
- **Shared caption:** add a single `.label-caption` utility (in `index.css` `@layer
components`, or a tiny `ui` helper) = `text-xs font-medium uppercase tracking-wide
text-muted-foreground`; replace the 4 ad-hoc recipes (object-detail, object-form,
publish-control, field-list, vocabulary-terms).
- **`ui/Card`:** adopt for clearly hand-rolled bordered panels where it's a clean swap (e.g. the
object-detail container). Not forced onto every bordered div — low priority within scope.
## Migration map (mechanical, ~120 sites, outside `components/ui/`)
| From | To |
|---|---|
| `text-red-600` | `text-destructive` |
| `text-neutral-400` / `-500` / `-600` | `text-muted-foreground` |
| `text-neutral-700` / `-900` | `text-foreground` |
| `bg-neutral-50` / `-100` | `bg-muted` |
| `bg-neutral-200` (active nav) | `bg-accent` |
| `bg-indigo-50` (selected row) | `bg-primary/10` |
| `bg-indigo-600` / `text-indigo-600` | `bg-primary` / `text-primary` |
| `bg-neutral-800` (publish stepper / tabs) | `bg-primary` (with `text-primary-foreground`) |
| bare `rounded` (×23) | `rounded-md` |
| `bg-amber-*`/`bg-green-*`/`bg-yellow-*` (badge/highlight) | via Badge variants / `--highlight` |
Apply judgment on a few one-offs (e.g. `border-red-300`/`border-green-300` on the combobox/drawer
→ `border-destructive`/`border-success` or keep neutral `border`). The goal: **zero raw color
utilities outside `components/ui/`** after the migration.
## Enforcement (`web/scripts/check-no-raw-colors.mjs`)
A grep-style Node script (mirroring `check-bundle-size.mjs`) that scans `src/**/*.{ts,tsx}`,
**excluding `src/components/ui/`**, and fails (exit 1) if it finds a raw palette utility matching
`(text|bg|border|ring|fill|stroke|from|to|via)-(neutral|gray|slate|zinc|stone|red|orange|amber|
yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}`.
Print each offending file:line. Add a `check:colors` script and wire it into the test gate (e.g.
the `check` flow / CI). A short inline allowlist (path or `eslint-disable`-style comment) only if
a genuine exception arises. **Added last**, after the migration, so the gate is green.
## Data flow / architecture
Pure styling refactor — no behavior, routing, API, or data changes. Tokens in `index.css` →
Tailwind utility classes (`bg-primary`, `text-muted-foreground`, …) in feature components →
resolved at build. `ui/*` components already consume tokens and gain the new Badge variants.
## Error handling / edges
- The migration must not change layout/spacing — only color/radius utilities (and Badge variant
selection). Don't restructure markup.
- `bg-primary/10` opacity modifier on a token works in Tailwind v4 — verify it resolves.
- A few utilities are genuinely semantic-ambiguous (e.g. `text-neutral-900` for an emphasized
value vs body) — map to `text-foreground`; muted captions → `text-muted-foreground`.
- The enforcement regex must not flag token utilities (`text-foreground`, `bg-primary`) or
non-color numerics (`gap-2`, `w-44`) — scope it to the palette names above.
## Testing
- **Existing `CssCheck` story** (`visibility-badge.stories.tsx`) asserts the public badge's
resolved `bg-green-100` oklch — it **must be updated** to the new `--success` token's resolved
value (run the story to read the actual computed color, as the original did). Add Badge
`success`/`warning` stories.
- `pnpm typecheck` / `lint` / `test` / `build` green; the **new `check:colors` passes** (proves
the migration is complete — zero raw utilities outside `ui/`); `check:size` ≈ unchanged (CSS
token churn only); no codename; en/sv untouched (no strings).
- Manual smoke: the app still renders (now indigo-accented); selected rows/links/buttons share
the indigo; visibility badges + search highlight use the status tokens; nothing relies on a
removed color. (No automated visual-regression in the repo — the guard + updated stories +
smoke are the safety net.)
## Acceptance criteria
1. `--primary`/`--ring` are indigo; primary buttons, selected rows, links, chips use it (one
accent). Status tokens (`--success`/`--warning`/`--highlight`) exist (+ `@theme` + `.dark`).
2. `VisibilityBadge` and the search highlight use token-based Badge variants / `--highlight`
(no hardcoded amber/green/yellow); one shared caption utility.
3. **No raw color utilities outside `components/ui/`**; bare `rounded` standardized to the radius
token; `check:colors` guard added to the gate and passing.
4. No behavior/layout change; `CssCheck` story updated; `typecheck`/`lint`/`test`/`build`/
`check:size` green; no codename.
5. Dark-mode tokens are coherent (light + `.dark`) so #59 (toggle) is unblocked — but the toggle
is not added here.
## Out of scope → follow-ups
- The **dark-mode toggle** + persistence (#59) — this only makes the tokens dark-ready.
- Per-screen **layout/spacing** redesign, density changes, typography scale (#57).
- Forcing `ui/Card` onto every panel (adopt only the clean swaps).