Skip to content

Refactor dashboard widget picker and fix gridstack layout bugs#129

Merged
roncodes merged 7 commits into
mainfrom
feature/dashboard-widget-picker-and-gridstack-fixes
May 20, 2026
Merged

Refactor dashboard widget picker and fix gridstack layout bugs#129
roncodes merged 7 commits into
mainfrom
feature/dashboard-widget-picker-and-gridstack-fixes

Conversation

@roncodes
Copy link
Copy Markdown
Member

@roncodes roncodes commented May 20, 2026

Summary

  • Widget picker rewrite — compact, sticky-header overlay with All / Recommended / On Dashboard tabs, category-grouped widgets, search across name + description + category, hover-reveal "+" button. New Dashboard::WidgetCard primitive with fixed-height cards, default icon fallback, badges aligned beneath the title.
  • Identity-map collision fix — the dashboard service and Dashboard.addWidget no longer use the widget registry slug as the Ember Data dashboard-widget record id; the slug is stashed under options.widget_key instead. Fixes the "The id fleet-ops-kpi-earnings-widget has already been used with another 'dashboard-widget' record" assertion.
  • System dashboard edit lockisSystemDashboard getter gates edit/add/delete actions on the virtual default dashboard so they don't 404 against the backend.
  • GridStack dashboard-switch bug — wrap the grid in {{#each (array @dashboard.id) key="@identity"}} so switching dashboards remounts the .grid-stack DOM cleanly. Resolves the empty band that previously appeared above the new dashboard's widgets when switching between dashboards of different sizes.
  • GridStack compact-on-remove — single widget deletion now triggers gridstack.compact() so the surrounding widgets slide up to fill the hole.

Test plan

  • Open the host dashboard, add a default-flagged FleetOps widget — no Ember Data assertion fires.
  • Add the same widget twice — no assertion; an On dashboard ×2 badge appears in the picker.
  • Switch between dashboards of different sizes — no empty band above the new dashboard.
  • On the virtual "Default Dashboard", confirm the action menu shows a "Create a dashboard to customize widgets" hint instead of Edit/Add/Delete.
  • Create + select a non-system dashboard, enable edit mode, delete a widget — the surrounding widgets slide up.
  • Picker: tabs (All / Recommended / On Dashboard) show correct counts; search filters across category as well as name/description; widgets render in a 2-column grid with consistent heights and no mid-word title truncation.
  • ember test — new integration tests (tests/integration/components/dashboard/widget-panel-test.js) and unit tests (tests/unit/services/dashboard-test.js) pass.

roncodes and others added 7 commits May 20, 2026 13:09
Picker (Dashboard::WidgetPanel):
- Rewrite as a compact, sticky-header overlay with All / Recommended / On
  Dashboard tabs, search across name + description + category, and category
  grouping driven by the registry Widget.category field.
- Add Dashboard::WidgetCard primitive with fixed-height (88px) cards, default
  icon fallback (puzzle-piece), inline Default and Added×N badges aligned
  beneath the title, and a hover-revealed plus button.
- Zero out the inner-wrapper padding via class="no-padding" + a
  dashboard-widget-panel-body scope so the sticky header sits flush.

Dashboard service / Dashboard model:
- Stop using the widget registry slug (e.g. fleet-ops-kpi-earnings-widget)
  as the Ember Data dashboard-widget record id. Strip it on createRecord and
  stash it under options.widget_key so persisted records can still resolve
  back to the registry. Fixes "The id … has already been used with another
  'dashboard-widget' record" assertion when adding a fleetops widget.
- Collapse the redundant peekAll + peekRecord lookup in
  _createDefaultDashboard, replace the double-next() reset race with a
  synchronous unloadAll pair.

System dashboard:
- Add isSystemDashboard getter on DashboardComponent; force @isEdit to false
  on Dashboard::Create and replace the action-menu Edit/Add/Delete items
  with a "Create a dashboard to customize widgets" hint when the active
  dashboard is the virtual system record.

GridStack layout:
- Wrap the GridStack subtree in {{#each (array @dashboard.id) key="@identity"}}
  so switching dashboards destroys + remounts the .grid-stack element. This
  is the only reliable way to clear gridstack's engine state AND the inline
  min-height/height styles it stamps on the container, which previously left
  a big empty band at the top after switching between dashboards of
  different sizes.
- compactGrid() is still invoked after widget removal so single deletions
  close the hole left behind.

Tests:
- Replace placeholder widget-panel test with real integration coverage for
  category grouping, On Dashboard badge counts, tab filtering, and search
  across name + description + category.
- Unit test asserts _createDefaultDashboardWidgets can be invoked twice
  without triggering the identity-map regression, that each record gets a
  fresh client UUID, and that the slug lands in options.widget_key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "clickable card" pattern needs descriptive children (icon, title,
description, badges) so the user can identify which widget they're about to
activate. ember-template-lint flags any role="button" with semantic
descendants, but the alternative — stripping content — would defeat the
picker's purpose. The card remains tabindex-focusable and click-activated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
deleteDashboard calls `this.loadDashboards.perform()` with no args. The task
signature destructures the first parameter, so the call hit a TypeError
("Cannot read properties of undefined (reading 'defaultDashboardId')") and
broke the post-delete reload. Default the destructured parameter to an
empty object so the defaults kick in instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`assert.doesNotThrow` is a Node `assert` API, not a QUnit one. The test was
silently flagged at runtime ("assert.doesNotThrow is not a function") and
counted as a failure. Replaced with a try/catch + `assert.strictEqual`
pattern so the test asserts the same invariant (no identity-map collision
on second materialization) using only standard QUnit methods.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@roncodes roncodes merged commit 0327ad2 into main May 20, 2026
4 checks passed
@roncodes roncodes deleted the feature/dashboard-widget-picker-and-gridstack-fixes branch May 20, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant