Problem
PR #283 closed #279 by making every summary card on the
Recommendations page render a per-cell range (savings, upfront,
payback) and narrow on selection. The Dashboard view's "Potential
Monthly Savings" card (frontend/src/dashboard.ts:128) still renders
the flat summary.potential_monthly_savings from
/api/dashboard/summary — the same overcounting bug class #272 closed
on the Recommendations page (sums every variant of every cell, ~6×
overstates achievable totals).
Splitting this out of #279 because the Dashboard work is structurally
different from the Recommendations-page work that's already shipped:
it's a separate page, has no per-row selection state to narrow on, and
needs either a backend /dashboard/summary extension or a frontend
extra-fetch to source the per-cell range data.
Acceptance criteria
References
Problem
PR #283 closed #279 by making every summary card on the
Recommendations page render a per-cell range (savings, upfront,
payback) and narrow on selection. The Dashboard view's "Potential
Monthly Savings" card (
frontend/src/dashboard.ts:128) still rendersthe flat
summary.potential_monthly_savingsfrom/api/dashboard/summary— the same overcounting bug class #272 closedon the Recommendations page (sums every variant of every cell, ~6×
overstates achievable totals).
Splitting this out of #279 because the Dashboard work is structurally
different from the Recommendations-page work that's already shipped:
it's a separate page, has no per-row selection state to narrow on, and
needs either a backend
/dashboard/summaryextension or a frontendextra-fetch to source the per-cell range data.
Acceptance criteria
per-cell range that the Recommendations page card does
(
pageLevelRange(groupRecsByCell(recs)).savingsMin/.savingsMax).dashboard.ts::loadDashboardfetches
/api/recommendationsalongside the existing/api/dashboard/summary+/api/upcomingcalls, computes the rangeclient-side using the existing
pageLevelRange/groupRecsByCellhelpers exported fromrecommendations.ts. Therecs endpoint is served from Postgres cache so the extra API call
is cheap. Backend change deferred — a future PR can move the range
computation server-side if needed.
view, no checkboxes. Card always reflects the range across whatever
recommendations the user's current provider/account filter exposes.
YTD Savings) keep their existing API-driven values — they're
aggregates that don't have the variant-fan-out overcounting issue.
/api/recommendationsfails, the Dashboard still renders —the savings card falls back to
formatCurrency(0)and the rest ofthe dashboard is unaffected (independent loaders).
dashboard.test.ts(or wherever dashboard tests live): assertthe savings card renders the range when recs are mocked.
getRecommendationsrejects, the othercards still render.
References
pageLevelRange/groupRecsByCellinfrontend/src/recommendations.ts(exported).