Imported from hstastna/simple-form-generator (
AGENTS.md). Install upstream withnpx skills add hstastna/simple-form-generator. Copyright stays with the author.
Project notes
Commands
npm run devstarts the dev server.- Verify changes with
npm run typecheck,npm run lint,npm test,npm run build. next lintwas removed in Next 16 — the lint scripts use the ESLint CLI with the flat config ineslint.config.mjs.
Structure
src/appholds the App Router files (layout.tsx,page.tsx,globals.css). Each tab lives insrc/components/tabs/<TabName>/, with its owncomponents/folder for parts used only by that tab.src/schemasholds the zod schemas that define the JSON config the app accepts;src/contextholds shared form state, with the rest insrc/constants.ts,src/utils.tsandsrc/formActions.ts.@/maps tosrc/(tsconfig.jsonand jest'smoduleNameMapper) — import as@/components/...instead of long relative paths.- A new field type needs two edits: add it to
formFieldTypesinsrc/schemas/formFieldSchema.ts, and add acasefor it inResultTab/components/FormField.tsx. Without the second one the form renders "Unknown field type". - The
on*keys in the JSON hold a handler name, never code.withResolvedHandlers(src/formActions.ts) turns a name listed informActionNamesinto the real function and drops any other name, so a string never reaches the DOM; unlisted names are reserved for the code the app will generate. - Field
onChangeandonBlurare validated but never run: the field components spreadregister()last, so react-hook-form owns those two events.
Dependencies
typescriptstays on 6.x: typescript-eslint (bundled by eslint-config-next) caps at<6.1.0; TS 7 breaks the lint toolchain.eslintstays on 9.x: eslint-plugin-react does not support ESLint 10 yet.@types/nodematches the Node runtime major (24, see Dockerfile).- Before bumping any of these, re-check
npm view <pkg> peerDependencies— the goal is zero warnings fromnpm install. .npmrcsetsmin-release-age=7(needs npm 11.6+): installs only resolve versions published at least 7 days ago, so a brand-new release not being found is expected.npm ciis unaffected — it installs the lockfile as-is.- Commit
package-lock.jsonwith everypackage.jsonchange; the Docker build runsnpm ciand fails if the two disagree.
Testing
- Jest with React Testing Library and jsdom. Tests sit next to the code they cover, named
<file>.test.ts(x). npm testalways writes a coverage report tocoverage/.- jsdom is missing browser APIs the CodeMirror editor needs;
jest.mocks.tspatchesmatchMediaandRange.getClientRects. Add further global patches there, not in single test files.
Conventions and gotchas
- Next 16 changed APIs and conventions — check the guides in
node_modules/next/dist/docs/before writing Next-specific code. - Formatting comes from
.prettierrc(single quotes, semicolons, 80 columns, 2 spaces) — runnpm run prettierbefore committing. - Commit messages follow Conventional Commits:
feat:,fix:,chore:,refactor:. - Tailwind CSS v4 dropped
cursor: pointeron buttons; the base-layer rule insrc/app/globals.cssrestores it — keep it. - The
smbreakpoint is overridden to 400px insrc/app/globals.css(Tailwind's default is 640px). Tailwind is mobile-first, sosm:compiles tomin-width: 400px. If you change it, update thesizesattribute of theImageinsrc/app/layout.tsxto match. - Never use deprecated Tailwind class names. v4 keeps old ones as working aliases, and neither ESLint nor the build flags them —
bg-gradient-to-*is nowbg-linear-to-*. The Tailwind VS Code extension is the only thing that reports them. - Dark mode follows
prefers-color-schemeviadark:variants. Useneutral-*instead ofgray-*for filled dark surfaces (Tailwind'sgrayis blue-tinted). - In
ResultTab, fields/buttons without anidin the JSON config get deterministic fallback ids (field-<index>); the React key, the react-hook-form registration, and theerrors[...]lookup must always use the same id. - Tabs render conditionally, so
ResultTabfully remounts on tab switch. A future "persist form data across tabs" feature must revisit the index-based fallback ids first.