Skip to content

feat(forms): adopt react-hook-form + valibot resolver, Y.1564 pilot (#325) - #328

Merged
krisarmstrong merged 1 commit into
mainfrom
feat/325-react-hook-form
May 27, 2026
Merged

feat(forms): adopt react-hook-form + valibot resolver, Y.1564 pilot (#325)#328
krisarmstrong merged 1 commit into
mainfrom
feat/325-react-hook-form

Conversation

@krisarmstrong

Copy link
Copy Markdown
Collaborator

Adds the forms stack for stem and converts the most complex
test-config form (Y.1564 / MEF, 13 fields + cross-field rule) as the
pilot. The 6 remaining ConfigForms follow the same pattern and are
tracked in #325 for sweep.

Stack additions

  • react-hook-form@7.x — uncontrolled form state, no re-render storms
  • @hookform/resolvers@5.x — valibot adapter
  • src/schemas/configs.ts — all 7 test-config schemas: RFC 2544/2889/6349,
    Y.1564/1731, TSN, TrafficGen. Range bounds derived from the existing
    HTML5 min/max attributes plus standards constraints (VLAN 0-4094,
    PCP 0-7, MEG level 0-7, etc.). Cross-field rules: Y.1564 FDV ≤ FD,
    RFC6349 minRTT ≤ maxRTT, TSN jitter ≤ latency.
  • src/forms/useConfigForm.ts — shared helper that wires a
    parent-owned config + setConfig to react-hook-form + valibot, with
    onChange validation. Only forwards shape-valid values back to the
    parent so the test runner never sees a bad config on dispatch.

Y.1564 pilot

  • Every migrated from manual onChange + Number(e.target.value)
    to register('field', { valueAsNumber: true }).
  • Field-level errors rendered inline via the FieldError component.
  • Cross-field error (FDV > FD) rendered at the form footer.
  • All derived displays (summary panel, conditional PCP, frame-size
    checkboxes, duration counts) now read from form.watch() so they
    stay in sync with internal form state.

Tests

  • src/schemas/configs.test.ts — 22 tests covering range bounds + the
    three cross-field rules across all 7 schemas.

Out of scope (for the sweep follow-up)

  • Convert RFC 2544, 2889, 6349, Y.1731, TSN, TrafficGen ConfigForms
    using the same pattern (schemas + helper already in place).
  • Convert App.tsx's login + MFA verify forms.
  • Convert components/recovery/RecoveryForm.tsx,
    components/setup/SetupWizard.tsx,
    pages/account/security/SecurityPage.tsx + TotpSetupModal.tsx.

Closes part of #325; remaining sweep tracked in the same issue.

…325)

Adds the forms stack for stem and converts the most complex
test-config form (Y.1564 / MEF, 13 fields + cross-field rule) as the
pilot. The 6 remaining ConfigForms follow the same pattern and are
tracked in #325 for sweep.

Stack additions
- react-hook-form@7.x — uncontrolled form state, no re-render storms
- @hookform/resolvers@5.x — valibot adapter
- src/schemas/configs.ts — all 7 test-config schemas: RFC 2544/2889/6349,
  Y.1564/1731, TSN, TrafficGen. Range bounds derived from the existing
  HTML5 min/max attributes plus standards constraints (VLAN 0-4094,
  PCP 0-7, MEG level 0-7, etc.). Cross-field rules: Y.1564 FDV ≤ FD,
  RFC6349 minRTT ≤ maxRTT, TSN jitter ≤ latency.
- src/forms/useConfigForm.ts — shared helper that wires a
  parent-owned config + setConfig to react-hook-form + valibot, with
  onChange validation. Only forwards shape-valid values back to the
  parent so the test runner never sees a bad config on dispatch.

Y.1564 pilot
- Every <input> migrated from manual onChange + Number(e.target.value)
  to register('field', { valueAsNumber: true }).
- Field-level errors rendered inline via the FieldError component.
- Cross-field error (FDV > FD) rendered at the form footer.
- All derived displays (summary panel, conditional PCP, frame-size
  checkboxes, duration counts) now read from form.watch() so they
  stay in sync with internal form state.

Tests
- src/schemas/configs.test.ts — 22 tests covering range bounds + the
  three cross-field rules across all 7 schemas.

Out of scope (for the sweep follow-up)
- Convert RFC 2544, 2889, 6349, Y.1731, TSN, TrafficGen ConfigForms
  using the same pattern (schemas + helper already in place).
- Convert App.tsx's login + MFA verify forms.
- Convert components/recovery/RecoveryForm.tsx,
  components/setup/SetupWizard.tsx,
  pages/account/security/SecurityPage.tsx + TotpSetupModal.tsx.

Closes part of #325; remaining sweep tracked in the same issue.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@krisarmstrong
krisarmstrong enabled auto-merge (squash) May 27, 2026 14:38
@github-actions

Copy link
Copy Markdown
Contributor

License Compliance Report

All dependencies pass license compliance checks

Go Dependencies

  • Unknown: 31 package(s)
  • MIT: 26 package(s)
  • BSD-3-Clause: 16 package(s)
  • Apache-2.0: 11 package(s)
  • BSD-2-Clause: 1 package(s)

npm Dependencies

See full report in workflow artifacts

Allowed Licenses: MIT, Apache-2.0, BSD-*, ISC, CC0-1.0, MPL-2.0
Forbidden: GPL, AGPL, SSPL (strong copyleft)

@krisarmstrong
krisarmstrong merged commit bf7937a into main May 27, 2026
26 of 28 checks passed
@krisarmstrong
krisarmstrong deleted the feat/325-react-hook-form branch May 27, 2026 14:42
krisarmstrong pushed a commit that referenced this pull request May 27, 2026
… + Setup (#332)

Final sweep completes the forms-stack migration started in stems #328
(foundation + Y.1564 pilot), #330 (6 ConfigForms), and #331 (MFA
setup + disable modals). All 13 forms in stem are now on
react-hook-form + valibot.

Migrations
- App.tsx login form — username + password, uses LoginSchema
- App.tsx MFA verify form — 6-digit code, uses MfaVerifySchema
- components/recovery/RecoveryForm.tsx — token + new password +
  confirm, uses new RecoveryCompleteSchema (cross-field check for
  password == confirmPassword). PasswordField sub-component refactored
  to accept UseFormRegisterReturn so register('password') threads
  through in one line.
- components/setup/SetupWizard.tsx — password + confirm (username is
  fixed from setup-status prop, not user-edited). Uses revised
  SetupWizardSchema (drops the username field that the form doesn't
  actually accept user input for; the v.check() cross-field rule
  remains).

Schema additions
- RecoveryCompleteSchema in src/schemas/auth.ts. The recovery-token
  flow is distinct from the recovery-code flow (RecoveryEnterSchema
  for the latter, which is still defined but not yet wired — that's
  for a future entry-by-code flow).

Out of scope
- The hand-rolled `passwordValid` / `passwordsMatch` derived state in
  the old RecoveryForm is gone — react-hook-form's formState.isValid
  now drives the submit button. The submit handler no longer
  re-validates because the resolver already gated it.

Closes #332.
krisarmstrong added a commit that referenced this pull request May 27, 2026
… + Setup (#332) (#333)

Final sweep completes the forms-stack migration started in stems #328
(foundation + Y.1564 pilot), #330 (6 ConfigForms), and #331 (MFA
setup + disable modals). All 13 forms in stem are now on
react-hook-form + valibot.

Migrations
- App.tsx login form — username + password, uses LoginSchema
- App.tsx MFA verify form — 6-digit code, uses MfaVerifySchema
- components/recovery/RecoveryForm.tsx — token + new password +
  confirm, uses new RecoveryCompleteSchema (cross-field check for
  password == confirmPassword). PasswordField sub-component refactored
  to accept UseFormRegisterReturn so register('password') threads
  through in one line.
- components/setup/SetupWizard.tsx — password + confirm (username is
  fixed from setup-status prop, not user-edited). Uses revised
  SetupWizardSchema (drops the username field that the form doesn't
  actually accept user input for; the v.check() cross-field rule
  remains).

Schema additions
- RecoveryCompleteSchema in src/schemas/auth.ts. The recovery-token
  flow is distinct from the recovery-code flow (RecoveryEnterSchema
  for the latter, which is still defined but not yet wired — that's
  for a future entry-by-code flow).

Out of scope
- The hand-rolled `passwordValid` / `passwordsMatch` derived state in
  the old RecoveryForm is gone — react-hook-form's formState.isValid
  now drives the submit button. The submit handler no longer
  re-validates because the resolver already gated it.

Closes #332.

Co-authored-by: Kris Armstrong <kris.armstrong@icloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant