* Update dependency zxing-wasm to v3.1.4
* Update type-fest version to 5.10.0
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
The persistent cache version still hashed `.yarn/patches`, which moved to
`patches/` in #54392. The lookup is guarded by `existsSync`, so it failed
silently and patch contents stopped contributing to the version at all.
That hash is the only thing that notices a patch change: a patch alters a
package's files but not its version, which is all rspack's node_modules
snapshot checks. Editing a patch therefore left the cache version
identical and the build reused stale compiled modules.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The landing page bundle includes the Intl polyfill. When a browser
needs a polyfill, it loads locale data from /static/locale-data/.
The landing page build never copied that directory, so these requests
returned 404. The language picker then showed raw language codes.
Fixeshome-assistant/landingpage#216
* Migrate from Yarn to pnpm
Switch the package manager to pnpm 11, keeping every resolved
dependency version from yarn.lock.
- Move resolutions to pnpm overrides and Yarn patches to patches/
- Enable supply-chain protection: 3 day minimum release age,
trust policy that blocks provenance downgrades, and dependency
build scripts denied by default
- Declare dependencies that were imported but only available through
Yarn's hoisting: zrender, @lezer/common, zxing-wasm,
@babel/helper-compilation-targets, core-js-compat, @types/geojson
- Look up pinned license overrides in pnpm's node_modules layout
- Update CI, scripts, git hooks, Renovate, and documentation
* Cache the pnpm store with node_modules in CI
Jobs that restore the shared node_modules cache skip the install, so
the pnpm store is empty there. The license file generation runs
`pnpm licenses list`, which reads package metadata from the store and
fails without it.
* Point the demo e2e comment at the real test:e2e:demo script
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Generate the map styles with @versatiles/style v6
v6 removed the colorful and eclipse builders; osm({ theme }) replaces
them, with eclipse mapping to the retuned colorful-dark palette. The
sprite sheet is renamed from basics to base and its icon ids change,
so core's tile proxy has to serve the base sheet before this ships.
v6 also defaults to the globe projection and the styles keep it. Only
the MapLibre engine renders them: from ha-map the Leaflet engine runs
without WebGL2 or raster-only after a MapLibre failure, so nothing
ties the map to Mercator any more.
* Bundle the VersaTiles base sprite sheet
The styles referenced the sprite sheet through core's proxy, which
fetched it from the OpenStreetMap Foundation. The OSMF only mirrors
the retired v5 basics sheet, and VersaTiles warns that the assets on
tiles.versatiles.org change with every release, so neither is a stable
source for the exact sheet the pinned @versatiles/style expects.
The base sheet now ships with the frontend under static/map/sprites,
fetched from the versatiles-style release matching the installed
package with `yarn gulp update-map-sprites`. The style build checks
that every icon the styles reference exists in the bundled sheet, so a
bump that renames icons fails the build instead of shipping POIs
without icons. Sprites resolve against the page rather than the
instance, since on Cast the frontend is not served by the instance,
and the demo no longer needs its own sprite override.
* Open a PR when the bundled map sprite sheet drifts
Runs the sprite update after a push to dev touches package.json, which
is how a @versatiles/style bump lands, and opens a PR with the
re-vendored sheet when it changed. Mirrors the device class and sensor
constant sync workflows.
* Exclude vendored map sprite metadata from Prettier
The sprite JSON is extracted verbatim from the @versatiles/style release
in its upstream compact form, which fails prettier --check. Ignoring it
keeps lint green and keeps the sync workflow's drift check byte-identical
with upstream.
* Format map-assets.js with Prettier
* Note that the sprite sheet ships with the frontend
The module header still said sprites come through core's proxy, which
stopped being true when the sheet was bundled.
* Gate merges on the bundled map sprite sheet
A @versatiles/style bump that references new icons used to fail only the
build job, which the dev ruleset does not require, so the bump could be
merged red and the post-merge sync workflow was left to repair it. Run
the sprite check in the required lint job instead, so the sheet has to be
re-vendored on the dependency PR itself, and drop the sync workflow that
existed to clean up after the merge.
* Only treat image properties as sprite references
The sprite check walked every string under layout and paint, so any
base-prefixed text value would have failed the required lint job without
asking for an image. Collect from the image-bearing properties only.
* Version the sprite URL with a hash of the vendored sheet
Core serves /static with a month of max-age and the service worker does
not precache the map assets, so a re-vendored sheet at the same URL would
keep coming from the browser cache next to a freshly fetched style that
expects the new icons. Append a short content hash of the four sheet
files as a query parameter. MapLibre appends the format and extension to
the pathname, so the query survives, and the test now guards that the
absolute-URL rewrite keeps it too.
* Run ha-map on a runtime-selected engine: MapLibre GL native or Leaflet
ha-map no longer drives Leaflet directly. All primitive operations -
camera, HTML element markers, meter-radius circles, history paths,
clustering with a pluggable icon builder, scale ruler, dark mode - go
through a MapEngine interface, and the engine is selected at runtime:
- MapLibreMapEngine renders the vector base map natively where WebGL2 is
available, without Leaflet in the loop: continuous fractional zoom,
DOM markers synced to the basemap every frame, GeoJSON zone and
accuracy circles below the labels, GeoJSON history trails with hover
popups, and a pixel-distance cluster grid recomputed when the camera
settles. Dark mode swaps the style while carrying the custom layers
over, and rotation and pitch stay disabled for north-up dashboards.
- LeafletMapEngine wraps the existing behavior unchanged (vector tiles
through the maplibre adapter with the raster fallback, markercluster)
for browsers without WebGL2 - the legacy floor includes iOS 12-14 and
kiosk browsers without hardware acceleration - and for
ha-locations-editor, which manages raw Leaflet layers with
leaflet-draw and declares engine=leaflet.
A permanently lost WebGL context rebuilds the map on the Leaflet engine
instead of leaving a dead canvas. Zoom levels keep Leaflet semantics
across engines. MapLibre's stylesheet is shipped to /static/map for the
native engine's controls and popups.
* Enhance keyboard accessibility for icon click
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix dark mode style application logic
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Address code review on the map engine
A fatal engine event during setup was dropped because the in-flight
setup owned the loading flag; the fallback request is now recorded and
setup switches to Leaflet once init settles. Entity markers accept Enter
and Space, as both engines make them focusable buttons. History points
show their popup on tap as well as hover, since touch has no hover.
Tests cover the engine choice and fallback paths, including a fatal
event mid-setup, and the MapLibre engine itself against a fake map:
style swaps, queued layer work, custom layer carry-over, failed style
requests, token refusal recovery, context-loss grace, and clustering.
* Address second code review round on the map engine
A cluster whose members share a spot, or that sits at maximum zoom,
could never be opened: fitting its bounds changed nothing and the next
regroup recreated it. Activating such a cluster now opens it: all its
members are shown in a bubble with a tail pointing at their spot, each
reachable on its own, until the map moves again.
An engine whose init failed, or was abandoned by a disconnect or a fatal
event during setup, was never destroyed and could keep a WebGL context;
setup now destroys any engine that did not become the active one.
The Leaflet engine sizes the cluster element like MapLibre does, so
cluster bubbles no longer shrink to their text.
* Keep the engine's own record of its map sources and layers
Carrying zone circles and history paths over a style swap relied on
MapLibre serializing the outgoing style into transformStyle's previous
argument. The engine now keeps the source and layer specifications it
added and rebuilds them into the new style itself, so the carry-over no
longer depends on what MapLibre hands over, and a missing previous style
cannot drop them.
* Address review: setup teardown, marker input, cluster focus
An engine still setting up could not be torn down: ha-map only held it
once init resolved, so removing the element mid-load kept its WebGL
context alive and left the loading guard set, ignoring a reconnect. The
engine being set up is now tracked and destroyed on disconnect, a stale
setup notices it was superseded, and the MapLibre engine settles a
pending init when destroyed.
Non-interactive markers let pointer input through, as they do on
Leaflet. Opening a cluster from the keyboard moves focus to its first
member, and a member that has focus when the bubble closes hands it to
the icon that replaces it.
* Tear down an engine that fails while still setting up
A fatal event during setup only flagged the fallback and waited for
init to settle, which relied on MapLibre still firing style.load. The
engine being set up is destroyed instead, which settles its init, so the
setup hands over to Leaflet without depending on the failed engine.
* Make history point popups readable on MapLibre
MapLibre's stylesheet is linked into the map's shadow root and wins over
the component styles on equal specificity, property by property: its
white popup background applied while our white text color did too,
leaving the timestamp invisible. The popup rules now carry !important,
as the Leaflet tooltip rules already do for the same reason.
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Remove web components polyfills and ES5 custom elements adapter
All supported browsers (legacy floor: Chrome 59, Safari/iOS 12, Firefox 94)
have native shadow DOM and custom elements, so the webcomponents bundle,
ShadyCSS branch, and lit polyfill-support are unreachable. The legacy build
now emits ES2017 classes, so custom-elements-es5-adapter and the
window.loadES5Adapter hook (no known third-party consumers) are removed.
Also stops shipping the never-loaded dialog-polyfill css and drops both
now-unused dependencies.
* Remove keyed-es5 Terser workaround
The custom keyed directive existed because Terser with ecma: 5 miscompiled
the destructured update() parameters (#28732). The legacy build now minifies
with ecma: 2017, so the stock lit-html keyed directive works in both builds.
* Remove old-browser JS shims and stale ES5 build references
Drops the IE-only navigator.msMaxTouchPoints check, replaces the
toggleAttribute helper with the native method (polyfilled automatically for
Chrome < 69 in the legacy build), and removes babel excludes for the
uninstalled proxy-polyfill and unfetch packages. Updates comments that
still described the legacy build as ES5.
* Remove vendor prefixes for no-longer-supported browsers
Deletes -ms- prefixes (IE/EdgeHTML only) and -webkit-/-moz- prefixed
declarations that every supported browser understands unprefixed, or that
Lightning CSS re-adds automatically from the unprefixed property in
production builds. Blocks that only had prefixed user-select now use the
standard property (previously Firefox got no user-select there at all).
Converts the four -webkit-linear-gradient() declarations - the sole
gradient syntax on those sliders - to standard linear-gradient().
* Remove dead html_url custom panel support
html_url pointed to an HTML Import, a Polymer-era feature removed from
Chrome in 2019 and never shipped elsewhere. The loader has not handled the
html type for years (it fell through to a rejection), and core's
panel_custom integration no longer accepts the option, so the branch was
unreachable. Also drops the ha-panel-${name} legacy tag naming that was
keyed on html_url.
* Repair list-plugins-and-polyfills script for Babel 8
The audit script died at startup since the Babel 8 update: preset-env no
longer exposes lib/debug.js (logPlugin is now inlined locally, built on the
public getInclusionReasons helper) and babel-plugin-polyfill-corejs3 v1 no
longer ships lib/shipped-proposals.js (list inlined).
Instead of invoking the preset with a hand-mocked plugin API - the part
that kept drifting - the plugin listing now runs a real transform of an
empty file with preset-env in debug mode, declaring the same caller
capabilities as babel-loader. The polyfill listing now passes the
configured core-js version to core-js-compat, mirroring the provider's own
filtering so the report cannot list modules the installed core-js lacks.
Output is byte-identical to the direct-invocation approach. Also documents
the script in the build-scripts README.
* Correct macOS floor for Safari 26 in companion app UA regex
Safari 26 ships for macOS 14 Sonoma and 15 Sequoia, not only macOS 26, so
the SAFARI_TO_MACOS entry breaking the minimum-supported-macOS pattern
would have sent updated macOS 14/15 companion apps to the legacy build
once the modern floor reaches Safari 26.
* Remove orphaned values left behind by vendor prefix removal
* Require macOS 14.6 for Safari 26 in companion app UA regex
* Load MapLibre RTL text plugin so Hebrew and Arabic map labels read correctly
The vector base map never registered MapLibre's RTL text plugin, so
right-to-left scripts were shaped left to right and every label came out
reversed. Ship @mapbox/mapbox-gl-rtl-text from /static/map/ alongside the
glyphs (no third-party host) and register it once, lazily, before the
first vector layer is created.
Fixes#53851
* Add the English name to map labels in non-Latin scripts
Before the move to vector tiles the CARTO basemap showed English names
everywhere. The OSM style shows local names, which most users cannot
read in Cyrillic, Arabic, Hebrew or CJK regions. Keep the local name and
add the English one from the tiles under it - in parentheses for street
labels, which cannot break lines. Latin-script names are left alone, so
Köln stays Köln.
Core now proxies both vector and raster tiles, which is what lets them be
requested with an application User-Agent and without a referrer - a
browser can set neither. So the frontend stops talking to OpenStreetMap
and CARTO directly and goes through /api/map_tiles.
The proxy is token gated, so `ha-map` fetches one over the WebSocket
before setting up, following the brands token pattern: cached at module
level, refreshed well inside its lifetime so a dashboard left open for
days keeps working. The blocking wait is kept to about a second - a
backend without the proxy must not hold the map hostage - and the
remaining retries run in the background to ride through the window after
a restart where the WebSocket is up but the handler is not registered
yet.
Two things that are not obvious and cost a measurement each:
MapLibre's `transformRequest` has to return absolute URLs. Tiles are
fetched from a worker, which has no document to resolve a relative URL
against, and the TileJSON that core serves has relative `tiles`. Measured
with a relative TileJSON on one origin: with absolute URLs the style
loads and 8,456 features render; without them exactly one request is made
- the style - and nothing else loads, with no error reported anywhere.
Leaflet bakes its URL template at layer creation and throws while
building a tile URL if a template variable is undefined. So the raster
layer takes the token as an option Leaflet substitutes per request, which
also means a refreshed token is picked up without recreating the layer,
and an absent one is empty rather than missing: the tiles 403 and the
markers still draw.
The asset pipeline shrinks to generating two styles. Glyphs and sprites
come from the proxy, so the 48 MB build-time download, the digest
verification, the glyph range filtering and the bold-range guard are all
gone, along with 5.4 MB from the wheel. `localIdeographFontFamily` goes
too: the complete glyph set is reachable now, so CJK renders in Noto
rather than a device font.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gallery build is the only e2e build that still emits source maps: unlike
demo and the e2e test app, `bundle.config.gallery` never accepted
`isTestBuild`, so the production build always used `nosources-source-map` —
even though the artifact is only ever loaded by Playwright.
The e2e run's gallery artifact contains 693 `.map` files (14 MB of a 105 MB
dist); the demo artifact, which already passes `is-test`, contains none.
Thread `isTestBuild` through the gallery config and set `is-test: true` for the
e2e gallery build, so those maps are no longer generated. The design
deployment and preview workflows do not set `IS_TEST`, so they keep their
source maps.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Compress with zopfli in a worker pool
compress-app gzips every build artifact with zopfli, but @gfx/zopfli is
synchronous WASM: it ran on the main thread, pinned a single core and blocked
the event loop for the whole step, so it dominated the production build.
Replace gulp-zopfli-green with an equivalent gulp transform that runs the same
compressor in a pool of worker_threads sized to availableParallelism(). The
compressed output is byte-identical, and @gfx/zopfli is no longer loaded on the
main thread of every gulp invocation.
On a 12-core machine compress-app drops from 6.98 min to 1.27 min, and the full
production build from 8.87 min to 3.32 min.
* Recover from a stale index.html at boot
A cached, stale index.html imports the previous build's content-hashed
entry bundles (core.<hash>.js / app.<hash>.js). After an upgrade those
files 404, app.js never runs, <home-assistant> is never defined, and the
launch screen never clears. No bundled JS can recover this, because the
bundle itself failed to load.
Add a tiny, prod-only inline guard in index.html that catches the failed
entry load (capture-phase resource error + unhandledrejection) and does a
single, loop-guarded cache-busting reload, dropping the service worker and
caches on https first. core.ts strips the cache-bust param after a
successful boot.
Part of home-assistant/epics#113.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Share stale-build recovery patterns via a single JSON source
Move the boot guard's chunk-detection regexes into
stale-build-patterns.json and inject them into the inline guard at build
time (entry-html.js), so they stay in sync with the bundled recovery util
(util/recover-stale-build.ts, in the follow-up post-boot recovery) which
reads the same file — no more manually kept-in-sync copies.
Part of home-assistant/epics#113.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Shared build and runner for all build,dev,test flows
* Harden managed process lifecycle
* Make build workflows deterministic
* Test build management contracts
* Queue shared generated output work
* Isolate generated inputs for dev servers
* Preserve dev server child failures
* Make generated lock test deterministic
* Keep test navigator configurable
* Block concurrent frontend workflows
* Simplify workflow lock ownership
* Focus workflow locking on managed commands
* Trim workflow lock unit tests
* Consolidate dev server lifecycle handlers
* Add managed modern production builds
* Harden managed build process controls
* Update managed process testing guidance
* Harden managed process file operations
* Share generated output process ownership
* Clean stale build state on status
* Address managed build review feedback
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
* Use single compress task
* 1
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
* Forward SIGHUP to foreground processes
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
* Fall back to English when nightly translations cannot be fetched
Local builds no longer fail when the nightly translations fetch errors
(offline or invalid GitHub credentials); they warn and continue with
English only, like the existing no-token path. CI still fails so
releases cannot silently ship without translations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3oj5uaCRRaoXRd5iEffhi
* Record fetched artifact only after successful extraction
A failure mid-fetch previously left a fresh artifact.json next to wiped
translations, so builds within the next 24 hours skipped the fetch and
silently built English only. Writing the artifact file last means a
failed fetch is retried by the next build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3oj5uaCRRaoXRd5iEffhi
* Update build-scripts/gulp/fetch-nightly-translations.js
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
* Dont open demo
* Scripts to run script/develop*
* Scripts for dev demo and gallery
* Background wrapper for agents with `--background`
* Background `yarn dev` and `dev:serve` for agents
* Dedupe lifecycle functions
* Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Update babel monorepo
* Migrate Core-JS polyfilling for Babel 8
Babel 8.0.1 removed preset-env's `useBuiltIns`/`corejs` options. Replace
them with the babel-plugin-polyfill-corejs3 provider directly
(`usage-global`), and pin transform-runtime's `moduleName` to
`@babel/runtime` so the provider doesn't redirect helpers to the
uninstalled `@babel/runtime-corejs3`.
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
* Update babel monorepo to v8
* Bugfixes option has been removed and is enabled by default
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>