FormGenerator
creation : FormGenerator
An entry from the chronicle of built things — its purpose, its materials, and the bargains struck along the way.
Provenance first: FormGenerator began as a hiring exercise and outgrew it. It takes a JSON schema describing fields and renders a working form — validation, conditional visibility, error announcement and focus management included — with no form library and no validation library underneath. Hand-rolling both is the point; the data modeling is the exhibit.
Fields are a discriminated union on their type, so an invalid schema does not compile — a select without options is unrepresentable — and the renderer narrows per case with no casts anywhere. The same compile-time-checklist trick appears three times over: the renderer's switch, the validation registry and the visibility operator dispatch each assign an unhandled case to never, so adding a field type, a rule or an operator stays a type error until every consumer handles it.
The engine is a headless hook. It owns state, validation timing and submission, and is exported on its own; the shipped component is only the default renderer over it. Fields the user cannot interact with — hidden by a visibility condition, or carrying a type the renderer does not know — are skipped by validation and dropped from the payload, so an invisible required field can never block a submit with nothing on screen to fix.
tech_stackInventory of Materials
- React 19Interface
- TypeScriptLanguage
- ViteBuild
- Tailwind CSSStyling
- VitestTests



Engineering Tradeoffs
The submit payload is typed as an open record rather than inferred per schema. Precise payload types need the schema as a compile-time literal, and these schemas arrive as runtime data — a wider honest type beats a narrower dishonest one.
Every schema failure fails open. An invalid regex, an unknown visibility operator or a duplicate field id warns and lets the form render, because a schema typo must not brick a form in production. The cost is that a broken schema is quiet — the console warning is the only signal, and nobody reads the console.
Validators receive the whole rules object rather than only their own config. TypeScript cannot correlate a registry entry with its config through a union key without a cast, and this codebase has none — the looser validator signature is what buys zero casts everywhere else.