Conversation
…mple Adds `formType` to `PdsGoalTotalFields` and short-circuits reimbursable total and 403b percentage to zero inside `calculatePdsGoalTotal` when the form type is Simple. Also pins `formType: Detailed` in the shared test wrapper defaults so that `gqlMock`-generated enum values no longer cause flaky dollar-amount assertions across the PdsGoalCalculator suite. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a useEffect that resets stepIndex to 0 whenever it falls outside the bounds of the current steps array. Guards against a future crash if formType switches from Detailed to Simple while the user is on a later step (e.g. SummaryReport at index 3 becomes out-of-bounds when steps shrinks from 4 to 3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…acy case Tightens the formType validation to only accept values in DesignationSupportFormType, and adds a test documenting that a null formType (pre-feature records) falls back to Default-view rendering (403b field shown). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add an optional `badge` prop to the shared GoalCard and render a MUI Chip labelled "Default" (Detailed form type) or "Simple" in PdsGoalCard, so users can distinguish goal types at a glance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements a presentational dialog that lets the user choose between Default (Detailed) and Simple DesignationSupportFormType before creating a new PDS goal. Follows TDD: failing test written first, then component. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion flow - Wrap createPdsGoalCalculation in try/catch with notistack error snackbar so network/server errors surface to the user and the dialog stays open for retry - Reset selected radio state on dialog close so the form starts clean if reopened - Add aria-labelledby to Dialog referencing DialogTitle id for accessibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bundle sizes [mpdx-react]Compared against 5869477
|
wjames111
left a comment
There was a problem hiding this comment.
Multi-Agent Code Review
Verdict: APPROVED_WITH_SUGGESTIONS — No blockers. 3 Important findings (sev 7.0–7.9), several medium findings, and many polish suggestions.
Risk Score: 10/10 (CRITICAL by formula — capped). Pragmatically HIGH: feature-scoped to PDS Goal Calculator, well-tested, ~487 non-test lines, but touches financial calculations + a new GraphQL field + nested wizard state.
Mode: standard | Agents: 6 (Architecture, Testing, Standards, Data Integrity, UX, Financial Reporting) + Dependency Impact
Top-Line Summary
Good feature work. The refactor of calculatePdsGoalTotal and usePdsSummaryData to share buildPdsGoalConstants + buildOtherExpensesConstants is a clear win for consistency. Tests cover the new behavior thoroughly with the project's standard GqlMockedProvider + mutationSpy + toHaveGraphqlOperation patterns. Localization, named exports, generated types, and cache id selection are all clean.
The two themes worth attention before merge:
- Step-state UX when toggling Form Type — the
activeStep↔stepIndexderivation can desync silently when steps shrink (Detailed → Simple while onReimbursableExpenses). Consensus from Architecture, UX, and Data Integrity agents. - A pre-existing display inconsistency between
PdsGoalCard(showsassessment~$1k) andPdsSummaryTable(showsoverallTotal~$10k) is now made more visible by the new badge. Worth a separate ticket — not introduced by this PR but amplified by it.
Important Findings (Severity 7.0–7.9)
These are strongly recommended fixes that don't block merge but cannot be dismissed via /dismiss (severity ≥ 7). Address them in this PR or create follow-up tickets.
- [Sev 7.0] Radio button accessible names include the full description —
CreateGoalDialog.tsx:99(UX) - [Sev 7.0]
PdsGoalCardgoalAmount diverges fromPdsSummaryTableTotal Goal —PdsGoalCard.tsx:43(Financial Reporting, [Pre-existing]) - [Sev 7.0]
handleStepChangesnackbar path and silent-fallback no-snackbar invariant are untested —PdsGoalCalculatorContext.test.tsx(Testing)
Medium Priority (Severity 5.0–6.9)
- [Sev 6.5] Silent fallback to Setup when
activeStepis dropped fromsteps— multi-agent consensus (Architecture, UX, Data Integrity) - [Sev 6.5]
CreatePdsGoalCalculationmutation lacksupdate/refetchQueries/cache.modify— Standards [Pre-existing pattern, but worth fixing] - [Sev 6.5] Cancel button disabled while creating removes only visible escape route — UX
- [Sev 6.5] Mocking
useStepsin context test weakens 4 pre-existing integration tests — Testing - [Sev 6.0]
activeStepretains orphaned step value whilestepIndexfalls back to 0 — Architecture + Data Integrity - [Sev 6.0] Variable shadowing of
dataanderrorinsidehandleCreateGoal— Architecture - [Sev 6.0] Form Type select renders empty for legacy
formType: nullgoals — Data Integrity - [Sev 6.0]
currentStep: PdsGoalCalculatorStepis typed non-nullable butsteps[stepIndex]can beundefined— Data Integrity - [Sev 6.0] No test pins the consistency invariant
calculatePdsGoalTotal === usePdsSummaryData.otherTotals.assessment— Financial Reporting - [Sev 5.5]
CreateGoalDialoguses rawuseStateform state where the rest of the calculator is Formik-driven — Standards (judgment call) - [Sev 5.0]
PdsSummaryTablereuses line label2Afor different content between Simple/Detailed — UX - [Sev 5.0] No defaults are seeded for
ministryCellPhone/ministryInternetwhen creating Simple — switching back to Detailed leaves them un-seeded — Data Integrity - [Sev 5.0] Simple
formTypetest asserts only inequality, not a pinned dollar amount — Financial Reporting
Suggestions (Severity < 5.0, informational only)
- [4.5] No user feedback when toggling Form Type silently removes 403b row + Reimbursable Expenses step — UX
- [4.0] Form Type select default for legacy
nullgoals could display "Default" — UX - [4.0] Add a unit test for
formType.tshelpers (isSimpleFormType,isDesignationSupportFormType) — Testing - [4.0]
useStepstest description saysnull/undefinedbut only testsnull— Testing - [4.0]
CreateGoalDialogonCreatetest only covers Simple branch — also assert Detailed — Testing - [3.5]
<Typography variant="subtitle1">defaults to<h6>(block element) inside<label>— invalid HTML — Architecture - [3.5]
CircularProgress size={16}deviates from project'ssize={20}convention — UX - [3.5] Chip badge has no color/variant differentiation between Default and Simple — UX
- [3.5] Inline 403b sum logic was previously extracted; PR re-inlines it into JSX — Architecture
- [3.0]
PdsGoalsListerror test should also assert the dialog stays open — Testing - [3.0] Spurious multi-line reformat of the React import block — Architecture
- [3.0] Redundant
getByRoleafterfindByRoleinPdsSummaryTable.test.tsx— Testing - [3.0]
isDesignationSupportFormTypeparameter could beunknownfor safer reuse — Data Integrity
Standards Checklist (from .claude/rules/code-review.md)
| Area | Status | Note |
|---|---|---|
| Named exports only | PASS | All new exports named |
| File naming | PASS | PascalCase components, .test.tsx colocated, .graphql PascalCase |
| GraphQL operation names | PASS | No Get/Load prefixes |
Hook names start with use |
PASS | useSteps, usePdsSummaryData, etc. |
All user-visible strings via t() |
PASS | No hardcoded JSX text, no string interpolation, no dynamic keys |
id in selection sets |
PASS | Fragment includes id and formType |
| Pagination handled | PASS | useFetchAllPages unaffected by additive field |
| Mutation cache updates | WARN | CreatePdsGoalCalculation missing — flagged above |
No any in new code |
PASS | Generated types used throughout |
| Forms use Formik + Yup | WARN | CreateGoalDialog raw useState — judgment call |
| Submit disabled while submitting | PASS | `disabled={!selected |
Tests colocated *.test.{ts,tsx} |
PASS | Every new file has tests |
GqlMockedProvider typed generics |
PASS | mocksOverride: ApolloErgonoMockMap properly typed |
findBy* over waitFor(() => getBy*) |
PASS | Idiomatic |
No console.log/debugger/orphan TODO |
PASS | Clean |
No new Date() (use Luxon) |
N/A | No date logic added |
Financial Checklist
| Item | Status |
|---|---|
| Arithmetic on money values safe | Yes (no new compounding float math) |
| Currency mixing prevented | N/A (USD only) |
| Rounding at display boundary only | Yes |
| Null/undefined amounts handled | Yes (isSimpleFormType(null) correctly returns false) |
| Luxon used for dates | N/A |
| Server-provided aggregations preferred | N/A (client-projected goal) |
Two-path consistency (calculatePdsGoalTotal vs usePdsSummaryData) |
Partial — paths share a helper, but display semantics still diverge (pre-existing) |
Dependency Impact
PdsGoalCalculatorContext.tsxhas 14+ direct dependents (provider). High blast radius for the step-state refactor.useSteps()signature change: 1 production caller, updated.GoalCardbadge?prop: optional, both callers compatible. No runtime breaks detected.- The
formTypefield added to thePdsGoalCalculationFieldsfragment must be returned by the REST proxy datahandler — if missing, the field isnull, which all consumers handle.
Files on Auto-Approval Eligibility
This PR scores LOW/MEDIUM in pragmatic risk terms but the formula caps at 10. Verdict logic uses non-dismissed, non-pre-existing findings only — no blockers, so verdict is APPROVED_WITH_SUGGESTIONS.
| @@ -38,6 +42,13 @@ export const PdsGoalCard: React.FC<PdsGoalCardProps> = ({ goal }) => { | |||
| return constants ? calculatePdsGoalTotal(goal, constants) : 0; | |||
| }, [goal, goalMiscConstants, goalGeographicConstantMap, hcmUser]); | |||
There was a problem hiding this comment.
calculatePdsGoalTotal()returnsotherExpenses.assessmentonly (the assessment line:(subtotal + creditCardFees + attrition) * adminRate).usePdsSummaryData.overallTotal = otherTotals.subtotal + otherTotals.attrition + otherTotals.creditCardFees + otherTotals.assessment.- The unit tests pin these distinct values:
expect(result).toBeCloseTo(1038.21, 1)forcalculatePdsGoalTotalvsexpect(overallTotal).toBeCloseTo(9815.77, 0).
Pre-dates this PR (introduced in commit edfa70868 per the goal-card landing PR #1755), but this PR makes the inconsistency more visible by adding the Default/Simple badge directly next to the card amount. Staff comparing card-vs-summary will see two very different numbers labeled "goal."
Recommended follow-up (not blocking this PR):
- Either change
PdsGoalCard.goalTotalto useusePdsSummaryData(goal, hcmUser).overallTotal, or renamecalculatePdsGoalTotaltocalculatePdsAssessmentand add acalculatePdsGoalOverallTotalhelper. Current naming is semantically misleading.
Flag for a separate ticket — does not block this PR.
|
Preview branch generated at https://MPDX-9475.d3dytjb8adxkk5.amplifyapp.com |
zweatshirt
left a comment
There was a problem hiding this comment.
This is great Will. This PR is big so there's a possibility I missed something, but from what I can tell, this is solid.
|
|
||
| <StyledCard> | ||
| <StyledHeaderBox> | ||
| <StyledHeaderBox sx={badge ? { gap: 1 } : undefined}> |
There was a problem hiding this comment.
This is opinion, but I'm wondering if it might look better to make Simple/Default a row in the card? I like the badge/chip but I'm just not sure I like where it's placed (right next to the name). What do you think of a 'Goal Type' row or similar? Either way, that's not blocking since it's just opinion
There was a problem hiding this comment.
I think that's a good call. I've got a few PR's out on against this one, so I'll create a ticket for this and circle back.
Description
Wires the existing API field
DesignationSupportCalculation.formType(valuesDETAILEDandSIMPLE) through the PDS Goal Calculator UI so users can choose between a full Default view and a streamlined Simple view at goal creation time and switch between them later from the Settings step.CreateGoalDialogopens when the user clicks Create a New Goal on the goals list — the user must explicitly pick Default or Simple before the goal is created. The chosen form type is sent asformTypeon the create mutation.PdsGoalCardshows a small Default / Simple chip near the goal name so users can identify form types from the list. Legacy goals with a nullformTypeshow no chip.SetupStephas a new Form Type select (right under Goal Name) that lets the user toggle between Default and Simple at any time. Switching is silent — saved field values are preserved on the record so toggling back restores the original calculation instantly.formType === Simple:usePdsSummaryData) and per-card total calculation (calculatePdsGoalTotal) zero outreimbursableTotalandfourOThreeBPercentagewhen building the constants forcalculateOtherExpenses(the pure helper itself is unchanged)useEffectclampsstepIndexto 0 if the steps array shrinks after aformTypechange, preventing acurrentStep === undefinedcrash.Related to MPDX-9475.
Testing
Goal creation
Form type toggle
Summary report
Goal card
formType: nullbehavior)Checklist: