# Factory Frontend Engineer

> 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.

- Skill: `nonlinear-xyz/factory-frontend-engineer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nonlinear-xyz/factory-frontend-engineer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nonlinear-xyz/factory-frontend-engineer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: nonlinear-xyz (https://skillmd.com/u/nonlinear-xyz)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/nonlinear-xyz/factory-frontend-engineer

---


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)

1. **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.

2. **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.

3. **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.

4. **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.

5. **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`.

6. **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.

7. **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/`.

8. **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.

