fix(dashboard): 'Failed to load dashboard: e is not iterable' — defensive guards on iterable fields · Issue #304 · LeanerCloud/cloud-commitments-cli · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(dashboard): 'Failed to load dashboard: e is not iterable' — defensive guards on iterable fields #304
Triggered when one of the dashboard data sources returns a non-array shape that the renderer then tries to iterate (for…of, .map, spread, Promise.allSettled over a non-array, etc.).
Likely fault site
frontend/src/dashboard.tsloadDashboard() — the try block at lines 95–115 calls renderDashboardSummary, renderSavingsChart, renderUpcomingPurchases, then loadSavingsTrendChart, then renderFreshness. The e is not iterable shape is most consistent with one of:
summaryData.by_service not being an object → handled by || {} fallback at line 98.
upcomingData.purchases not being an array → handled by || [] fallback at line 99.
loadSavingsTrendChart / renderFreshness iterating over a missing array.
The minified e symbol points at a callee — not the body of loadDashboard itself — so the next debug step is to log the unminified stack from a deployed build, or run locally with vite preview and reproduce.
Acceptance criteria
Repro the error consistently in dev (likely with an empty backend or one provider returning a 5xx).
Add defensive Array.isArray(...) / nullish-coalescing guards at every call site that iterates dashboard data.
Catch + soft-fail in non-critical widgets (savings trend, freshness) so a single failing widget doesn't blank the dashboard.
Regression test in frontend/src/__tests__/dashboard.test.ts covering: (a) summaryData payload missing the iterable field, (b) one of the parallel fetches rejecting.
Primary guard in dashboard.tsloadDashboard(): Array.isArray(rawRecs) coerces any non-array fulfilled value — including null (returned by apiRequest's catch block when response.json() fails on a 2xx with empty/non-JSON body) and unexpected object envelopes — to [] before the value reaches groupRecsByCell(), which iterates via for...of and was the crash site.
Secondary soft-fail guard in renderDashboardSummary(): the groupRecsByCell() call is wrapped in a try/catch so the Potential Monthly Savings card degrades to $0 rather than blanking the dashboard if a non-iterable bypasses the primary guard.
Regression tests in frontend/src/__tests__/dashboard.test.ts: three new #304-tagged tests covering (a) getRecommendations returning null, (b) returning a non-array object envelope, and (c) summaryData missing the by_service field — matching both required shapes from the acceptance criteria.
Activity
fix(dashboard): defensive guards prevent 'is not iterable' on partial…
cristim commentedon May 13, 2026
Verified resolved by PR #311.
dashboard.tsloadDashboard():Array.isArray(rawRecs)coerces any non-array fulfilled value — includingnull(returned byapiRequest's catch block whenresponse.json()fails on a 2xx with empty/non-JSON body) and unexpected object envelopes — to[]before the value reachesgroupRecsByCell(), which iterates viafor...ofand was the crash site.renderDashboardSummary(): thegroupRecsByCell()call is wrapped in a try/catch so the Potential Monthly Savings card degrades to$0rather than blanking the dashboard if a non-iterable bypasses the primary guard.frontend/src/__tests__/dashboard.test.ts: three new#304-tagged tests covering (a)getRecommendationsreturningnull, (b) returning a non-array object envelope, and (c)summaryDatamissing theby_servicefield — matching both required shapes from the acceptance criteria.Recommending closure.