Files
85c7d071fe Recover from a stale build after boot (#53582)
* Recover from a stale build after boot (lazy-chunk 404s)

When the app stays open across a Home Assistant upgrade, the previous
build's content-hashed lazy chunks are deleted, so opening a dialog,
more-info, card, or panel that was not yet loaded 404s. Today that
dead-ends: dialogs fail silently, panels show a Back-only error screen.

Add a shared recovery authority (recover-stale-build.ts): detect a stale
hashed-chunk load failure and either reload onto the current build (drop
the service worker + caches, cache-busting nav, one-shot cooldown guard)
or, when an editor has unsaved changes, show a non-dismissable toast that
reloads once the dirty state clears. Hook it into the global
error/unhandledrejection handlers, the router's swallowed load error, and
give hass-error-screen a reload action. Chunk-error patterns come from a
JSON single source shared with the boot guard.

Part of home-assistant/epics#113.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Wire stale-build recovery into more paths, add tests

- make-dialog-manager: don't cache a rejected dialog import, so a stale
  chunk 404 (or transient failure) no longer permanently breaks that
  dialog until a full page reload — a later open re-imports.
- home-assistant: _checkUpdate now uses reloadFresh() instead of the
  no-longer-effective location.reload(true) (forceGet is ignored by
  modern browsers).
- connection-mixin: drop the dead reload(true) forceGet arg on the
  safe_mode reload.
- Add unit tests for isStaleBuildError and the recoverFromStaleBuild
  clean / dirty / dev-demo / non-stale / loop-guard branches.

Part of home-assistant/epics#113.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Recover in the companion app via the reload_and_clear_cache command

The iOS/Android companion app (WKWebView) has no service worker, and its
document HTTP cache is not cleared by the Cache API, so the web-level
reload path is ineffective there. When an external bus is present,
reloadFresh() now fires the native `frontend/reload_and_clear_cache`
command (shipped in home-assistant/iOS#5190) so the app purges its cache
and reloads; browsers still take the web path.

Adds the outgoing message type and extracts the bus transport into an
exported fireExternalBusMessage() so it can be sent without a hass/bus
reference.

Closes #53405. Part of home-assistant/epics#113.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Address Copilot review feedback

- reloadFresh: only send frontend/reload_and_clear_cache to the WebKit
  (iOS) bridge; Android and browsers take the service-worker + cache-clear
  path (Android's WebView has a service worker). Fail closed when the
  sessionStorage cooldown marker can't be persisted so it can't loop. Return
  whether a reload was actually started, so a guard-blocked failure is still
  surfaced/logged instead of silently swallowed.
- Add a dirty-aware reloadForUpdate() and route _checkUpdate and the error
  screen's Refresh button through it; drop the dirty toast's immediate
  action (it auto-reloads once changes are saved/discarded).
- Set showReload on the error-screen element at the call site so router
  overrides (e.g. ToolsRouter) can't drop it.
- Tests: exercise the WebKit bridge and the SW/cache-clear branch; assert
  the dirty toast has no reload action.

Part of home-assistant/epics#113.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 13:37:44 +02:00
..