Factory forms builder
Skill nonlinear-xyz/factory-kit/skills/factory-forms-builder
Personal Claude Code factory — synthesized cross-build skills, specialist subagents, and slash commands. Phase 0.
npx -y skills add nonlinear-xyz/factory-kit --skill factory-forms-builderAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 9 stars9 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when building multi-step forms, complex intake flows, drawer-CRUD forms, or anything beyond a one-shot Mantine form. Carries the factory's form conventions — react-hook-form + Zod via resolver, three Zod variants (server strict / client lenient / patch), modular section files from day one, field registry for AI-context-aware multi-step forms, declarative conditional visibility with auto-cleanup, debounced auto-save with dirty tracking, masked inputs for sensitive fields, dynamic field arrays, S3 presigned upload, `useTransition` for async submission, question numbering via context, review section with gap detection. Outputs form code that fits the house style — not bespoke form-state machinery.
SKILL.md
7.4 KB, as published. Nobody here has run it
Apply the forms-builder specialist workflow. Build forms grounded in the factory's conventions, not generic react-hook-form code. Load factory-forms through the host's skill capability when needed.
How to think (in order)
-
What kind of form? Pick one:
- Single-step drawer-CRUD form — create/edit one entity → use the drawer mode union from
factory-frontend.md - Single-step page form — login, settings, simple intake → vanilla react-hook-form
- Multi-step wizard — onboarding, complex intake → section files + field registry
- Inline edit — DataTable cell editing → different pattern, see
factory-frontend.md
- Single-step drawer-CRUD form — create/edit one entity → use the drawer mode union from
-
How many fields?
- < 10 — inline schemas, hand-written JSX
- 10–30 — section files but no registry needed
- 30+ or AI-context-aware — field registry
-
Schemas — three variants (see
factory-forms.md):- Server input — strict (real types, no empty-string fallbacks)
- Client form — lenient (accepts empty strings because controlled inputs default to
'') - Patch — partial input for updates
-
Conditional visibility? If any field's display depends on another's value:
- Declarative rules in
src/lib/utils/conditional-visibility.ts - Auto-cleanup when parent toggles off — set hidden child's value to
undefined - Otherwise stale conditional data sneaks into submissions
- Declarative rules in
-
Sensitive fields? SSN, EIN, government IDs, financial accounts:
- Masked input (
mask="99-9999999"etc.) - KMS encryption at rest — see
factory-security.md - Mask in display, decrypt only at the handler that returns plaintext
- Masked input (
-
Auto-save? If the form takes more than a couple minutes to fill out:
- 2s debounce on field blur
- 3min safety-net interval flush
- Server accepts the lenient schema during auto-save; strict schema on submission
-
Dynamic field arrays? (contacts, beneficial owners, line items)
useFieldArrayfrom react-hook-form- Max-count rule via
rules: { maxLength: { value: N, message: '...' } } - Add/remove buttons; consider drag-reorder for ordered arrays
-
File uploads? Direct-to-S3 with presigned URLs (don't proxy through your server):
- Server action generates upload URL
- Client PUTs file to S3
- Server action confirms upload (records fileKey)
-
Submission UX?
useTransitionwrapping the async submitisPendinggates the submit button- Toast on success / error
- Invalidate the right TanStack Query keys on success
Reference: canonical drawer-CRUD form skeleton
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; // or mantine-form-zod-resolver
import { customerFormSchema } from './schema';
import { useCreateCustomer, useUpdateCustomer } from './hooks';
type CustomerDrawerMode =
| { kind: 'closed' }
| { kind: 'create' }
| { kind: 'edit'; customer: Customer };
export function CustomerDrawer({ mode, onClose }: { mode: CustomerDrawerMode; onClose: () => void }) {
const form = useForm({
resolver: zodResolver(customerFormSchema),
defaultValues: emptyCustomerFormValues,
});
const createMutation = useCreateCustomer();
const updateMutation = useUpdateCustomer();
const [isPending, startTransition] = useTransition();
useEffect(() => {
if (mode.kind === 'edit') {
form.reset(toFormValues(mode.customer));
} else if (mode.kind === 'create') {
form.reset(emptyCustomerFormValues);
}
}, [mode.kind, mode.kind === 'edit' ? mode.customer.id : null]);
const onSubmit = form.handleSubmit((values) => {
startTransition(async () => {
const mutation = mode.kind === 'edit' ? updateMutation : createMutation;
const result = await mutation.mutateAsync(values);
if ('error' in result) { toast.error(result.error); return; }
toast.success(mode.kind === 'edit' ? 'Updated' : 'Created');
onClose();
});
});
return (
<Drawer opened={mode.kind !== 'closed'} onClose={onClose}>
<form onSubmit={onSubmit}>
{/* fields */}
<Button type="submit" loading={isPending}>{mode.kind === 'edit' ? 'Save' : 'Create'}</Button>
</form>
</Drawer>
);
}
Reference: field registry shape (for ≥30-field forms)
// src/lib/fields/types.ts
export type FieldDef = {
id: string;
label: string;
type: 'text' | 'email' | 'masked' | 'select' | 'date' | 'checkbox' | 'array';
required?: boolean;
validation: z.ZodTypeAny;
options?: { value: string; label: string }[];
mask?: string;
sensitive?: boolean;
aiContext?: string; // for AI-augmented review / autofill
};
// src/lib/fields/sections/section-1-company.ts
export const SECTION_1_FIELDS: FieldDef[] = [
{ id: 'company_name', label: 'Legal name', type: 'text', required: true, validation: z.string().min(1) },
{ id: 'ein', label: 'EIN', type: 'masked', mask: '99-9999999', required: true, sensitive: true, validation: z.string().regex(/^\d{2}-\d{7}$/) },
// ...
];
Output format
## Restated request
<one sentence>
## Form shape
- Type: <drawer-CRUD / page / multi-step / inline edit>
- Field count: <count>
- Field registry: <yes / no — why>
- Conditional visibility: <yes / no>
- Auto-save: <yes / no>
- Sensitive fields: <list, with encryption strategy>
## Schemas
- Server input: <path>
- Client form: <path>
- Patch: <path>
## Files to create or modify
<bulleted with paths>
## Code
<organized by file>
## Conventions check
- Three Zod variants: <yes>
- Drawer mode union (if applicable): <yes>
- useTransition wrapping submit: <yes>
- Conditional auto-cleanup (if applicable): <yes>
- Sensitive fields encrypted at rest: <yes / N/A>
## Open questions
<things the user should confirm>
What you do NOT do
- Don't write a monolithic single-file form. Modular section files from day one.
- Don't unify server and client schemas. Server strict, client lenient.
- Don't leave stale conditional data in submissions. Auto-cleanup on visibility change.
- Don't proxy file uploads through your server. Direct-to-S3 with presigned URLs.
- Don't store SSN / PHI / government IDs in plaintext. KMS-at-rest, mask in display.
- Don't
awaitthe audit log on the submission path. Fire-and-forget. - Don't skip
useTransition. Double-submission is a real bug class. - Don't reach for the field registry for a 5-field form. Reserved for ≥30-field or AI-context-aware forms.
When the request is too small for this framework
If the user asks to add one field to an existing form or change validation on a single field, do it directly. The framework is for new forms, new sections, or substantial form-state refactors.