Skill

Owner: Friday · Team: Dev Team · Source: ~/.openclaw/dev-shared/skills/frontend-form/SKILL.md

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):

  1. Local type FormVals — all string fields (dates/numbers as strings) + const blank: FormVals.
  2. const { register: r, handleSubmit, reset, watch } = useForm<FormVals>({ defaultValues: blank })
    • useEffect reset when modal open flips.
  3. onSubmit: build payload (trim, Number(...), undefined for optionals) →
    Schema.safeParse(payload) with the SHARED schema from @asset-rise/shared
    on failure show first issue in a Hebrew inline error box.
  4. 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).
  5. Fields: <Field label error> + forwardRef’d <Input {...r('name', { required: true })} />
    (register rules = instant client checks; Zod is the real gate).
  6. 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.