Fixed
- The overview card presented a disconnected browser as stale inverter data (C-020). If the Home Assistant frontend loses its WebSocket — tab suspended, laptop asleep, network blip — the page's copy of
last_updatefreezes while the card keeps computing its age asDate.now() - last_update, so the displayed age grows without bound. A user reported "API - last updated 45 minutes ago" with solar reading zero; the integration had in fact polled successfully every 5 minutes throughout (every pollsuccess: True, server-sideage_secondsnever above 300 s across 22 consecutive intervals). The card now checkshass.connectedand distinguishes the two causes, because they need opposite responses: a disconnected frontend means check the browser or network, whereas genuinely old data means check the inverter or the cloud API. When either applies the readings are dimmed and desaturated behind a full-strength amber banner naming the cause and the age, so a stale card cannot be mistaken for a live one at a glance. Styled entirely from theme variables, so it reads correctly in light and dark themes, and translated into all nine languages the card ships. - The stale badge cried wolf, which is why the real signal was missed. Its threshold was a flat 30 s against a 300 s REST poll interval, so a perfectly healthy install rendered the stale badge for roughly 90% of every interval — "API - 2m" almost always, making the genuinely broken "API - 45m" indistinguishable from normal. Thresholds are now anchored to each source's actual cadence: 60 s for the WebSocket (which pushes every ~5 s) and 900 s for REST polling (three missed polls). The badge shows a bare
APIwhen healthy and only appends an age when something is actually wrong. Regression classtests/test_overview_card_stale.py(13 tests driving the shipped card in a real Chromium DOM, asserting computed opacity and filter rather than just class names); the E2E test that previously asserted the 30 s behaviour now guards the inverse.
Changed
-
Test infrastructure: the
Flaky Test Detectionworkflow's chronic panel-load failures are fixed (C-031) — one of the workflow's two independent causes of redness; see the job-timeout entry below for the other. No user-visible change — the integration is untouched; only the E2E harness changed. The workflow had never passed sincev1.0.21-beta.2(June 2026): on roughly 1 in 100 dashboard loads a test errored at setup withE2EConditionTimeout: lovelace-stage:ha-panel-lovelace: pass_check not satisfied within 74991ms, always at setup, never as an assertion failure, on random tests and in both connection modes. The captured screenshot showed the reason the message did not: a fully-booted Home Assistant with "Error while loading page lovelace." in the content area. Home Assistant loads its Lovelace panel as aPromise.allover 51 separately-fetched JavaScript chunks; if any one of those requests is lost — a starved GitHub runner drops one occasionally —hass-router-pagecatches the rejection, replaces the panel with<hass-error-screen>and never re-issues the import. The document is dead from that moment, so the harness's 75-second wait could never succeed; it was waiting for a page that had already failed, and then reporting a timeout, which sent three months of investigation towards slow-boot theories. The harness now (a) recogniseshass-error-screenin its fail-fast check — previously it knew onlyha-panel-error/hui-error-card, neither of which HA uses here — so the failure surfaces in about a second instead of 75, and (b) recovers by re-navigating, bounded to three navigations, because a rejected dynamic import is only ever retried by a new document; a plain timeout still fails on the first attempt, so this is recovery from an identified terminal state rather than a blind retry. Reproduced deterministically by fault injection against a real container (tests/e2e/test_ui.py::TestPanelLoadFailureRecoveryaborts exactly the chunk that definesha-panel-lovelace), which produced a pixel-identical failure capture and the verbatim CI error before the fix, and passes after it — setup time for that test fell from 87.3s to 14.0s. The trigger itself is environmental and does not occur locally: 820 consecutive local dashboard loads (500 unconstrained, 320 pinned to four cores, both at high xdist parallelism) produced zero failures. -
Test infrastructure: E2E failure captures now say why the browser failed (C-020's spirit applied to the harness). Each page records console errors, uncaught page errors and failed network requests; on a wait failure the tail goes into the pytest message and the whole buffer is written next to the existing HTML and PNG as a
.logartefact. The DOM summary is also now a recursive shadow-DOM search that reports the error element and its message — previously it used a flatdocument.querySelectorfor tags Home Assistant nests several shadow roots deep, which is why every capture said onlyelements present: ['home-assistant', 'home-assistant-main']. Had either of these existed in June, the failure above would have named the lost asset and the net error on day one. Regression classtests/test_e2e_lovelace_panel_load_error.py(11 tests, three of which drive a real Chromium shadow DOM). -
Test infrastructure: a third of the
Flaky Test Detectionmatrix was cancelled on every run, independently of test health, and no longer is. The workflow gave each of its 20 jobs "a random half of the tests", but selected at file granularity — and there are only three E2E files (76 + 90 + 8 tests), so one of the three equally-likely pairs draws the two heaviest, 166 of 174 tests. That overruns the job's 30-minute limit and is cancelled: 6 of 20 jobs on the last run, 10 on 2026-06-19. Selection is now per test, so every job runs 87 tests and finishes in the same time (~13 min against a 30-minute limit), while keeping the randomised test co-location the workflow exists to probe. The timeout was deliberately not raised — the jobs were not legitimately slow, they were unevenly sized.matrix.runalso moved from${{ }}interpolation insiderun:intoenv:, following GitHub's workflow-injection guidance; it was never untrusted input, but the pattern is worth not modelling badly.