---
name: scaffold-ui-primitives
description: Scaffold theme-compliant UI primitives into the client project (EntityLookup combobox first; EnumSelect, DateInput planned). Idempotent, honors @customised marker.
group: D
phase: devFrontend
kind: main
section_label: 'SCAFFOLD-UI-PRIMITIVES (EntityLookup + future EnumSelect / DateInput in src/components/ui/)'
allowed-tools: [Read, Write, Edit, Glob, Grep]
---

# scaffold-ui-primitives — Theme-compliant primitives scaffolder

## Context

You are scaffolding **shared UI primitives** into a SmartStack-generated client
app. Today the only primitive is `EntityLookup` — the combobox `scaffold-component`
imports for every FK Guid field of a Form/Detail page. Tomorrow this same skill
will host `EnumSelect`, `DateInput`, etc.

These primitives live **locally** in `src/components/ui/` (not in
`@atlashub/smartstack`) for three reasons:

1. **Theme compliance** — they must use SmartStack tokens (`var(--bg-card)`,
   `var(--color-accent-*)`, …) so a theme swap from the admin re-paints them
   instantly. Hardcoded Tailwind colors (`bg-blue-500`, `dark:bg-gray-700`)
   are **forbidden** and the test snapshot enforces it.
2. **No upstream churn** — the `EntityLookup` paradox (component documented
   in CLI but absent from `@atlashub/smartstack`) was the trigger for this
   skill. Scaffold-component now imports `@/components/ui/EntityLookup` and
   this skill is what makes that import resolve.
3. **Local override surface** — clients can replace the file (or mark it
   `// @customised`) without forking the SDK.

Without this skill, `scaffold-component` emits `<EntityLookup>` JSX → TypeScript
fails to resolve the import → audit `DEV-UI-022` fires BLOCKING. So the
orchestrator (`/ba-develop` Phase 3.0) MUST invoke `scaffold-ui-primitives`
before any feature page is scaffolded.

## Invocation

```
npx tsx skills/development/frontend/ui-primitives/cli/scaffold-ui-primitives/index.ts \
  --spec '{"projectPath":"<abs>","appCode":"<app>"}'
```

`--spec` JSON:

| Field | Type | Required | Notes |
|---|---|---|---|
| `projectPath` | string | yes | Absolute path to the web root (holds `src/`). |
| `appCode` | string | yes | Reserved for future per-app branding hooks. |
| `force` | boolean | no | Overwrite even when content matches. Default false. |

## What it produces

- `src/components/ui/EntityLookup.tsx` — debounced + paginated combobox with
  keyboard nav, clear button, loading/empty/error states. All styling via
  SmartStack tokens; zero Tailwind color classes; zero `dark:` prefix (theme
  swap handles dark mode at the token layer).
- `src/i18n/locales/{fr,en,it,de}/common.json` — deep-merged with the
  `entityLookup.*` namespace (`loading`, `empty`, `error`, `search`, `clear`,
  `placeholder`). Existing keys are preserved.

## Idempotency contract

`/* @customised */` (or `// @customised`) marker at the top of any file =
preserve. Otherwise overwrite when content drifts. AUTO-GENERATED header
included so a re-run doesn't surprise the dev.

Locale files use **deep merge**: existing keys are preserved verbatim; only
the `entityLookup.*` subtree is reset to the canonical scaffold. Other
namespaces (`auth.*`, `nav.*`, …) untouched.

## When NOT to use

- Adding a custom field-level component (e.g. `RichTextEditor`) → those are
  feature-scoped, scaffold them in `src/features/<module>/<entity>/components/`
  via the regular `scaffold-component` flow.
- Changing the styling of an existing primitive → either mark the file
  `// @customised` (one-off override) or change the tokens in `index.css` via
  `scaffold-theme` (global). Don't patch the primitive.

## Linked checks

- `DEV-UI-022` (audit-dev-frontend, BLOCKING) — fires when a Form page renders
  a FK field as `<input>`/`<select>` instead of `<EntityLookup>`.
- `smoke-generation --quick` — used to flag `[BROKEN] @/components/ui/EntityLookup`;
  once this skill runs at Phase 3.0, the import resolves and the flag clears.
