Skip to content

UN-4288 [FEAT] Add the v1 form system on react-hook-form and zod - #2465

Draft
athul-rs wants to merge 13 commits into
UN-4287-v1-text-inputsfrom
UN-4288-v1-form-system
Draft

athul-rs wants to merge 13 commits into
UN-4287-v1-text-inputsfrom
UN-4288-v1-form-system

Conversation

@athul-rs

@athul-rs athul-rs commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

What

  • The v1 form system under frontend/src/v1/components/patterns/form/:
    • useAppForm({schema, defaultValues}): react-hook-form with a zod schema. A field validates when it first loses focus, then on every change. A failed submit focuses the first invalid field.
    • FormField: binds one value to the v1 Field from P1-T03, so its error shows under the control and marks it invalid.
    • applyServerErrors(form, body): puts each backend error (drf_standardized_errors) under the field its attr names when that field is on screen. Every other error, and any body of another shape, becomes one form-level message.
    • FormFooter: No changes / N unsaved changes / Saving / Cannot save, with Save changes and Discard.
    • useLeaveGuard(dirty, confirm): the browser asks before the tab closes while the form is dirty, and the returned function asks confirm before a dialog closes or the caller navigates.
  • New dependencies: @hookform/resolvers ^7.0.3 and zod ^4.6.7. react-hook-form is already on main.

Why

  • P1-T06 (UN-4288): every v1 form validates, reports errors and guards unsaved work the same way. P1-T20 (schema-driven forms) and the Profile, Human Review settings, LLMWhisperer profile and API Hub admin pages build on it.

How

  • Errors go to a field only when a field for that path is mounted, so an error for a value no field shows, or a schema rule with no path, appears in the footer instead of disabling Save with no message. A field that is hidden drops its error; the next submit validates again.
  • Several errors on one attr are joined. {detail} (middleware), {error} (SDK handler) and malformed bodies fall back to their message or "Could not save. Try again."
  • Callers call form.reset(saved) after a successful save. Discard and the change count compare against the last reset.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • No. Everything is new code under frontend/src/v1, which no route renders yet. v0 does not import it. The two new packages are only imported from v1.

Database Migrations

  • None

Env Config

  • None

Relevant Docs

Related Issues or PRs

Dependencies Versions

  • @hookform/resolvers ^7.0.3 (new)
  • zod ^4.6.7 (new)

Notes on Testing

  • form.test.tsx, 35 tests covering:
    • on-blur then on-change validation;
    • first-invalid focus;
    • server errors on fields, nested fields, hidden fields and form level;
    • malformed bodies;
    • footer states and button states while saving;
    • Discard;
    • the leave guard.
  • Each guard was checked by mutation: 19 mutants, each fails the suite.
  • typecheck:v1, lint:v1 and bun install --frozen-lockfile pass.

Screenshots

  • None: no v1 route renders a form yet. The sample form lives in the tests.

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

athul-rs and others added 6 commits November 5, 2026 13:51
…rver errors, save footer and leave guard

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…eld, accept any error body

- Route server errors by mounted field, not by value: an error for a value no
  field renders, or a schema refine with no path, now shows in the footer and
  no longer locks Save.
- mode onTouched: a field that failed on blur clears as soon as it is fixed.
- Join several server errors on one attr; accept {detail}, {error} and
  malformed bodies with a fallback message.
- Hide the form-level error once the change is reverted.
- Docs: reset(saved) after save, defaultValues read once, the leave guard is
  caller-awaited; no plan task codes in comments.
- Tests for each, plus Saving/clean button states; drop unrelated bun.lock churn.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…en the error body guard

- FormField clears its error on unmount, so the footer re-renders instead of
  staying on "Cannot save" with no message; a submit re-validates.
- applyServerErrors skips null entries and non-string details, and takes the
  first non-blank detail, error or message before the generic fallback.
- Tests: nested field through the footer, hide-then-error, no fallback when a
  field takes the error, whitespace and malformed bodies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@athul-rs athul-rs added the main-only Stays on main: not backported to a release line label Nov 9, 2026
athul-rs added a commit that referenced this pull request Nov 10, 2026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

main-only Stays on main: not backported to a release line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant