Skill
Build forms in silver-castle apps/web — the exact RHF + manual Zod safeParse pattern (no resolvers), Field primitives, busy states, toasts, and invalidation.
Playbook (mirrored from disk)
Frontend Form
House pattern (deliberate — NO @hookform/resolvers, ever):
- Local
type FormVals— all string fields (dates/numbers as strings) +const blank: FormVals. const { register: r, handleSubmit, reset, watch } = useForm<FormVals>({ defaultValues: blank })useEffectreset when modalopenflips.
onSubmit: build payload (trim,Number(...),undefinedfor optionals) →
Schema.safeParse(payload)with the SHARED schema from@asset-rise/shared→
on failure show first issue in a Hebrew inline error box.await mutation.mutateAsync(parsed.data)in try/catch:
success →toast.celebrate('<Hebrew>')+void utils.<domain>.list.invalidate()+onClose();
failure →setSubmitErr(msg)+toast.show(msg)(server TRPCError messages are already Hebrew).- Fields:
<Field label error>+ forwardRef’d<Input {...r('name', { required: true })} />
(register rules = instant client checks; Zod is the real gate). - Busy:
const busy = mutation.isLoading(v4 naming) →loading={busy} disabled={busy}+
Hebrew label swap ('שומר…').
Allowed: dynamic arrays as useState<string[]> alongside RHF (e.g., poll options).
Forbidden: useState-only forms; inline duplicate schemas (schema lives in packages/shared —
missing one → coordinate with Forge via Vision).
Gates: same as frontend-component.