[e2e-test-engineer]
BUG: ReportWizardPage (#1879) — wizard crashes on step 1→2 transition, plus multiple compile errors, missing i18n keys, and spec-deviating claim-success UI
Severity: Blocker
Component: Frontend UI — client/src/pages/ReportWizardPage/, client/src/components/WizardStepper/, client/src/components/reports/
Found in: e2e-test-engineer authoring e2e/tests/budget/reportWizard.spec.ts for Story #1879 (static code review + tsc --noEmit -p client/tsconfig.json, since the E2E environment cannot build/run in this sandbox — see Verification section). Not yet confirmed against a live browser; confirmed via source inspection and Array.prototype/type analysis.
1. Blocker: budgetSources state is set to the raw API envelope, not an array
ReportWizardPage.tsx line ~103:
const [sources, settings, status] = await Promise.all([
fetchBudgetSources(),
...
]);
setBudgetSources(sources);
fetchBudgetSources() (client/src/lib/budgetSourcesApi.ts) resolves to BudgetSourceListResponse = { budgetSources: BudgetSource[] } (shared/src/types/budgetSource.ts), not BudgetSource[]. setBudgetSources therefore stores the envelope object itself as budgetSources state.
Every subsequent use of budgetSources as an array throws at runtime:
handleUseCaseChange (fired on the very first Step 1 use-case selection): budgetSources.map(...) → TypeError: budgetSources.map is not a function.
selectedSource useMemo: budgetSources.find(...).
Step2Source.tsx: [...sources].sort(...).
Effect: the wizard cannot progress past Step 1 for any user — handleUseCaseChange throws before Promise.all(...).then(...)/.finally(...) ever run, so step2Loading stays true forever and Step 2 renders a permanent <Skeleton>. This blocks 100% of the wizard's actual purpose (generate a report) and blocks essentially every E2E scenario in the new spec.
Fix: setBudgetSources(sources.budgetSources).
Confirmed via: tsc --noEmit -p client/tsconfig.json reports this exact mismatch (TS2345: Argument of type 'BudgetSourceListResponse' is not assignable to parameter of type 'SetStateAction<BudgetSource[]>'), and manual read of budgetSourcesApi.ts + shared/src/types/budgetSource.ts.
2. ~30 TypeScript compile errors across the new files
npx tsc --noEmit -p client/tsconfig.json fails with errors in every new file under ReportWizardPage/, reportPdf/, and components/reports/. These do not block the Docker/webpack build (webpack uses babel-loader, which strips types without checking them), but they DO fail CI's Static Analysis/typecheck gate. Highlights (full list reproducible via the command above):
PageLayoutProps has no subtitle field — ReportWizardPage.tsx passes subtitle={t('sourceReports.subtitle')}; silently dropped by React at runtime (subtitle never renders anywhere).
TriStateCheckbox accepts label, not ariaLabel — ReportInvoiceList.tsx's select-all checkbox passes ariaLabel={...}; silently dropped, so the select-all checkbox has no accessible name at runtime.
SelectionActionBar requires clearLabel and children — ReportInvoiceList.tsx omits both; the "Clear selection" button renders with empty text.
FormError has no onRetry prop — ReportWizardPage.tsx's Step 3 error state passes onRetry={...}; no retry button is rendered at all, so a failed report fetch has no recovery UI.
Step4Options prop is _source, not source — ReportWizardPage.tsx passes source={selectedSource}; the component never receives the selected source (unused in current logic, but will silently break if used later).
HouseholdSettings has no name/address fields — coverLetterPdf.ts reads household?.name/household?.address throughout; always undefined, so the cover letter's sender block never renders even when household settings ARE populated.
SourceReportDocument has no id field — merge.ts reads doc.id in 4 places for the Paperless document-fetch/embed path; always undefined, so document embedding for the "Attach invoice PDFs" option cannot work (the fetch URL is built from undefined).
- Several more in
overviewPdf.ts/merge.ts/loader.ts/shared.ts (implicit any, pdfMake.vfs typing, Content[] typing, Date-vs-string mismatch) — see full tsc output.
3. Missing i18n keys — WizardStepper and every wizard nav button render raw/missing translation keys
WizardStepper.tsx calls useTranslation() (default namespace common) and looks up reportWizard.stepOfTotal / reportWizard.stepperAriaLabel. No reportWizard key exists in any locale file (checked all of client/src/i18n/en/*.json). The desktop stepper's aria-label and the entire mobile "Step N of 4" text are broken.
ReportWizardPage.tsx calls t('common.button.next'), t('common.button.back'), t('common.button.cancel'), t('common.button.confirm'), t('common.button.retry') — but budget.json's own common object is a flat shape ({loading, error, retry, save, cancel, ...}), not nested under button. None of these keys resolve — every Next/Back/Cancel/Confirm/Retry button in the wizard shows a broken/missing-key string instead of real text.
Likely fix: either add a reportWizard namespace section + flatten the common.button.* calls to match the existing budget.json's common.* shape (e.g. t('common.retry')), or align WizardStepper.tsx's namespace/keys with wherever the intended strings actually live.
4. SubNav aria-label uses a broken key + is inconsistent with sibling budget pages
ReportWizardPage.tsx: <SubNav tabs={BUDGET_TABS} ariaLabel={t('common.subnav.budget')} />. common.json's subnav.budget is an object ({overview, invoices, sources, subsidies, reports}), not a string, so this resolves to nothing useful. Every other budget page (BudgetOverviewPage, InvoicesPage) uses the literal string "Budget section navigation" — per the Frontend Spec ("existing literal 'Budget section navigation' aria label"), ReportWizardPage should do the same for consistency and to avoid a broken aria-label.
5. Claim success: hardcoded count: 0 and no link to /budget/invoices
Step4Options.tsx line ~93: {t('sourceReports.claimSuccess', { count: 0 })} — always renders "0 invoice(s) marked as claimed" regardless of how many invoices were actually claimed (the real count is available in the parent's handleMarkClaimed but never threaded through to claimSuccess/Step4Options).
Additionally, per the Frontend Spec ("Success → success banner + Link to /budget/invoices") and this story's E2E spec (Scenario 1: "confirm → success banner + link → /budget/invoices shows claimed statuses"), the success state should include a link to /budget/invoices. The current implementation renders only a static banner <div> — no <Link>/anchor anywhere in Step4Options.tsx or ReportWizardPage.tsx.
6. Step4Options.tsx's "Mark N invoices as claimed" button omits the {count} interpolation
Line ~114: {t('sourceReports.markClaimed')} — the key is "Mark {{count}} invoices as claimed" but no count param is passed, so the button literally renders "Mark {{count}} invoices as claimed" instead of e.g. "Mark 2 invoices as claimed".
Impact on E2E coverage
e2e/tests/budget/reportWizard.spec.ts (11 scenarios, e2e/pages/ReportWizardPage.ts POM) was authored against the story's Frontend/E2E specs (intended/spec-conformant behavior, per the test-failure-debugging protocol — correct tests are not weakened to fit buggy code). Every scenario that proceeds past Step 1 is expected to fail in CI until bug #1 (the blocking crash) is fixed; several specific assertions (claim success count/link, mobile stepper text, step nav button labels) are expected to fail until bugs #3/#5/#6 are fixed too. Tests validated via npx playwright test --list, eslint, prettier, and scoped tsc -p e2e (all clean) — full containerized execution was not possible in this sandbox (no dhi.io registry credentials for the E2E Docker image, a known/documented sandbox limitation, not specific to this bug).
Reproduction (once buildable)
- Navigate to
/budget/reports.
- Select any use case card (e.g. "Claim") on Step 1.
- Open the browser console → observe
TypeError: budgetSources.map is not a function (or equivalent, depending on minification).
- Click "Next" → Step 2 shows a skeleton loader that never resolves; no budget sources are ever selectable.
Suggested owner
frontend-developer (all six items are frontend-only; no backend/shared changes implicated — Story #1878's backend, sourceReportService.ts, was independently verified correct/fixed on this branch).
[e2e-test-engineer]
BUG: ReportWizardPage (#1879) — wizard crashes on step 1→2 transition, plus multiple compile errors, missing i18n keys, and spec-deviating claim-success UI
Severity: Blocker
Component: Frontend UI —
client/src/pages/ReportWizardPage/,client/src/components/WizardStepper/,client/src/components/reports/Found in: e2e-test-engineer authoring
e2e/tests/budget/reportWizard.spec.tsfor Story #1879 (static code review +tsc --noEmit -p client/tsconfig.json, since the E2E environment cannot build/run in this sandbox — see Verification section). Not yet confirmed against a live browser; confirmed via source inspection andArray.prototype/type analysis.1. Blocker:
budgetSourcesstate is set to the raw API envelope, not an arrayReportWizardPage.tsxline ~103:fetchBudgetSources()(client/src/lib/budgetSourcesApi.ts) resolves toBudgetSourceListResponse={ budgetSources: BudgetSource[] }(shared/src/types/budgetSource.ts), notBudgetSource[].setBudgetSourcestherefore stores the envelope object itself asbudgetSourcesstate.Every subsequent use of
budgetSourcesas an array throws at runtime:handleUseCaseChange(fired on the very first Step 1 use-case selection):budgetSources.map(...)→TypeError: budgetSources.map is not a function.selectedSourceuseMemo:budgetSources.find(...).Step2Source.tsx:[...sources].sort(...).Effect: the wizard cannot progress past Step 1 for any user —
handleUseCaseChangethrows beforePromise.all(...).then(...)/.finally(...)ever run, sostep2Loadingstaystrueforever and Step 2 renders a permanent<Skeleton>. This blocks 100% of the wizard's actual purpose (generate a report) and blocks essentially every E2E scenario in the new spec.Fix:
setBudgetSources(sources.budgetSources).Confirmed via:
tsc --noEmit -p client/tsconfig.jsonreports this exact mismatch (TS2345: Argument of type 'BudgetSourceListResponse' is not assignable to parameter of type 'SetStateAction<BudgetSource[]>'), and manual read ofbudgetSourcesApi.ts+shared/src/types/budgetSource.ts.2. ~30 TypeScript compile errors across the new files
npx tsc --noEmit -p client/tsconfig.jsonfails with errors in every new file underReportWizardPage/,reportPdf/, andcomponents/reports/. These do not block the Docker/webpack build (webpack usesbabel-loader, which strips types without checking them), but they DO fail CI'sStatic Analysis/typecheck gate. Highlights (full list reproducible via the command above):PageLayoutPropshas nosubtitlefield —ReportWizardPage.tsxpassessubtitle={t('sourceReports.subtitle')}; silently dropped by React at runtime (subtitle never renders anywhere).TriStateCheckboxacceptslabel, notariaLabel—ReportInvoiceList.tsx's select-all checkbox passesariaLabel={...}; silently dropped, so the select-all checkbox has no accessible name at runtime.SelectionActionBarrequiresclearLabelandchildren—ReportInvoiceList.tsxomits both; the "Clear selection" button renders with empty text.FormErrorhas noonRetryprop —ReportWizardPage.tsx's Step 3 error state passesonRetry={...}; no retry button is rendered at all, so a failed report fetch has no recovery UI.Step4Optionsprop is_source, notsource—ReportWizardPage.tsxpassessource={selectedSource}; the component never receives the selected source (unused in current logic, but will silently break if used later).HouseholdSettingshas noname/addressfields —coverLetterPdf.tsreadshousehold?.name/household?.addressthroughout; alwaysundefined, so the cover letter's sender block never renders even when household settings ARE populated.SourceReportDocumenthas noidfield —merge.tsreadsdoc.idin 4 places for the Paperless document-fetch/embed path; alwaysundefined, so document embedding for the "Attach invoice PDFs" option cannot work (the fetch URL is built fromundefined).overviewPdf.ts/merge.ts/loader.ts/shared.ts(implicitany,pdfMake.vfstyping,Content[]typing,Date-vs-stringmismatch) — see fulltscoutput.3. Missing i18n keys —
WizardStepperand every wizard nav button render raw/missing translation keysWizardStepper.tsxcallsuseTranslation()(default namespacecommon) and looks upreportWizard.stepOfTotal/reportWizard.stepperAriaLabel. NoreportWizardkey exists in any locale file (checked all ofclient/src/i18n/en/*.json). The desktop stepper'saria-labeland the entire mobile "Step N of 4" text are broken.ReportWizardPage.tsxcallst('common.button.next'),t('common.button.back'),t('common.button.cancel'),t('common.button.confirm'),t('common.button.retry')— butbudget.json's owncommonobject is a flat shape ({loading, error, retry, save, cancel, ...}), not nested underbutton. None of these keys resolve — every Next/Back/Cancel/Confirm/Retry button in the wizard shows a broken/missing-key string instead of real text.Likely fix: either add a
reportWizardnamespace section + flatten thecommon.button.*calls to match the existingbudget.json'scommon.*shape (e.g.t('common.retry')), or alignWizardStepper.tsx's namespace/keys with wherever the intended strings actually live.4. SubNav aria-label uses a broken key + is inconsistent with sibling budget pages
ReportWizardPage.tsx:<SubNav tabs={BUDGET_TABS} ariaLabel={t('common.subnav.budget')} />.common.json'ssubnav.budgetis an object ({overview, invoices, sources, subsidies, reports}), not a string, so this resolves to nothing useful. Every other budget page (BudgetOverviewPage,InvoicesPage) uses the literal string"Budget section navigation"— per the Frontend Spec ("existing literal 'Budget section navigation' aria label"),ReportWizardPageshould do the same for consistency and to avoid a brokenaria-label.5. Claim success: hardcoded
count: 0and no link to/budget/invoicesStep4Options.tsxline ~93:{t('sourceReports.claimSuccess', { count: 0 })}— always renders "0 invoice(s) marked as claimed" regardless of how many invoices were actually claimed (the real count is available in the parent'shandleMarkClaimedbut never threaded through toclaimSuccess/Step4Options).Additionally, per the Frontend Spec ("Success → success banner + Link to /budget/invoices") and this story's E2E spec (Scenario 1: "confirm → success banner + link → /budget/invoices shows claimed statuses"), the success state should include a link to
/budget/invoices. The current implementation renders only a static banner<div>— no<Link>/anchor anywhere inStep4Options.tsxorReportWizardPage.tsx.6.
Step4Options.tsx's "Mark N invoices as claimed" button omits the{count}interpolationLine ~114:
{t('sourceReports.markClaimed')}— the key is"Mark {{count}} invoices as claimed"but nocountparam is passed, so the button literally renders"Mark {{count}} invoices as claimed"instead of e.g."Mark 2 invoices as claimed".Impact on E2E coverage
e2e/tests/budget/reportWizard.spec.ts(11 scenarios,e2e/pages/ReportWizardPage.tsPOM) was authored against the story's Frontend/E2E specs (intended/spec-conformant behavior, per the test-failure-debugging protocol — correct tests are not weakened to fit buggy code). Every scenario that proceeds past Step 1 is expected to fail in CI until bug #1 (the blocking crash) is fixed; several specific assertions (claim success count/link, mobile stepper text, step nav button labels) are expected to fail until bugs #3/#5/#6 are fixed too. Tests validated vianpx playwright test --list,eslint,prettier, and scopedtsc -p e2e(all clean) — full containerized execution was not possible in this sandbox (nodhi.ioregistry credentials for the E2E Docker image, a known/documented sandbox limitation, not specific to this bug).Reproduction (once buildable)
/budget/reports.TypeError: budgetSources.map is not a function(or equivalent, depending on minification).Suggested owner
frontend-developer(all six items are frontend-only; no backend/shared changes implicated — Story #1878's backend,sourceReportService.ts, was independently verified correct/fixed on this branch).