---
name: ui-components
description: >
  Verify that a React page (or the whole generated app) conforms to the SmartStack
  design system — design-system tokens instead of hardcoded colors, the theme's
  helper classes (.btn .btn-primary, .badge-*) instead of inline re-assembly, and
  the project's own page anatomy — then auto-fix the violations. Thin orchestrator
  over the ui-polish engine: it does NOT reimplement any audit/fix logic, it runs
  the deterministic ui-polish CLI (project-aware) and finishes the residual cases
  the CLI cannot map mechanically. Runs the FULL ruleset by default.
argument-hint: "[page-path] [--check|--fix] [--theme-only]"
model: sonnet
allowed-tools: Read, Grep, Glob, Bash, Edit
---

# ui-components — Design-system conformity check & auto-fix

Confirms a page uses the design-system **tokens** AND the theme's **helper classes**
(never re-assembling them inline), respects the project's **page anatomy**, and
repairs the violations. This is the user-facing entry point; the actual engine is
the colocated **ui-polish** skill (`development/frontend/ui-polish`). This skill
**delegates** to its CLI and never duplicates its rules.

> **The CLI is project-aware.** It derives the conformance contract from what the
> TARGET project actually provides — the helper classes / tokens defined in its CSS
> and the components present under `src/components`. So it flags bypass of primitives
> the project *ships* (e.g. an inline CTA where `.btn .btn-primary` exists) and never
> demands components the project *lacks* (e.g. it won't force `<PageTemplate>` on an
> app that has none — that is reported as an architectural note, not an error).

> **Scope boundary — platform chrome is NOT audited.** The CLI lints the app's own
> source (`src/pages`, `src/features`, local `@/components` imports). It does **not**
> scan the compiled `@atlashub/smartstack` package — so the platform-rendered chrome
> (the app shell, toolbar, search box, the `DataView`/`ViewToggle` segmented control,
> admin pages) is invisible to it, and deliberately so: those are platform components
> the client cannot fix (a `node_modules` edit is overwritten on update; their theming
> is governed by SmartStack.app). **Consequence:** a "0 findings" app audit means the
> app's *own* code is clean — it does NOT certify the rendered screen. If the user
> points at a URL and the visible problem is in platform chrome (e.g. a view-toggle
> whose accent looks wrong), say so explicitly: name the platform component, and route
> the fix to either the tenant's accent/theme config or a SmartStack.app change — never
> imply the app audit covered it.

## When to use

- "Vérifie que cette page respecte le thème / qu'il n'y a aucune couleur hardcodée
  et qu'elle utilise bien les classes du thème."
- Before committing a page you wrote or edited by hand.
- After scaffolding, to confirm generated pages match the reference theme.

## What it checks (full ruleset — run by default)

Every rule in `tokens.json` runs unless you pass `--theme-only`. The headline rules:

| Rule | What | Auto-fix |
|------|------|----------|
| **R21** | **Primary CTA reinvented inline** (`bg-[var(--color-accent-500)] text-white …` on a `<button>`) instead of the `.btn .btn-primary` helper | → switched to the helper (keeps extras like `w-full`) |
| **R22** | **Ad-hoc accent tint** (`bg-[var(--color-accent-500)]/10`) instead of `.badge-accent` / the `--accent-bg|text|border` triplet | reported (semantic) |
| **R24** | **Dangling token** — `var(--x)` in className not defined in the project's theme (renders inconsistently across pages). Project-aware: only when the theme is local-authoritative; skips `--sl-*`/`--tw-*` | reported (add token / pick a defined one) |
| **R25** | **Page content width** — page hard-codes width (`container mx-auto`, `max-w-2xl+ mx-auto`) instead of `<PageTemplate width="standard"\|"focused">`. Only two widths sanctioned (full / centered cap), rest banned. Project-aware; skips modals | reported (move width to PageTemplate) |
| **R26** | **Native form control** — `<input type="date">`, `<select>`, `<select multiple>` instead of `<DateInput>` / `<EnumSelect>` / `<MultiSelect>` (browser-chrome widgets that ignore the theme: corners, surface, dark mode). Project-aware: only when the matching primitive ships | reported (re-scaffold so the field renders the primitive) |
| R14 | Any **saturated** Tailwind color anywhere in `className` | → accent ramp / status tokens; status dots → `var(--<status>-dot)` |
| R15 | Any **neutral** color (`bg-white`, `bg-gray-100`, …) | backgrounds → `var(--bg-*)`; text/border reported (no token) |
| R16 | Status token bound to the wrong utility (illegible / invisible) | → flipped back to its role |
| R17 | Status token used as **decoration** (color-name key → `var(--<status>-…)`) | reported (manual) |
| R18 / R19 | Tailwind color in **JS data structures** / **hex in `style={{}}`** | reported |
| R20 | **Accent overuse** on non-CTA (thick border, pale section bg, full fill) | reported (design review) |
| R1 / R11 | **Page anatomy** — `<PageTemplate>` / `<Slot>`. **Project-aware:** ERROR when the project defines the component but a page skips it; **INFO** "architecture gap" when the project has no such component (an app-wide migration decision, not a page fix) | guarded — never imports a component the project lacks |
| R2–R13 | Hex in className, raw-Tailwind CTA→helper, i18n, icons, banner/badge anatomy, permission keys, … | mixed |

> Tokens + helper classes come from `development/frontend/ui-polish/tokens.json` as a
> baseline, UNIONed at runtime with the target project's own CSS. The theme tokenises
> **accent + status + surfaces**; neutral text/border are intentionally not tokenised,
> so R15 reports them for review rather than guessing a wrong token.

## Steps

1. **Locate the web root** — the folder that contains `src/pages` (the generated
   SmartStack web app). If unsure:
   ```bash
   # from the project root
   ls src/pages 2>/dev/null && echo "." || find . -maxdepth 4 -type d -name pages -path "*/src/*" 2>/dev/null
   ```
   Use the parent of `src` as `<web-root>` (often `.`, or `web/<app>-web`).

2. **Verify (audit)** — run the **full** ruleset (omit `--rules`). Pass `--page` to
   target a single page, `--url` to resolve a URL, or omit both to scan the app:
   ```bash
   npx --prefer-offline tsx skills/development/frontend/ui-polish/cli/ui-polish/index.ts \
     --project-path "<web-root>" \
     --mode audit \
     --page "src/pages/<module>/<Entity>ListPage.tsx"
   ```
   Read `report.findings[]` and `report.counts` and summarise every violation to the
   user — including INFO findings (e.g. an R1 architecture gap), which are real signal,
   not noise. **Only add `--theme-only`** (which appends `--rules R2,R3,R7,R12,R13,R14,R15,R16,R17,R18,R19,R20,R21,R22`)
   when the user explicitly wants the color/helper pass without the anatomy rules; say
   so in the summary ("theme-only — anatomy not checked").

   `--page` also scans 1st-level local imports (`@/components/**`, `@/features/**`),
   so shared-component violations are visible.

3. **Fix (apply)** — only if the user asked to correct (or `--fix`). Regeneration is
   **disabled** (`--regenerate-threshold 0`) so a page is never destructively rebuilt
   — only conformity violations are rewritten in place:
   ```bash
   npx --prefer-offline tsx skills/development/frontend/ui-polish/cli/ui-polish/index.ts \
     --project-path "<web-root>" \
     --mode apply --regenerate-threshold 0 \
     --page "src/pages/<module>/<Entity>ListPage.tsx"
   ```
   The CLI rewrites the `autoFixable` rules (R1, R3, R7, R10, R11, R13, R14, R15, R16,
   **R21**) and is project-aware: it will NOT import a component the project lacks (a
   PageTemplate-less app gets no phantom `<PageTemplate>` import). R17/R18/R19/R20/R22
   are `autoFixable: false` — reported for manual handling.

4. **Review the apply diff** — `git diff` the rewritten files. The engine fixes
   mechanically; a few cases need your eye:

   - **(a) R21 CTA→helper residue.** The fix prepends `btn btn-primary` and strips the
     helper-owned color utilities, but conservatively KEEPS spacing/flex (`gap-1.5`,
     `px-3`, …) so layout can't break. Tidy the redundant utilities by hand so the
     button reads cleanly as `<button className="btn btn-primary">` (+ genuine extras
     like `w-full`).
   - **(b) Categorical badges collapsed onto one accent — the engine CANNOT fix this.**
     R14 maps every brand family (`blue`, `indigo`, `violet`, …) to the **single** accent
     ramp. Re-assign a **distinct token per category** (`success`/`warning`/`error`/
     `info`/`accent`) using the **soft badge pattern** — `bg-[var(--<token>-bg)]
     text-[var(--<token>-text)] border border-[var(--<token>-border)]` (or `.badge-<status>`).
   - **(c) Spot-check status dots (R14)** — tiny `rounded-full` indicators should be
     `var(--<status>-dot)`, not the pale `var(--<status>-bg)`.
   - **(d) Spot-check R16 token-role flips** — complete the soft triplet on badges/banners.

   Apply these manual edits with `Edit` — this and step 5 are the only places this skill
   touches files directly; the bulk is done by the CLI.

5. **Resolve every residual & re-audit** — re-run step 2. Saturated colors, R16
   role-misuse, badges and inline CTAs (R21) should now be `0`. Each REMAINING finding
   must be **driven to closure** — there is no "leave it, it looks fine" disposition:

   - **map** it to an existing token / helper class, OR
   - **add** the missing token via `scaffold-theme` (then it becomes auto-fixable), OR
   - **switch** to the correct helper class (`.btn .btn-primary`, `.badge-accent`, `.card`, `.input`), OR
   - **record it as rule-not-applicable-for-this-project, with evidence** — and only on
     the project-aware grounds the engine itself reports (e.g. *"R1: this app defines no
     PageTemplate component (INFO) — adopting it is a separate app-wide migration; tracked,
     not fixed here"*). State the evidence; never assert "idiomatic" or "false positive"
     to wave a finding away. A neutral with no token (e.g. `bg-black/50` scrim) is closed
     by **adding the token** (`scaffold-theme`) or by an explicit "no token exists yet —
     open follow-up", NOT by declaring it acceptable.

6. **Report honestly** — typecheck the app (`npm run typecheck` / `tsc --noEmit` at the
   web root). Then state the result precisely:
   - **0 errors AND every warning/info resolved-or-tracked** → say so, and distinguish
     *theme-token-clean* from *architecturally conformant* (note any R1/R11 architecture
     gap explicitly).
   - **any ERROR remaining** → it is **not** conformant; never call a page "100%
     compliant" while an ERROR or an un-triaged warning stands.
   Present the final `git diff`.

## Guardrails

- **Never** edit `tokens.json` or the ui-polish CLI from here — change rules in the
  `development/frontend/ui-polish` skill instead.
- **Never** pass `--regenerate-threshold > 0` in this skill — full-page regeneration
  belongs to `ba-develop`, not to a conformity check.
- **Full ruleset is the default.** Use `--theme-only` only when the user explicitly
  asks for the color/helper pass without anatomy, and label the result accordingly.
- **Never rationalize a finding away.** "Idiomatic", "false positive", "intentional" are
  not closures. A finding is closed by a fix, a new token, a helper class, or an explicit
  project-aware not-applicable note with evidence (step 5).
- The CLI is the single source of truth for detection and mechanical fixes; this skill
  only orchestrates and finishes what the CLI flags as unfixable.
