---
name: scaffold-ui-primitives
description: Scaffold theme-compliant UI primitives into the client project (EntityLookup combobox, DateInput calendar, EnumSelect + MultiSelect dropdowns, TruncatedText + ResponsiveDataTable for responsive list tables with truncation tooltips). Idempotent, honors @customised marker.
group: D
phase: devFrontend
kind: main
section_label: 'SCAFFOLD-UI-PRIMITIVES (EntityLookup, DateInput, EnumSelect, MultiSelect, TruncatedText, ResponsiveDataTable 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. The primitives are the theme-compliant form controls `scaffold-component`
imports for a Form/Detail page: `EntityLookup` (FK combobox), `DateInput`
(calendar — replaces the native `<input type="date">`), `EnumSelect` (single-
select dropdown for enum fields) and `MultiSelect` (multi-value chips). All four
are self-styled with theme tokens — no native browser-chrome widgets.

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 --prefer-offline 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 FK combobox with
  keyboard nav, clear button, loading/empty/error states.
- `src/components/ui/DateInput.tsx` — calendar date picker (React-rendered
  popover, not the native `<input type="date">`); ISO `YYYY-MM-DD` value;
  locale-aware via `Intl.DateTimeFormat`; keyboard nav.
- `src/components/ui/EnumSelect.tsx` — single-select dropdown for enum / fixed-
  list fields (`options: {value,label}[]`); ARIA listbox; keyboard nav; clearable.
- `src/components/ui/MultiSelect.tsx` — multi-select with removable chips
  (`value: string[]`).
  All four style exclusively via SmartStack tokens (they read `--radius-input`, so
  the project's square/rounded choice — scaffold-theme `radiusControl` — applies);
  zero Tailwind color classes; zero `dark:` prefix (theme swap handles dark mode at
  the token layer).
- `src/components/ui/TruncatedText.tsx` — clips overflowing cell content with an
  ellipsis and shows the full value in a tooltip on hover/focus (reuses the package
  `Tooltip`, gated so the popup only appears when actually truncated —
  `scrollWidth > clientWidth`). Pure layout (no colors, no i18n).
- `src/components/ui/ResponsiveDataTable.tsx` — wrapper around the local
  `@/components/ui/DataTable` that (1) shows/hides columns by viewport breakpoint
  (`minBreakpoint?: 'sm'|'md'|'lg'|'xl'` per column; columns with no `minBreakpoint`
  — code, label, actions — are ALWAYS visible, extra columns appear as the screen
  widens) and (2) clips `truncate` columns via `TruncatedText`. The column drop is
  done in JS (`window.innerWidth`) before reaching `<DataTable>`, so it works on any
  DataTable version. `scaffold-component` emits list pages against this wrapper.
- `src/i18n/locales/{fr,en,it,de}/common.json` — deep-merged with the
  `entityLookup.*`, `dateInput.*`, `enumSelect.*` and `multiSelect.*` namespaces.
  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.
