@@ -581,6 +581,7 @@ const FRONTEND_LOCALES = {
581581 "hero.banner.night_charge" : "Night Smart Charge Active" ,
582582 "hero.banner.charging" : "EV Charging" ,
583583 "hero.forecast_tomorrow" : "Tomorrow Forecast" ,
584+ "hero.grid_lost" : "⚠ Grid lost" ,
584585 "toast.boost.target_reached" : "Boost can't start: EV at {ev}% (≥ target {target}%). Raise the target or wait for the battery to drain." ,
585586 "toast.boost.missing_soc" : "Boost can't start: EV SOC sensor unavailable. Check the mapped sensor." ,
586587 "toast.dismiss_aria" : "Dismiss notification" ,
@@ -729,6 +730,7 @@ const FRONTEND_LOCALES = {
729730 "hero.banner.night_charge" : "Night Smart Charge in corso" ,
730731 "hero.banner.charging" : "EV in ricarica" ,
731732 "hero.forecast_tomorrow" : "Forecast Domani" ,
733+ "hero.grid_lost" : "⚠ Rete assente" ,
732734 "toast.boost.target_reached" : "Boost non può partire: EV al {ev}% (≥ target {target}%). Alza il target o aspetta che la batteria scenda." ,
733735 "toast.boost.missing_soc" : "Boost non può partire: sensore SOC EV non disponibile. Controlla la mappatura." ,
734736 "toast.dismiss_aria" : "Chiudi notifica" ,
@@ -877,6 +879,7 @@ const FRONTEND_LOCALES = {
877879 "hero.banner.night_charge" : "Slim nachtelijk laden actief" ,
878880 "hero.banner.charging" : "EV aan het laden" ,
879881 "hero.forecast_tomorrow" : "Verwachting morgen" ,
882+ "hero.grid_lost" : "⚠ Net weg" ,
880883 "toast.boost.target_reached" : "Boost kan niet starten: EV op {ev}% (≥ doel {target}%). Verhoog het doel of wacht tot de batterij zakt." ,
881884 "toast.boost.missing_soc" : "Boost kan niet starten: EV SOC-sensor niet beschikbaar. Controleer de mapping." ,
882885 "toast.dismiss_aria" : "Melding sluiten" ,
@@ -1862,6 +1865,35 @@ class EvSmartChargerDashboard extends HTMLElement {
18621865 return `<span class="priority-pill state-${ modifier } ">${ label } </span>` ;
18631866 }
18641867
1868+ /**
1869+ * v2.6.0 (issue #36): tri-state read of the optional grid_available
1870+ * binary_sensor, mirroring the backend ChargingModel.is_grid_available.
1871+ * Returns true (present) / false (lost) / null (unmapped or unavailable/
1872+ * unknown → "don't know", never a false alarm).
1873+ */
1874+ _gridAvailable ( ) {
1875+ const entityId = this . _config ?. grid_available_entity ;
1876+ if ( ! entityId ) return null ;
1877+ const raw = this . _stateObj ( entityId ) ?. state ;
1878+ if ( raw === undefined || raw === null || raw === "unavailable" || raw === "unknown" ) {
1879+ return null ;
1880+ }
1881+ return [ "on" , "true" , "1" , "yes" , "home" , "present" ] . includes (
1882+ String ( raw ) . toLowerCase ( )
1883+ ) ;
1884+ }
1885+
1886+ /**
1887+ * v2.6.0 (issue #36): render a compact "Grid lost" warning pill next to the
1888+ * priority / forecast pills, shown ONLY when grid_available is explicitly
1889+ * off. Empty string otherwise (unmapped / present / unknown) — so existing
1890+ * dashboards and a boot-time `unavailable` never surface a false alarm.
1891+ */
1892+ _renderGridLostPill ( ) {
1893+ if ( this . _gridAvailable ( ) !== false ) return "" ;
1894+ return `<span class="grid-lost-pill" role="status" aria-live="polite">${ this . _t ( "hero.grid_lost" ) } </span>` ;
1895+ }
1896+
18651897 /**
18661898 * v1.11.8: Render the next-day PV forecast as an orange chip placed
18671899 * next to the priority pill. Returns an empty string when the
@@ -2594,6 +2626,7 @@ class EvSmartChargerDashboard extends HTMLElement {
25942626 <div class="hero-pill-row">
25952627 ${ this . _renderPriorityPill ( priorityState ) }
25962628 ${ this . _renderForecastPill ( ) }
2629+ ${ this . _renderGridLostPill ( ) }
25972630 </div>
25982631 <h1>${ this . _config . title || this . _t ( "title.default" ) } </h1>
25992632 <div class="evsc-metric-row">
@@ -2841,6 +2874,9 @@ class EvSmartChargerDashboard extends HTMLElement {
28412874 // left the banner stale in three-phase — Risk #5), and a power-only signal
28422875 // (no status entity mapped) still rebuilds.
28432876 const chargingOnForKey = this . _isDrawingNow ( ) ;
2877+ // v2.6.0 (issue #36): the grid-lost pill appears/disappears, so it is
2878+ // structural — include it so a transition triggers exactly one rebuild.
2879+ const gridLost = this . _gridAvailable ( ) === false ;
28442880
28452881 return JSON . stringify ( {
28462882 view : this . _view ,
@@ -2855,6 +2891,7 @@ class EvSmartChargerDashboard extends HTMLElement {
28552891 bo : boostOn ,
28562892 ni : nightOn ,
28572893 ch : chargingOnForKey ,
2894+ gl : gridLost ,
28582895 } ) ;
28592896 }
28602897
@@ -3972,6 +4009,27 @@ class EvSmartChargerDashboard extends HTMLElement {
39724009 box-shadow: 0 0 10px currentColor;
39734010 animation: evsc-pulse-slow 3.2s ease-in-out infinite;
39744011 }
4012+ /* v2.6.0 (issue #36): grid-lost warning pill — shown only when the
4013+ optional grid_available sensor reads explicitly off. */
4014+ .grid-lost-pill {
4015+ display: inline-flex; align-items: center; gap: 8px;
4016+ padding: 6px 12px;
4017+ border-radius: var(--evsc-radius-pill);
4018+ font-size: 0.85rem;
4019+ font-weight: 600;
4020+ color: #fff;
4021+ background: var(--evsc-sys-red);
4022+ box-shadow: 0 0 14px 1px var(--evsc-sys-red);
4023+ }
4024+ .grid-lost-pill::before {
4025+ content: ""; width: 7px; height: 7px; border-radius: 50%;
4026+ background: #fff;
4027+ box-shadow: 0 0 10px #fff;
4028+ animation: evsc-pulse-slow 1.8s ease-in-out infinite;
4029+ }
4030+ @media (prefers-reduced-motion: reduce) {
4031+ .grid-lost-pill::before { animation: none; }
4032+ }
39754033 @keyframes evsc-pulse-slow {
39764034 0%, 100% { opacity: 1; box-shadow: 0 0 10px currentColor; }
39774035 50% { opacity: 0.55; box-shadow: 0 0 16px currentColor; }
0 commit comments