MPDX-9943 - Offer a guaranteed None geographic option instead of clearing - #1991
MPDX-9943 - Offer a guaranteed None geographic option instead of clearing#1991wjames111 wants to merge 1 commit into
Conversation
Backspacing a geographic autocomplete to empty fired an extra mutation mid-typing because MUI clears the value the moment the input empties, resetting the field to None while the user was still typing. - Guarantee a leading 'None' (0 multiplier) option in goalGeographicConstantMap so every geographic dropdown offers it even when a curated year dataset omits the row (the 2026 data does) - Make the PDS, MPD, and Salary Calculator fields non-clearable: an unset location displays as None, selecting None is the one way to unset, and emptying the input reverts on blur without saving - Make the NSO geographic question optional with a None default: drop its required validation and Ministry-step completion gating, since an unanswered question displays and calculates as None; hide the select's placeholder row when a default is shown - Replace helper copy that instructed users to select None MPDX-9943 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bundle sizes [mpdx-react]Compared against 8b6c94e No significant changes found |
wjames111
left a comment
There was a problem hiding this comment.
Multi-Agent Code Review (standard mode)
Verdict: APPROVED WITH SUGGESTIONS — no blockers. 5 specialized agents + a fresh gap-review agent + dependency analysis, with cross-examination debate and rebuttal rounds.
Key verified result: the Financial Reporting agent traced every consumer of goalGeographicConstantMap and confirmed no goal-calculation result changes for any existing saved data (null, saved city, or legacy 'None') — the only money-math lookup is pdsGoalConstants.ts:47 and it is invariant under this change. Dependency analysis found no breaking changes; NS FinancialInformationSection (unchanged) correctly gains the None option its new initial value requires.
Findings (post-debate severities; 7 medium-priority items posted as line comments, none blocking):
- [6.5] NS mapping is the one surface where the None display fallback becomes persisted data (untested write direction)
- [6.5] MPD lacks a positive "selecting None saves" test (PDS/NSO have one)
- [6.0] The byte-match-critical
'None'literal is hardcoded at 6 sites — export one constant - [6.0] Salary lacks a positive-save test; AutosaveAutocomplete's clearable branch is untested/unused
- [5.5] Degraded constants states (loading/error/unavailable) render an enabled one-option 'None' dropdown — gate like the ministries dropdown
- [5.5] PDS/MPD helper text lost the "leave it as None" guidance that Salary/NSO kept
- [5.5→anchored] SelectQuestion JSDoc claims that are stale/false (MUI suppresses same-value selection — confirmed in vendored 7.3.11 source)
Notable debate outcomes: Testing's 7.0 on the NS write path was revised to 5.5 after Financial proved the server accepts arbitrary location strings (the feared validation-rejection mode doesn't exist); UX's "data-loss path" was recalibrated to 5.5 "fail-deceptive degraded state" (the pre-PR clearable-X was a worse loss path); one finding (SelectQuestion || vs ??) was withdrawn — useAutoSave stringifies all values, and ?? would be a regression.
Findings on Related Files (Not in This PR)
[Suggestion] src/components/HrTools/NsoMpdQuestionnaire/Summary/useSummarySections.ts:138 — With the geographic question optional, the Summary review screen renders an unanswered city as error-red italic "No value provided" (blanket required: true row mapping) while the form shows "None" — and because MUI suppresses same-value selection, the user cannot repair the row from the form. Severity 4.0; fix at the required-flag mapping plus a product decision on the display text ("None" vs "No value provided"). Flagged by Gap Review, confirmed by 4 agents.
Risk: 10/10 (breadth-driven — 9 medium-risk feature files; no critical file classes touched). Suggestions below severity 5 are informational. Full agent transcripts available on request.
| advocacyTransfers: toNumberInput(calc.advocacyTransfers), | ||
| geographicLocation: calc.geographicLocation ?? '', | ||
| // An unset location defaults to the 'None' (0 multiplier) constant option | ||
| geographicLocation: calc.geographicLocation ?? 'None', |
There was a problem hiding this comment.
| // dropdowns are not clearable. Seed it first so it always exists even when a | ||
| // curated year dataset omits it; a server-provided 'None' row overwrites the | ||
| // value but keeps the position. | ||
| goalGeographicConstantMap.set('None', 0); |
There was a problem hiding this comment.
| ); | ||
| }); | ||
|
|
||
| it('does not save while the Geographic Location is cleared by typing', async () => { |
There was a problem hiding this comment.
| expect(await findAllByRole('option')).toHaveLength(3); | ||
| }); | ||
|
|
||
| it('does not save while the location is cleared by typing and reverts on blur', async () => { |
There was a problem hiding this comment.
| options={locations} | ||
| // The None option takes the place of clearing the field, so | ||
| // emptying the input never fires a mid-typing null save | ||
| disableClearable |
There was a problem hiding this comment.
| label={t('Geographic Multiplier')} | ||
| helperText={t('If not applicable, select "None"')} | ||
| helperText={t( | ||
| 'Do you live within 50 miles of one of these major cities?', |
There was a problem hiding this comment.
| /** Whether an answer is required. Defaults to true. */ | ||
| required?: boolean; | ||
| /** | ||
| * Option displayed when the field has no saved value (e.g. 'None'). Must match one of the |
There was a problem hiding this comment.
| // With an emptyValue option there is nothing to clear to, and disabling | ||
| // clearing also stops MUI from firing a mid-typing null save when the | ||
| // input is emptied | ||
| disableClearable={emptyValue !== undefined} |
There was a problem hiding this comment.
| const stepRequiredFields: Record<string, QuestionnaireField[]> = { | ||
| // Personal Information is a read-only review step, so it has no fields the user must fill in. | ||
| [NsoMpdQuestionnaireStepEnum.PersonalInformation]: [], | ||
| // geographicLocation is intentionally absent: it is optional and an |
There was a problem hiding this comment.
| }); | ||
|
|
||
| it('does not save while the Geographic Multiplier is cleared by typing', async () => { | ||
| mutationSpy.mockClear(); |
There was a problem hiding this comment.
Description
Supersedes #1987 (closed), reimplemented per review feedback there: instead of deferring clear-to-null saves to blur, the geographic autocompletes are now non-clearable and "None" is a guaranteed option — much less code for the same user-facing outcome.
geographicLocation: nullmutation mid-typing (MUI clears the value the moment the input empties) and reset the visible field to "None" while the user was still typing.goalGeographicConstantMapnow seeds a leadingNone(0 multiplier) option, so all five tools' dropdowns offer it even though the live 2026 dataset lost its None row (being restored by hand via the admin UI — the ActiveAdmin page is the canonical way to manage these constants going forward).disableClearable: an unset location displays as "None", explicitly selecting "None" is the single way to unset, and emptying the input just reverts on blur without saving anything.nulland'None'remain equivalent everywhere (client map lookups and the server'smultiplier_for!both resolve to a ×1.0 multiplier), so legacy null values are unaffected.Related Jira ticket: MPDX-9943
Testing
hrTools/pdsGoalCalculator, open a goal, and go to the Setup stepgeographicLocation: "None"Checklist:
/quality:agent-reviewcommand locally and fixed any relevant suggestions🤖 Generated with Claude Code