Repository navigation
feat(onboarding): set job title eligibility result into form - #1417
Conversation
📦 Bundle Size Report
Size Limits
Largest Files (Top 5)
View All Files (291 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 |
7ffa10c to
1c27e5f
Compare
… countries When an employment id is present, isContractDetailsEnabled is true regardless of the country's schema version, so jsf v1 countries fetched contract_details twice: once through the v0 useJSONSchema path (result unused) and once through useJsfV1ContractDetails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd-1869-eligibility-result-into-schema
…wer values When two checks fire before React re-renders (reachable from a headless consumer calling checkJobTitleEligibility back to back), the older query never gets an observer, so React Query doesn't abort it and its result used to overwrite the newer, cleared eligibility values. check() now resolves with undefined when the params it sent are no longer the latest. In the prebuilt form the re-render between blurs already aborts the stale request; the new UI test pins that behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed by newer values The stale-result guard only covered the success path. A superseded request that failed still resolved with cleared values, wiping the newer check's verdict, and logged an error for a result nobody was waiting on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o its own PR checkJobTitleEligibility is only called from the prebuilt form's onBlur, where React Query already aborts a superseded check. The guard for back-to-back calls now lives in a separate PR to decide on later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ils form's blur handler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd-1869-eligibility-result-into-schema
…nd-1869-eligibility-result-into-schema
a84713a to
6b04847
Compare
…ibility-check' into found-1869-eligibility-result-into-schema # Conflicts: # src/flows/Onboarding/tests/OnboardingFlowJobTitleEligibility.test.tsx
…dges_risk The contract details schema already hides employer_acknowledges_risk unless additional_job_title_eligibility_check_result is yes_with_ack, so the SDK-level hidden override only ever took effect in that one state, which is exactly when the employer has to see and accept it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rns yes_with_ack The gateway serves employer_acknowledges_risk to API partners as a plain optional checkbox and withholds the server-owned additional_job_title_eligibility_check_result field along with its yes_with_ack conditional, leaving it to the client to decide when to show the acknowledgement (see tiger's AdditionalJobTitleEligibilityCheck component). Confirmed against local tiger for DEU, ITA and PRT with employment-scoped contract_details requests. The contract details schema now gets the result field and that conditional added before the form is built, whenever it carries the check slug and the acknowledgement but not the result. The checkbox stays hidden when no check has run (including with the job_title_eligibility feature off) and appears only once the verdict is eligible_with_risk_acknowledgement. The job title eligibility fixture now mirrors the served shape (no result field, no conditional, Role Requirements fieldset, additionalProperties false), so the tests exercise the SDK's injection rather than a schema the backend never serves. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(onboarding): run job title eligibility check on submit Submitting without a blur (pressing Enter, or a click that lands before the blur-triggered check flips isCheckingJobTitleEligibility) sent contract details without the eligibility slug and result. The prebuilt form now re-runs the check right before submitting and merges its values into the payload; the query cache makes this free when the blur already fetched the same params. If the verdict changed, the values are written and the submit is held so the user sees any risk acknowledgement it now requires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): only hold submit when the new verdict invalidates the form Holding every submit whose verdict changed made users click Continue twice even when the new verdict required nothing new (e.g. needs_review). Revalidate after writing the new verdict instead, so the submit is only stopped when the new verdict makes fields required that aren't filled, such as the risk acknowledgement for yes_with_ack. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(onboarding): remove debug log Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(onboarding): name the revalidation after a verdict change Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * trim comments --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2255a43
into
found-1869-ignore-cancelled-eligibility-check
…k is superseded (#1415) * fix(onboarding): don't log an error when a job title eligibility check is superseded When the role fields change while a check is in flight, React Query aborts the previous request. The imperative query in check() rejected with a CancelledError, which the catch reported as "Failed to fetch job title eligibility check" even though nothing failed. Skip the log for cancellations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(onboarding): replace deprecated isCancelledError with instanceof CancelledError Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(onboarding): set job title eligibility result into form (#1417) * wip set values in form * fix(onboarding): skip legacy contract details schema fetch for jsf v1 countries When an employment id is present, isContractDetailsEnabled is true regardless of the country's schema version, so jsf v1 countries fetched contract_details twice: once through the v0 useJSONSchema path (result unused) and once through useJsfV1ContractDetails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): drop a job title eligibility result superseded by newer values When two checks fire before React re-renders (reachable from a headless consumer calling checkJobTitleEligibility back to back), the older query never gets an observer, so React Query doesn't abort it and its result used to overwrite the newer, cleared eligibility values. check() now resolves with undefined when the params it sent are no longer the latest. In the prebuilt form the re-render between blurs already aborts the stale request; the new UI test pins that behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): ignore a failed job title eligibility check superseded by newer values The stale-result guard only covered the success path. A superseded request that failed still resolved with cleared values, wiping the newer check's verdict, and logged an error for a result nobody was waiting on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * revert(onboarding): move the superseded job title eligibility guard to its own PR checkJobTitleEligibility is only called from the prebuilt form's onBlur, where React Query already aborts a superseded check. The guard for back-to-back calls now lives in a separate PR to decide on later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(onboarding): scope checkJobTitleEligibility to the contract details form's blur handler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): let the eligibility verdict reveal employer_acknowledges_risk The contract details schema already hides employer_acknowledges_risk unless additional_job_title_eligibility_check_result is yes_with_ack, so the SDK-level hidden override only ever took effect in that one state, which is exactly when the employer has to see and accept it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): hide employer_acknowledges_risk until the check returns yes_with_ack The gateway serves employer_acknowledges_risk to API partners as a plain optional checkbox and withholds the server-owned additional_job_title_eligibility_check_result field along with its yes_with_ack conditional, leaving it to the client to decide when to show the acknowledgement (see tiger's AdditionalJobTitleEligibilityCheck component). Confirmed against local tiger for DEU, ITA and PRT with employment-scoped contract_details requests. The contract details schema now gets the result field and that conditional added before the form is built, whenever it carries the check slug and the acknowledgement but not the result. The checkbox stays hidden when no check has run (including with the job_title_eligibility feature off) and appears only once the verdict is eligible_with_risk_acknowledgement. The job title eligibility fixture now mirrors the served shape (no result field, no conditional, Role Requirements fieldset, additionalProperties false), so the tests exercise the SDK's injection rather than a schema the backend never serves. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): run job title eligibility check on submit (#1469) * fix(onboarding): run job title eligibility check on submit Submitting without a blur (pressing Enter, or a click that lands before the blur-triggered check flips isCheckingJobTitleEligibility) sent contract details without the eligibility slug and result. The prebuilt form now re-runs the check right before submitting and merges its values into the payload; the query cache makes this free when the blur already fetched the same params. If the verdict changed, the values are written and the submit is held so the user sees any risk acknowledgement it now requires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): only hold submit when the new verdict invalidates the form Holding every submit whose verdict changed made users click Continue twice even when the new verdict required nothing new (e.g. needs_review). Revalidate after writing the new verdict instead, so the submit is only stopped when the new verdict makes fields required that aren't filled, such as the risk acknowledgement for yes_with_ack. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(onboarding): remove debug log Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(onboarding): name the revalidation after a verdict change Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * trim comments --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 3413699. Configure here.
| return; | ||
| } | ||
| } | ||
| await onSubmit({ ...values, ...eligibilityValues }); |
There was a problem hiding this comment.
Cancelled check still submits form
Medium Severity
handleSubmit treats a cancelled checkJobTitleEligibility the same as a successful no-op: undefined skips revalidation and still calls onSubmit with the original values. A superseded submit-time check can therefore send contract details with a stale or missing additional_job_title_eligibility_check_result.
Reviewed by Cursor Bugbot for commit 3413699. Configure here.
… runs (#1413) * feat(onboarding): add job_title_eligibility feature flag Splits the flag declaration out of #1395 so it can land on its own: adds 'job_title_eligibility' to the OnboardingFeatures union (with its JSDoc) and enables it in the example app. No behavior wired up yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(onboarding): wire onBlur events to job title eligibility check Adds the useJobTitleEligibilityCheck hook and jobTitleEligibilityCheckOptions query, and wires OnboardingForm's onBlur to call the flowBag's new checkJobTitleEligibility(values). The check only runs on the contract_details step, when 'job_title_eligibility' is enabled, and when the role fields (role_description, role_is_onsite, role_requires_license) are filled and valid. Dedup: params are compared against the previous check (fast-deep-equal) before firing a request, and jobTitleEligibilityCheckOptions sets staleTime: Infinity since the query key already encodes employmentId + params. Without both, react-query's built-in dedup only covers truly concurrent calls with an identical key — an imperative queryClient.query() call is not a mounted observer, so it treats every call as a fresh mount and refetches under the default staleTime: 0. The result isn't consumed yet (no UI reacts to eligibility yet) - follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(onboarding): drop debug logs and fix job title eligibility dedup Removes the console.log statements left over from development. Also fixes a real bug surfaced while writing tests for this: paramsChanged was read immediately after calling setParams(updaterFn), assuming the functional updater runs synchronously. It doesn't reliably do that, so paramsChanged came back false almost every time - meaning the eligibility check never actually fired a request, for any input. Fixed by comparing against the params value already in the closure before calling setParams, instead of depending on a side effect inside the updater. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore: remove accidentally committed local settings file .claude/settings.json is a personal/local permission-allowlist file, not meant for this PR - it got swept into the previous commit because it was already staged from earlier local work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(onboarding): match checkJobTitleEligibility's docs to its behavior The JSDoc claimed the check also runs on entering the contract details step and before submitting it, but nothing calls it that way - only OnboardingForm's onBlur does. Since useOnboarding is reachable through the public ./flows/* entry point, this overstated the contract for anyone building a custom UI around the headless hook. Reworded to describe only what's actually wired up, and pointed custom UIs at calling checkJobTitleEligibility themselves for other trigger points instead of implying it happens automatically. Also drops stepValues, initialContractDetailsValues, and fieldValues from useJobTitleEligibilityCheck's params - they were threaded through from hooks.tsx but never used inside the hook, leftover scaffolding for the same unimplemented triggers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(onboarding): close race between overlapping eligibility checks check() compared nextParams against params captured from the render closure after two awaits (handleValidation, parseFormValues). If a second blur's check() started before the first one's setParams had committed a re-render, both calls would compare against the same stale params - depending on timing, that can either fire a duplicate request or wrongly treat a later complete result as unchanged and skip it. Flagged by Bugbot on PR #1406, independently of the same race I'd already called out in review. Fixed by tracking params in a ref alongside the state (state still drives the reactive useQuery's enabled/key). Ref writes are synchronous and immediately visible to any concurrently-resolving check() call, so the comparison always sees the latest known params regardless of render timing, closing the window entirely rather than narrowing it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(onboarding): cover the job title eligibility blur dedup Adds two integration tests against the rendered OnboardingFlow (not the headless hook) since that's the surface most consumers actually hit: blurring the role fields once they're complete fires the check, and a later blur with unchanged values doesn't fire it again. Also adds unit tests for the pure getJobTitleEligibilityParams (slug field gate, incomplete/invalid fields, a hidden param field still allowing the others through, job_title inclusion). Adds a minimal, purpose-built contract_details fixture (following the v1-italy-apl.ts "trimmed down" convention) with just the four fields the check cares about, rather than driving the full multi-hundred-field country schemas through this scenario. The real dedup bug this uncovered (paramsChanged never actually true) is fixed upstream in the parent branch, not in this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(onboarding): give the dedup test time for a broken fetch to land Bugbot flagged that the second test's final assertion ran right after user.tab() resolves, but checkJobTitleEligibility is fire-and-forget from OnboardingForm's onBlur - it awaits handleValidation, parseFormValues, and the request itself, none of which user.tab() waits on. A regression that fires a second request wouldn't have reached the spy by assertion time, so the test would have passed even with a broken dedup. Verified by temporarily disabling both dedup layers (forcing paramsChanged true and setting staleTime: 0): without this fix the test still passed; with it, it correctly fails and catches the regression within the 200ms window. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(onboarding): explain the inverted waitFor in the dedup test The waitFor + .catch pattern for asserting an async side effect does not eventually happen is unusual enough to warrant a why-comment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(onboarding): reword the waitFor comment in plain language Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(onboarding): cover retrying the job title eligibility check after a failure A failed check must be retried on the next blur even when the role answers are unchanged. Depends on the fix in #1412; fails until it lands on the base branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): retry job title eligibility check after a failed request Same change as #1412, so this PR's retry test passes on its own. The hunk is identical, so merging both is clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): disable submit while the job title eligibility check runs Track in-flight checks with a pending counter so the button is disabled from validation through the request settling, including the blur fired by clicking submit itself. Exposed as isCheckingJobTitleEligibility on the bag for custom UIs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(onboarding): drive the submit lock from the query's isFetching Only disable submit while the eligibility request is actually in flight, not while the form validates. A previous request is already aborted when params change, so the query for the current key is all we need to watch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(onboarding): cover the eligibility lock in OnboardingSubmit unit tests The mocked bag lacked isCheckingJobTitleEligibility, so disabled resolved to undefined instead of false. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(example): rename vite config to .mts to silence native config loader warning (#1394) The example package is "type": "commonjs" (dev_server.js and api/ use require), so Vite flagged ESM syntax in vite.config.ts as unsupported by the upcoming native config loader. The .mts extension marks just the config as ESM without flipping the whole package. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(scripts): send checkbox const value, not [true], in seed-onboarding (#1389) fakeValueFor() hardcoded [true] for checkbox fields, mimicking a jsonType-array shape that doesn't match how the real form submits const-based checkboxes (e.g. FRA's ack_non_eligible_job_titles, a jsonType string with const: "acknowledged"). parseFormValuesToAPI swaps the RHF boolean for field.const at submit time; mirror that here instead of sending the raw fill value, or the API 422s with a type mismatch. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * feat(scripts): seed onboarding directly against a gateway env (#1371) * feat(scripts): seed onboarding directly against a gateway env Lets seed-onboarding.mjs bypass the local example dev server via --env=<name>, reusing example/api/{utils,get_token,proxy}.js for auth against .env.<env> at the repo root. Prints a ready-to-click app link (?employmentId= prefilled, via VITE_APP_URL) when set. Onboarding.tsx now reads employmentId from the URL query string so that link actually prefills the intro form. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(skills): add verify-sandbox-deployed-app skill Captures the manual sandbox-verification loop (seed via --env=sandbox, log past the Vercel password gate with VITE_APP_PASSWORD, drive the flow, check the claim) as a repeatable skill instead of re-deriving it each time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * style: fix oxfmt formatting in verify-sandbox-deployed-app skill Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(skills): remove verify-sandbox-deployed-app skill Split into its own PR (#1379) so it can be reviewed independently of the seed-onboarding env flag. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(e2e): give sandbox onboarding specs room for slow backend writes The basic and Germany onboarding specs create real employments against the sandbox and ran against Playwright's 30s test / 5s expect defaults. Traces from PR #1371 and main show nothing hanging: Germany reached the final ILA sign click at 27.2s, leaving <3s for the sign POST, and Spain failed step-title assertions while ~3s employment writes were in flight. - test.slow() on both onboarding specs (90s budget) - expectOnboardingStep helper with a 15s timeout for step transitions, which are gated on sandbox writes rather than rendering - wait on the ILA sign POST and assert it succeeded before checking the dialog closed, so a backend rejection reports as such Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(e2e): gate onboarding steps on save responses, set timeouts globally Replaces the per-spec test.slow() and the 15s step-title timeout with: - submitAndWaitForSave / clickAndWaitForSave helpers that wait for the request each step's submit fires (POST /v1/employments, POST engagement-agreement-details, PATCH /v1/employments/{id}, PUT benefit-offers, POST .../sign) and assert it succeeded, instead of waiting for the "Loading..." text to disappear. A backend failure now reports method, path and status. - Global timeout (60s) and expect timeout (10s) in playwright.config.ts, matching Dragon's defaults. Response waits make failures precise but do not make the sandbox faster; the Germany flow alone takes ~30s on a good day, so the 30s default budget has to go up regardless. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * 1.57.0 (#1407) * chore: prepare release v1.57.0 * docs: group 1.57.0 changelog by type Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * update changelog --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * docs: surface the inline-comment convention in CLAUDE.md (#1405) * docs: surface the inline-comment convention in CLAUDE.md The comment-density rule (zero by default, one line max for a genuinely non-obvious why) only lived in the untracked CLAUDE.local.md and in .cursor/BUGBOT.md, which Cursor's Bugbot reads for its own reviews but Claude Code doesn't load as project instructions. Add a pointer in CLAUDE.md's Conventions section so it's visible up front instead of surfacing after the fact via a bot comment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: describe what a good inline comment looks like Extends the opt-in comment rule with what "good" actually means when a comment does get written (the rare non-obvious-why case, or on explicit request): plain English a newcomer can follow, not jargon-dense shorthand - even if that takes a couple of sentences. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: exempt human-requested comments from the one-line cap The one-line cap exists to stop unprompted, jargon-dense comments - it's not meant to force a human-requested explanation into an unreadable single line. Carves out that exception in both CLAUDE.md and .cursor/BUGBOT.md: a comment explicitly asked for can take as many plain sentences as it needs; one added on the AI's own initiative for the non-obvious-why case stays capped at one line, and shouldn't exist at all if the code it's next to is actually simple. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * docs: document import order convention (#1404) React first, then external packages, then @remoteoss/* packages, then absolute (@/) imports, then relative imports. Not enforced by oxlint or oxfmt today (verified: import/order is not a supported oxlint rule and oxfmt does not reorder imports) — this documents the existing manual convention so it's explicit for humans and Claude Code. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * chore(deps): update dependency @remoteoss/remote-json-schema-form-kit to v1.1.2 (#1409) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(onboarding): block invite while employment is in job_title_review (#1403) Dragon hard-gates its Invite button on employment.status !== 'job_title_review' (SendInvitation.jsx); we didn't. Tiger already rejects the invite call server-side while in this status (409 'Can't invite employee in status job_title_review' on the partner API, 422 on the internal one), so this closes the client-side gap rather than introducing new blocking behavior. job_title_review is independent of the (still unmerged) job title eligibility check feature flag: it's part of the generated Employment['status'] type already, and Tiger can put an employment into this status any time its stage-2 AI check needs human review, so the SDK should respect it regardless of whether a partner has opted into that feature. Addresses checklist item 1 in JOB_TITLE_ELIGIBILITY_CHECKLIST.md (on feat/onboarding-job-title-eligibility-check). Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * chore: ignore CLAUDE.local.md (#1400) * chore: ignore CLAUDE.local.md Adds an explicit .gitignore entry for CLAUDE.local.md, a local-only companion to CLAUDE.md for per-developer notes that shouldn't be checked in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore: copy CLAUDE.local.md into new worktrees CLAUDE.local.md is gitignored, so a plain `git worktree add` doesn't carry it over and Claude Code loses that context in worktree sessions. Copy it the same way example/.env is already seeded. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(scripts): copy root .env.<env> files into new worktrees npm run seed:onboarding -- --env=<name> reads .env.<name> from the repo root, but create-worktree.ts only ever copied example/.env - a fresh worktree silently lost access to gateway-targeting credentials like .env.sandbox even though it's a root-level, not example/-level, file. Mirrors the CLAUDE.local.md copy step just above it: glob root for any .env.* file and copy each one into the new worktree. Also gitignores .env.* (previously only .env itself was ignored), since these files carry real credentials and weren't excluded from `git add -A`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * chore(deps-dev): update dependency dotenv to v18.0.1 (#1410) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps-dev): update dependency tsx to v4.23.15 (#1411) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * revert * fix(onboarding): only report the eligibility check as running on contract details The eligibility request can still be in flight after the user leaves contract_details, which kept isCheckingJobTitleEligibility true and disabled Continue on the next step until the request settled. Gate it on the current step, matching how the query itself is enabled. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): don't log an error when a job title eligibility check is superseded (#1415) * fix(onboarding): don't log an error when a job title eligibility check is superseded When the role fields change while a check is in flight, React Query aborts the previous request. The imperative query in check() rejected with a CancelledError, which the catch reported as "Failed to fetch job title eligibility check" even though nothing failed. Skip the log for cancellations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(onboarding): replace deprecated isCancelledError with instanceof CancelledError Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(onboarding): set job title eligibility result into form (#1417) * wip set values in form * fix(onboarding): skip legacy contract details schema fetch for jsf v1 countries When an employment id is present, isContractDetailsEnabled is true regardless of the country's schema version, so jsf v1 countries fetched contract_details twice: once through the v0 useJSONSchema path (result unused) and once through useJsfV1ContractDetails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): drop a job title eligibility result superseded by newer values When two checks fire before React re-renders (reachable from a headless consumer calling checkJobTitleEligibility back to back), the older query never gets an observer, so React Query doesn't abort it and its result used to overwrite the newer, cleared eligibility values. check() now resolves with undefined when the params it sent are no longer the latest. In the prebuilt form the re-render between blurs already aborts the stale request; the new UI test pins that behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): ignore a failed job title eligibility check superseded by newer values The stale-result guard only covered the success path. A superseded request that failed still resolved with cleared values, wiping the newer check's verdict, and logged an error for a result nobody was waiting on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * revert(onboarding): move the superseded job title eligibility guard to its own PR checkJobTitleEligibility is only called from the prebuilt form's onBlur, where React Query already aborts a superseded check. The guard for back-to-back calls now lives in a separate PR to decide on later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(onboarding): scope checkJobTitleEligibility to the contract details form's blur handler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(onboarding): let the eligibility verdict reveal employer_acknowledges_risk The contract details schema already hides employer_acknowledges_risk unless additional_job_title_eligibility_check_result is yes_with_ack, so the SDK-level hidden override only ever took effect in that one state, which is exactly when the employer has to see and accept it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): hide employer_acknowledges_risk until the check returns yes_with_ack The gateway serves employer_acknowledges_risk to API partners as a plain optional checkbox and withholds the server-owned additional_job_title_eligibility_check_result field along with its yes_with_ack conditional, leaving it to the client to decide when to show the acknowledgement (see tiger's AdditionalJobTitleEligibilityCheck component). Confirmed against local tiger for DEU, ITA and PRT with employment-scoped contract_details requests. The contract details schema now gets the result field and that conditional added before the form is built, whenever it carries the check slug and the acknowledgement but not the result. The checkbox stays hidden when no check has run (including with the job_title_eligibility feature off) and appears only once the verdict is eligible_with_risk_acknowledgement. The job title eligibility fixture now mirrors the served shape (no result field, no conditional, Role Requirements fieldset, additionalProperties false), so the tests exercise the SDK's injection rather than a schema the backend never serves. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): run job title eligibility check on submit (#1469) * fix(onboarding): run job title eligibility check on submit Submitting without a blur (pressing Enter, or a click that lands before the blur-triggered check flips isCheckingJobTitleEligibility) sent contract details without the eligibility slug and result. The prebuilt form now re-runs the check right before submitting and merges its values into the payload; the query cache makes this free when the blur already fetched the same params. If the verdict changed, the values are written and the submit is held so the user sees any risk acknowledgement it now requires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(onboarding): only hold submit when the new verdict invalidates the form Holding every submit whose verdict changed made users click Continue twice even when the new verdict required nothing new (e.g. needs_review). Revalidate after writing the new verdict instead, so the submit is only stopped when the new verdict makes fields required that aren't filled, such as the risk acknowledgement for yes_with_ack. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(onboarding): remove debug log Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(onboarding): name the revalidation after a verdict change Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * trim comments --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>


Summary
When the job title eligibility check finishes, onboarding now writes its outcome into the contract details form. If the verdict requires it, the form shows the risk acknowledgement, and the check's ID and outcome are sent when the contract details are submitted.
Why
Before this change the check ran, but nothing used its outcome. So the schema's conditional fields (such as
employer_acknowledges_riskon ayes_with_ackverdict) never appeared, and the backend never received the result it needs to accept the contract details.On top of that, the gateway leaves the decision to show the acknowledgement to the SDK. For API partners it serves
employer_acknowledges_riskas a plain optional checkbox, and withholds the server-ownedadditional_job_title_eligibility_check_resultfield along with theyes_with_ackconditional. Tiger'sAdditionalJobTitleEligibilityChecksays so on purpose: "a partner decides when to show it from whatever it already knows about the verdict." Without SDK logic the checkbox shows all the time. #1463 hid it all the time instead, which made ayes_with_ackverdict impossible to submit: the field was required but hidden.What changed
Toggle details
Mapping the verdict (
utils.ts): the newgetJobTitleEligibilityValuesturns the API response into values for the two hidden fields.check_idbecomesadditional_job_title_eligibility_check_slug. The verdict becomesadditional_job_title_eligibility_check_result:eligible → yes,not_eligible → no,needs_review → maybe,eligible_with_risk_acknowledgement → yes_with_ack,not_assessed → null.checkJobTitleEligibilitynow resolves with those values (useJobTitleEligibilityCheck.tsx,hooks.tsx):nullwhen the role fields are incomplete or the request fails, so an earlier verdict is cleared.undefinedwhen there is nothing to write: the feature is off, it's the wrong step, the schema has no slug field, or a newer check superseded this one.Adding the result field and the conditional to the schema (
utils.ts,api.ts, brought down from fix(onboarding): hide employer_acknowledges_risk until the check returns yes_with_ack #1470): the newwithJobTitleEligibilityRiskAcknowledgementruns in theselectof both contract details schema hooks (jsf v0 and v1), before the form is built. When the schema has the slug field and the acknowledgement but no result field, it adds:additional_job_title_eligibility_check_resultproperty (also needed because served schemas useadditionalProperties: false)if result === 'yes_with_ack' → required / else → falseconditional tiger adds to its own validation buildA schema that already declares the result field is left alone. Tiger accepts the submitted result: it validates against a build that includes the server-owned field, then overwrites the value with its own verdict.
Removes feat(onboarding): hide employer_acknowledges_risk by default #1463's always-hide of
employer_acknowledges_riskfrom the contract detailsjsfModify(hooks.tsx). The checkbox now stays hidden until a check returnsyes_with_ack, and stays hidden when no check has run. feat(onboarding): hide employer_acknowledges_risk by default #1463'sFieldSetFieldchange (skippinghiddenfields inside fieldsets) stays.Prebuilt form (
OnboardingForm.tsx): theonBlurhandler awaits the check and callsform.setValuefor each returned field. The schema's conditionals then react on their own.Public API:
checkJobTitleEligibilityon theuseOnboardingbag used to resolve withvoidand now resolves with values orundefined. That's additive for callers that ignored the result. It's documented in the JSDoc and in thejob_title_eligibilityfeature doc intypes.ts, and it stays behind thejob_title_eligibilityfeature flag.Tests:
x-jsf-order,additionalProperties: false, and no result field or conditional.role_descriptiondrops itsminLength: 100to keep the tests short.yes_with_ack;eligiblesubmits the slug and result with no acknowledgement;not_assessedsubmits neither; changing the role to an eligible one hides the acknowledgement again and submits the new slug; with thejob_title_eligibilityfeature off, no check runs and the acknowledgement stays hidden.Also includes fix(onboarding): skip legacy contract details schema fetch for jsf v1 countries #1419 (merged in): jsf v1 countries no longer fetch the contract details schema a second time through the legacy path when an employment already exists.
Screenshots
N/A
Related Resources
Testing
example/app in a browserjob_title_eligibility🤖 Generated with Claude Code
Note
Medium Risk
Changes contract-details submit payloads and conditional compliance UI (risk acknowledgement) behind a feature flag; incorrect mapping or validation could block or mis-submit onboarding.
Overview
Job title eligibility outcomes now drive contract-details UI and submission when the
job_title_eligibilityfeature is on. The check response is mapped into hiddenadditional_job_title_eligibility_check_slug/additional_job_title_eligibility_check_resultfields; served schemas get a client-side hidden result field plus theyes_with_ackconditional viawithJobTitleEligibilityRiskAcknowledgement(replacing always hidingemployer_acknowledges_risk).The prebuilt form syncs those values on blur and re-runs the check before submit (e.g. Enter without blur). If the verdict changes, it re-validates and can block submit until risk acknowledgement is satisfied, then PATCH includes slug, result, and acknowledgement when required.
checkJobTitleEligibilitynow returns mapped values or clears stale state on failure/incomplete role fields.Tests cover jsf v0/v1, submit-without-blur, and verdict-specific payloads.
Reviewed by Cursor Bugbot for commit 3413699. Bugbot is set up for automated code reviews on this repo. Configure here.