Skip to content

Feature/favorites view#10

Merged
cayossarian merged 97 commits intomainfrom
feature/favorites-view
Apr 17, 2026
Merged

Feature/favorites view#10
cayossarian merged 97 commits intomainfrom
feature/favorites-view

Conversation

@cayossarian
Copy link
Copy Markdown
Member

Grid view changes for multiple columns, code review changes

Adds a synthetic "Favorites" entry to the dashboard panel dropdown that
aggregates favorited circuits and sub-devices (BESS, EVSE) from every
configured SPAN panel into a single workspace.

- Heart toggles in the Graph Settings side panel and per-circuit /
  per-sub-device side panels (dashboard mode only — never in standalone
  card). Per-row hearts in panel-mode for both circuits and sub-devices.
- Favorites view shows By Activity / By Area / Monitoring tabs (no By
  Panel) with circuit names prefixed by panel name when more than one
  panel contributes. Sub-devices render as tiles above the circuit list.
  Monitoring stacks per-panel blocks.
- Persistent panel-stats header lifted out of the By-Panel grid so it
  stays visible across all tabs (real panels). Favorites pseudo-panel
  shows a summary strip + W/A unit toggle instead.
- Stateful Favorites view: active tab, expanded composite ids, and
  search query persist via localStorage and restore exactly on return.
- Side-panel domain service calls thread an optional config_entry_id so
  cross-panel favorites edits target the originating panel.
  DashboardController bypasses single-entry caches in favorites view
  and fetches per-entry settings on demand.
- Heart UI uses the entity_id-based service API — UUIDs and sub-device
  device_ids are an internal storage concern.
- Skip rendering ha-menu-button until hass is set; it reads
  this.hass.kioskMode in willUpdate.

Bumps version to 0.9.3.
The sub-device-mode side panel's horizon segment buttons (BESS, EVSE)
called set_subdevice_graph_horizon / clear_subdevice_graph_horizon
without config_entry_id, so the backend's _get_horizon_manager fell
back to the FIRST loaded SPAN entry's manager. On multi-panel installs
the override was written to the wrong panel's manager and the chart
stayed at the global default. Panel-mode (Graph Settings list) and the
circuit-mode side panel already threaded it; the sub-device side panel
now does too, sourced from cfg.configEntryId set by DashboardController.

Also documents the 0.9.3 release in CHANGELOG.
In the Favorites view, un-favoriting a circuit or sub-device used to
leave the row/tile in the rendered list until the next panel or tab
switch. _refreshFavorites was deliberately skipping _scheduleTabRender
to keep the open Graph Settings side panel alive on real panels.

Branch the behavior by context: re-render when in the Favorites view
(the user just removed what they were inspecting, so tearing down the
side panel as a side effect is acceptable), keep the no-re-render
behavior on real panels so the Graph Settings side panel stays open
while toggling multiple hearts in a row.
…usion

The per-circuit and per-sub-device side panels rendered the Favorite
section with an ha-switch that sat directly under the breaker relay
switch. Visually similar; accidentally hitting the wrong one could trip
a breaker. Replaced both with the same filled/outlined heart icon used
in the Graph Settings list. The two render paths now share a single
_appendFavoriteHeartSection helper.
Addresses findings from the deep code review of the favorites branch.

Critical fixes:
- Per-entry horizon resolution in Favorites view. ``onGraphSettingsChanged``
  and ``fetchAndBuildHorizonMaps`` now branch on ``_favRefs``; in favorites
  mode they fetch graph settings per contributing config entry in
  parallel and route each composite circuit/sub-device id through its
  ``FavoriteRef`` to the originating entry's settings. Without this, a
  per-target horizon override on a non-primary panel was masked by the
  primary entry's settings cache.
- Refresh-token guard in ``_refreshFavorites``: a monotonic ``_refreshSeq``
  causes superseded callbacks to bail out, preventing rapid heart toggles
  from interleaving renders or scheduling duplicate tab rebuilds.
- ``_renderFavoritesMonitoring`` builds tabs into a local map and only
  commits to the instance field after the loop, with per-panel try/catch
  so a single failing entry can't orphan tabs from the cleanup loop.

UX / a11y / cleanup:
- Heart buttons are now ``role=switch`` with ``aria-pressed`` and
  ``aria-label``; ``_toggleFavoriteEntity`` syncs ``aria-pressed``
  alongside the optimistic class flip. Single ``_buildHeartButton``
  helper used by both circuit/sub-device panel rows and the per-target
  side-panel Favorite section.
- Favorites summary uses explicit ``t("panel.favorites_summary_one")``/
  ``t("panel.favorites_summary_many")`` so the i18n validator no longer
  reports them unused; messages dropped the ungrammatical
  "across N panels" suffix and now read "1 favorite" / "{circuits} favorites".
- ``_renderFavoritesMonitoring`` heading uses ``h2`` to fit the
  document outline.
- Comment on ``PanelModeConfig.favoriteCircuitUuids`` /
  ``favoriteSubDeviceIds`` documents that they are open-time snapshots,
  not live — gear re-clicks rebuild from ``_panelFavorites``.
- ``_buildFavoriteHeart`` warns to console when a circuit has no
  current/power sensor (heart silently suppressed otherwise).

Dead code removed:
- ``parseCompositeId`` (exported but unused).
- ``FavoritesBuildResult.panelTopologies`` (built but not consumed).
updateCollapsedRows refreshed each row's power/current in place but
never re-evaluated sort order, so 'By Activity' (and within-area sort)
froze to the initial render order. Partition .list-view into groups
bounded by .area-header and reorder rows (keeping expanded-content
siblings glued to their parents) after updating values.
Widen the favorites-mode sidebar's per-panel section from 'favorited
circuits only' to 'every circuit in that panel's topology'. Heart state
derives from section.favoriteCircuitUuids, so users can toggle existing
favorites off and add new ones for any circuit on a contributing panel
without leaving the Favorites view -- consistent with the real-panel
gear sidebar's full circuit listing and the Favorites Monitoring tab's
full per-panel data.

Extracts the row-selection step as sortedCircuitsForSection() in
favorites-sections.ts so the alphabetical-sort + every-circuit contract
is unit-testable without a DOM (5 new tests; 209 total).
Cache races:
- favorites-store, monitoring-status: add generation counter so
  invalidate() supersedes in-flight fetches instead of letting a stale
  pre-invalidate result commit to cache for up to 30s.
- monitoring-status: coalesce concurrent fetches onto a single promise
  instead of returning the possibly-null prior state to racing callers.

Lifecycle leaks:
- span-panel-card, span-panel: guard subscribeAreaUpdates with an
  _areaSubscribing flag so disconnect-before-resolve unsubscribes
  immediately instead of storing the handle on a detached element.
- side-panel: add disconnectedCallback; clear _debounceTimers on close()
  so pending threshold/horizon writes cannot fire against a torn-down
  config.

Null-safety:
- tab-monitoring: replace 8 querySelector<T>(...)!.value assertions with
  a null-checked readGlobalFields() helper; surface failures via the
  status element instead of crashing the save handler.
- tab-monitoring, tab-settings: coerce unvalidated WS responses via
  dedicated narrowing functions instead of unsafe `as` casts.
- card-discovery: harden identifier-pair destructuring against malformed
  shapes.
- retry-manager: replace `throw lastError!` with nullish fallback.
- span-panel, error-store: narrow err with instanceof Error before
  reading .message; isolate subscriber exceptions in _notify().

Escaping:
- grid-renderer, header-renderer, list-renderer, sub-device-renderer:
  escape every i18n-sourced string interpolated into title=, data-, and
  inline style attributes so future translations containing `"` cannot
  break markup.

Refactor:
- span-panel-card, span-panel: centralise shadow-root access via a
  private _root getter; removes 24 `this.shadowRoot!` assertions.

State pruning:
- span-panel: only prune favorites view-state expansion ids when a
  populated topology is available, so a rapid search keystroke during a
  tab re-render doesn't drop every expanded row.
- favorites-cache: assert invalidate() supersedes in-flight fetches and
  that concurrent callers coalesce.
- monitoring-cache: same cache-race coverage plus a null-not-cached
  regression so a transient fetch error cannot mask subsequent success.
- grid-renderer: renderCircuitSlot escapes user-controllable names,
  uuids, and i18n shedding labels inside title= attributes.
Two fixes to the Favorites view's BESS sub-device charts:

1. Line visibility: loadHistory() now runs before renderActivityView
   / renderAreaView, so the single updateDOM pass happens with real
   history. Previously the view rendered with an empty history first
   and updated after, which left residual ECharts state that hid the
   line stroke on SOE/POWER charts in Favorites.
2. Resize: wire setupResizeObserver into the Favorites render path so
   BESS and EVSE charts shrink on viewport changes without a refresh.

Also flatten the chart area gradient to a uniform alpha, so the per-
chart fill no longer varies with the filled polygon height.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 50 out of 53 changed files in this pull request and generated 5 comments.

Comment thread src/core/retry-manager.ts Outdated
Comment thread src/core/monitoring-status.ts Outdated
Comment thread src/core/favorites-store.ts
Comment thread src/core/header-renderer.ts Outdated
Comment thread CHANGELOG.md Outdated
- RetryManager offline short-circuit: add ErrorStore.hasAnyPanelOffline()
  so retries skip correctly under Favorites multi-panel watch (per-entity
  panel-offline keys), not just the legacy single-unnamed key.
- FavoritesCache / MonitoringStatusCache: track inflight generation so
  invalidate() supersedes pending requests; fetch() after invalidate()
  issues a fresh call instead of awaiting a stale in-flight promise.
- header-renderer buildSheddingLegendHTML: escape label / icon / color /
  textLabel via escapeHtml to match the escaping used elsewhere in the
  renderer and guard against stray markup in i18n strings.
- CHANGELOG: correct 'Persisted per device' wording — list-columns
  storage is a single localStorage key, global to the browser.
The real panel Monitoring tab is rendered as a pure configuration view
with no panel-stats header (gear icon, slide-to-enable, shedding legend,
W/A toggle). The Favorites pseudo-panel's Monitoring view was inserting
that same summary strip on top of its per-panel MonitoringTab stack,
which doesn't match real panels and clutters a screen that is entirely
threshold / notification config.

Remove the insertAdjacentHTML call so the Favorites Monitoring view
renders just the per-panel headings and MonitoringTab bodies, matching
the real-panel behavior in `_renderTab` case "monitoring".
@cayossarian cayossarian merged commit fa383a5 into main Apr 17, 2026
2 checks passed
@cayossarian cayossarian deleted the feature/favorites-view branch April 19, 2026 20:22
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.

2 participants