← ClaudeAtlas

form-ruleslisted

Form discipline for TypeScript — the schema is the only converter, input and output are different types, a required field is seeded blank not absent, submit is never gated on validity, and each failure names itself.
g-bastianelli/nuthouse · ★ 1 · API & Backend · score 67
Install: claude install-skill g-bastianelli/nuthouse
# subroutine — form discipline Apply this to a form's schema and the components that bind it. The nearest `AGENTS.md` picks the form, validation, design-system and i18n libraries and wins; the APIs below assume Zod >= 4.1 and React Hook Form. A schema no form binds is out of scope. ## Convert in the schema, nowhere else - An `<input>` holds a string: convert in a codec, never in the form library (`valueAsNumber`, `setValueAs`) or by hand. Two converters disagree on what empty means — `valueAsNumber` yields `NaN`, which `z.number()` rejects with an untranslated developer string. - Split the codec: the input schema owns the shape, so `decode` stays a bare `Number`; the exported value schema owns the range, so `-1` is told to be positive instead of unreadable. - Blank, malformed and out-of-range are three failures with three messages, and `abort` after blank. Use the library's format constants, never a copied regex. - Mirror the contract's bounds: a form looser than its API turns an inline message into a failed request. - Route messages through the repo i18n as thunks (`{ error: () => m.key() }`) — a plain string in a module-scope schema freezes the locale active at import. - A closed option set (Select, Switch, radio) parses nothing: convert in the control's `onChange`. ```ts // `abort` matters: without it a blank field fails both checks and reports // "invalid" for a value the user never entered. const slaveId = z.codec( z .string() .trim()