Problem
frontend/src/api/recommendations.ts:17-19 serializes the savings-floor filters straight to the query string without a finite-number guard:
if (filters.minSavingsUsd) params.set('min_savings_usd', String(filters.minSavingsUsd));
if (filters.minSavingsPct) params.set('min_savings_pct', String(filters.minSavingsPct));
If filters.minSavingsUsd is ever a non-finite TypeScript number (NaN, Infinity, -Infinity), String(...) produces "NaN" / "Infinity" and the request goes out as ?min_savings_usd=NaN. The backend now (correctly) returns a 400 (PR LeanerCloud/cloud-commitments-cli#1233 fixed parseMinSavingsParam), so the user-visible failure mode is opaque — the recommendations list silently shows an error toast instead of falling back to the unfiltered list or surfacing the input as invalid.
Today no UI input writes these fields (a grep across frontend/src/** finds only the API wrapper, types, and tests — no widget reads a value into minSavingsUsd / minSavingsPct), so this is defense-in-depth, not a live user-facing bug. Filing as a follow-up so that when the eventual sliders / number inputs land, they go through a finite-number gate.
Suggested fix
Per feedback_strict_int_parse.md (strict numeric parsing on the FE), guard the filter setters before serialising:
if (filters.minSavingsUsd !== undefined && Number.isFinite(filters.minSavingsUsd) && filters.minSavingsUsd > 0) {
params.set('min_savings_usd', String(filters.minSavingsUsd));
}
if (filters.minSavingsPct !== undefined && Number.isFinite(filters.minSavingsPct) && filters.minSavingsPct > 0) {
params.set('min_savings_pct', String(filters.minSavingsPct));
}
Two-line change. Add a Jest case that constructs the filter with minSavingsUsd: NaN and asserts the param is absent from the request URL.
Why
Currently the backend (PR LeanerCloud/cloud-commitments-cli#1233) is the only line of defense. If a slider component lands and clamps an unset value to NaN before the user interacts with it, every page-load fires a 400 against the recommendations API. The strict-parsing rule in the project memory garden exists exactly to keep the FE from shipping non-finite numbers to the backend.
Acceptance
Labels (triage rubric)
priority/p3, severity/low, urgency/low, impact/low, effort/xs, type/defense-in-depth, area/frontend, triaged
Problem
frontend/src/api/recommendations.ts:17-19serializes the savings-floor filters straight to the query string without a finite-number guard:If
filters.minSavingsUsdis ever a non-finite TypeScriptnumber(NaN,Infinity,-Infinity),String(...)produces"NaN"/"Infinity"and the request goes out as?min_savings_usd=NaN. The backend now (correctly) returns a 400 (PR LeanerCloud/cloud-commitments-cli#1233 fixedparseMinSavingsParam), so the user-visible failure mode is opaque — the recommendations list silently shows an error toast instead of falling back to the unfiltered list or surfacing the input as invalid.Today no UI input writes these fields (a grep across
frontend/src/**finds only the API wrapper, types, and tests — no widget reads a value intominSavingsUsd/minSavingsPct), so this is defense-in-depth, not a live user-facing bug. Filing as a follow-up so that when the eventual sliders / number inputs land, they go through a finite-number gate.Suggested fix
Per
feedback_strict_int_parse.md(strict numeric parsing on the FE), guard the filter setters before serialising:Two-line change. Add a Jest case that constructs the filter with
minSavingsUsd: NaNand asserts the param is absent from the request URL.Why
Currently the backend (PR LeanerCloud/cloud-commitments-cli#1233) is the only line of defense. If a slider component lands and clamps an unset value to NaN before the user interacts with it, every page-load fires a 400 against the recommendations API. The strict-parsing rule in the project memory garden exists exactly to keep the FE from shipping non-finite numbers to the backend.
Acceptance
frontend/src/api/recommendations.tsguards bothminSavingsUsdandminSavingsPctwithNumber.isFinite.getRecommendations({ minSavingsUsd: NaN })produces a URL with nomin_savings_usdquery param.frontend/src/__tests__/api.test.tsregression tests for issue fix(recs): disambiguate min-savings percentage (CLI) vs dollar (GUI/API) to prevent unit confusion cloud-commitments-cli#1089 (dollar vs percentage distinct query params).Labels (triage rubric)
priority/p3,severity/low,urgency/low,impact/low,effort/xs,type/defense-in-depth,area/frontend,triaged