From 1421131a0ae2d44a5eb6ef976a7b26af00322c81 Mon Sep 17 00:00:00 2001
From: cayossarian <23534755+cayossarian@users.noreply.github.com>
Date: Tue, 14 Apr 2026 23:30:19 -0700
Subject: [PATCH 01/97] feat: cross-panel Favorites view with sub-device
support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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.
---
dist/span-panel-card.js | 24 +-
dist/span-panel.js | 40 +--
package.json | 2 +-
src/card/span-panel-card.ts | 6 +-
src/constants.ts | 2 +-
src/core/dashboard-controller.ts | 175 +++++++++++-
src/core/favorites-controller.ts | 136 +++++++++
src/core/favorites-store.ts | 142 ++++++++++
src/core/header-renderer.ts | 20 +-
src/core/list-view-controller.ts | 84 +++++-
src/core/side-panel.ts | 336 ++++++++++++++++++++--
src/i18n.ts | 32 +++
src/panel/span-panel.ts | 470 ++++++++++++++++++++++++++++---
src/panel/tab-dashboard.ts | 15 +
src/panel/tab-monitoring.ts | 6 +-
src/types.ts | 42 +++
16 files changed, 1415 insertions(+), 117 deletions(-)
create mode 100644 src/core/favorites-controller.ts
create mode 100644 src/core/favorites-store.ts
diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js
index 9d806e7..1532a14 100644
--- a/dist/span-panel-card.js
+++ b/dist/span-panel-card.js
@@ -1,42 +1,42 @@
-let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.all_targets":"All","notification.event_bus_target":"Event Bus (HA event bus)","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.configure_subdevice":"Configure device","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (bus de eventos de HA)","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (bus d'événements HA)","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.configure_subdevice":"Configurer l'appareil","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.all_targets":"すべて","notification.event_bus_target":"イベントバス (HAイベントバス)","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.configure_subdevice":"デバイスを設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (barramento de eventos do HA)","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){e=n&&t[n]?n:"en"}function i(n){return t[e]?.[n]??t.en?.[n]??n}const s="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},r="span_panel",c="CLOSED",l="pv",d="bess",h="evse",p="sub_",u=500,g={power:{entityRole:"power",label:()=>i("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>i("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},_={soc:{entityRole:"soc",label:()=>i("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>i("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:g.power},f={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>i("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>i("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>i("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>i("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>i("shedding.unknown")}},m="#ff9800";function v(e,t,n,i){var s,o=arguments.length,a=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,n,i);else for(var r=e.length-1;r>=0;r--)(s=e[r])&&(a=(o<3?s(a):o>3?s(t,n,a):s(t,n))||a);return o>3&&a&&Object.defineProperty(t,n,a),a}"function"==typeof SuppressedError&&SuppressedError;
+let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.all_targets":"All","notification.event_bus_target":"Event Bus (HA event bus)","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.configure_subdevice":"Configure device","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.global_default":"Global Default","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.relay_failed":"Relay toggle failed:","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.shedding_failed":"Shedding update failed:","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.monitoring_toggle_failed":"Monitoring toggle failed:","sidepanel.clear_monitoring_failed":"Clear monitoring failed:","sidepanel.save_threshold_failed":"Save threshold failed:","sidepanel.favorite":"Favorite","sidepanel.save_to_favorites":"Save to favorites","sidepanel.favorite_failed":"Favorite update failed:","panel.favorites":"Favorites","panel.favorites_summary_one":"1 circuit across {panels} panels","panel.favorites_summary_many":"{circuits} circuits across {panels} panels","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.loading":"Loading...","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (bus de eventos de HA)","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.global_default":"Predeterminado Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.relay_failed":"Error al cambiar relé:","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.shedding_failed":"Error al actualizar desconexción:","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.monitoring_toggle_failed":"Error al cambiar monitoreo:","sidepanel.clear_monitoring_failed":"Error al limpiar monitoreo:","sidepanel.save_threshold_failed":"Error al guardar umbral:","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Guardar en favoritos","sidepanel.favorite_failed":"Error al actualizar favoritos:","panel.favorites":"Favoritos","panel.favorites_summary_one":"1 circuito en {panels} paneles","panel.favorites_summary_many":"{circuits} circuitos en {panels} paneles","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.loading":"Cargando...","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (bus d'événements HA)","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.configure_subdevice":"Configurer l'appareil","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.global_default":"Défaut Global","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.relay_failed":"Échec du basculement du relais :","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.shedding_failed":"Échec de la mise à jour du délestage :","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.monitoring_toggle_failed":"Échec du basculement de surveillance :","sidepanel.clear_monitoring_failed":"Échec de l'effacement de surveillance :","sidepanel.save_threshold_failed":"Échec de la sauvegarde du seuil :","sidepanel.favorite":"Favori","sidepanel.save_to_favorites":"Enregistrer dans les favoris","sidepanel.favorite_failed":"Échec de la mise à jour du favori :","panel.favorites":"Favoris","panel.favorites_summary_one":"1 circuit sur {panels} panneaux","panel.favorites_summary_many":"{circuits} circuits sur {panels} panneaux","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.loading":"Chargement...","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.all_targets":"すべて","notification.event_bus_target":"イベントバス (HAイベントバス)","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.configure_subdevice":"デバイスを設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.global_default":"グローバルデフォルト","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.relay_failed":"リレー切り替え失敗:","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.shedding_failed":"シェディング更新失敗:","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.monitoring_toggle_failed":"モニタリング切り替え失敗:","sidepanel.clear_monitoring_failed":"モニタリングクリア失敗:","sidepanel.save_threshold_failed":"しきい値保存失敗:","sidepanel.favorite":"お気に入り","sidepanel.save_to_favorites":"お気に入りに保存","sidepanel.favorite_failed":"お気に入りの更新に失敗:","panel.favorites":"お気に入り","panel.favorites_summary_one":"{panels} パネルにわたる 1 回路","panel.favorites_summary_many":"{panels} パネルにわたる {circuits} 回路","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.loading":"読み込み中...","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (barramento de eventos do HA)","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","sidepanel.graph_horizon_failed":"Graph horizon update failed:","sidepanel.clear_graph_horizon_failed":"Clear graph horizon failed:","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.global_default":"Padrão Global","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.relay_failed":"Falha ao alternar relé:","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.shedding_failed":"Falha ao atualizar desligamento:","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.monitoring_toggle_failed":"Falha ao alternar monitoramento:","sidepanel.clear_monitoring_failed":"Falha ao limpar monitoramento:","sidepanel.save_threshold_failed":"Falha ao salvar limite:","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Salvar nos favoritos","sidepanel.favorite_failed":"Falha ao atualizar favorito:","panel.favorites":"Favoritos","panel.favorites_summary_one":"1 circuito em {panels} painéis","panel.favorites_summary_many":"{circuits} circuitos em {panels} painéis","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.loading":"Carregando...","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function i(i){e=i&&t[i]?i:"en"}function n(i){return t[e]?.[i]??t.en?.[i]??i}const s="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},r="span_panel",c="CLOSED",l="pv",d="bess",h="evse",p="sub_",u=500,g={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},_={soc:{entityRole:"soc",label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:g.power},f={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},m="#ff9800";function v(e,t,i,n){var s,o=arguments.length,a=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,i,n);else for(var r=e.length-1;r>=0;r--)(s=e[r])&&(a=(o<3?s(a):o>3?s(t,i,a):s(t,i))||a);return o>3&&a&&Object.defineProperty(t,i,a),a}"function"==typeof SuppressedError&&SuppressedError;
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
-const b=globalThis,y=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,w=Symbol(),x=new WeakMap;let S=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==w)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(y&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=x.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&x.set(t,e))}return e}toString(){return this.cssText}};const C=e=>new S("string"==typeof e?e:e+"",void 0,w),$=y?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return C(t)})(e):e,{is:E,defineProperty:z,getOwnPropertyDescriptor:k,getOwnPropertyNames:A,getOwnPropertySymbols:M,getPrototypeOf:P}=Object,L=globalThis,N=L.trustedTypes,T=N?N.emptyScript:"",D=L.reactiveElementPolyfillSupport,H=(e,t)=>e,O={toAttribute(e,t){switch(t){case Boolean:e=e?T:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},R=(e,t)=>!E(e,t),I={attribute:!0,type:String,converter:O,reflect:!1,useDefault:!1,hasChanged:R};
+const b=globalThis,y=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,w=Symbol(),x=new WeakMap;let S=class{constructor(e,t,i){if(this._$cssResult$=!0,i!==w)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(y&&void 0===e){const i=void 0!==t&&1===t.length;i&&(e=x.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),i&&x.set(t,e))}return e}toString(){return this.cssText}};const C=e=>new S("string"==typeof e?e:e+"",void 0,w),E=y?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const i of e.cssRules)t+=i.cssText;return C(t)})(e):e,{is:$,defineProperty:z,getOwnPropertyDescriptor:k,getOwnPropertyNames:A,getOwnPropertySymbols:P,getPrototypeOf:M}=Object,L=globalThis,N=L.trustedTypes,D=N?N.emptyScript:"",T=L.reactiveElementPolyfillSupport,I=(e,t)=>e,H={toAttribute(e,t){switch(t){case Boolean:e=e?D:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let i=e;switch(t){case Boolean:i=null!==e;break;case Number:i=null===e?null:Number(e);break;case Object:case Array:try{i=JSON.parse(e)}catch(e){i=null}}return i}},F=(e,t)=>!$(e,t),R={attribute:!0,type:String,converter:H,reflect:!1,useDefault:!1,hasChanged:F};
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
- */Symbol.metadata??=Symbol("metadata"),L.litPropertyMetadata??=new WeakMap;let j=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=I){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&z(this.prototype,e,i)}}static getPropertyDescriptor(e,t,n){const{get:i,set:s}=k(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const o=i?.call(this);s?.call(this,t),this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??I}static _$Ei(){if(this.hasOwnProperty(H("elementProperties")))return;const e=P(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(H("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(H("properties"))){const e=this.properties,t=[...A(e),...M(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift($(e))}else void 0!==e&&t.push($(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(y)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const n of t){const t=document.createElement("style"),i=b.litNonce;void 0!==i&&t.setAttribute("nonce",i),t.textContent=n.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){const s=(void 0!==n.converter?.toAttribute?n.converter:O).toAttribute(t,n.type);this._$Em=e,null==s?this.removeAttribute(i):this.setAttribute(i,s),this._$Em=null}}_$AK(e,t){const n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=n.getPropertyOptions(i),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:O;this._$Em=i;const o=s.fromAttribute(t,e.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(e,t,n,i=!1,s){if(void 0!==e){const o=this.constructor;if(!1===i&&(s=this[e]),n??=o.getPropertyOptions(e),!((n.hasChanged??R)(s,t)||n.useDefault&&n.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:i,wrapped:s},o){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,n,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};j.elementStyles=[],j.shadowRootOptions={mode:"open"},j[H("elementProperties")]=new Map,j[H("finalized")]=new Map,D?.({ReactiveElement:j}),(L.reactiveElementVersions??=[]).push("2.1.2");
+ */Symbol.metadata??=Symbol("metadata"),L.litPropertyMetadata??=new WeakMap;let O=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=R){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const i=Symbol(),n=this.getPropertyDescriptor(e,i,t);void 0!==n&&z(this.prototype,e,n)}}static getPropertyDescriptor(e,t,i){const{get:n,set:s}=k(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:n,set(t){const o=n?.call(this);s?.call(this,t),this.requestUpdate(e,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??R}static _$Ei(){if(this.hasOwnProperty(I("elementProperties")))return;const e=M(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(I("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(I("properties"))){const e=this.properties,t=[...A(e),...P(e)];for(const i of t)this.createProperty(i,e[i])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,i]of t)this.elementProperties.set(e,i)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const i=this._$Eu(e,t);void 0!==i&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const i=new Set(e.flat(1/0).reverse());for(const e of i)t.unshift(E(e))}else void 0!==e&&t.push(E(e));return t}static _$Eu(e,t){const i=t.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const i of t.keys())this.hasOwnProperty(i)&&(e.set(i,this[i]),delete this[i]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(y)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const i of t){const t=document.createElement("style"),n=b.litNonce;void 0!==n&&t.setAttribute("nonce",n),t.textContent=i.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,i){this._$AK(e,i)}_$ET(e,t){const i=this.constructor.elementProperties.get(e),n=this.constructor._$Eu(e,i);if(void 0!==n&&!0===i.reflect){const s=(void 0!==i.converter?.toAttribute?i.converter:H).toAttribute(t,i.type);this._$Em=e,null==s?this.removeAttribute(n):this.setAttribute(n,s),this._$Em=null}}_$AK(e,t){const i=this.constructor,n=i._$Eh.get(e);if(void 0!==n&&this._$Em!==n){const e=i.getPropertyOptions(n),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:H;this._$Em=n;const o=s.fromAttribute(t,e.type);this[n]=o??this._$Ej?.get(n)??o,this._$Em=null}}requestUpdate(e,t,i,n=!1,s){if(void 0!==e){const o=this.constructor;if(!1===n&&(s=this[e]),i??=o.getPropertyOptions(e),!((i.hasChanged??F)(s,t)||i.useDefault&&i.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,i))))return;this.C(e,t,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:i,reflect:n,wrapped:s},o){i&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||i||(t=void 0),this._$AL.set(e,t)),!0===n&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,i]of e){const{wrapped:e}=i,n=this[t];!0!==e||this._$AL.has(t)||void 0===n||this.C(t,void 0,i,n)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};O.elementStyles=[],O.shadowRootOptions={mode:"open"},O[I("elementProperties")]=new Map,O[I("finalized")]=new Map,T?.({ReactiveElement:O}),(L.reactiveElementVersions??=[]).push("2.1.2");
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
-const q=globalThis,F=e=>e,W=q.trustedTypes,U=W?W.createPolicy("lit-html",{createHTML:e=>e}):void 0,B="$lit$",G=`lit$${Math.random().toFixed(9).slice(2)}$`,V="?"+G,Q=`<${V}>`,J=document,X=()=>J.createComment(""),K=e=>null===e||"object"!=typeof e&&"function"!=typeof e,Z=Array.isArray,Y="[ \t\n\f\r]",ee=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,te=/-->/g,ne=/>/g,ie=RegExp(`>|${Y}(?:([^\\s"'>=/]+)(${Y}*=${Y}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),se=/'/g,oe=/"/g,ae=/^(?:script|style|textarea|title)$/i,re=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),ce=Symbol.for("lit-noChange"),le=Symbol.for("lit-nothing"),de=new WeakMap,he=J.createTreeWalker(J,129);function pe(e,t){if(!Z(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==U?U.createHTML(t):t}const ue=(e,t)=>{const n=e.length-1,i=[];let s,o=2===t?"":3===t?"":"")),i]};class ge{constructor({strings:e,_$litType$:t},n){let i;this.parts=[];let s=0,o=0;const a=e.length-1,r=this.parts,[c,l]=ue(e,t);if(this.el=ge.createElement(c,n),he.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=he.nextNode())&&r.length0){i.textContent=W?W.emptyScript:"";for(let n=0;nZ(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==le&&K(this._$AH)?this._$AA.nextSibling.data=e:this.T(J.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,i="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=ge.createElement(pe(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new fe(i,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=de.get(e.strings);return void 0===t&&de.set(e.strings,t=new ge(e)),t}k(e){Z(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,i=0;for(const s of e)i===t.length?t.push(n=new me(this.O(X()),this.O(X()),this,this.options)):n=t[i],n._$AI(s),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=le}_$AI(e,t=this,n,i){const s=this.strings;let o=!1;if(void 0===s)e=_e(this,e,t,0),o=!K(e)||e!==this._$AH&&e!==ce,o&&(this._$AH=e);else{const i=e;let a,r;for(e=s[0],a=0;ae,U=j.trustedTypes,W=U?U.createPolicy("lit-html",{createHTML:e=>e}):void 0,G="$lit$",B=`lit$${Math.random().toFixed(9).slice(2)}$`,V="?"+B,Q=`<${V}>`,J=document,X=()=>J.createComment(""),K=e=>null===e||"object"!=typeof e&&"function"!=typeof e,Z=Array.isArray,Y="[ \t\n\f\r]",ee=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,te=/-->/g,ie=/>/g,ne=RegExp(`>|${Y}(?:([^\\s"'>=/]+)(${Y}*=${Y}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),se=/'/g,oe=/"/g,ae=/^(?:script|style|textarea|title)$/i,re=(e=>(t,...i)=>({_$litType$:e,strings:t,values:i}))(1),ce=Symbol.for("lit-noChange"),le=Symbol.for("lit-nothing"),de=new WeakMap,he=J.createTreeWalker(J,129);function pe(e,t){if(!Z(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==W?W.createHTML(t):t}const ue=(e,t)=>{const i=e.length-1,n=[];let s,o=2===t?"":3===t?"":"")),n]};class ge{constructor({strings:e,_$litType$:t},i){let n;this.parts=[];let s=0,o=0;const a=e.length-1,r=this.parts,[c,l]=ue(e,t);if(this.el=ge.createElement(c,i),he.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(n=he.nextNode())&&r.length0){n.textContent=U?U.emptyScript:"";for(let i=0;iZ(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==le&&K(this._$AH)?this._$AA.nextSibling.data=e:this.T(J.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:i}=e,n="number"==typeof i?this._$AC(e):(void 0===i.el&&(i.el=ge.createElement(pe(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===n)this._$AH.p(t);else{const e=new fe(n,this),i=e.u(this.options);e.p(t),this.T(i),this._$AH=e}}_$AC(e){let t=de.get(e.strings);return void 0===t&&de.set(e.strings,t=new ge(e)),t}k(e){Z(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let i,n=0;for(const s of e)n===t.length?t.push(i=new me(this.O(X()),this.O(X()),this,this.options)):i=t[n],i._$AI(s),n++;n2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=le}_$AI(e,t=this,i,n){const s=this.strings;let o=!1;if(void 0===s)e=_e(this,e,t,0),o=!K(e)||e!==this._$AH&&e!==ce,o&&(this._$AH=e);else{const n=e;let a,r;for(e=s[0],a=0;a{const i=n?.renderBefore??t;let s=i._$litPart$;if(void 0===s){const e=n?.renderBefore??null;i._$litPart$=s=new me(t.insertBefore(X(),e),e,void 0,n??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ce}}$e._$litElement$=!0,$e.finalized=!0,Ce.litElementHydrateSupport?.({LitElement:$e});const Ee=Ce.litElementPolyfillSupport;Ee?.({LitElement:$e}),(Ce.litElementVersions??=[]).push("4.2.2");
+ */class Ee extends O{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=((e,t,i)=>{const n=i?.renderBefore??t;let s=n._$litPart$;if(void 0===s){const e=i?.renderBefore??null;n._$litPart$=s=new me(t.insertBefore(X(),e),e,void 0,i??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ce}}Ee._$litElement$=!0,Ee.finalized=!0,Ce.litElementHydrateSupport?.({LitElement:Ee});const $e=Ce.litElementPolyfillSupport;$e?.({LitElement:Ee}),(Ce.litElementVersions??=[]).push("4.2.2");
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
-const ze={attribute:!0,type:String,converter:O,reflect:!1,hasChanged:R},ke=(e=ze,t,n)=>{const{kind:i,metadata:s}=n;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),o.set(n.name,e),"accessor"===i){const{name:i}=n;return{set(n){const s=t.get.call(this);t.set.call(this,n),this.requestUpdate(i,s,e,!0,n)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=n;return function(n){const s=this[i];t.call(this,n),this.requestUpdate(i,s,e,!0,n)}}throw Error("Unsupported decorator location: "+i)};
+const ze={attribute:!0,type:String,converter:H,reflect:!1,hasChanged:F},ke=(e=ze,t,i)=>{const{kind:n,metadata:s}=i;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===n&&((e=Object.create(e)).wrapped=!0),o.set(i.name,e),"accessor"===n){const{name:n}=i;return{set(i){const s=t.get.call(this);t.set.call(this,i),this.requestUpdate(n,s,e,!0,i)},init(t){return void 0!==t&&this.C(n,void 0,e,t),t}}}if("setter"===n){const{name:n}=i;return function(i){const s=this[n];t.call(this,i),this.requestUpdate(n,s,e,!0,i)}}throw Error("Unsupported decorator location: "+n)};
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
- */function Ae(e){return(t,n)=>"object"==typeof n?ke(e,t,n):((e,t,n)=>{const i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)}
+ */function Ae(e){return(t,i)=>"object"==typeof i?ke(e,t,i):((e,t,i)=>{const n=t.hasOwnProperty(i);return t.constructor.createProperty(i,e),n?Object.getOwnPropertyDescriptor(t,i):void 0})(e,t,i)}
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
- */function Me(e){return Ae({...e,state:!0,attribute:!1})}const Pe={"&":"&","<":"<",">":">",'"':""","'":"'"};function Le(e){return String(e).replace(/[&<>"']/g,e=>Pe[e]??e)}const Ne=g.power;function Te(e){return Ne.unit(e)}function De(e){return(e<0?"-":"")+Ne.format(e)}function He(e){return(Math.abs(e)/1e3).toFixed(1)}function Oe(e){return Math.ceil(e/2)}function Re(e){return e%2==0?1:0}function Ie(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return Oe(t)===Oe(n)?"row-span":Re(t)===Re(n)?"col-span":"row-span"}function je(e){const t=e.chart_metric??s;return g[t]??g[s]}function qe(e,t){const n=function(e){return je(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class Fe{constructor(){this._status=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._status;if(this._status&&n-this._lastFetch<3e4)return this._status;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const s=await e.callWS({type:"call_service",domain:r,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=s?.response??null,this._lastFetch=n}catch{this._status=null}finally{this._fetching=!1}return this._status}invalidate(){this._lastFetch=0}get status(){return this._status}clear(){this._status=null,this._lastFetch=0}}function We(e,t){return e?.circuits?e.circuits[t]??null:null}function Ue(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}function Be(e,t,n,s,o,a,r,d,h,p=!1){const u=t.entities?.power,g=u?a.states[u]:null,_=g&&parseFloat(g.state)||0,v=t.device_type===l||_<0,b=t.entities?.switch,y=b?a.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===c,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",C=Le(t.name||i("grid.unknown")),$=je(r);let E;if("current"===$.entityRole){const e=t.entities?.current,n=e?a.states[e]:null,i=n&&parseFloat(n.state)||0;E=`${$.format(i)}A`}else E=`${De(_)}${Te(_)}`;const z=h||"unknown";let k="";if("unknown"!==z){const e=f[z]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};k=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}const A=d&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(d),M=A?m:"#555",P=``;let L="";if(null!=d?.utilization_pct){const e=d.utilization_pct;L=`${Math.round(e)}%`}const N=function(e){return!!e&&null!=e.over_threshold_since}(d);return`\n
';for(const e of l){const i=c.get(e);if(!i)continue;const n=zt(i,t,s);d+=Ct(e);for(const[e,i]of n){const n=We(o,kt(i)),a=$t(i,t),r=this._expandedUuids.has(e);d+=xt(e,i,t,s,n,a,r),r&&(d+=St(e,i,t,s,n,a))}}d+="
';for(const e of l){const i=c.get(e);if(!i)continue;const n=kt(i,t,s);d+=Ct(e);for(const[e,i]of n){const n=We(o,At(i)),a=$t(i,t),r=this._expandedUuids.has(e);d+=xt(e,i,t,s,n,a,r),r&&(d+=St(e,i,t,s,n,a))}}d+="
`:"");try{"activity"===l?this._listCtrl.renderActivityView(e,this.hass,s,h,null,p):this._listCtrl.renderAreaView(e,this.hass,s,h,null,p),e.insertAdjacentHTML("afterbegin",``),await this._listDashCtrl.loadHistory(),this._listDashCtrl.updateDOM(e),this._listDashCtrl.startIntervals(e)}catch(t){const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=t.message,e.appendChild(n)}}async _renderFavoritesMonitoring(e,t,n){if(!this.hass)return;e.insertAdjacentHTML("beforeend",this._buildFavoritesSummaryHTML());const i=document.createElement("div");i.className="favorites-monitoring-stack",e.appendChild(i);const s=new Map;for(const e of n){const t=e.config_entries?.[0];t&&s.set(t,e)}const o=new Map;for(const e of t){const t=s.get(e),n=document.createElement("div");n.className="favorites-monitoring-block",n.style.marginBottom="24px";const a=document.createElement("h2");a.style.margin="8px 0 12px",a.style.fontSize="1em",a.textContent=t?.name_by_user??t?.name??e,n.appendChild(a);const r=document.createElement("div");n.appendChild(r),i.appendChild(n);const l=new Ot;o.set(e,l);try{await l.render(r,this.hass,e)}catch(t){console.warn("SPAN Panel: favorites monitoring render failed",e,t);const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=t.message??String(t),r.appendChild(n)}}this._favoritesMonitoringTabs=o}_applyPanelFavorites(){if(!this._selectedPanelId||this._isFavoritesView)return this._listDashCtrl.setPanelFavorites(null),void this._dashboardTab.setPanelFavorites(null);const e=this._favorites[this._selectedPanelId],t={panelDeviceId:this._selectedPanelId,circuitUuids:new Set(e?.circuits??[]),subDeviceIds:new Set(e?.sub_devices??[])};this._listDashCtrl.setPanelFavorites(t),this._dashboardTab.setPanelFavorites(t)}};nn.styles=((e,...t)=>{const n=1===e.length?e[0]:t.reduce((t,n,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+e[i+1],e[0]);return new x(n,e,y)})`
:host {
color: var(--primary-text-color);
}
@@ -185,4 +185,4 @@ const Ce={attribute:!0,type:String,converter:N,reflect:!1,hasChanged:L},Ee=(e=Ce
opacity: 1;
border-bottom-color: var(--app-header-text-color, white);
}
- `,v([ke({attribute:!1})],ti.prototype,"hass",void 0),v([ke({type:Boolean,reflect:!0})],ti.prototype,"narrow",void 0),v([ze()],ti.prototype,"_panels",void 0),v([ze()],ti.prototype,"_selectedPanelId",void 0),v([ze()],ti.prototype,"_activeTab",void 0),v([ze()],ti.prototype,"_discovered",void 0),v([ze()],ti.prototype,"_discoveryError",void 0),v([ze()],ti.prototype,"_chartMetric",void 0),v([ze()],ti.prototype,"_favorites",void 0),ti=v([(e=>(t,i)=>{void 0!==i?i.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)})("span-panel")],ti),console.warn("%c SPAN-PANEL %c v0.9.3 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;");let ii=!1;const ni=ti.prototype.connectedCallback;ti.prototype.connectedCallback=function(){ii=!0,ni.call(this)};const si=ti.prototype.disconnectedCallback;ti.prototype.disconnectedCallback=function(){ii=!1,si.call(this)},document.addEventListener("visibilitychange",()=>{"visible"===document.visibilityState&&(ii||window.location.pathname.includes("span-panel")&&setTimeout(()=>{ii||location.reload()},200))});
+ `,v([ke({attribute:!1})],nn.prototype,"hass",void 0),v([ke({type:Boolean,reflect:!0})],nn.prototype,"narrow",void 0),v([ze()],nn.prototype,"_panels",void 0),v([ze()],nn.prototype,"_selectedPanelId",void 0),v([ze()],nn.prototype,"_activeTab",void 0),v([ze()],nn.prototype,"_discovered",void 0),v([ze()],nn.prototype,"_discoveryError",void 0),v([ze()],nn.prototype,"_chartMetric",void 0),v([ze()],nn.prototype,"_favorites",void 0),nn=v([(e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)})("span-panel")],nn),console.warn("%c SPAN-PANEL %c v0.9.3 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;");let sn=!1;const on=nn.prototype.connectedCallback;nn.prototype.connectedCallback=function(){sn=!0,on.call(this)};const an=nn.prototype.disconnectedCallback;nn.prototype.disconnectedCallback=function(){sn=!1,an.call(this)},document.addEventListener("visibilitychange",()=>{"visible"===document.visibilityState&&(sn||window.location.pathname.includes("span-panel")&&setTimeout(()=>{sn||location.reload()},200))});
diff --git a/src/core/list-view-controller.ts b/src/core/list-view-controller.ts
index bc42fe1..db526bb 100644
--- a/src/core/list-view-controller.ts
+++ b/src/core/list-view-controller.ts
@@ -44,14 +44,88 @@ function getSheddingPriority(circuit: Circuit, hass: HomeAssistant): string {
return selectState ? selectState.state : "unknown";
}
+function compareCircuits(a: Circuit, b: Circuit, hass: HomeAssistant, config: CardConfig): number {
+ const infoA = getCircuitSortInfo(a, hass, config);
+ const infoB = getCircuitSortInfo(b, hass, config);
+ if (infoA.isOn && !infoB.isOn) return -1;
+ if (!infoA.isOn && infoB.isOn) return 1;
+ return infoB.value - infoA.value;
+}
+
function sortCircuitEntries(entries: [string, Circuit][], hass: HomeAssistant, config: CardConfig): [string, Circuit][] {
- return entries.sort((a, b) => {
- const infoA = getCircuitSortInfo(a[1], hass, config);
- const infoB = getCircuitSortInfo(b[1], hass, config);
- if (infoA.isOn && !infoB.isOn) return -1;
- if (!infoA.isOn && infoB.isOn) return 1;
- return infoB.value - infoA.value;
- });
+ return entries.sort((a, b) => compareCircuits(a[1], b[1], hass, config));
+}
+
+interface RowUnit {
+ row: HTMLElement;
+ expanded: HTMLElement | null;
+ uuid: string;
+ circuit: Circuit;
+}
+
+interface RowGroup {
+ anchor: HTMLElement | null;
+ units: RowUnit[];
+}
+
+// Partition .list-view's direct children into groups. Activity view produces a
+// single anchor-less group; area view produces one group per .area-header.
+function partitionRowGroups(listView: HTMLElement, topology: PanelTopology): RowGroup[] {
+ let current: RowGroup = { anchor: null, units: [] };
+ const groups: RowGroup[] = [current];
+ const children = [...listView.children] as HTMLElement[];
+
+ for (let i = 0; i < children.length; ) {
+ const el = children[i]!;
+ if (el.classList.contains("area-header")) {
+ current = { anchor: el, units: [] };
+ groups.push(current);
+ i++;
+ continue;
+ }
+ if (el.classList.contains("list-row")) {
+ const uuid = el.dataset.rowUuid;
+ const circuit = uuid ? topology.circuits[uuid] : undefined;
+ if (uuid && circuit) {
+ const next = children[i + 1];
+ const expanded = next && next.classList.contains("list-expanded-content") && next.dataset.expandedUuid === uuid ? next : null;
+ current.units.push({ row: el, expanded, uuid, circuit });
+ i += expanded ? 2 : 1;
+ continue;
+ }
+ }
+ i++;
+ }
+
+ return groups;
+}
+
+function reorderListRows(root: Element | ShadowRoot, hass: HomeAssistant, topology: PanelTopology, config: CardConfig): void {
+ const listView = root.querySelector(".list-view");
+ if (!listView) return;
+
+ for (const group of partitionRowGroups(listView, topology)) {
+ if (group.units.length < 2) continue;
+
+ const sorted = [...group.units].sort((a, b) => compareCircuits(a.circuit, b.circuit, hass, config));
+
+ const changed = sorted.some((unit, j) => unit.uuid !== group.units[j]!.uuid);
+ if (!changed) continue;
+
+ let after: Element | null = group.anchor;
+ for (const unit of sorted) {
+ if (after) {
+ after.after(unit.row);
+ } else {
+ listView.prepend(unit.row);
+ }
+ after = unit.row;
+ if (unit.expanded) {
+ after.after(unit.expanded);
+ after = unit.expanded;
+ }
+ }
+ }
}
function getCircuitEntityId(circuit: Circuit): string {
@@ -261,6 +335,8 @@ export class ListViewController {
// Toggle circuit-off class
row.classList.toggle("circuit-off", !isOn);
}
+
+ reorderListRows(root, hass, topology, config);
}
stop(): void {
From 458f2c4f0c5f86049150a677a8753277db6b52d6 Mon Sep 17 00:00:00 2001
From: cayossarian <23534755+cayossarian@users.noreply.github.com>
Date: Wed, 15 Apr 2026 14:00:34 -0700
Subject: [PATCH 07/97] docs: spec for compact expanded list rows
---
...04-15-favorites-expanded-compact-design.md | 186 ++++++++++++++++++
1 file changed, 186 insertions(+)
create mode 100644 docs/superpowers/specs/2026-04-15-favorites-expanded-compact-design.md
diff --git a/docs/superpowers/specs/2026-04-15-favorites-expanded-compact-design.md b/docs/superpowers/specs/2026-04-15-favorites-expanded-compact-design.md
new file mode 100644
index 0000000..8e290a0
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-15-favorites-expanded-compact-design.md
@@ -0,0 +1,186 @@
+# Favorites list view: compact expanded row
+
+Date: 2026-04-15 Repo: `span-card` (consumed by `span` HA integration)
+
+## Problem
+
+In the By-Area and By-Activity views, expanding a list row currently renders a full breaker "slot" above the chart: breaker badge, name, power/current value,
+toggle pill, shedding icon, utilization %, gear. Every value in that expanded header is already shown in the collapsed list row beside it. The duplication
+wastes vertical space that would be better used by the chart.
+
+The expanded view does contribute two things the collapsed row does not: a tappable on/off toggle, and a gear that opens the side panel for that circuit.
+
+## Goal
+
+Make the expanded list row show only the chart. Move the two unique controls into the collapsed row so they are always reachable without losing affordance.
+
+## Scope
+
+Applies to every invocation of `ListViewController` — both the Favorites full-page panel and the span-panel card dashboard's By-Area / By-Activity views.
+Breaker grid view is not affected.
+
+## Design
+
+### 1. List row (collapsed)
+
+New layout, left to right:
+
+```text
+[breaker badge] [name] [shedding] [util%] [ON/OFF badge*] [power] [gear] [chevron]
+```
+
+Changes versus today:
+
+- **Gear button** added between power and chevron. Uses the same `.gear-icon.circuit-gear` class and `data-uuid` attribute the breaker grid uses. Gear color
+ follows the existing "has custom overrides" rule (`MONITORING_COLORS.custom` vs `#555`). Clicks route to `DashboardController.onGearClick` via the existing
+ `.gear-icon` delegated handler — no new wiring.
+- **ON/OFF badge becomes tappable** only when `circuit.is_user_controllable !== false && circuit.entities?.switch`. When tappable the badge gains a
+ `list-status-toggle` class (adds `cursor: pointer`, hover state). When not tappable, the badge renders exactly as it does today — static.
+- Tappable badge clicks route to `DashboardController.onToggleClick`. Event delegation in `list-view-controller.ts` extends its toggle selector to
+ `.toggle-pill, .list-status-toggle`.
+
+The existing `list-status-on` / `list-status-off` classes continue to control badge color.
+
+OFF circuits continue to render an empty power cell.
+
+### 2. Expanded content (chart-only)
+
+New helper in `core/list-renderer.ts`:
+
+```ts
+export function buildExpandedChartHTML(
+ uuid: string,
+ circuit: Circuit,
+ hass: HomeAssistant,
+ config: CardConfig,
+ monitoringInfo: MonitoringPointInfo | null
+): string;
+```
+
+Produces:
+
+```html
+
+
+
+
+
+```
+
+Where `{state-classes}` is a space-joined string of any of `circuit-off`, `circuit-producer`, `circuit-alert`, `circuit-custom-monitoring` — produced by a new
+shared helper (see §3).
+
+`buildExpandedCircuitHTML` is deleted. All three call sites in `list-view-controller.ts` switch to `buildExpandedChartHTML`.
+
+Preserving the `.circuit-slot[data-uuid]` wrapper is intentional: `DashboardController.updateDOM` walks that selector to attach charts to their
+`.chart-container`. Zero changes required in the chart-attachment pipeline.
+
+The `circuit-chart-only` class is a CSS-only marker (tight padding, full-bleed chart). It does not gate any logic.
+
+### 3. Shared state-class helper
+
+Extract into `core/list-renderer.ts` (or a new `core/circuit-state.ts` — picked during implementation based on import cleanliness):
+
+```ts
+export function getCircuitStateClasses(circuit: Circuit, monitoringInfo: MonitoringPointInfo | null, isOn: boolean, isProducer: boolean): string;
+```
+
+Returns a class string containing any of: `circuit-off`, `circuit-producer`, `circuit-alert`, `circuit-custom-monitoring`.
+
+`grid-renderer.ts` `renderCircuitSlot` adopts this same helper so grid and list stay in sync. This is the only factoring change in `renderCircuitSlot`.
+
+### 4. Event handling in `ListViewController`
+
+`_bindEvents` changes:
+
+- Toggle selector widens: `target.closest(".toggle-pill, .list-status-toggle")`.
+- Gear selector unchanged (`.gear-icon`). The list-row gear uses the same class, so it is already covered.
+
+`updateCollapsedRows` additions:
+
+- When a circuit flips, it already updates badge text and `list-status-on`/`list-status-off`. Also ensure `list-status-toggle` is added/removed if
+ controllability changes (rare, but cheap).
+- Sort logic and expanded-row pairing in `partitionRowGroups` / `reorderListRows` unchanged.
+
+`_toggleExpand`, `_applyFilter`, favorites state persistence (`FAVORITES_VIEW_STATE_CHANGED_EVENT`, `_expandedUuids`, `_searchQuery`) all unchanged.
+
+### 5. CSS (`card/card-styles.ts`)
+
+Add:
+
+```css
+.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);
+}
+
+.list-status-badge.list-status-toggle {
+ cursor: pointer;
+ user-select: none;
+}
+.list-status-badge.list-status-toggle:hover {
+ filter: brightness(1.15);
+}
+
+.list-expanded-content {
+ padding: 0;
+}
+
+.circuit-slot.circuit-chart-only {
+ border: none;
+ padding: 8px 12px;
+ margin: 0;
+}
+```
+
+Chart height within `.circuit-chart-only .chart-container` must match today's expanded chart height. Exact value is pinned during implementation by reading the
+current rule; noted as a verification point.
+
+Remove the now-redundant `.list-expanded-content .circuit-slot { border: none; margin: 0; }` override.
+
+Existing `.circuit-alert` / `.circuit-custom-monitoring` rules continue to style `.circuit-slot` and therefore also apply to `.circuit-slot.circuit-chart-only`
+— alert/custom border signaling is preserved on the expanded chart.
+
+## Files touched
+
+- `src/core/list-renderer.ts` — add `buildExpandedChartHTML`, update `buildListRowHTML` (gear, tappable badge), extract `getCircuitStateClasses`, delete
+ `buildExpandedCircuitHTML`.
+- `src/core/grid-renderer.ts` — adopt `getCircuitStateClasses` in `renderCircuitSlot`.
+- `src/core/list-view-controller.ts` — swap renderer at three sites; widen toggle selector; badge-class upkeep in `updateCollapsedRows`.
+- `src/card/card-styles.ts` — gear rules, tappable badge rules, chart-only slot rules; remove redundant override.
+
+Not touched: `panel/span-panel.ts`, `card/span-panel-card.ts`, `core/dashboard-controller.ts`, `core/header-renderer.ts`, `core/favorites-*`,
+`core/area-resolver.ts`, `core/history-loader.ts`, `core/graph-settings.ts`, chart module, side panel.
+
+## Verification points for implementation
+
+1. Confirm `DashboardController.onToggleClick` resolves `data-uuid` from an ancestor that reaches `.list-row`. If today the lookup walks only to
+ `.circuit-slot`, widen to `.circuit-slot, .list-row`.
+2. Enumerate tests that assert presence of `.circuit-header` / `.circuit-status` inside expanded content; update them to the new chart-only structure.
+3. Measure current `.chart-container` height in an expanded list row and match it in the new rule so the change does not shrink the chart.
+
+## Manual test plan
+
+- Expand rows in By-Activity and By-Area, in both Favorites and the regular dashboard card.
+- Confirm header/status no longer duplicate list-row info.
+- Tap the ON/OFF badge on a controllable circuit: state flips, row re-sorts, badge color updates.
+- Tap the ON/OFF badge on a non-controllable (PV) circuit: nothing happens, no hover cursor.
+- Tap the list-row gear: side panel opens for that circuit.
+- Trigger a monitoring alert: expanded chart area shows the alert border.
+- Reload the Favorites page: expansion state restores; chart remounts; no layout jumps.
+- Breaker grid view: unchanged.
+
+## Out of scope
+
+- Adding alert/error visuals to the collapsed list row.
+- Restyling the side panel or breaker-grid view.
+- Any change to the tab bar, monitoring tabs, or settings.
+- Changes to `span` HA integration backend code.
From 4f891e581df06a5b8b55da5f216be0b86dab2667 Mon Sep 17 00:00:00 2001
From: cayossarian <23534755+cayossarian@users.noreply.github.com>
Date: Wed, 15 Apr 2026 14:08:05 -0700
Subject: [PATCH 08/97] docs: implementation plan for compact expanded list
rows
---
.../2026-04-15-favorites-expanded-compact.md | 865 ++++++++++++++++++
1 file changed, 865 insertions(+)
create mode 100644 docs/superpowers/plans/2026-04-15-favorites-expanded-compact.md
diff --git a/docs/superpowers/plans/2026-04-15-favorites-expanded-compact.md b/docs/superpowers/plans/2026-04-15-favorites-expanded-compact.md
new file mode 100644
index 0000000..f74be39
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-15-favorites-expanded-compact.md
@@ -0,0 +1,865 @@
+# Favorites list view — compact expanded row — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan
+> task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** In By-Activity and By-Area list views, expanding a row should reveal only the chart; the two unique controls (gear and ON/OFF toggle) move onto the
+always-visible list row.
+
+**Architecture:** A new `buildExpandedChartHTML` renderer replaces the duplicative `buildExpandedCircuitHTML`. The list row gains a gear button and a tappable
+ON/OFF badge. A small shared `getCircuitStateClasses` helper keeps state-visualization classes consistent between the grid and the new chart-only expanded slot.
+
+**Tech Stack:** TypeScript, Lit (lightly used here — this card uses manual innerHTML), Vitest, Rollup, CSS-in-JS via tagged template literals.
+
+**Spec:** `docs/superpowers/specs/2026-04-15-favorites-expanded-compact-design.md`
+
+---
+
+## File map
+
+**Create:**
+
+- `src/core/circuit-state.ts` — shared `getCircuitStateClasses` helper.
+- `tests/circuit-state.test.ts` — unit tests for the helper.
+
+**Modify:**
+
+- `src/core/grid-renderer.ts` — adopt `getCircuitStateClasses`.
+- `src/core/list-renderer.ts` — add `buildExpandedChartHTML`, update `buildListRowHTML`, delete `buildExpandedCircuitHTML`.
+- `src/core/list-view-controller.ts` — swap renderer calls, widen toggle selector, extend `updateCollapsedRows`.
+- `src/core/dashboard-controller.ts` — widen `onToggleClick` pill selector to include `list-status-toggle`.
+- `src/card/card-styles.ts` — add gear/badge/chart-only rules.
+
+**Test:**
+
+- `tests/circuit-state.test.ts` (new, above).
+- `tests/list-renderer.test.ts` (new — covers both renderers' new output shape).
+
+**Sync (post-build):**
+
+- `../../../HA/span/custom_components/span_panel/frontend/dist/` — via `scripts/build-frontend.sh` in the HA integration, or the `sync-frontend` skill.
+
+---
+
+## Task 1: Add shared `getCircuitStateClasses` helper
+
+Extracts the `circuit-off` / `circuit-producer` / `circuit-alert` / `circuit-custom-monitoring` class logic that `renderCircuitSlot` computes inline today. Grid
+and list will both call this so they stay in sync.
+
+**Files:**
+
+- Create: `src/core/circuit-state.ts`
+- Create: `tests/circuit-state.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+Create `tests/circuit-state.test.ts`:
+
+```ts
+import { describe, it, expect } from "vitest";
+import { getCircuitStateClasses } from "../src/core/circuit-state.js";
+import type { Circuit, MonitoringPointInfo } from "../src/types.js";
+
+const baseCircuit: Circuit = { name: "Test" };
+
+describe("getCircuitStateClasses", () => {
+ it("returns empty string when circuit is on, not producer, no monitoring info", () => {
+ expect(getCircuitStateClasses(baseCircuit, null, true, false)).toBe("");
+ });
+
+ it("adds circuit-off when isOn is false", () => {
+ expect(getCircuitStateClasses(baseCircuit, null, false, false)).toBe("circuit-off");
+ });
+
+ it("adds circuit-producer when isProducer is true", () => {
+ expect(getCircuitStateClasses(baseCircuit, null, true, true)).toBe("circuit-producer");
+ });
+
+ it("adds both when off and producer", () => {
+ const result = getCircuitStateClasses(baseCircuit, null, false, true);
+ expect(result).toContain("circuit-off");
+ expect(result).toContain("circuit-producer");
+ });
+
+ it("adds circuit-alert when monitoringInfo indicates alert", () => {
+ const info: MonitoringPointInfo = { utilization_pct: 95, over_threshold_since: "2024-01-01T00:00:00Z" };
+ const result = getCircuitStateClasses(baseCircuit, info, true, false);
+ expect(result).toContain("circuit-alert");
+ });
+
+ it("adds circuit-custom-monitoring when continuous_threshold_pct is set", () => {
+ const info: MonitoringPointInfo = { continuous_threshold_pct: 80 };
+ const result = getCircuitStateClasses(baseCircuit, info, true, false);
+ expect(result).toContain("circuit-custom-monitoring");
+ });
+
+ it("handles all classes together", () => {
+ const info: MonitoringPointInfo = {
+ utilization_pct: 99,
+ over_threshold_since: "2024-01-01T00:00:00Z",
+ continuous_threshold_pct: 80,
+ };
+ const result = getCircuitStateClasses(baseCircuit, info, false, true);
+ expect(result).toContain("circuit-off");
+ expect(result).toContain("circuit-producer");
+ expect(result).toContain("circuit-alert");
+ expect(result).toContain("circuit-custom-monitoring");
+ });
+});
+```
+
+- [ ] **Step 2: Run the test to verify it fails**
+
+Run: `cd /Users/bflood/projects/HA/cards/span-card && npx vitest run tests/circuit-state.test.ts`
+
+Expected: FAIL with "Cannot find module '../src/core/circuit-state.js'".
+
+- [ ] **Step 3: Implement the helper**
+
+Create `src/core/circuit-state.ts`:
+
+```ts
+import { hasCustomOverrides, isAlertActive } from "./monitoring-status.js";
+import type { Circuit, MonitoringPointInfo } from "../types.js";
+
+/**
+ * Build the set of state-visualization classes that apply to a circuit's
+ * rendered slot. Shared by the breaker grid and the list view's
+ * chart-only expanded slot so both render the same border/background
+ * signaling.
+ */
+export function getCircuitStateClasses(_circuit: Circuit, monitoringInfo: MonitoringPointInfo | null, isOn: boolean, isProducer: boolean): string {
+ const classes: string[] = [];
+ if (!isOn) classes.push("circuit-off");
+ if (isProducer) classes.push("circuit-producer");
+ if (isAlertActive(monitoringInfo)) classes.push("circuit-alert");
+ if (hasCustomOverrides(monitoringInfo)) classes.push("circuit-custom-monitoring");
+ return classes.join(" ");
+}
+```
+
+Note: `_circuit` is retained in the signature so future extensions (e.g. device-type specific classes) don't require a breaking change. The underscore prefix
+tells ESLint it's intentionally unused.
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd /Users/bflood/projects/HA/cards/span-card && npx vitest run tests/circuit-state.test.ts`
+
+Expected: 7 passing tests.
+
+- [ ] **Step 5: Typecheck**
+
+Run: `cd /Users/bflood/projects/HA/cards/span-card && npm run typecheck`
+
+Expected: no TypeScript errors.
+
+- [ ] **Step 6: Commit**
+
+```bash
+cd /Users/bflood/projects/HA/cards/span-card
+git add src/core/circuit-state.ts tests/circuit-state.test.ts
+git commit -m "feat(core): extract shared getCircuitStateClasses helper"
+```
+
+---
+
+## Task 2: Adopt `getCircuitStateClasses` in `renderCircuitSlot`
+
+Replace the inline class computation in `renderCircuitSlot` with a call to the shared helper, so the grid and list stay in lockstep.
+
+**Files:**
+
+- Modify: `src/core/grid-renderer.ts`
+
+- [ ] **Step 1: Read the current state-class logic**
+
+Open `src/core/grid-renderer.ts`. Lines 193-195 currently contain:
+
+```ts
+const alertActive = isAlertActive(monitoringInfo);
+const alertClass = alertActive ? "circuit-alert" : "";
+const customClass = hasOverridesFlag ? "circuit-custom-monitoring" : "";
+```
+
+And line 202 uses them:
+
+```ts
+
` line with:
+
+```ts
+const stateClasses = getCircuitStateClasses(circuit, monitoringInfo, isOn, isProducer);
+```
+
+(add this line after the existing `const customClass` removal, before the template returns). Then change the opening div of the return template from:
+
+```ts
+
{
+ it("produces a list-expanded-content wrapper with the circuit uuid", () => {
+ const html = buildExpandedChartHTML("uuid-123", onCircuit, mockHass, mockConfig, null);
+ expect(html).toContain('class="list-expanded-content"');
+ expect(html).toContain('data-expanded-uuid="uuid-123"');
+ });
+
+ it("wraps a circuit-slot that carries the chart-only marker class and data-uuid", () => {
+ const html = buildExpandedChartHTML("uuid-123", onCircuit, mockHass, mockConfig, null);
+ expect(html).toContain("circuit-slot");
+ expect(html).toContain("circuit-chart-only");
+ expect(html).toContain('data-uuid="uuid-123"');
+ });
+
+ it("contains exactly one chart-container div", () => {
+ const html = buildExpandedChartHTML("uuid-123", onCircuit, mockHass, mockConfig, null);
+ const matches = html.match(/class="chart-container"/g);
+ expect(matches?.length).toBe(1);
+ });
+
+ it("does NOT contain circuit-header or circuit-status duplicated content", () => {
+ const html = buildExpandedChartHTML("uuid-123", onCircuit, mockHass, mockConfig, null);
+ expect(html).not.toContain("circuit-header");
+ expect(html).not.toContain("circuit-status");
+ expect(html).not.toContain("toggle-pill");
+ expect(html).not.toContain("breaker-badge");
+ expect(html).not.toContain("power-value");
+ });
+
+ it("applies circuit-off class when circuit is off", () => {
+ const off: Circuit = { ...onCircuit, relay_state: "OPEN", entities: { switch: undefined, power: "sensor.circuit_a_power" } };
+ const html = buildExpandedChartHTML("uuid-123", off, mockHass, mockConfig, null);
+ expect(html).toContain("circuit-off");
+ });
+
+ it("omits circuit-off class when circuit is on", () => {
+ const html = buildExpandedChartHTML("uuid-123", onCircuit, mockHass, mockConfig, null);
+ expect(html).not.toContain("circuit-off");
+ });
+
+ it("escapes unsafe uuids", () => {
+ const html = buildExpandedChartHTML('">', onCircuit, mockHass, mockConfig, null);
+ expect(html).not.toContain("', onCircuit, mockHass, mockConfig, null);
+ expect(html).not.toContain("', onCircuit, mockHass, mockConfig, null);
- expect(html).not.toContain("', onCircuit, mockHass, mockConfig, null);
+ expect(html).not.toContain("', onCircuit, mockHass, mockConfig, null);
- expect(html).not.toContain("