Skip to content

Deep-link / invalid /page/:id crashes the dashboard (loadDashboard has no null guard) #205

Description

@mairas

Problem

DashboardComponent.loadDashboard(dashboardId) dereferences the dashboard without a null/undefined guard, and the effect that drives it can be called with an invalid index. A deep-link (or reload) to a /page/:id that isn't a valid dashboard index leaves activeDashboard at null, and once the gridstack grid is ready the component crashes / renders blank.

// dashboard.component.ts
effect(() => {
  const activeIdx = this.dashboard.activeDashboard();   // number | null
  untracked(() => { this.loadDashboard(activeIdx); });  // called with null / invalid
});

private loadDashboard(dashboardId: number): void {
  const dashboard = cloneDeep(this.dashboard.dashboards()[dashboardId]); // dashboards()[null|9|1.5] === undefined
  const _gridstack = this._gridstack();
  if (!_gridstack?.grid) { queueMicrotask(() => this.loadDashboard(dashboardId)); return; }
  _gridstack.grid.load(dashboard.configuration);        // undefined.configuration -> TypeError
  ...
}

Two ways in:

  1. Out-of-range id — boot to /page/9 on a 3-dashboard install. applyDashboardParam('9', true) parses 9 (not NaN), so neither defaultToZero branch fires; setActiveDashboardIndex(9) rejects the out-of-range index and activeDashboard stays null. /page/abc is safe (NaN → default 0); only the numeric-but-invalid case leaks through.
  2. Fractional id/page/1.5. dashboard.service.ts now rejects non-integer indices (from dashboard.service: delete() leaves activeDashboard pointing at a shifted index #149), so activeDashboard stays null; before dashboard.service: delete() leaves activeDashboard pointing at a shifted index #149 it set activeDashboard to 1.5, which was also broken (dashboards()[1.5] is undefined). Either way the page crashes — dashboard.service: delete() leaves activeDashboard pointing at a shifted index #149 only changed the route in.

Scope / root cause

  • DashboardService.applyDashboardParam (dashboard.service.ts) only falls back to index 0 for null/empty/NaN, not for a finite-but-invalid (out-of-range / non-integer) id.
  • DashboardComponent.loadDashboard (dashboard.component.ts) has no guard for an out-of-range/null dashboardId.

Suggested direction

  • loadDashboard: bail (or clamp to a valid index) when dashboards()[dashboardId] is undefined, instead of dereferencing it.
  • applyDashboardParam: treat any id that setActiveDashboardIndex would reject the same as NaN — fall back to 0 on the defaultToZero path so a stale/invalid deep link lands on page 0.

Context

Pre-existing; surfaced by an adversarial review pass on PR #201 (which fixed the sibling delete() out-of-range regression but deliberately left this component-level crash out of scope). Not introduced by #149.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions