Skip to content

MPDX-9611 Scope PDS autosave disable to per-field saves#1797

Merged
wjames111 merged 4 commits into
mainfrom
MPDX-9611
May 22, 2026
Merged

MPDX-9611 Scope PDS autosave disable to per-field saves#1797
wjames111 merged 4 commits into
mainfrom
MPDX-9611

Conversation

@wjames111

Copy link
Copy Markdown
Contributor

Description

  • The Form Type, Employment Status, and Pay Type selects in the PDS Goal Calculator Setup step briefly rendered disabled whenever an unrelated field (e.g. Benefits) was being saved, because they were gated on the global isMutating flag from the calculator context.
  • Added per-field saving state to PdsGoalCalculatorContext: a new isFieldSaving(fieldName) selector and a trackFieldMutation(promise, fields) wrapper that tags the fields being written for the duration of the mutation.
  • useSaveField now uses trackFieldMutation and passes Object.keys(attributes), so each in-flight save tags only the fields it actually writes.
  • usePdsGoalAutoSave and the manual Pay Type select in SetupStep now check isFieldSaving(fieldName) instead of isMutating. Same-field race protection on formType (and friends) is preserved — only the cross-field flicker is gone.
  • Added a regression test that saving an unrelated field does not disable a saveOnChange select.
  • Jira: MPDX-9611

Testing

  • Open the PDS Goal Calculator Setup step for a Full-time Salaried goal so all of Form Type, Employment Status, Pay Type, Pay Rate, and Benefits are visible.
  • Edit the Benefits field, then tab/click away to trigger the autosave.
  • Watch Form Type, Employment Status, and Pay Type while the Benefits save is in flight — they should remain interactive (no flicker to a disabled state).
  • Toggle Form Type (Default ↔ Simple) rapidly and confirm the Form Type select itself is briefly disabled while its own save is in flight — same-field race protection still applies.
  • Toggle Pay Type (Salaried ↔ Hourly) and confirm the Pay Type select disables only during its own save, and the atomic salaryOrHourly + payRate: null write still happens.

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

🤖 Generated with Claude Code

The Form Type, Employment Status, and Pay Type selects briefly flickered
disabled whenever any other field (e.g. Benefits) was being saved,
because they were gated on the global isMutating flag. Track saving
state per field via trackFieldMutation so a select only disables while
its own save is in flight — preserving the same-field race protection
without affecting unrelated fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Bundle sizes [mpdx-react]

Compared against bdb41fd

No significant changes found

@wjames111 wjames111 left a comment

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.

Multi-Agent Code Review — VERDICT: BLOCKERS_FOUND

6 agents · Architecture · Data Integrity · Testing · UX · Standards · Financial Reporting

Top blocker

Cross-field stale-write race (consensus from Data Integrity, UX, Financial Reporting). The PR narrows the in-flight save guard from "any mutation in flight" to "same field in flight," but UpdatePdsGoalCalculation responses carry the full PdsGoalCalculationFields fragment. A second save's response can land after a first save's, stamping stale field values (notably formType) into the Apollo cache — the exact silent goal-total miscalculation the in-line comment warns about. See inline comment on usePdsGoalAutoSave.ts.

Risk assessment

  • Risk: 5/10 — MEDIUM
  • Reviewer level recommended: mid-level / senior

Findings summary

Tier Count
Critical (9-10) 0
High Priority Blocker (8-8.9) 1
Important (7-7.9) 2
Medium (5-6.9) 2
Suggestions (<5) ~10

Verdict reasoning

The per-field disable guard is correctly motivated for rapid same-field toggling, but is insufficient because (a) the server response carries the full fragment, so cross-field responses can interleave and stomp the cache, and (b) the Pay Type → payRate: null multi-field atomic save isn't protected on the Pay Rate text field (which has saveOnChange=false). Either widen the guard back for formType/salaryOrHourly, or drop the saveOnChange === true qualifier so blur-driven fields also disable during their own save.


Findings on Related / Pre-existing Code (Not in This PR's Diff)

[Suggestion] SetupStep.tsx:316-332 (Geographic Multiplier Autocomplete)

  • Severity: 4.0/10
  • Flagged by: UX, Data Integrity
  • Note: disabled={!calculation} only — no in-flight protection of any kind. Same race class as the blocker above. Pre-existing (not introduced by this PR). Worth a follow-up ticket, not a blocker here.

Standards & Quality summary

  • Named exports: PASS
  • TypeScript hygiene: PASS (no any, no @ts-ignore, no !)
  • i18n: PASS (no new strings)
  • Lint/TS clean for touched files: PASS
  • Test colocation + GqlMockedProvider typed generics: PASS
  • isMutating is NOT dead code — page-level SavingStatus still consumes it via pages/.../pdsGoalCalculator/[pdsGoalId].page.tsx.

To resolve: address the blocker comment on usePdsGoalAutoSave.ts, then either fix or reply /dismiss: <reason> to severity < 7 findings you disagree with. Severity 7.0-7.9 findings cannot be dismissed but don't trigger BLOCKERS_FOUND on their own.

Comment thread src/components/HrTools/PdsGoalCalculator/Shared/Autosave/usePdsGoalAutoSave.ts Outdated
Comment thread src/components/HrTools/PdsGoalCalculator/Setup/SetupStep.tsx Outdated
Comment thread src/components/HrTools/PdsGoalCalculator/Shared/PdsGoalCalculatorContext.tsx Outdated
Comment thread src/components/HrTools/PdsGoalCalculator/Shared/Autosave/usePdsGoalAutoSave.ts Outdated

@wjames111 wjames111 left a comment

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.

Multi-Agent Code Review — Standard Mode

Verdict: APPROVED WITH SUGGESTIONS
Risk: 5/10 (MEDIUM)
Agents: 5 specialized reviewers (Architecture, Data Integrity, Testing, UX, Standards)
Diff: 7 files, +214/-9

Headline

The per-field saving state machine is correct under concurrency, handles resolve and reject paths, and is well-tested. No blocking issues in PR files. The most consequential finding is a related-file finding (not in this PR): the PR description names Pay Type as one of three selects being fixed, but the Pay Type select still binds to the global isMutating flag — see the "Findings on Related Files" section below.

Summary of in-PR Findings

Tier Count Notes
Critical (9-10) 0
High (8-9) 0
Important (7-8) 0 in-PR (1 related-file, informational) Cannot be dismissed if any
Medium (5-7) 7 Posted as line comments below
Suggestions (<5) ~10 Informational

All Standards-checklist items PASS. yarn lint + yarn lint:ts confirmed clean. Apollo cache normalization preserved (id + __typename in optimistic response).

Cross-cutting consistency check

The new trackFieldMutation is correctly invoked by the single consumer (useSaveField) with Object.keys(attributes), so multi-field atomic writes (e.g. salaryOrHourly + payRate: null) correctly tag both fields as saving for the lifetime of the single mutation. HoursPerWeekGrid still uses trackMutation (correctly — it's a multi-step hours workflow, not a per-field save).


Findings on Related Files (Not in This PR)

These were identified during review on files related to the changes but not directly modified in this PR. They cannot be posted as line comments.

[Important] src/components/HrTools/PdsGoalCalculator/Setup/SetupStep.tsx:217 — Pay Type select still uses global isMutating

  • Severity: 7.5/10
  • Flagged by: Architecture + UX (consensus)
  • Problem: The PR description states the fix removes the disabled flicker for "Form Type, Employment Status, and Pay Type selects" in the Setup step. Form Type and Employment Status route through AutosaveTextField → usePdsGoalAutoSave, which the diff updates correctly. But Pay Type is a manual <TextField> (it must atomically clear payRate), and at SetupStep.tsx:217 it still reads disabled={!calculation || isMutating}. After this PR merges, Pay Type will still flicker disabled while Benefits / Goal Name / Pay Rate is being saved — exactly the behavior the PR claims to remove.
  • Recommended fix (single-line change you can land on this branch as a follow-up commit):
// SetupStep.tsx:41 — pull isFieldSaving instead of isMutating
const { calculation, hcmUser, isFieldSaving, setRightPanelContent } =
  usePdsGoalCalculator();

// SetupStep.tsx:217
disabled={
  !calculation ||
  isFieldSaving('salaryOrHourly') ||
  isFieldSaving('payRate')
}

useSaveField already passes Object.keys(attributes), so the multi-field atomic write tags both fields and only Pay Type + Pay Rate will disable during their own save.


Pre-existing notes (informational)

  • useSaveField.ts:52 catch { ... enqueueSnackbar ... } drops the error object entirely (no console.error, no Sentry). Server validation messages are invisible. Pre-existing pattern; the PR rewrites lines around it.
  • Form-wide: disabled is used as a "saving in progress" signal where aria-busy would be more semantically correct. Pattern is repo-wide; the codebase already uses aria-busy elsewhere (CsvHeaders.tsx:337, CsvPreview.tsx:346).

Comment thread src/components/HrTools/PdsGoalCalculator/Shared/Autosave/usePdsGoalAutoSave.ts Outdated
Comment thread src/components/HrTools/PdsGoalCalculator/Shared/Autosave/usePdsGoalAutoSave.ts Outdated
Comment thread src/components/HrTools/PdsGoalCalculator/PdsGoalCalculatorTestWrapper.tsx Outdated
Comment thread src/components/HrTools/PdsGoalCalculator/PdsGoalCalculatorTestWrapper.tsx Outdated
Comment thread src/components/HrTools/PdsGoalCalculator/PdsGoalCalculator.test.tsx

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

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.

@wjames111 wjames111 self-assigned this May 22, 2026
@wjames111 wjames111 added On Staging Will be merged to the staging branch by Github Actions Preview Environment Add this label to create an Amplify Preview labels May 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Preview branch generated at https://MPDX-9611.d3dytjb8adxkk5.amplifyapp.com

@wjames111 wjames111 merged commit 7469fb8 into main May 22, 2026
65 of 66 checks passed
@wjames111 wjames111 deleted the MPDX-9611 branch May 22, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

On Staging Will be merged to the staging branch by Github Actions Preview Environment Add this label to create an Amplify Preview

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant