Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
1421131
feat: cross-panel Favorites view with sub-device support
cayossarian Apr 15, 2026
46b56ae
fix: thread config_entry_id from sub-device side panel horizon
cayossarian Apr 15, 2026
4767f0f
fix: re-render Favorites view when a target is un-favorited
cayossarian Apr 15, 2026
611c804
fix: replace Favorite ha-switch with heart icon to avoid breaker conf…
cayossarian Apr 15, 2026
fea70b4
fix: code review hardening for Favorites view
cayossarian Apr 15, 2026
294a3b2
fix: re-sort list view rows on hass updates
cayossarian Apr 15, 2026
458f2c4
docs: spec for compact expanded list rows
cayossarian Apr 15, 2026
4f891e5
docs: implementation plan for compact expanded list rows
cayossarian Apr 15, 2026
036d571
feat(core): extract shared getCircuitStateClasses helper
cayossarian Apr 15, 2026
778d31f
refactor(grid): use shared getCircuitStateClasses
cayossarian Apr 15, 2026
ee71d26
feat(list): chart-only expanded rows; gear + tappable badge on list row
cayossarian Apr 15, 2026
ee4da8c
fix(list): scope dom-updater circuit selector to .circuit-slot
cayossarian Apr 15, 2026
88a25f6
fix(dashboard): accept .list-status-toggle as a toggle target
cayossarian Apr 15, 2026
a1c3837
style: list-row gear, tappable badge, chart-only slot
cayossarian Apr 15, 2026
661a5f0
fix(list): enable slide-confirm for tappable badge in list views
cayossarian Apr 15, 2026
798d1e2
fix(favorites): merge monitoring status across entries for utilization %
cayossarian Apr 15, 2026
8e62a8f
fix(list): move utilization % next to breaker badge
cayossarian Apr 15, 2026
37ed62a
fix(grid): move utilization % next to breaker badge in By Panel view
cayossarian Apr 15, 2026
ea81743
build: rebuild dist bundle
cayossarian Apr 15, 2026
63ee065
style: move panel tabs inline with panel selector dropdown
cayossarian Apr 15, 2026
d324ff8
build: rebuild dist bundle
cayossarian Apr 15, 2026
badfbff
style: drop redundant Favorites title+count summary
cayossarian Apr 15, 2026
5444852
build: rebuild dist bundle
cayossarian Apr 15, 2026
5eaf258
feat(list): real toggle-pill armed by slide-confirm; add slider to Fa…
cayossarian Apr 15, 2026
386196f
build: rebuild dist bundle
cayossarian Apr 15, 2026
32e5a90
feat(favorites): render per-panel status grid below slider+W/A row
cayossarian Apr 15, 2026
bbe4a93
fix(panel): coalesce concurrent tab renders; clear history on metric …
cayossarian Apr 15, 2026
de3a46a
build: rebuild dist bundle
cayossarian Apr 15, 2026
b50a9a6
feat(list): configurable 1/2/3 columns via Graph Settings side panel
cayossarian Apr 15, 2026
54644ff
build: rebuild dist bundle
cayossarian Apr 15, 2026
b020536
fix(list): wrap row+expansion in .list-cell so grid keeps chart in-co…
cayossarian Apr 15, 2026
6d2facd
build: rebuild dist bundle
cayossarian Apr 15, 2026
b312b93
refactor(list): scope toggleExpand DOM lookups to the owning cell
cayossarian Apr 16, 2026
911c24e
build: rebuild dist bundle
cayossarian Apr 16, 2026
5b290e7
refactor(panel): render tokens, keyed monitoring cache, debounced vie…
cayossarian Apr 16, 2026
1521f8b
refactor: CSS.escape for selectors; single CARD_STYLES emit; panel-na…
cayossarian Apr 16, 2026
145aceb
refactor(panel): extract FavoritesViewState persistence to its own mo…
cayossarian Apr 16, 2026
92b5b8f
test: cover FavoritesController.build and render coalescing logic
cayossarian Apr 16, 2026
acef1df
build: rebuild dist bundle
cayossarian Apr 16, 2026
cb4137e
release: bump card version to 0.9.4
cayossarian Apr 16, 2026
11347b9
fix: address copilot review (orphaned JSDoc, aria-checked, coalesce e…
cayossarian Apr 16, 2026
015938b
build: rebuild dist bundle
cayossarian Apr 16, 2026
a3e5233
docs: add error management design spec
cayossarian Apr 16, 2026
9ada6ad
docs: clarify backend helper usage in error management spec
cayossarian Apr 16, 2026
3e3e432
docs: add error management implementation plan
cayossarian Apr 16, 2026
be661a1
feat: add ErrorStore with two-lane persistent/transient error management
cayossarian Apr 16, 2026
f66843d
fix: export AddInput type from error-store
cayossarian Apr 16, 2026
642cdea
fix: align error-store i18n strings with spec
cayossarian Apr 16, 2026
c59025d
feat: add RetryManager with exponential backoff and panel-offline sho…
cayossarian Apr 16, 2026
2c8c01c
fix: clear stale error on successful panel-offline short-circuit
cayossarian Apr 16, 2026
bd1f249
feat: add error i18n keys and panel_status entity type
cayossarian Apr 16, 2026
898d020
feat: add span-error-banner LitElement component
cayossarian Apr 16, 2026
75b63d2
fix: harden error-banner lifecycle and apply codebase conventions
cayossarian Apr 16, 2026
65d35ce
feat: wire error banner and panel status watching into card
cayossarian Apr 16, 2026
ce2650a
fix: harden error-store clear and card discovery re-entry
cayossarian Apr 16, 2026
e37b35e
feat: wire error banner and panel status watching into panel
cayossarian Apr 16, 2026
1a2f9e6
fix: panel watch teardown, re-entry guard, and banner contradiction
cayossarian Apr 16, 2026
2209bbc
refactor: route side panel errors through ErrorStore
cayossarian Apr 16, 2026
89e3294
chore: remove unused ERROR_DISPLAY_MS constant
cayossarian Apr 16, 2026
d502956
fix: preserve error diagnostics in side panel catch blocks
cayossarian Apr 16, 2026
d134b62
feat: wire retry manager and error surfacing into dashboard controller
cayossarian Apr 16, 2026
ee33cd0
feat: surface cache fetch and area resolver errors via ErrorStore
cayossarian Apr 16, 2026
6c49abc
feat: surface tab-monitoring service errors via ErrorStore
cayossarian Apr 16, 2026
3b66480
fix: panel-switch race and localize area-resolver error
cayossarian Apr 16, 2026
36816ec
fix: address remaining error management review issues
cayossarian Apr 16, 2026
9327d3b
fix: remove retry from relay toggle — state changes must fail immedia…
cayossarian Apr 16, 2026
792959d
remove extraneous docs
cayossarian Apr 16, 2026
cac4314
refactor: scope retry to discovery and fetches, remove dead code
cayossarian Apr 16, 2026
0b25d34
Revert "remove extraneous docs"
cayossarian Apr 16, 2026
63c1852
docs: remove docs from repo per AGENTS.md (belong in SpanPanel_Docs w…
cayossarian Apr 16, 2026
2baab6b
docs: add CLAUDE.md with doc artifact location and attribution rules
cayossarian Apr 16, 2026
e2f178f
fix: address Copilot review comments on PR #10
cayossarian Apr 16, 2026
120be0a
build: rebuild dist bundle (Copilot review fixes)
cayossarian Apr 16, 2026
64ce0c7
fall back to live current/breaker rating for utilization pct
cayossarian Apr 16, 2026
63eea28
feat(i18n): add tf() placeholder substitution and named panel-status …
cayossarian Apr 16, 2026
ea509fb
feat(error-store): widen watchPanelStatus to multi-panel watch
cayossarian Apr 16, 2026
36dcf37
refactor(error-store): drop dead cast, unify cleanup, extract key hel…
cayossarian Apr 16, 2026
236ad4d
feat(favorites): show per-panel offline banner rows in Favorites view
cayossarian Apr 16, 2026
a0f05d0
docs(changelog): note Favorites offline banner fix in 0.9.4
cayossarian Apr 16, 2026
dcf8220
fix(side-panel): style unit-toggle so grid selector shows current set…
cayossarian Apr 16, 2026
07046af
build: rebuild dist bundle
cayossarian Apr 16, 2026
23339cf
refactor(header): extract buildSheddingLegendHTML for reuse in Favori…
cayossarian Apr 16, 2026
0407c0c
feat(favorites): add gear + shedding legend to summary strip
cayossarian Apr 16, 2026
beca7a0
refactor(favorites): drop empty CSS rule and refresh stale docstring
cayossarian Apr 16, 2026
c5c4d9f
feat(side-panel): add favorites mode rendering with per-panel sections
cayossarian Apr 16, 2026
c960860
feat(favorites): open graph-settings sidebar grouped by contributing …
cayossarian Apr 16, 2026
59a66fe
build: rebuild dist bundle
cayossarian Apr 16, 2026
7776bc5
fix(favorites): anchor summary gear flush-left in the summary row
cayossarian Apr 16, 2026
843818d
fix(favorites): defer tab re-render while favorites-mode sidebar is open
cayossarian Apr 16, 2026
bb7b706
feat(favorites): list every circuit per contributing panel in sidebar
cayossarian Apr 16, 2026
58cbdba
fix(side-panel): defer tab re-render for every sidebar mode, not just…
cayossarian Apr 16, 2026
a555311
fix: address deep code-review findings
cayossarian Apr 17, 2026
91863d2
test: cover cache-race and attribute-escape regressions
cayossarian Apr 17, 2026
3b0b4b0
build: rebuild dist bundle
cayossarian Apr 17, 2026
ca3a460
fix(favorites): BESS chart line visibility and resize
cayossarian Apr 17, 2026
5f1a047
fix: address Copilot review feedback
cayossarian Apr 17, 2026
4108f85
fix(panel): drop redundant summary header from Favorites Monitoring view
cayossarian Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,94 @@
# Changelog

## 0.9.4

### Added

- **Compact expanded list rows** — Expanding a row in By Activity / By Area now shows only the chart. The gear icon and a real toggle-pill (arm-protected by the
slide-to-confirm) moved onto the always-visible list row so expanding no longer duplicates information above the chart.
- **Configurable list view columns** — 1 / 2 / 3 column grid for By Activity and By Area, set in Graph Settings → List View Columns. Persisted in localStorage
as a browser-wide preference (single key, not scoped per device). Narrow viewports (< 600px) force single-column regardless. Expanded charts stay in their own
column so row-to-chart association stays clear.
- **Favorites per-panel status grid** — The Favorites view now renders a responsive grid of per-contributing-panel status cards (Site / Grid / Upstream /
Downstream / Solar / Battery) below the slider + W/A row. One card per panel that contributes to the Favorites set; live values update on each tick.
- **Slide-to-arm in Favorites** — Favorites view header now hosts the slide-confirm control so tappable ON/OFF toggles in list rows can actually fire. The
non-Favorites list views (By Activity / By Area on real panels) also gain a working slide-to-arm (previously the slider rendered but drag handlers were never
bound).
- **Panel tabs inline with dropdown** — The panel tab bar (By Panel / By Activity / By Area / Monitoring) now sits on the same row as the panel-selector
dropdown in the toolbar.

### Fixed

- **Favorites utilization % now renders** — The list view was passing `null` monitoring status for Favorites, leaving utilization badges blank. Added per-entry
fetch + merge (`mergeMonitoringStatuses`) with a keyed 30s cache so cross-panel favorites show the same utilization data the single-panel views show.
- **List / panel view flashing on tab, W/A, and panel switches** — Event handlers were both mutating `@state` and explicitly scheduling renders, so every
interaction fired two concurrent re-renders. Fixed by moving to the reactive-only path and adding a render coalescer plus a supersession-token guard so
superseded renders bail out at each async boundary.
- **Amps chart didn't redraw on unit switch** — `powerHistory` merged new-metric points into the existing Watts map under the same UUID key. Now cleared before
every full re-render.
- **`[data-uuid]` selector shadowing** — `dom-updater` scoped to `.circuit-slot[data-uuid]` so the expanded chart-only slot is targeted instead of the list row
(which now also carries `data-uuid`).
- **ON/OFF badge no longer silently non-functional** — The tappable badge now routes through the real toggle pipeline with the slide-confirm gate; list views
previously dropped clicks because no `.slide-confirm` element existed in their header.
- **Favorites view now shows offline banner per contributing panel** — Previously the Favorites view silently hid the red "SPAN Panel unreachable" banner even
when a contributing panel was offline. The view now renders one banner row per offline panel, labeled with the panel's name (e.g. "Span Panel 2 unreachable"),
so users mixing favorites from multiple panels can see which one is down.
- **Graph Settings → List View Columns selector now shows the current setting** — the 1/2/3 segmented control rendered without any styling in the side-panel's
shadow DOM, so the active option was invisible. The side-panel now ships its own `.unit-toggle` styles and highlights the current column count.

### Changed

- **Utilization % moved next to breaker badge** — In both the list rows and the By Panel breaker-grid slots, the utilization percent now sits immediately after
the breaker badge rather than alongside the battery shedding icon, where it competed visually with battery SoC.
- **Favorites header simplified** — Removed the redundant "Favorites · N favorites" text (the dropdown already says "Favorites"). The header row now holds just
the slide-to-arm and W/A unit toggle.
- **`buildListRowHTML` replaced the static ON/OFF badge with a real toggle-pill** for controllable circuits. Non-controllable circuits (PV, no switch entity)
keep a static text badge so they can't be accidentally toggled.

### Architecture

- Extracted `buildPanelStatsHTML` / `updatePanelStatsBlock` so stats-block render + update are shared between the persistent panel header and the Favorites
per-panel grid.
- Extracted `mergeMonitoringStatuses` pure helper plus a new `MonitoringStatusMultiCache` per-entry keyed cache;
`DashboardController.fetchMergedMonitoringStatus` now reuses cached results within the 30s TTL instead of fanning out WS calls on every render.
- Extracted `FavoritesViewState` persistence (`src/panel/favorites-view-state.ts`) and the render coalescing / token helpers (`src/panel/coalesce.ts`) out of
the ~1000-line panel element. Both are pure and now tested.
- `CARD_STYLES` emitted once via Lit `static styles` rather than `insertAdjacentHTML`'d per tab render.
- `CSS.escape` wrapper applied to every identifier-interpolated `querySelector`.
- Each circuit in a list view now wraps its row + optional expansion in a `.list-cell` container so the expansion stays in the same CSS-grid column as its row
in multi-column mode.
- Debounced Favorites view-state localStorage persistence (250ms) so long search queries don't thrash storage.
- Tests grew from 109 → 132 (new coverage: `getCircuitStateClasses`, `mergeMonitoringStatuses`, `FavoritesController.build` composite-id construction, and
render-coalescing / supersession-token contracts).

## 0.9.3

### Added

- **Cross-panel Favorites view** — A synthetic "Favorites" entry appears in the dashboard panel dropdown (only when at least one favorite is configured) and
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 the standalone Lovelace card).
- Favorites view shows By Activity / By Area / Monitoring tabs (no By Panel). When more than one panel contributes, circuit and sub-device names are prefixed
with their panel name. Sub-device tiles render above the circuit list. Monitoring stacks per-panel blocks.
- Stateful: active tab, expanded rows, and search query persist via localStorage and restore on return.
- **Persistent panel-stats header** — Site / Grid / Upstream / Downstream / Solar / Battery stats now stay visible across all tabs (By Panel, By Activity, By
Area, Monitoring) on real panels. Lifted out of the By-Panel grid into the wrapper. Favorites pseudo-panel shows a count summary instead.

### Fixed

- **Sub-device per-target horizon override** — Setting an individual BESS or EVSE horizon from the sub-device side panel had no effect when more than one SPAN
panel was configured: the service call omitted `config_entry_id`, so the backend wrote the override to the first loaded entry's manager (wrong panel). The
per-circuit side panel and the panel-mode (Graph Settings) list already threaded it; the sub-device side panel now does too.
- **`<ha-menu-button>` first-render crash** — The dashboard panel no longer creates `ha-menu-button` until Home Assistant has assigned `hass`; HA's component
reads `this.hass.kioskMode` in `willUpdate` and would throw before the property was set.

### Changed

- **Side-panel domain service calls thread `config_entry_id`** — Circuit horizon, sub-device horizon, and circuit threshold service calls now route to the
originating panel's config entry. Required for cross-panel Favorites edits to target the right panel and fixes the same-panel bug above.
- **W/A unit toggle moved to the persistent header** — The duplicate toggle below the search bar was removed since the persistent panel-stats header now owns
it. The Favorites pseudo-panel's summary strip carries its own toggle.

## 0.9.2

### Added
Expand Down
29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# CLAUDE.md

## Doc Artifacts

Design docs, specs, plans, brainstorming output, and superpowers artifacts for this repo live in the SpanPanel_Docs workspace, NOT in this repo:

- `/Users/bflood/projects/HA/SpanPanel_Docs/span/docs/superpowers/plans/` — superpowers plan artifacts
- `/Users/bflood/projects/HA/SpanPanel_Docs/span/docs/superpowers/specs/` — superpowers spec artifacts
- `/Users/bflood/projects/HA/SpanPanel_Docs/span/docs/dev/` — developer plans, specs, design documents

When invoking the brainstorming or writing-plans skill, override its default location (which is `docs/superpowers/...` in the current repo) with the correct
path in SpanPanel_Docs. Never write `.md` design artifacts into this repo.

If a doc was already written to the wrong location, copy it to the correct location before removing from this repo. Do not auto-commit in SpanPanel_Docs since
that repo may have unrelated pending changes — leave new files as untracked for the user to commit.

This matches the AGENTS.md convention in the sibling `span` integration repo (`/Users/bflood/projects/HA/span/AGENTS.md`) — the two repos are maintained
together and share the same doc-artifact rule.

## Attribution

Never include references to AI models, AI assistants, or AI-generated content in any code, comments, commit messages, PR descriptions, documentation, or other
output. This includes "Co-Authored-By" tags, "Generated with" footers, and any similar attribution.

## Related

- Sibling integration repo: `/Users/bflood/projects/HA/span` (has its own AGENTS.md with the integration-specific conventions)
- After any change under `src/`, the integration's bundled JS must be rebuilt and copied via the `sync-frontend` skill or
`/Users/bflood/projects/HA/span/scripts/build-frontend.sh`.
93 changes: 74 additions & 19 deletions dist/span-panel-card.js

Large diffs are not rendered by default.

133 changes: 103 additions & 30 deletions dist/span-panel.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "span-panel-card",
"version": "0.9.2",
"version": "0.9.4",
"private": true,
"type": "module",
"scripts": {
Expand Down
37 changes: 21 additions & 16 deletions src/card/card-discovery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { INTEGRATION_DOMAIN } from "../constants.js";
import { resolveAndAssignAreas } from "../core/area-resolver.js";
import { RetryManager } from "../core/retry-manager.js";
import { t } from "../i18n.js";
import type { HomeAssistant, PanelTopology, PanelDevice, DiscoveryResult, Circuit, CircuitEntities } from "../types.js";

Expand All @@ -25,23 +26,23 @@ interface EntityRegistryEntry {

// ── Primary discovery via custom WebSocket API ───────────────────────────────

export async function discoverTopology(hass: HomeAssistant, deviceId: string | undefined): Promise<DiscoveryResult> {
export async function discoverTopology(hass: HomeAssistant, deviceId: string | undefined, retry?: RetryManager | null): Promise<DiscoveryResult> {
if (!deviceId) {
throw new Error(t("card.device_not_found"));
}
const topology = await hass.callWS<PanelTopology>({
type: `${INTEGRATION_DOMAIN}/panel_topology`,
device_id: deviceId,
});

const topologyMsg = { type: `${INTEGRATION_DOMAIN}/panel_topology`, device_id: deviceId };
const topology = retry ? await retry.callWS<PanelTopology>(hass, topologyMsg, { errorId: "fetch:topology" }) : await hass.callWS<PanelTopology>(topologyMsg);

const panelSize = topology.panel_size ?? panelSizeFromCircuits(topology.circuits);
if (!panelSize) {
throw new Error(t("card.topology_error"));
}

const devices = await hass.callWS<DeviceRegistryEntry[]>({
type: "config/device_registry/list",
});
const devicesMsg = { type: "config/device_registry/list" };
const devices = retry
? await retry.callWS<DeviceRegistryEntry[]>(hass, devicesMsg, { errorId: "fetch:topology" })
: await hass.callWS<DeviceRegistryEntry[]>(devicesMsg);
const panelDevice = deviceToPanelDevice(devices.find(d => d.id === deviceId));

await resolveAndAssignAreas(hass, topology);
Expand Down Expand Up @@ -80,14 +81,12 @@ function deviceToPanelDevice(entry: DeviceRegistryEntry | undefined): PanelDevic

// ── Fallback discovery from entity registry ──────────────────────────────────

export async function discoverEntitiesFallback(hass: HomeAssistant, deviceId: string | undefined): Promise<DiscoveryResult> {
export async function discoverEntitiesFallback(hass: HomeAssistant, deviceId: string | undefined, retry?: RetryManager | null): Promise<DiscoveryResult> {
const devicesMsg = { type: "config/device_registry/list" };
const entitiesMsg = { type: "config/entity_registry/list" };
const [devices, entities] = await Promise.all([
hass.callWS<DeviceRegistryEntry[]>({
type: "config/device_registry/list",
}),
hass.callWS<EntityRegistryEntry[]>({
type: "config/entity_registry/list",
}),
retry ? retry.callWS<DeviceRegistryEntry[]>(hass, devicesMsg, { errorId: "fetch:topology" }) : hass.callWS<DeviceRegistryEntry[]>(devicesMsg),
retry ? retry.callWS<EntityRegistryEntry[]>(hass, entitiesMsg, { errorId: "fetch:topology" }) : hass.callWS<EntityRegistryEntry[]>(entitiesMsg),
]);

const panelDevice = deviceToPanelDevice(devices.find(d => d.id === deviceId));
Expand Down Expand Up @@ -164,7 +163,13 @@ export async function discoverEntitiesFallback(hass: HomeAssistant, deviceId: st
let serial = "";
if (panelDevice.identifiers) {
for (const pair of panelDevice.identifiers) {
if (pair[0] === INTEGRATION_DOMAIN) serial = pair[1];
// Identifier pairs are [domain, value]. Skip malformed shapes rather
// than silently indexing past the end.
if (!Array.isArray(pair) || pair.length < 2) continue;
const [domain, value] = pair;
if (domain === INTEGRATION_DOMAIN && typeof value === "string") {
serial = value;
}
}
}

Expand Down
105 changes: 103 additions & 2 deletions src/card/card-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,65 @@ export const CARD_STYLES: string = `
gap: 16px 32px;
}

/* Favorites view header: gear + slide-to-arm + right-anchored legend/W-A cluster. */
.favorites-summary {
padding: 8px 24px;
border-bottom: 1px solid var(--divider-color, #e0e0e0);
display: flex;
align-items: center;
gap: 12px;
}
/* Override the generic .gear-icon { margin-left: auto } rule so the
favorites gear stays flush-left instead of floating to the right of
the flex row (same idea as .panel-identity .panel-gear does for
real-panel headers). */
.favorites-summary .favorites-gear {
margin-left: 0;
}
/* Right-anchored cluster wrapping the shedding legend + W/A unit toggle.
margin-left:auto moved here from .favorites-summary-unit-toggle so the
legend and toggle cluster together, matching the real-panel header
layout. */
.favorites-summary-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 16px;
}
.favorites-subdevices-section {
padding: 8px 16px 0;
}

/* Favorites view: responsive grid of per-contributing-panel status cards. */
.favorites-panel-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 12px;
padding: 12px 24px;
border-bottom: 1px solid var(--divider-color, #333);
}
.favorites-panel-card {
background: var(--secondary-background-color, rgba(255, 255, 255, 0.04));
border: 1px solid var(--divider-color, #333);
border-radius: 8px;
padding: 10px 14px;
display: flex;
flex-direction: column;
gap: 6px;
}
.favorites-panel-card-title {
font-size: 0.85em;
font-weight: 600;
color: var(--primary-text-color);
opacity: 0.85;
}
.favorites-panel-card .panel-stats {
gap: 10px 20px;
}
.favorites-panel-card .stat-value {
font-size: 1.15em;
}

.stat { display: flex; flex-direction: column; }
.stat-label { font-size: 0.8em; color: var(--secondary-text-color, #999); margin-bottom: 2px; }
.stat-row { display: flex; align-items: baseline; gap: 2px; }
Expand Down Expand Up @@ -494,6 +553,33 @@ export const CARD_STYLES: string = `
flex-direction: column;
gap: 6px;
}
/* Each circuit is wrapped in a .list-cell so the row + its optional
expanded chart stay together. In single-column flex mode the cell
just stacks naturally. In multi-column grid mode the cell becomes
one grid item, so the chart is always in the same column as its
row. Area headers (rendered as siblings, not inside a cell) span
all columns via their inline "grid-column: 1 / -1". */
.list-cell {
display: flex;
flex-direction: column;
min-width: 0;
}
.list-view[data-columns="2"],
.list-view[data-columns="3"] {
display: grid;
grid-template-columns: repeat(var(--list-cols), minmax(0, 1fr));
gap: 6px 8px;
flex-direction: initial;
}
/* On narrow viewports a 2/3-column list would squeeze rows into an
unreadable shape, so force stacking regardless of user preference. */
@media (max-width: 599px) {
.list-view[data-columns="2"],
.list-view[data-columns="3"] {
display: flex;
flex-direction: column;
}
}

.list-row {
display: flex;
Expand Down Expand Up @@ -570,10 +656,23 @@ export const CARD_STYLES: string = `
transform: rotate(180deg);
}

.list-row .gear-icon {
background: transparent;
border: none;
padding: 2px;
cursor: pointer;
color: #555;
display: inline-flex;
align-items: center;
}
.list-row .gear-icon:hover {
color: var(--primary-text-color);
}

/* ── Expanded circuit content ──────────────────────────── */

.list-expanded-content {
padding: 12px;
padding: 0;
background: var(--card-background-color, #1c1c1c);
border: 1px solid var(--divider-color, #333);
border-top: none;
Expand All @@ -582,10 +681,12 @@ export const CARD_STYLES: string = `
margin-bottom: 2px;
}

.list-expanded-content .circuit-slot {
.circuit-slot.circuit-chart-only {
border: none;
margin: 0;
background: none;
padding: 8px 12px;
min-height: 0;
}

/* ── Area headers ──────────────────────────────────────── */
Expand Down
Loading
Loading