Repository navigation
refactor(onboarding): build basic information once through useHeadlessForm - #1440
gabrielseco wants to merge 2 commits into
Conversation
…sForm Basic information moves from rebuilding the form on every change to useHeadlessForm's buildOnce strategy, with validation, value changes and submit parsing all going through the hook. useHeadlessForm's buildOnce now keeps form null (isBuilding) until the first replay of the latest values has resolved visibility, keeps the previous form while a jsfModify rebuild resolves, and drops it when the schema changes. This makes the read-only review, headless consumers and first paint see the right conditional fields without any per-flow code. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…hrows Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
📦 Bundle Size Report
Size Limits
Largest Files (Top 5)
View All Files (283 total)
✅ Bundle size check passed |
|
Deploy preview for adp-cost-calculator ready!
Deployed with vercel-action |
|
Deploy preview for remote-flows ready!
Deployed with vercel-action |
📊 Coverage Report✅ Coverage increased! 🎉
Detailed BreakdownLines Coverage
Statements Coverage
Functions Coverage
Branches Coverage
✅ Coverage check passed |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 28f8206. Configure here.
| cancelled = true; | ||
| }; | ||
| }, [validate, isBuildOnce]); | ||
| }, [validateForm, builtForm, schema, isBuildOnce]); |
There was a problem hiding this comment.
Form resolves without employment values
High Severity
The first buildOnce replay can finish before employment.basic_information is in values. isBuilding then drops and nothing replays again unless onValuesChange runs, so a cached or faster schema leaves conditionals hidden for read-only review and headless consumers.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 28f8206. Configure here.


Summary
The Onboarding basic information step now builds its form once and works out which fields to show from the values it holds, instead of rebuilding the form on every keystroke. No change for users or partners.
Why
checkFieldUpdateson mount, and visibility lost whenoptionswere passed inline. Here they move into the hook, so the next flow gets them for free.Stacked on #1433. Merge that first; this PR then retargets to
main.What changed
Toggle details
useHeadlessFormbuildOnce(src/common/useHeadlessForm.ts, internal):formstaysnulland the newisBuildingistrueuntil the first replay of the latest values has resolved visibility. Nothing can read a form with default visibility. That one rule fixes the read-only review, headless consumers and first paint.jsfModifyrebuild, the previous resolved form is kept until the new one is ready. Otherwise the step would flip to loading, remount and lose what the user typed.schema(another country) goes back tonullinstead of showing the old country's fields.rebuildis unchanged (isBuildingis alwaysfalse).useBasicInformationSchema(src/flows/Onboarding/api.ts): abuildOncehook likeuseContractDetailsSchema. It returnsisLoading || isBuilding, so the step never mounts with empty fields (itsdefaultValueswould be computed from them). Its query key is new (onboarding-basic-information-schema); the request is the same.useOnboarding(src/flows/Onboarding/hooks.tsx):initialValues, thenemployment.basic_information, then the step's saved values, then livefieldValueswhile the step is current. Before, it gotmergedFormValues, which on other steps held that step's data. Basic information has no money fields, so the saved values are already in form units.handleValidation,parseFormValuesandcheckFieldUpdates(viaonValuesChange) for basic information go through the hook. The localuseJSONSchemawrapper is gone.useJSONSchemaForminOnboarding/api.tsstays, becauseJsonSchemaComparisonstill uses it.isBuildingthrough itsisLoading, since it uses the same strategy. It still replays only the mounted step'sfieldValues: replaying saved values would need cents converted to form units first. That's noted in the rollout doc.docs/USE_HEADLESS_FORM_ROLLOUT.md: documents thebuildOncecontract, adds an "AdoptingbuildOncein a step" checklist and marks basic information done.No public API change: nothing new is exported from
src/index.tsxorinternals.Screenshots
N/A
Related Resources
Testing
src/flows/Onboarding/tests/basicInformationBuildOnce.test.tsxcovers each #1430 pitfall through the real flow, with MSW. Each test was checked against a deliberately broken hook:seniority_dateon first paint{}, or the step's values leave out the employmentinitialValuesreveal it on first paint{}{}, or the employment is left outoptions, and the revealed field stayscheckFieldUpdatesstops callingonValuesChangeuseOnboarding()sees it visible without calling anything{}, or the employment is left outThe existing
OnboardingFlow.test.tsxseniority radio test and the "no flickering" read-only test also fail under those breaks.useHeadlessForm.test.tsxadds: no form before resolution, previous form kept during ajsfModifyrebuild (fails if the hook hands outnullthere), and a schema change drops the old form.npx vitest run src/flows/Onboarding src/commonTested against the
example/app in a browserFeature flag: N/A
🤖 Generated with Claude Code
Note
Medium Risk
Touches core onboarding form lifecycle and conditional-field visibility; behavior is intended to be unchanged but regressions could affect first paint, review, and custom UIs.
Overview
Onboarding basic information now uses shared
useHeadlessFormwithbuildOnceinstead of rebuilding the form on every value change viauseJSONSchemaForm.useHeadlessForm(buildOnce) exposesisBuildingand only returns aformafter the first value replay resolves conditional visibility—avoiding default-hidden fields on first paint, read-only review, and headless consumers. DuringjsfModifyrebuilds it keeps the previous form until the new one is ready; a schema change clears the form until resolved again.useBasicInformationSchemafetches the step schema and wiresbuildOnce, foldingisBuildingintoisLoading.useOnboardingpasses step-scoped values (initialValues → employment → step state → live fields), routes validation/parse/onValuesChangethrough the hook, and drops the localuseJSONSchemawrapper for this step.Rollout docs add a
buildOnceadoption checklist and mark basic information done. New integration tests cover the #1430 edge cases (saved employment,initialValues, read-only review, inline options re-renders, headlessuseOnboarding).Reviewed by Cursor Bugbot for commit 28f8206. Bugbot is set up for automated code reviews on this repo. Configure here.