Skip to content

No-Jira - Disable Continue on Setup Step until values filled by user#1722

Draft
zweatshirt wants to merge 2 commits intomainfrom
pds-disable-continue
Draft

No-Jira - Disable Continue on Setup Step until values filled by user#1722
zweatshirt wants to merge 2 commits intomainfrom
pds-disable-continue

Conversation

@zweatshirt
Copy link
Copy Markdown
Contributor

Description

  • Since future steps depend on these values in SetupStep, it is important to enforce input from the user before they can continue.

Testing

  • Go to reports/goalCalculator
  • Ensure all user input fields must be filled before continuing the calculator

Checklist:

  • I have given my PR a title with the format "MPDX-(JIRA#) (summary sentence max 80 chars)"
  • I have applied the appropriate labels (Add the label "Preview" to automatically create a preview environment)
  • I have run the Claude Code /pr-review command locally and fixed any relevant suggestions
  • I have requested a review from another person on the project
  • I have tested my changes in preview or in staging
  • I have cleaned up my commit history

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

Bundle sizes [mpdx-react]

Compared against 2cfad2c

No significant changes found

@zweatshirt zweatshirt force-pushed the pds-disable-continue branch from aee607f to 6a3da71 Compare April 16, 2026 19:16
Copy link
Copy Markdown
Contributor Author

@zweatshirt zweatshirt left a comment

Choose a reason for hiding this comment

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

Multi-Agent Code Review

Verdict: APPROVED_WITH_SUGGESTIONS
Risk: MEDIUM (5/10)
Mode: standard (5 agents: Architecture, Testing, Standards, UX, Financial Reporting)

Summary

The PR correctly wires AutosaveForm + disableNext to disable the Continue button when Setup step fields are empty. The shared-component change (DirectionButtons) was carefully scoped — the new disableNext prop defaults to undefined, so existing callers (MHA StepOne/Two/Three, AdditionalSalaryRequest) are unaffected. Verified across all 5 callsites.

What landed well

  • Mirrors the SalaryCalculator pattern (AutosaveForm + StepContent extraction).
  • New disableNext prop is purposefully separate from isValid to avoid disabling Continue in the ASR RequestPage flow.
  • Conditional fields (hoursWorkedPerWeek, benefits) re-enable Continue correctly on unmount via the existing useAutosave cleanup.
  • i18n: all new required-error messages wrapped in t().
  • Financial Reporting agent: no financial calculation code in this PR — all checks pass or N/A.

Top findings

  1. [Important 8.0] SetupStep.tsx:259 — The Geographic Multiplier Autocomplete displays 'None' when the underlying value is null (value={calculation?.geographicLocation ?? 'None'}), which now looks like a filled field even though the required check fails. Users see 'None' selected but Continue disabled with no error state on the control. See the inline comment on the new useEffect for the suggested fix.

  2. [Medium 7.0] DirectionButtons.tsx:146 — Disabled Continue has no tooltip/aria explanation. Once the Autocomplete error state is fixed, field-level messages cover most cases, but a <Tooltip> on the button itself still helps.

  3. [Medium 6.5] SetupStep.tsx:69 — The new geographicLocation yup rule is effectively dead (the runtime check is a manual useEffect testing truthiness). Either delete the rule or introduce AutosaveAutocomplete so validation flows through useAutoSave consistently.

Important-tier note

WARNING: 1 Important finding (severity 8.0) — strongly recommended fix that doesn't block merge but can't be dismissed via /dismiss. Address in this PR or create a follow-up ticket.

Agents

Agent Findings
Architecture 3 medium + 2 suggestions (useEffect pattern, prop bloat, duplicate hook call)
Testing 2 medium (Submit-branch leak test, schema-invalid value test) + 2 suggestions
Standards 2 concerns (useEffect pattern, server-side validation parity WARN); checklist otherwise PASS
UX 1 important + 2 medium + 3 suggestions (Autocomplete, tooltip, loading flicker)
Financial Reporting No financial calculation code in this PR

No agent challenged another's findings. No severity spreads ≥4 points. All 5 changed files received coverage from 2+ agents — no gap review needed.


const autosaveForm = useOptionalAutosaveForm();
const geographicLocationValue = calculation?.geographicLocation;
useEffect(() => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Important] **Geographic Multiplier UX trap.** The Autocomplete (line 259 in this file) uses `value={calculation?.geographicLocation ?? 'None'}`, which displays `'None'` (a real option in `mpdGoalGeographicConstants`) whenever the underlying value is `null`. After this PR makes the field required, the user sees `'None'` as if selected, but this `useEffect` fires `markInvalid` and Continue is disabled — with no error state on the Autocomplete. Other fields show red helperText via AutosaveTextField, but this one bypasses that path.

Suggested fix:

<Autocomplete
  options={locations}
  value={calculation?.geographicLocation ?? null}
  onChange={(_, newValue) => saveField({ geographicLocation: newValue })}
  disabled={!calculation}
  size="small"
  renderInput={(params) => (
    <TextField
      {...params}
      label={t('Geographic Multiplier')}
      required
      error={!!calculation && !calculation.geographicLocation}
      helperText={
        !!calculation && !calculation.geographicLocation
          ? t('Geographic Multiplier is a required field')
          : t('If not applicable, select "None"')
      }
    />
  )}
/>

Passing null lets MUI render the field empty with floating label. required + error + helperText match the other fields' UX and add aria-invalid for screen readers.

.nullable()
.required(t('Benefits is a required field'))
.min(0, t('Benefits must be a positive number')),
geographicLocation: yup
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Medium] **Dead schema rule + unprecedented pattern.** The `geographicLocation: yup.string().required(...)` rule you're adding here is never validated against — the runtime check is a manual `useEffect` at line 85 that only tests truthiness. And calling `markValid`/`markInvalid` from a component's useEffect is a new pattern (no other callers in the repo outside `useAutosave.ts`). SalaryCalculator has an `AutosaveAutocomplete` wrapper (`src/components/Reports/SalaryCalculator/Autosave/AutosaveAutocomplete.tsx`) that would be the cleaner precedent.

Options:

  • Cheap: delete the yup rule with a comment (validation is driven by the effect).
  • Preferred (likely follow-up PR): introduce AutosaveAutocomplete mirroring AutosaveTextField so validity flows through useAutoSave. That also addresses the IMP-1 UX symptom because the wrapper would consume error/helperText from the schema.

Flagged by Architecture and Standards agents.

payRate: yup
.number()
.nullable()
.required(t('Pay Rate is a required field'))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Suggestion] `yup.number().required().min(0)` accepts literal `0`. For a staff salary, `$0/yr` isn't meaningful. Consider `.moreThan(0, t('Pay Rate must be greater than zero'))` for `payRate` and also for `hoursWorkedPerWeek` (line 63). `benefits` can stay `.min(0)` since zero benefits is legitimate. Out of scope for the Continue-button work — file for follow-up if desired.

name: yup.string().required(t('Goal Name is a required field')),
status: yup.string().nullable(),
salaryOrHourly: yup.string().nullable(),
status: yup
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Suggestion] Verify server-side parity: the new `required()` rules are client-only enforcement. If `DesignationSupportCalculationUpdateInput` (or the REST proxy) doesn't also reject missing values, a stale client or alternate entry path could still submit incomplete data. Not blocking — the same fields were `.nullable()` before this PR, so no new risk — but good to confirm.

variant="contained"
color="primary"
onClick={overrideNext ?? handleNextStep}
disabled={disableNext}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Medium] **No feedback on why Continue is disabled.** `disabled={disableNext}` alone gives users no explanation once they've noticed they can't advance. After IMP-1 is fixed, field-level errors will explain most cases, but a tooltip on the button is still the clearest affordance.

Suggested:

<Tooltip title={disableNext ? t('Complete all required fields to continue') : ''}>
  <span>
    <Button
      variant="contained"
      color="primary"
      onClick={overrideNext ?? handleNextStep}
      disabled={disableNext}
      aria-describedby={disableNext ? 'continue-disabled-reason' : undefined}
    >
      {buttonTitle ?? t('Continue')}
      <ChevronRight sx={{ ml: 1 }} />
    </Button>
  </span>
</Tooltip>

(The <span> wrapper is required because disabled buttons don't fire pointer events — MUI tooltip gotcha.)

Note: SalaryCalculator's ContinueButton has the same gap — fixing it here improves both.

additionalApproval?: boolean;
splitAsr?: boolean;
disableSubmit?: boolean;
disableNext?: boolean;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Medium] **Prop bloat.** This shared component now carries 24 props covering three distinct use cases (MHA formik submit, ASR formik submit, PDS autosave continue). Continue uses `disableNext`, Submit uses `submitCount ? !isValid : false`, and `disableSubmit` affects the SubmitModal. The next contributor who wants PDS to also show validity on a submission step, or ASR to disable Continue while invalid, will have to add yet another combination.

Follow-up refactor idea (not blocking this PR): consolidate to a single disabled prop computed by callers. The Formik callers already have isValid/submitCount locally. Shrinks the contract from 'know the branch + pick right prop' to 'compute disabled once.'

formTitle={currentStep.title}
handleNextStep={handleContinue}
handlePreviousStep={handlePreviousStep}
disableNext={!allValid}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Medium] **Loading-state flicker.** `useAutosaveForm()` starts with `invalidFields = []` → `allValid = true`. Child fields then mount, run their validity effects, and `allValid` flips to `false`. On first render (or slow Apollo load) Continue briefly shows enabled, then disabled.

Suggested: include the loading gate in the disabled check.

const { currentStep, stepIndex, steps, handleContinue, handlePreviousStep, calculation } =
  usePdsGoalCalculator();
...
disableNext={!calculation || !allValid}

The Autocomplete at SetupStep.tsx:263 already gates itself on !calculation — the Continue button should follow suit.

).toBeInTheDocument();
});

it('disables Continue on Setup step when a required field is missing', async () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Medium] **Missing edge-case coverage.** The three new tests cover empty/null values, but not 'field is filled but schema-invalid' — e.g., `payRate: -5` violates `.min(0)`. Without this, a future change that silently drops `.min(0)` from the schema won't be caught at the feature level.
it('disables Continue on Setup step when a required field is invalid', async () => {
  const { findByRole } = render(
    <PdsGoalCalculatorTestWrapper
      calculationMock={{ ...completeSetupMock, payRate: -5 }}
    >
      <PdsGoalCalculator />
    </PdsGoalCalculatorTestWrapper>,
  );
  const continueButton = await findByRole('button', { name: /continue/i });
  await waitFor(() => expect(continueButton).toBeDisabled());
});

expect(await findByRole('button', { name: title })).toBeInTheDocument();
});

it('disables Continue when disableNext is true', async () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Medium] **Missing regression test for Submit branch.** All three new `disableNext` tests exercise the Continue branch (default `isSubmission=false`). The code is correct — `disabled={disableNext}` lives only in the `!isSubmission` branch — but a test would pin that contract. A refactor that accidentally applies `disabled={disableNext}` to the Submit branch wouldn't be caught here.
it('does not disable Submit even when disableNext is true', async () => {
  const { findByRole } = render(
    <TestComponent isSubmission={true} disableNext={true} />,
  );
  expect(await findByRole('button', { name: /submit/i })).toBeEnabled();
});

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

AI Review Auto-Approval

Risk Level: MEDIUM (5/10)
Verdict: APPROVED_WITH_SUGGESTIONS (suggestions posted, no blockers)

This PR was auto-approved because:

  • The multi-agent AI review determined it is medium risk
  • No blocking issues were found
  • All suggestions have been posted as review comments for the developer to consider

If you believe this PR needs human review, dismiss this approval and request a review manually.

@zweatshirt zweatshirt marked this pull request as ready for review April 16, 2026 19:55
@zweatshirt zweatshirt marked this pull request as draft April 16, 2026 20:29
@zweatshirt zweatshirt marked this pull request as draft April 16, 2026 20:29
@zweatshirt zweatshirt self-assigned this Apr 16, 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