Skip to content

refactor(onboarding): build basic information once through useHeadlessForm - #1440

Closed
gabrielseco wants to merge 2 commits into
refactor/use-headless-formfrom
refactor/basic-information-build-once
Closed

gabrielseco wants to merge 2 commits into
refactor/use-headless-formfrom
refactor/basic-information-build-once

Conversation

@gabrielseco

@gabrielseco gabrielseco commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

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

Stacked on #1433. Merge that first; this PR then retargets to main.

What changed

Toggle details
  • useHeadlessForm buildOnce (src/common/useHeadlessForm.ts, internal):
    • form stays null and the new isBuilding is true until 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.
    • After a jsfModify rebuild, 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.
    • A new schema (another country) goes back to null instead of showing the old country's fields.
    • rebuild is unchanged (isBuilding is always false).
  • useBasicInformationSchema (src/flows/Onboarding/api.ts): a buildOnce hook like useContractDetailsSchema. It returns isLoading || isBuilding, so the step never mounts with empty fields (its defaultValues would be computed from them). Its query key is new (onboarding-basic-information-schema); the request is the same.
  • useOnboarding (src/flows/Onboarding/hooks.tsx):
    • Basic information gets its own values: partner initialValues, then employment.basic_information, then the step's saved values, then live fieldValues while the step is current. Before, it got mergedFormValues, 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, parseFormValues and checkFieldUpdates (via onValuesChange) for basic information go through the hook. The local useJSONSchema wrapper is gone.
    • useJSONSchemaForm in Onboarding/api.ts stays, because JsonSchemaComparison still uses it.
  • jsf v1 contract details also reports isBuilding through its isLoading, since it uses the same strategy. It still replays only the mounted step's fieldValues: 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 the buildOnce contract, adds an "Adopting buildOnce in a step" checklist and marks basic information done.

No public API change: nothing new is exported from src/index.tsx or internals.

Screenshots

N/A

Related Resources

Testing

src/flows/Onboarding/tests/basicInformationBuildOnce.test.tsx covers each #1430 pitfall through the real flow, with MSW. Each test was checked against a deliberately broken hook:

Case Fails when
Saved employment reveals seniority_date on first paint the replay uses {}, or the step's values leave out the employment
Partner initialValues reveal it on first paint the replay uses {}
Read-only (invited) employment keeps it in the review without mounting the step an unresolved form is handed out, the replay uses {}, or the employment is left out
Partner re-renders with inline options, and the revealed field stays checkFieldUpdates stops calling onValuesChange
Headless useOnboarding() sees it visible without calling anything the replay uses {}, or the employment is left out

The existing OnboardingFlow.test.tsx seniority radio test and the "no flickering" read-only test also fail under those breaks. useHeadlessForm.test.tsx adds: no form before resolution, previous form kept during a jsfModify rebuild (fails if the hook hands out null there), and a schema change drops the old form.

  • npx vitest run src/flows/Onboarding src/common

  • Tested against the example/ app in a browser

  • Feature 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 useHeadlessForm with buildOnce instead of rebuilding the form on every value change via useJSONSchemaForm.

useHeadlessForm (buildOnce) exposes isBuilding and only returns a form after the first value replay resolves conditional visibility—avoiding default-hidden fields on first paint, read-only review, and headless consumers. During jsfModify rebuilds it keeps the previous form until the new one is ready; a schema change clears the form until resolved again.

useBasicInformationSchema fetches the step schema and wires buildOnce, folding isBuilding into isLoading. useOnboarding passes step-scoped values (initialValues → employment → step state → live fields), routes validation/parse/onValuesChange through the hook, and drops the local useJSONSchema wrapper for this step.

Rollout docs add a buildOnce adoption checklist and mark basic information done. New integration tests cover the #1430 edge cases (saved employment, initialValues, read-only review, inline options re-renders, headless useOnboarding).

Reviewed by Cursor Bugbot for commit 28f8206. Bugbot is set up for automated code reviews on this repo. Configure here.

gabrielseco and others added 2 commits October 5, 2026 10:48
…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>
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

Metric Current Previous Change Status
Total (gzip) 223.4 kB 223.2 kB +197 B (+0.1%) 🔴
Total (raw) 624.98 kB 624.05 kB +929 B (+0.1%) 🔴
CSS (gzip) 21.94 kB 21.94 kB 0 B (0%) 🟢
CSS (raw) 114.43 kB 114.43 kB 0 B (0%) 🟢

Size Limits

  • ✅ Total gzipped: 223.4 kB / 350 kB (63.8%)
  • ✅ Total raw: 624.98 kB / 850 kB (73.5%)
  • ✅ CSS gzipped: 21.94 kB / 25 kB (87.8%)

Largest Files (Top 5)

  1. index.esm-wIkXrqU6.js - 11.4 kB (0 B (0%))
  2. styles.css - 10.97 kB (0 B (0%))
  3. index.css - 10.97 kB (0 B (0%))
  4. internals-DMEMNkMw.js - 6.14 kB (new)
  5. hooks-DTHLFPWz.js - 5.81 kB (new)
View All Files (283 total)
File Size (gzip) Change
index.esm-wIkXrqU6.js 11.4 kB 0 B (0%)
styles.css 10.97 kB 0 B (0%)
index.css 10.97 kB 0 B (0%)
internals-DMEMNkMw.js 6.14 kB new
hooks-DTHLFPWz.js 5.81 kB new
sdk.gen-hikpofx9.js 5.48 kB 0 B (0%)
index.js 5.45 kB 0 B (0%)
flows/Onboarding/hooks.js 4.35 kB +11 B (+0.3%)
utils-qfe-oQBs.js 4.07 kB 0 B (0%)
FieldSetField-Bds5UIuj.js 4.03 kB 0 B (0%)

✅ Bundle size check passed

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Deploy preview for adp-cost-calculator ready!

Project:adp-cost-calculator
Status: ✅  Deploy successful!
Preview URL:https://adp-cost-calculator-fa782ck1t-remotecom.vercel.app
Latest Commit:28f8206

Deployed with vercel-action

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Deploy preview for remote-flows ready!

Project:remote-flows
Status: ✅  Deploy successful!
Preview URL:https://remote-flows-4l7nj7de7-remotecom.vercel.app
Latest Commit:28f8206

Deployed with vercel-action

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage Report

✅ Coverage increased! 🎉

Metric Current Previous Change Status
Lines 86.48% 86.45% +0.02% 🟢
Statements 86.03% 86.04% -0.01% 🔴
Functions 85.09% 85.05% +0.04% 🟢
Branches 78.08% 78.03% +0.05% 🟢

Detailed Breakdown

Lines Coverage
  • Covered: 4904 / 5671
  • Coverage: 86.48%
  • Change: +0.02% (15 lines)
Statements Coverage
  • Covered: 4994 / 5805
  • Coverage: 86.03%
  • Change: -0.01% (14 statements)
Functions Coverage
  • Covered: 1307 / 1536
  • Coverage: 85.09%
  • Change: +0.04% (4 functions)
Branches Coverage
  • Covered: 3056 / 3914
  • Coverage: 78.08%
  • Change: +0.05% (20 branches)

✅ Coverage check passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 28f8206. Configure here.

@gabrielseco gabrielseco closed this Oct 5, 2026
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