Skip to content

BUG: ReportWizardPage (#1879) crashes on step 1→2 transition (budgetSources envelope), plus compile errors, missing i18n keys, spec-deviating claim UI #1886

Description

@steilerDev

[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 ariaLabelReportInvoiceList.tsx's select-all checkbox passes ariaLabel={...}; silently dropped, so the select-all checkbox has no accessible name at runtime.
  • SelectionActionBar requires clearLabel and childrenReportInvoiceList.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 sourceReportWizardPage.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)

  1. Navigate to /budget/reports.
  2. Select any use case card (e.g. "Claim") on Step 1.
  3. Open the browser console → observe TypeError: budgetSources.map is not a function (or equivalent, depending on minification).
  4. 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).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions