fix(dashboard): 'Failed to load dashboard: e is not iterable' — defensive guards on iterable fields · Issue #304 · LeanerCloud/cloud-commitments-cli · GitHub
Skip to content

fix(dashboard): 'Failed to load dashboard: e is not iterable' — defensive guards on iterable fields #304

Closed

Description

@cristim

Summary

Dashboard fails to load with the error banner:

Failed to load dashboard: e is not iterable

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.ts loadDashboard() — 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:

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.

Activity

  1. added a commit that references this issue on May 5, 2026
    0b61fcb
  2. cristim commented on May 13, 2026

    @cristim
    MemberAuthor

    Verified resolved by PR #311.

    • Primary guard in dashboard.ts loadDashboard(): 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.
    • All dashboard tests pass per the PR body.

    Recommending closure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions