PR #159 merged: fix: surface form validation errors generically across the app
What Adele saw
Edit a job, click Save, page stays on /edit, no visible change. From her POV: "it hasn't saved." Screen recording confirmed the POST returns 422 but no inline error appears.
Root cause (Edit job)
Edit.tsx rendered InputError per-field. Of 16 form fields, 8 had no error display wired up (parking, access_instructions, budget_max, num_bathrooms, has_pets, schedule_type, budget_type, property_category). A 422 on any of those was silent — and a future field added without the InputError wiring would silently break the same way.
But this isn't unique to Edit job — every form in the app relies on the dev remembering to add per-field error JSX. So the fix needs to be generic.
The fix
Core mechanism
-
resources/js/hooks/use-form-error-focus.ts— given theerrorsmap from Inertia'suseFormor<Form>render prop:- Marks each erroring field with
aria-invalid="true". The design-system Input/Textarea/Select already paint that red via thearia-invalid:border-destructiveTailwind variant — red border for free. - Injects an inline
<p>error message after each erroring field, matching the styling of hand-written InputError elements. - Skips injection if an existing
InputErroris already rendering for that field (detected via adata-slot="input-error"marker added to the sharedInputErrorcomponent) — no double-rendering. - Scrolls a summary ref into view so feedback isn't missed on long forms.
- Cleans up markers + injected messages on the next submit.
- Marks each erroring field with
-
resources/js/components/form-error-surface.tsx— a drop-in<FormErrorSurface errors={errors} />component that wires the hook + renders anAlertErrorsummary banner listing every error. Renders nothing when errors are empty.
Rollout
Applied to every form in the codebase (23 files total):
- Edit job (the original repro)
- Post-job wizard (all 6 steps)
- Cleaner onboarding (steps 1-4) + verification upload
- Auth: register (already covered separately), login + 2FA + confirm-password + forgot-password + verify-email
- Settings: addresses, two-factor
- Admin: support reply, verifications rejection
- Other: contact form, message thread reply, support widget, client job review
Result
Any 422 anywhere in the app surfaces:
- Summary banner at top of the form (always)
- Red border on each erroring field (where
name/idmatches the validation key — true for the vast majority of fields) - Inline message after each field (auto-injected unless the form already has an InputError for that field)
Adding new fields requires no error-handling JSX — the surface catches everything.
Test plan
- Pest browser test (
EditJob422SurfaceTest) — triggers parking > 200 chars, asserts the summary banner + inline error text both appear, asserts DB unchanged - Visual local repro on Edit job: parking > 200 + access_instructions > 500 → both fields red-bordered, both inline errors injected, summary banner with both at top (screenshot)
- Visual local repro on settings/addresses: submit empty → 3 errors in summary banner
- React re-render survival: typed in another field after error rendered — injected message persists
- Cleanup: fixed the field + re-saved → injected message removed, redirect to show page
- Pint/Prettier/ESLint/TypeScript clean