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

11 KiB
Raw Permalink Blame History

Design-Token Adoption Across Feature Screens — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans. Steps use checkbox (- [ ]) syntax.

Goal: Route every feature screen through the OKLCH design tokens — one indigo brand accent (--primary), token-based status colors (success/warning/highlight), the radius token, and a shared caption utility — and add a guard that keeps raw color utilities out of src (outside components/ui/).

Architecture: Pure styling refactor. Phase 1 adds/changes tokens + ui Badge variants + the visibility badge / highlight / caption helpers. Phase 2 mechanically migrates ~120 raw utilities across 27 files to tokens + the radius token. Phase 3 adds the check:colors guard (which can only pass once the migration is complete) and runs the gate. No behavior, layout, routing, API, or data changes.

Tech Stack: React 19 + TS + pnpm, Tailwind v4 (OKLCH tokens in index.css), Base UI, Vitest+RTL+MSW (incl. Storybook browser project).

Conventions: pnpm; no any/eslint-disable/@ts-ignore; no codename; en/sv untouched (no strings); check:size budget 250 KB gz (no real change expected). Stories single-quote/no-semicolon; source double-quote/semicolon. Do not change markup/layout/spacing — only color/radius utilities + Badge variant selection.

Spec: docs/superpowers/specs/2026-06-07-design-token-adoption-design.md

Migration surface (27 files with raw color utilities, outside components/ui/): app.tsx, auth/login-page.tsx, authorities/authorities-page.tsx, components/delete-confirm-dialog.tsx, fields/field-form.tsx, fields/field-list.tsx, objects/{delete-object-dialog,flexible-field-value,object-detail-drawer,object-detail,object-edit-form,object-form,objects-page,objects-table,options-combobox,publish-control,visibility-badge,visibility-badge.stories}.tsx, search/{highlight,search-panel,search-result-row,select-search-prompt}.tsx, shell/{lang-switch,sidebar}.tsx, vocab/{select-vocabulary-prompt,vocabulary-list,vocabulary-terms}.tsx.


Task 1: Token + component foundation

Files: web/src/index.css, web/src/components/ui/badge.tsx (+ badge.stories.tsx if present), web/src/objects/visibility-badge.tsx, web/src/objects/visibility-badge.stories.tsx, web/src/search/highlight.tsx.

  • Step 1: Indigo primary + status tokens in web/src/index.css. 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);
  --success: oklch(0.627 0.194 149.214);            /* green-600 — readable as text */
  --success-foreground: oklch(0.985 0 0);
  --warning: oklch(0.666 0.179 58.318);             /* amber-700-ish — readable as text */
  --warning-foreground: oklch(0.985 0 0);
  --highlight: oklch(0.905 0.182 98.111);           /* ~yellow-300 search highlight */
  --highlight-foreground: oklch(0.205 0 0);

In .dark (keep coherent for #59): --primary: oklch(0.673 0.182 276.935) (indigo-400), --primary-foreground: oklch(0.205 0 0), --ring to match; --success/--warning slightly lighter for dark; --highlight unchanged or darker-text. In @theme inline add the --color-* mappings: --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-highlight: var(--highlight); --color-highlight-foreground: var(--highlight-foreground);. Add a shared caption utility in @layer components:

@layer components {
  .label-caption { @apply text-xs font-medium uppercase tracking-wide text-muted-foreground; }
}

(Implementer may fine-tune the oklch to match exact Tailwind shades; keep *-foreground contrast ≥ AA.)

  • Step 2: Badge variants. In web/src/components/ui/badge.tsx, add to the cva variants (mirror the destructive shape):
        success:
          "bg-success/10 text-success [a]:hover:bg-success/20",
        warning:
          "bg-warning/10 text-warning [a]:hover:bg-warning/20",
  • Step 3: VisibilityBadge → variants. In web/src/objects/visibility-badge.tsx, replace the hardcoded STYLES (amber/green/neutral) with variant selection:
const VARIANT: Record<Visibility, "secondary" | "warning" | "success"> = {
  draft: "secondary",
  internal: "warning",
  public: "success",
};

export function VisibilityBadge({ visibility }: { visibility: Visibility }) {
  const { t } = useTranslation();
  return <Badge variant={VARIANT[visibility]}>{t(`visibility.${visibility}`)}</Badge>;
}

(Drop the variant="outline" className={STYLES[...]} patching.)

  • Step 4: Highlight token. In web/src/search/highlight.tsx, bg-yellow-200bg-highlight text-highlight-foreground.

  • Step 5: Update stories. Add Success/Warning stories to the Badge story file (if badge.stories.tsx exists; else create alongside). Update the CssCheck story in visibility-badge.stories.tsx: it asserts the public badge background oklch(0.962 0.044 156.743) (old green-100). Public is now the success variant (bg-success/10). Run the story, read the new getComputedStyle(...).backgroundColor, and pin that value (keep the CssCheck — it proves Tailwind + tokens load). Update the comment.

  • Step 6: cd web && pnpm test -- visibility-badge badge && pnpm typecheck && pnpm lint. The visibility badge renders with token colors; CssCheck passes with the new value. Commit feat(web): indigo brand token + status tokens + Badge success/warning variants (#49).


Task 2: Migrate feature screens to tokens + radius

Files: the 27 migration-surface files listed above (excluding visibility-badge.tsx/.stories.tsx + highlight.tsx done in Task 1).

Apply the migration map mechanically. Use the guard regex (Task 3) as your completeness checker: after migrating, grep -rE "(text|bg|border|ring)-(neutral|gray|slate|red|amber|green|yellow|indigo|…)-[0-9]+" src --include="*.tsx" | grep -v "components/ui/" must return nothing.

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, sidebar) bg-accent
bg-indigo-50 (selected row) bg-primary/10
bg-indigo-600 / text-indigo-600 bg-primary / text-primary (+ text-primary-foreground where on bg-primary)
bg-neutral-800 (publish stepper / authority tabs active) bg-primary text-primary-foreground
border-red-300 (combobox/drawer error) border-destructive (or keep neutral border if it's not an error state)
border-green-300 border-success (or neutral)
bare rounded (×23) rounded-md
  • Step 1: Migrate by area, file-by-file, replacing per the map. Also collapse the uppercase-caption recipes (object-detail, object-form, publish-control, field-list, vocabulary-terms) to the shared label-caption class (<div className="label-caption">…). Do not change any non-color/radius classes, markup, or layout. For the few ambiguous one-offs, follow the map's intent (muted captions → text-muted-foreground; emphasized values → text-foreground; error text → text-destructive). Optionally adopt ui/Card for an obviously hand-rolled bordered panel (e.g. object-detail) — only if a clean swap; skip otherwise.
  • Step 2: Completeness check — run the grep above; iterate until zero raw color utilities remain outside components/ui/. Also confirm no bare rounded remains (→ rounded-md).
  • Step 3: Verify no regressionscd web && pnpm typecheck && pnpm lint && pnpm test (all existing tests pass; the styling change shouldn't break behavioral tests — if a test asserts a specific old color/class, update it to the token equivalent). pnpm build.
  • Step 4: Commit refactor(web): migrate feature screens to design tokens + radius token (#49).

Task 3: Enforcement guard + final verification

Files: web/scripts/check-no-raw-colors.mjs (new), web/package.json (a check:colors script), wire into the gate.

  • Step 1: Guard script web/scripts/check-no-raw-colors.mjs (mirror check-bundle-size.mjs style): recursively scan web/src/**/*.{ts,tsx} excluding src/components/ui/; fail (exit 1, printing each file:line) on any match of:
/(?:text|bg|border|ring|fill|stroke|from|to|via|decoration|outline|divide|placeholder)-(?:neutral|gray|slate|zinc|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950)\b/

Skip comments if practical; the goal is to catch real className usages. (It must NOT flag token utilities like text-foreground/bg-primary or numerics like gap-2.)

  • Step 2: Wire it in — add "check:colors": "node scripts/check-no-raw-colors.mjs" to web/package.json; include it in the project's check/CI flow (e.g. the .gitea/workflows web job, or alongside check:size). Run it → it must pass now (Task 2 cleared the surface).
  • Step 3: Final verification:
cd web && pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm check:size && pnpm check:colors

All green. pnpm test -- i18n (parity unaffected). git grep -in 'biggus\|dickus' -- web/src || echo CLEAN. git status --short clean.

  • Step 4: Manual smoke (recommended): run the app — buttons/links/selected rows/active nav share the indigo accent; visibility badges (success/warning/neutral) + search highlight use the status tokens; nothing renders an unstyled/transparent element from a removed color.
  • Step 5: Commit chore(web): add check:colors guard banning raw color utilities outside ui/ (#49).

Self-Review (completed)

Spec coverage: indigo --primary/--ring + status tokens + @theme + .dark (T1 S1); Badge success/warning + VisibilityBadge + highlight + label-caption (T1 S2S4); ~120-utility migration + radius (T2); guard added last + gate (T3); CssCheck updated (T1 S5); dark-mode toggle out (#59), no behavior/layout change. ✓ Placeholder scan: concrete token values, badge variants, VisibilityBadge code, guard regex, and the explicit migration map + 27-file list. The CssCheck new value is "run to read" (the original story did the same — a genuine measurement step, not a placeholder). The few "ambiguous one-off" mappings are governed by the map's stated intent. Type/consistency: success/warning Badge variants (T1) consumed by VisibilityBadge VARIANT map; --color-success/warning/highlight tokens (T1) back bg-success/bg-warning/bg-highlight; the guard regex (T3) matches exactly the palette utilities the migration (T2) removes.

Notes

  • No new dependency; CSS token churn only → check:size ≈ unchanged.
  • The guard is the durable win — it makes the consistency self-enforcing (closes the loop that caused #49).
  • If a behavioral test asserts an old raw class/color, update it to the token equivalent (don't weaken it).