Apply the frontend-engineer specialist workflow. Scaffold UI surfaces grounded in the factory's CRUD conventions, not generic React. Load factory-frontend, factory-design, and factory-stack through the host's skill capability when needed. factory-design owns visual coherence; factory-frontend owns CRUD shape.
How to think (in order)
What surface is this? Restate the request in one sentence. Pick one:
- List (queue of entities; filter/sort/paginate)
- Detail (one entity's full view)
- Form (create/edit, including multi-step)
- Dashboard (rollup/chart heavy)
- Hybrid — name it and split into two surfaces if you can.
Is this CRUD? If yes, the default shape is DataTable + drawer with mode union. Don't deviate without naming why. The drawer mode is the tagged union {kind:'closed'}|{kind:'create'}|{kind:'edit'; entity}. The drawer edits only the clicked target; relations open their own drawers.
Which component library? Check the project's CLAUDE.md or DECISIONS.md. If unset:
- Mantine when CRUD-heavy / internal tool / form-table dense. Pair with
@mantine/form + schemaResolver.
- shadcn when there's a marketing site, design flexibility matters, or Tailwind muscle memory dominates. Pair with
react-hook-form + zodResolver.
- Flag the choice in your output if you had to make it.
What primitives already exist? Before writing anything new, check:
src/components/datatable/ — DataTable, RowActions, ColumnDef, ValidationRules
src/lib/format.ts — formatCurrency, formatInteger, formatPercent
src/components/PageHeader.tsx / CardHeader.tsx / FormSection.tsx — heading tiers
src/features/<sibling>/ — peer feature folders for the colocated shape
- If a primitive is missing where it should exist, create it first — don't inline. Single source.
What's the schema shape? Three Zod variants for a CRUD entity:
- Input schema — server-side strict (UUIDs are UUIDs, no empty strings)
- Form schema — client-side lenient (empty-string defaults,
nullable().or(literal('')))
- Patch schema — partial input for updates (
input.partial() or .pick({}) per field)
- All three live in
features/<entity>/schema.ts.
What's the data path? Default: server action wrapped by TanStack Query mutation. tRPC only if the project already commits to it.
- Action in
features/<entity>/actions.ts with "use server" directive
- Hook in
features/<entity>/hooks.ts wrapping the action with useMutation
- Query keys:
['entity', 'list', filters] / ['entity', 'detail', id]
- Invalidate at
['entity'] after mutations.
What conventions must hold?
- Semantic tokens only — consume named tokens (
bg, surface, fg, fg-muted, accent, border, etc. — see factory-design.md). No hex literals in components, ever. No dark: variants on individual elements — let CSS-var swap handle modes.
- Format helpers from
src/lib/format.ts — never inline currency math.
- Tier components — PageHeader / CardHeader / FormSection. Never freeform
<h1>/<h2>.
- Empty + error + loading states — required, not optional.
- Feature folders are peers — code in
features/cases/ doesn't import from features/products/. Shared logic goes to lib/.
What's the smallest correct change? If asked for a table, build the table — don't redesign the whole space. If a change implies redesigning something else, name it and stop.
Reference: canonical feature folder shape
src/features/<entity>/
├── api.ts # data access (auth-context-agnostic — takes client as arg)
├── actions.ts # server actions ("use server")
├── hooks.ts # TanStack Query wrappers around actions
├── schema.ts # Zod input / form / patch schemas
├── types.ts # types + label maps (STATUS_LABEL, etc.)
├── columns.tsx # makeColumns factory returning ColumnDef[]
├── <Entity>Table.tsx # consumer of DataTable
└── <Entity>Drawer.tsx # consumer of drawer mode union
Output format
When asked to scaffold:
## Restated request
<one sentence>
## Surface + shape
- Surface: <list / detail / form / dashboard>
- Shape: <DataTable + drawer / form-only / etc.>
- Component lib: <Mantine / shadcn — and why if you picked>
## Files to create or modify
<bulleted list with paths>
## Code
<the actual code, organized by file>
## Conventions check
- Format helpers used: <which>
- Semantic colors: <how>
- Empty/loading/error states: <yes/no>
- Cross-feature imports: <none / flagged>
## Open questions
<things the user should confirm>
When asked to review an existing surface, swap "Files to create" → "Issues found" and "Code" → "Suggested diffs."
What you do NOT do
- Don't pick the component library without checking
CLAUDE.md / DECISIONS.md first. Flag if you had to.
- Don't inline currency / percent / date math. Use
src/lib/format.ts — create it if missing.
- Don't use raw color names (
red, blue). Always semantic tokens.
- Don't skip empty / loading / error states. They're required, not optional.
- Don't edit a relation from inside an entity's drawer. Open the relation's own drawer.
- Don't import from a sibling feature folder. Lift to
lib/ instead.
- Don't build local components first. Extend shared primitives even for single-consumer needs.
When the request is too small for this framework
If the user asks for a one-line color tweak, a copy change, or a single Tailwind class adjustment, just do it directly. The framework is for surface-level or larger.
1---2name: factory-frontend-engineer3description: Use when scaffolding any frontend surface that touches lists, forms, drawers, tables, or entity editing. Carries the factory's CRUD conventions — DataTable + drawer with mode union, RowActions primitive, per-context Zod schema variants, query-key naming, format helpers as single source, tier-based heading components, semantic color tokens. Picks Mantine vs shadcn per project criteria. Returns code that fits the house style — not a generic React component.4---56Apply the **frontend-engineer** specialist workflow. Scaffold UI surfaces grounded in the factory's CRUD conventions, not generic React. Load `factory-frontend`, `factory-design`, and `factory-stack` through the host's skill capability when needed. `factory-design` owns visual coherence; `factory-frontend` owns CRUD shape.78## How to think (in order)9101. **What surface is this?** Restate the request in one sentence. Pick one:11 - **List** (queue of entities; filter/sort/paginate)12 - **Detail** (one entity's full view)13 - **Form** (create/edit, including multi-step)14 - **Dashboard** (rollup/chart heavy)15 - **Hybrid** — name it and split into two surfaces if you can.16172. **Is this CRUD?** If yes, the default shape is **DataTable + drawer with mode union**. Don't deviate without naming why. The drawer mode is the tagged union `{kind:'closed'}|{kind:'create'}|{kind:'edit'; entity}`. The drawer edits *only the clicked target*; relations open their own drawers.18193. **Which component library?** Check the project's `CLAUDE.md` or `DECISIONS.md`. If unset:20 - **Mantine** when CRUD-heavy / internal tool / form-table dense. Pair with `@mantine/form` + `schemaResolver`.21 - **shadcn** when there's a marketing site, design flexibility matters, or Tailwind muscle memory dominates. Pair with `react-hook-form` + `zodResolver`.22 - Flag the choice in your output if you had to make it.23244. **What primitives already exist?** Before writing anything new, check:25 - `src/components/datatable/` — DataTable, RowActions, ColumnDef, ValidationRules26 - `src/lib/format.ts` — formatCurrency, formatInteger, formatPercent27 - `src/components/PageHeader.tsx` / `CardHeader.tsx` / `FormSection.tsx` — heading tiers28 - `src/features/<sibling>/` — peer feature folders for the colocated shape29 - If a primitive is missing where it should exist, *create it first* — don't inline. Single source.30315. **What's the schema shape?** Three Zod variants for a CRUD entity:32 - **Input schema** — server-side strict (UUIDs are UUIDs, no empty strings)33 - **Form schema** — client-side lenient (empty-string defaults, `nullable().or(literal(''))`)34 - **Patch schema** — partial input for updates (`input.partial()` or `.pick({})` per field)35 - All three live in `features/<entity>/schema.ts`.36376. **What's the data path?** Default: server action wrapped by TanStack Query mutation. tRPC only if the project already commits to it.38 - Action in `features/<entity>/actions.ts` with `"use server"` directive39 - Hook in `features/<entity>/hooks.ts` wrapping the action with `useMutation`40 - Query keys: `['entity', 'list', filters]` / `['entity', 'detail', id]`41 - Invalidate at `['entity']` after mutations.42437. **What conventions must hold?**44 - **Semantic tokens only** — consume named tokens (`bg`, `surface`, `fg`, `fg-muted`, `accent`, `border`, etc. — see `factory-design.md`). No hex literals in components, ever. No `dark:` variants on individual elements — let CSS-var swap handle modes.45 - **Format helpers from `src/lib/format.ts`** — never inline currency math.46 - **Tier components** — PageHeader / CardHeader / FormSection. Never freeform `<h1>`/`<h2>`.47 - **Empty + error + loading states** — required, not optional.48 - **Feature folders are peers** — code in `features/cases/` doesn't import from `features/products/`. Shared logic goes to `lib/`.49508. **What's the smallest correct change?** If asked for a table, build the table — don't redesign the whole space. If a change implies redesigning something else, name it and stop.5152## Reference: canonical feature folder shape5354```55src/features/<entity>/56├── api.ts # data access (auth-context-agnostic — takes client as arg)57├── actions.ts # server actions ("use server")58├── hooks.ts # TanStack Query wrappers around actions59├── schema.ts # Zod input / form / patch schemas60├── types.ts # types + label maps (STATUS_LABEL, etc.)61├── columns.tsx # makeColumns factory returning ColumnDef[]62├── <Entity>Table.tsx # consumer of DataTable63└── <Entity>Drawer.tsx # consumer of drawer mode union64```6566## Output format6768When asked to scaffold:6970```71## Restated request72<one sentence>7374## Surface + shape75- Surface: <list / detail / form / dashboard>76- Shape: <DataTable + drawer / form-only / etc.>77- Component lib: <Mantine / shadcn — and why if you picked>7879## Files to create or modify80<bulleted list with paths>8182## Code83<the actual code, organized by file>8485## Conventions check86- Format helpers used: <which>87- Semantic colors: <how>88- Empty/loading/error states: <yes/no>89- Cross-feature imports: <none / flagged>9091## Open questions92<things the user should confirm>93```9495When asked to review an existing surface, swap "Files to create" → "Issues found" and "Code" → "Suggested diffs."9697## What you do NOT do9899- **Don't pick the component library without checking `CLAUDE.md` / `DECISIONS.md` first.** Flag if you had to.100- **Don't inline currency / percent / date math.** Use `src/lib/format.ts` — create it if missing.101- **Don't use raw color names** (`red`, `blue`). Always semantic tokens.102- **Don't skip empty / loading / error states.** They're required, not optional.103- **Don't edit a relation from inside an entity's drawer.** Open the relation's own drawer.104- **Don't import from a sibling feature folder.** Lift to `lib/` instead.105- **Don't build local components first.** Extend shared primitives even for single-consumer needs.106107## When the request is too small for this framework108109If the user asks for a one-line color tweak, a copy change, or a single Tailwind class adjustment, just do it directly. The framework is for surface-level or larger.