Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e872b0b
feat(onboarding): add job_title_eligibility feature flag
gabrielseco Sep 25, 2026
33aabca
feat(onboarding): wire onBlur events to job title eligibility check
gabrielseco Sep 25, 2026
330242b
fix(onboarding): drop debug logs and fix job title eligibility dedup
gabrielseco Sep 25, 2026
edadc3c
chore: remove accidentally committed local settings file
gabrielseco Sep 25, 2026
382c58f
fix(onboarding): match checkJobTitleEligibility's docs to its behavior
gabrielseco Sep 25, 2026
7686a5e
fix(onboarding): close race between overlapping eligibility checks
gabrielseco Sep 25, 2026
130d062
test(onboarding): cover the job title eligibility blur dedup
gabrielseco Sep 25, 2026
836274c
fix(onboarding): give the dedup test time for a broken fetch to land
gabrielseco Sep 25, 2026
56b8bad
docs(onboarding): explain the inverted waitFor in the dedup test
gabrielseco Sep 25, 2026
c438891
docs(onboarding): reword the waitFor comment in plain language
gabrielseco Sep 25, 2026
38681bb
Merge branch 'main' into found-1869-hook-up-the-form-onblur-events-to…
gabrielseco Sep 28, 2026
e13fb13
test(onboarding): cover retrying the job title eligibility check afte…
gabrielseco Sep 28, 2026
57bd08a
fix(onboarding): retry job title eligibility check after a failed req…
gabrielseco Sep 28, 2026
f641052
Merge branch 'main' into found-1869-hook-up-the-form-onblur-events-to…
gabrielseco Sep 28, 2026
1d6f2e5
fix(onboarding): disable submit while the job title eligibility check…
gabrielseco Sep 28, 2026
036811e
refactor(onboarding): drive the submit lock from the query's isFetching
gabrielseco Sep 28, 2026
61079c1
test(onboarding): cover the eligibility lock in OnboardingSubmit unit…
gabrielseco Sep 28, 2026
1fb6fda
chore(example): rename vite config to .mts to silence native config l…
gabrielseco Sep 25, 2026
522c18f
fix(scripts): send checkbox const value, not [true], in seed-onboardi…
gabrielseco Sep 25, 2026
8f20c58
feat(scripts): seed onboarding directly against a gateway env (#1371)
gabrielseco Sep 25, 2026
603325a
1.57.0 (#1407)
gabrielseco Sep 25, 2026
9e454ff
docs: surface the inline-comment convention in CLAUDE.md (#1405)
gabrielseco Sep 28, 2026
908261f
docs: document import order convention (#1404)
gabrielseco Sep 28, 2026
3f0a584
chore(deps): update dependency @remoteoss/remote-json-schema-form-kit…
renovate[bot] Sep 28, 2026
30d7c12
fix(onboarding): block invite while employment is in job_title_review…
gabrielseco Sep 28, 2026
a238d4b
chore: ignore CLAUDE.local.md (#1400)
gabrielseco Sep 28, 2026
d85505d
chore(deps-dev): update dependency dotenv to v18.0.1 (#1410)
renovate[bot] Sep 28, 2026
b4c8db3
chore(deps-dev): update dependency tsx to v4.23.15 (#1411)
renovate[bot] Sep 28, 2026
ca023c9
Merge branch 'found-1869-hook-up-the-form-onblur-events-to-trigger-th…
gabrielseco Sep 28, 2026
b2ddab9
Merge branch 'found-1869-job-title-eligibility-tests' into found-1869…
gabrielseco Sep 28, 2026
6b6034b
Merge branch 'main' into found-1869-hook-up-the-form-onblur-events-to…
gabrielseco Sep 30, 2026
cc39e39
Merge branch 'main' into found-1869-hook-up-the-form-onblur-events-to…
gabrielseco Oct 6, 2026
5071433
Merge branch 'found-1869-hook-up-the-form-onblur-events-to-trigger-th…
gabrielseco Oct 6, 2026
621deeb
Merge branch 'found-1869-job-title-eligibility-tests' into found-1869…
gabrielseco Oct 6, 2026
efb27e6
Merge branch 'main' into found-1869-hook-up-the-form-onblur-events-to…
gabrielseco Oct 7, 2026
317a13d
revert
gabrielseco Oct 7, 2026
1cca2cd
Merge branch 'found-1869-hook-up-the-form-onblur-events-to-trigger-th…
gabrielseco Oct 7, 2026
08d1a9b
Merge branch 'found-1869-job-title-eligibility-tests' into found-1869…
gabrielseco Oct 7, 2026
b962750
fix(onboarding): only report the eligibility check as running on cont…
gabrielseco Oct 7, 2026
182a2ad
Merge remote-tracking branch 'origin/main' into found-1869-disable-su…
gabrielseco Oct 8, 2026
709da5f
Merge branch 'main' into found-1869-disable-submit-during-eligibility…
gabrielseco Oct 9, 2026
9dce6e3
fix(onboarding): don't log an error when a job title eligibility chec…
gabrielseco Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/flows/Onboarding/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
getContractDetailsSchemaVersion,
getBasicInformationSchemaVersion,
getBenefitOffersSchemaVersion,
withJobTitleEligibilityRiskAcknowledgement,
} from '@/src/flows/Onboarding/utils';
// oxlint-disable-next-line no-restricted-imports -- TODO: move onto useHeadlessForm, see docs/USE_HEADLESS_FORM_ROLLOUT.md
import { createHeadlessForm } from '@/src/common/createHeadlessForm';
Expand Down Expand Up @@ -317,7 +318,8 @@ export const useLegacyContractDetailsSchema = ({
return response;
},
enabled: options?.queryOptions?.enabled,
select: ({ data }) => data?.data || {},
select: ({ data }) =>
withJobTitleEligibilityRiskAcknowledgement(data?.data || {}),
});

const { form } = useHeadlessForm({
Expand Down Expand Up @@ -385,7 +387,8 @@ export const useContractDetailsSchema = ({
return response;
},
enabled: options?.queryOptions?.enabled,
select: ({ data }) => data?.data,
select: ({ data }) =>
data?.data && withJobTitleEligibilityRiskAcknowledgement(data.data),
});

const { form, handleValidation, parseFormValues } = useHeadlessForm({
Expand Down
25 changes: 23 additions & 2 deletions src/flows/Onboarding/components/OnboardingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { JSONSchemaFormFields } from '@/src/components/form/JSONSchemaForm';
import { Form } from '@/src/components/ui/form';
import { useEffect } from 'react';
import { useOnboardingContext } from '@/src/flows/Onboarding/context';
import { JOB_TITLE_ELIGIBILITY_RESULT_FIELD } from '@/src/flows/Onboarding/utils';
import { JSFFields } from '@/src/types/remoteFlows';
import {
BasicInformationFormPayload,
Expand Down Expand Up @@ -48,6 +49,15 @@ export function OnboardingForm({
// oxlint-disable-next-line react-hooks/exhaustive-deps
}, []);

const syncJobTitleEligibility = async (values: Record<string, unknown>) => {
const eligibilityValues =
await onboardingBag.checkJobTitleEligibility(values);
Object.entries(eligibilityValues ?? {}).forEach(([name, value]) =>
form.setValue(name, value),
);
return eligibilityValues;
};

const handleSubmit = async (
values: Record<string, unknown>,
event?: React.BaseSyntheticEvent,
Expand Down Expand Up @@ -92,7 +102,18 @@ export function OnboardingForm({
}
} else {
// Handle normal form submission
await onSubmit(values);
const eligibilityValues = await syncJobTitleEligibility(values);
const hasVerdictChanged =
eligibilityValues &&
(values[JOB_TITLE_ELIGIBILITY_RESULT_FIELD] ?? null) !==
eligibilityValues[JOB_TITLE_ELIGIBILITY_RESULT_FIELD];
if (hasVerdictChanged) {
const isValidWithNewVerdict = await form.trigger();
if (!isValidWithNewVerdict) {
return;
}
}
await onSubmit({ ...values, ...eligibilityValues });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale ack blocks later submit

Medium Severity

A submit that re-runs the eligibility check can keep employer_acknowledges_risk in form state after the verdict leaves yes_with_ack. handleSubmit then re-validates those leftover values against a schema that forbids the field (additionalProperties: false plus the injected else branch), so the native Enter submit path can fail closed even though the new verdict does not need an acknowledgement.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9dce6e3. Configure here.

}
};

Expand All @@ -102,7 +123,7 @@ export function OnboardingForm({
id={formId}
onSubmit={form.handleSubmit(handleSubmit)}
className='space-y-4 RemoteFlows__OnboardingForm'
onBlur={() => onboardingBag.checkJobTitleEligibility(form.getValues())}
onBlur={() => syncJobTitleEligibility(form.getValues())}
>
<JSONSchemaFormFields
components={components}
Expand Down
6 changes: 5 additions & 1 deletion src/flows/Onboarding/components/OnboardingSubmit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export function OnboardingSubmit({
<CustomButton
{...props}
form={formId}
disabled={props.disabled || onboardingBag.isSubmitting}
disabled={
props.disabled ||
onboardingBag.isSubmitting ||
onboardingBag.isCheckingJobTitleEligibility
}
>
{children}
</CustomButton>
Expand Down
13 changes: 8 additions & 5 deletions src/flows/Onboarding/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -644,9 +644,6 @@ export const useOnboarding = ({
},
},
},
employer_acknowledges_risk: {
presentation: { hidden: true },
},
...(isDailyScheduleEnabled
? {
daily_schedule: {
Expand Down Expand Up @@ -1417,12 +1414,18 @@ export const useOnboarding = ({
/**
* Runs the job title eligibility check with the given contract details values when the
* 'job_title_eligibility' feature is enabled, the current step is contract_details, and the
* role fields are filled. The prebuilt form calls it on blur; call it yourself from a custom
* UI to trigger the same check at another point (e.g. on step entry or before submitting).
* role fields are filled. The prebuilt form runs it on blur and again right before submitting,
* since a submit can happen without a blur (e.g. pressing Enter); a custom UI should do both.
* @param values - Current form values
*/
checkJobTitleEligibility: jobTitleEligibility.check,

/**
* True while the job title eligibility request is in flight. The prebuilt submit button is
* disabled meanwhile; use it to do the same in a custom UI.
*/
isCheckingJobTitleEligibility: jobTitleEligibility.isFetching,
Comment thread
cursor[bot] marked this conversation as resolved.

/**
* Function to handle form submission
* @param values - Form values to submit
Expand Down
45 changes: 35 additions & 10 deletions src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ValidationResult } from '@remoteoss/remote-json-schema-form-kit';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import {
CancelledError,
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import equal from 'fast-deep-equal';
import { useRef, useState } from 'react';
import { FieldValues } from 'react-hook-form';
Expand All @@ -9,9 +13,16 @@ import { useClient } from '@/src/context';
import { jobTitleEligibilityCheckOptions } from '@/src/flows/Onboarding/api';
import {
getJobTitleEligibilityParams,
getJobTitleEligibilityValues,
JOB_TITLE_ELIGIBILITY_SLUG_FIELD,
StepKeys,
} from '@/src/flows/Onboarding/utils';
import { JSFFields } from '@/src/types/remoteFlows';
import { JSFField, JSFFields } from '@/src/types/remoteFlows';

const CLEARED_JOB_TITLE_ELIGIBILITY_VALUES = getJobTitleEligibilityValues({
check_id: null,
verdict: 'not_assessed',
});

/**
* Owns the state and query for the job title eligibility check.
Expand Down Expand Up @@ -92,7 +103,15 @@ export const useJobTitleEligibilityCheck = ({
const jobTitle = submittedJobTitle ?? fallbackJobTitle;

const check = async (values: FieldValues) => {
if (!enabled || !employmentId || currentStepName !== 'contract_details') {
const hasSlugField = (contractDetailsFields as JSFField[]).some(
(field) => field.name === JOB_TITLE_ELIGIBILITY_SLUG_FIELD,
);
if (
!enabled ||
!employmentId ||
currentStepName !== 'contract_details' ||
!hasSlugField
) {
return;
}
const validation = await handleValidation(values);
Expand All @@ -106,17 +125,23 @@ export const useJobTitleEligibilityCheck = ({
if (!equal(paramsRef.current, nextParams)) {
setParams(nextParams);
}
if (nextParams) {
await queryClient
.query(getOptions(nextParams))
.catch(() =>
console.error('Failed to fetch job title eligibility check'),
);
if (!nextParams) {
return CLEARED_JOB_TITLE_ELIGIBILITY_VALUES;
}
try {
const result = await queryClient.query(getOptions(nextParams));
return getJobTitleEligibilityValues(result);
} catch (error) {
if (error instanceof CancelledError) {
return;
}
console.error('Failed to fetch job title eligibility check');
return CLEARED_JOB_TITLE_ELIGIBILITY_VALUES;
}
};

return {
isFetching: query.isFetching,
isFetching: currentStepName === 'contract_details' && query.isFetching,
check,
};
};
Loading
Loading