* Add an ESPHome device setup checklist on the device page.
Surface advertised proxy capabilities in a banner and dialog so users can continue Bluetooth, audio, Z-Wave, and serial setup after the device is added.
* Harden the ESPHome setup wizard after review feedback.
Show cached capabilities while offline, wait for user data before the banner, and expose setup status to assistive tech.
* Mark ESPHome serial setup completed when a UART port is in use.
* Mark ESPHome Z-Wave setup completed when zwave_js matches the proxy home ID.
* Align the ESPHome setup wizard with Sendspin audio and the existing add-integration dialog.
Music stays incomplete until Music Assistant is installed and Sendspin is on, and serial setup no longer offers a hardcoded integration list.
* Fix storage chart size labels after bidirectional isolate
bidiIsolate can return undefined, so the gigabyte formatter needs a string fallback for the type check.
* Show the ESPHome setup banner only for music, Z-Wave, or serial devices, and use the finished headline once nothing remains to set up.
* Let the ESPHome music row finish once Music Assistant is installed.
The Sendspin switch is optional and matched by object id, a finished checklist uses the reminder, and the storage chart label matches dev after the bidiIsolate change.
* Give ESPHome music setup its own sequence so Sendspin is enabled after the Music Assistant offer.
* Show ESPHome audio install progress after the offer buttons hide, and drop the guest PIN step until that switch exists.
* Keep ESPHome setup on the device's own Z-Wave and music steps when discovery already started or Music Assistant is cancelled.
A detected controller was treated as not started, cancelling the Music Assistant dialog still enabled Sendspin, and a missing discovery flow opened a generic Z-Wave flow.
* Keep ESPHome setup honest when USB usage or Z-Wave flow lookup fails.
A failed USB scan was shown as unused ports, overlapping capability requests could overwrite a newer result, and a rejected Z-Wave flow lookup never showed the retry error.
* Use the repo consume decorator in the ESPHome setup UI.
The access token rotates every half hour and lives in the query string, so it
is part of the browser's cache key: every rotation orphans every cached tile,
and the week-long max-age core sends is worth nothing. The service worker works
around it by stripping the token from its own cache key, but a service worker
only runs on https or localhost - on a plain http LAN origin, which is how a
lot of instances and the companion apps are reached, there is no cache at all
between the map and the network.
Anything that can set a header sends the token there instead, which leaves the
URL, and with it the cache key, stable across rotations. Raster tiles come from
an <img> and stay on the query parameter, and so does Cast: cross-origin a
custom header would cost a CORS preflight per tile.
Needs the matching core change; against a core without it the proxy refuses
every tile.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Compare the backup, onboarding and cloud data modules with the
handlers that produce them, and fix the mismatches.
Several backup calls declared the wrong result. Deleting a backup
returns agent errors, generating one returns a job id rather than a
backup id, and restoring one returns nothing. The backup list carries
extended content, not the base type.
Add the blocked manager state, which is the state the manager starts
in and replays to new subscribers, along with the event reason fields
and the core_restart restore state.
Correct the onboarding installation type, which listed a value the
backend never sends and omitted one it does, and narrow the cloud
onboarding status to the single field that endpoint returns.
Remove the cloud strict connection preference and its translations.
No such preference exists in core, so the warning it gated could never
render.
Claude-Session: https://claude.ai/code/session_01YDqKNfAAvMV7zJJNodsHfK
Co-authored-by: Claude <noreply@anthropic.com>
Compare the entity domain and helper data modules with the domains
that produce the attributes, and fix the mismatches.
Add supported feature flags the domains define and the frontend did
not list, and add attributes the domains report. Remove attributes
that are service fields rather than state, and the light colour
temperature keys core replaced with the kelvin forms.
Correct the weather forecast, where the event carries the whole list
and the wind speed is a number, and correct the image update command,
which sent the wrong id.
Claude-Session: https://claude.ai/code/session_01YDqKNfAAvMV7zJJNodsHfK
Co-authored-by: Claude <noreply@anthropic.com>
* Describe trace graph nodes for screen readers
The trace graph is drawn entirely in SVG. Its nodes and branches are
keyboard reachable, but carried no accessible name and no role, so a
screen reader announced nothing while tabbing through a run. Everything
the graph says about the outcome of a step — the error badge, the
strike-through, the dashed outline, the repeat count — was visual only.
Each focusable stop now carries the same step description the automation
editor shows, followed by what the run did with that step. The ARIA is
written at the call sites in hat-script-graph, which is the only place
that knows whether an element is a step or just layout: the branch heads,
the then/else arrows, the failed-condition cross and the spacers are
marked aria-hidden, while the branch that merely groups the triggers is
left alone so the triggers inside it stay in the accessibility tree.
The names themselves are built by buildTraceLabels, a pure function that
walks the trace tree and returns one string per node path, reusing the
same describers the editor uses. The trace panels call it because they
already own hass, and memoize it on the trace, so the graph takes a plain
record and stays a leaf: it subscribes to nothing, and entity state
changes no longer rerender it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Process code review comments
Fix the memoization of the trace labels: `hass` is replaced on every
state update, so comparing it rebuilt every label on every state event,
which was worse than the rerendering the labels were hoisted out of the
graph to avoid. Compare everything but `hass`, and let an absent entity
registry stay undefined rather than allocating a new array each render.
Report whether a condition passed or failed instead of only that it ran.
That outcome is otherwise shown by the tracked path and by a cross on a
node that is aria-hidden, so it never reached a screen reader.
Drop `role="button"` from the nodes. Selection follows focus and nothing
handles clicks or key presses, so the role promised an activation that
does not exist. The branches keep `role="group"`, which makes no such
promise.
Pass the integration manifests through from both trace panels, so the
names match the ones the automation editor shows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Process code review comments
Give the leaf nodes `role="img"`. Dropping `role="button"` left them with
the implicit generic role, on which ARIA prohibits an author-provided
name, so the labels risked being ignored and the nodes going back to
being unnamed focus stops. A node draws one step and slots only
decoration, so `img` names it without claiming it is a control. The
branches keep `role="group"`, since `img` would make their focusable
children presentational. `aria-disabled` goes with the button role: it is
unsupported on `img`, and the state is already part of the name.
Assert the nested steps in the label coverage test. It only checked the
building blocks and the choose options, so a regression in the recursion
through branch children would have gone unnoticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Render real trigger/condition/action icons and unfinished branch state in trace graph
* Process review: repeat body tracking, service and action icons
* Infer empty repeat body entry from the repeat config
* Track repeat bodies only by their traced steps
* Communicate entity control to external bus
* Prefer target entity ids and report disconnecting calls
* Split and lowercase entity ids like Core does
* Keep entity control reporting best effort
* Assert wildcard entity ids are dropped
* Resolve statistic labels with structured entity names
getStatisticLabel resolved names through computeStateName, so every
statistic-shaped label — the energy dashboard cards, the sankey cards,
hui-statistic-card, chart series names and the energy configuration
dialogs — showed the flat friendly_name attribute while the rest of the
UI composed names from the device, area and floor.
It now asks hass.formatEntityName for the default composition, which
also makes the entity branch of computeEnergyLabel redundant.
The metadata fallback stays: external statistics have no entity to
resolve a name against.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Keep the entity name shape out of the core chunk
recorder.ts sits in the core entry chunk, so importing DEFAULT_ENTITY_NAME
from compute_entity_name_display dragged the registry-aware formatters and
their dependencies in with it and blew the core bundle budget by 4.1%.
The constant and the name types have no runtime dependencies, so they move
to their own module; compute_entity_name_display re-exports them so nothing
else has to change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Add timer data helpers for formatter-based display and duration serialization
Refactor computeDisplayTimer to take formatEntityState instead of hass so
context-migrated components can reuse it, add finishes_at to TimerEntity,
and add durationDataToTimerString for serializing duration input values.
* Modernize timer more-info dialog with state header and live countdown
Use ha-more-info-state-header with a ticking remaining-time display and
the standard more-info control layout. Adds timer to
DOMAINS_WITH_NEW_MORE_INFO, replacing the legacy state-card row.
* Add timer-actions and timer-presets card features
timer-actions shows start/pause/cancel/finish buttons (finish opt-in)
with state-aware disabling; start becomes restart while active.
timer-presets shows configurable one-tap durations that call timer.start.
Timer tile suggestions default to the timer-actions feature.
* Pulse timer red when it finishes
When a timer runs out or timer.finish is called, the tile icon and the
more-info countdown pulse red twice. Cancelling does not pulse: the new
timerJustFinished helper distinguishes the transitions via the
last_transition attribute. Honors prefers-reduced-motion.
* Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Send normalized duration for timer presets
Presets are parsed leniently by createDurationData like other duration
inputs, but the raw config value was sent to timer.start. A malformed
preset such as 1:nope:00 rendered as 1 h yet was rejected by core. Store
the normalized seconds so the label and the service call always match.
* Store timer presets on the entity instead of the card config
Review feedback: presets belong to the timer entity, like cover favorite
positions and light favorite colors. They now live in the entity registry
options and are edited in the more info dialog through the shared favorites
UI, which also brings reset and copy to other timers. The tile feature keeps
its style option but reads the presets from the registry.
* Reject a zero duration when adding or editing a timer preset
A zero duration passed the form's required check but was then treated as a
cancel, so saving the prefilled 0:00:00 silently did nothing. Form dialogs
now take an optional submit-time validate hook that blocks the submit and
shows a field error, and the preset dialog uses it to require at least one
second.
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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>
* Deduplicate in-flight requests
* Use callWS for in-flight request ownership
* Avoid mutating shared request results
* Deep-freeze shared in-flight request results
Centralize immutability in shareInFlightRequest using deep-freeze so
callers do not need readonly types on shared API shapes.
---------
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
* Fix energy dashboard staying on yesterday after midnight.
* Catch up the energy live day before subscribe fetches.
* Keep energy day math in the server timezone so DST cannot skip a live day.
Browser-local addDays can jump a calendar day on a 23-hour DST fallback.
Assert against tz-internal endOfDay/addDays under Europe/Berlin so UTC CI
catches a regression, and prove the 01:00 timer and catch-up refresh fetch
the live day rather than only updating collection.start.
* Don't follow the live day from midnightRollover alone.
A stored non-today preset would otherwise be discarded on the first subscribe. Drop tautological UTC DST tests that cannot fail.
* Add child devices UI
Surface child devices throughout the config UI so they read as first-class
devices nested under their parent:
- Device page: a "Sub-devices" card lists a device's children, and a child's
page shows a "Part of <parent>" link (hardware/model/config-entry are
already inherited from the parent by the registry resolver).
- Integration page: children are nested and indented under their parent
device in the config-entry and subentry device lists.
- Device picker: children are ordered and indented under their parent with a
tree connector, mirroring the area/floor picker.
- Naming: the device picker's secondary label and search now include the
parent device name for children, so they stay identifiable in flat views.
Follow-up to the child devices data layer (#53617).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add child devices to demo and gallery mock data
A power strip parent with two outlet children in the demo device stubs and
the ha-selector gallery demo, so child device rendering (nesting, tree
indentation, parent-context naming, inherited area) can be exercised without
a running backend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Refine child devices UI and target resolution
Follow-up polish so child devices behave like a normal device everywhere:
- Targets & filters: a parent device qualifies for and resolves to its
children's entities (getDevices filter, deviceMeetsFilter,
deviceMeetsTargetSelector, resolveEntityIDs and the target-chip "split into
entities" expand), matching core's server-side target resolution. Selecting a
parent excludes its children from the picker.
- Pickers: keep a parent visible when a child matches the search (device and
target pickers), fix the target picker's nested order (unsorted search +
recomputed last-child flag), and render the sub-device tree in the target
picker's device group.
- Area/naming: the area field shows only the (inherited) area again; the parent
name remains a search term.
- Integration page: correct the tree end connector and align it in narrow mode.
- A parent-disabled child can no longer be enabled from the settings dialog.
- Devices dashboard: show "Part of <parent>" under a sub-device's name and add a
hidden-by-default, searchable and groupable Parent device column that groups a
parent together with its children.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Address review on child devices UI
Align target resolution with core and fix picker/table details:
- Only a directly targeted device expands to its child devices. Labels are
never inherited by children (core's target helper is explicit about this) and
areas resolve by effective-area membership, so deviceMeetsFilter and
deviceMeetsTargetSelector evaluate a device's own entities again.
- Add devicesInEffectiveArea, mirroring core's dr.async_entries_for_area: an
area contains its devices plus children that inherit the area, but not a child
with a different explicit area. Used for area expansion, area matching and the
area chip's split action.
- Device picker: add a searchFn that restores the nested parent/child order
after the fuzzy search and recomputes the last visible child, so a child can
no longer be ranked above its parent and connectors stay correct.
- Devices dashboard: derive the family group name from the family's parent
device with the same fallback for parents and children, so an unnamed parent
cannot end up in a different group than its children.
- Child devices card: pass the device registry so a child shows its inherited
area.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Fix formatting in device picker row renderer
Reindent the row renderer template after it gained a block body, so Prettier
is satisfied.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Make the child device tree connector span the full row
The connector was a fixed 48px box centred in a taller two-line row, and its
SVG was letterboxed by the default preserveAspectRatio, so the dashed line
stopped short of the row edges and consecutive children never visually
connected.
Let the indicator stretch (preserveAspectRatio="none") and give it the full row
height, so the line runs edge to edge with the elbow at the vertical centre.
non-scaling-stroke keeps the line width and dash pattern identical however far
it is stretched; existing 48x48 usages render unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Add child devices to the device registry
Child devices arrive over the WebSocket as stripped entries in the flat
config/device_registry/list response (discriminated by the absence of
full-device fields). Resolve them into complete DeviceRegistryEntry
objects at ingestion so hass.devices only ever holds full entries and the
~200 downstream consumers stay unchanged:
- config-entry association comes from the child's own config_entry_id, so
children still appear under their integration;
- hardware/display fields are inherited from the parent device;
- connections/via_device_id are not inherited.
Adds parent_device_id and the new "device" disabled_by value to the types.
Frontend data layer for home-assistant/core#178666.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Resolve child device effective area in the UI
A child device without an area of its own inherits its parent's area,
mirroring core's async_get_effective_area_id. getDeviceArea now takes the
devices map and falls back to the parent, so children show in the parent's
area everywhere a normal device would: the device dashboard (area column,
grouping and filtering via computeDeviceAreaLabel) and the device page
header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Address review on child devices data layer
- Discriminate stripped child entries on `connections` (present on every full
device, never on a stripped child) instead of the deprecated `config_entries`
compatibility field that core plans to remove.
- Make getDeviceArea's `devices` argument required and update all callers, so a
child device's effective (inherited) area is resolved consistently everywhere,
including the device picker's selected-value renderer and the integration
device rows.
- Lock the enable switch in the device settings dialog when a device is disabled
by its parent (disabled_by "device"); core ignores enabling a child while its
parent is disabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Inherit the parent device area when resolving entity areas
Extract getDeviceAreaId so an entity on a child device resolves to the child's
effective area (the parent's area when the child has none), matching core's
entity area resolution. Applies to both getEntityAreaId and
getEntityEntryContext, which previously read device.area_id directly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Show target-state entity icon in scene editor review mode
Render the entity badge in review mode (previously live-only) across both
the device-grouped and standalone entity lists. In review mode the badge
uses the scene's stored target state instead of the entity's live state, so
the icon previews what the scene will set once applied.
* Strip stale entity_picture from the synthesized review-mode state
state-badge hides the icon and renders a background image whenever a state
carries an entity_picture (see its willUpdate). A scene snapshots that URL
with an access token that is stale by the time review mode renders, so an
image-backed entity - for example a robot vacuum's "Map data" camera entity
on its device - showed a blank badge in review mode instead of an icon.
Drop entity_picture / entity_picture_local from the synthesized state so the
entity's own icon resolves. This is not a pre-existing defect: it handles a
case that rendering the badge in review mode (previous commit) introduces.
* Handle null and scalar scene entity values in review mode badges
An entity left without a value in the YAML editor parses as null, which
crashed the review-mode render. The scene config API also returns raw
scenes.yaml content without validation, so hand-edited scenes deliver
boolean states as-is (YAML 1.1 parses unquoted on/off as booleans);
these previously rendered as if the entity had no state at all.
Booleans map to on/off to match how the backend applies them when a
scene is activated (_convert_states in the homeassistant scene
platform). The backend rejects null and numeric states at save, but
review mode renders before save, so the frontend has to tolerate them.
* Extract and memoize the scene target-state synthesis
The editor re-renders on every hass change, and building a fresh state
object per row each time defeated Lit dirty-checking: every state-badge
re-ran willUpdate and every ha-state-icon restarted its async icon
resolution. Memoizing the synthesized objects per config keeps the
references stable so unchanged badges skip all of that.
Moving the synthesis to src/data/scene.ts makes it unit-testable; the
null, boolean, numeric, string, and picture-stripping cases are now
covered by tests.
* Sanitize brightness and rgb_color in the synthesized review-mode state
state-badge does arithmetic on brightness and joins rgb_color, assuming
backend-shaped values. Hand-typed YAML can hold both as strings: a
string rgb_color threw a TypeError that left the badge blank, and a
string brightness computed a brightness(36049%) filter that washed the
icon out to invisible. Coerce numeric-string brightness and drop
malformed values so the badge always renders the target state.
* Borrow the live device_class for review-mode badge icons
Icon resolution keys on device_class, which string-shorthand and
hand-written minimal scene entries do not carry, so a garage cover fell
back to the generic window icon and sensors to the domain default. Only
this identity attribute is borrowed from the live state - merging
stateful attributes like rgb_color would mis-color an off target.
* Reject unusable scene targets and trim the badge-state synthesis
Review-mode badges now render only when the scene holds a usable
target state. Entries with no state to show - null values, dicts
without a state key, arrays, non-scalar states - yield no badge
instead of falling back to the live state, which was
indistinguishable from a real target and, for dicts without a state,
crashed state-badge via stateColorCss on lights.
rgb_color and brightness are dropped from inactive targets: a live
entity never carries them while off, and state-badge applies them
without checking activity, so a scene turning a light off rendered an
active-looking colored icon.
Entity pictures are stripped only for DOMAINS_WITH_DYNAMIC_PICTURE,
matching createHistoricState in the logbook; stable pictures on other
domains are kept.
The brightness/rgb_color type coercion and the live device_class
borrowing are removed: they defended against hand-typed shapes that
state-badge already warns about, and the borrowing made the memoized
synthesis depend on hass state outside its memoize key.
* Apply suggestion from @MindFreeze
---------
Co-authored-by: Przemysław Szypowicz <2733699+pszypowicz@users.noreply.github.com>
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
* Add manufacturer, model, and model_id filtering to entity selector
* Implement suggestion from review
* fix: simplify device data type
* Nest device filtering under `device` key in entity selector
* Fix race condition when fetching data
* Avoid config entries refetch loop on fetch failure
The catch handler reset _fetchedConfigEntries to false while also
assigning _configEntries, which re-rendered the component and re-armed
the fetch guard in updated() — an unbounded, no-backoff retry loop on
persistent failure. Keep the flag set so a failed fetch is not retried
on every re-render; the connection-change handler still retries on
reconnect.
---------
Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
Add a "Frequencies" column to the radio frequency devices (proxy) list so
users can see which frequency bands each transmitter supports. The supported
frequency ranges are formatted into a human-readable, locale-aware string
(picking Hz/kHz/MHz/GHz automatically) with a helper in the data layer.
Claude-Session: https://claude.ai/code/session_01SYyMTtBdrt7EBrVEt869Uw
Co-authored-by: Claude <noreply@anthropic.com>
* Use related context in entity picker
* Include current item in context builder
* Fix
* Add tests
* Remove comment
* Support area context in card
* Add from rebase
* Add window.haContext.related to tests
A state condition with match "any" joins its entities with "or", but the
summary kept a plural verb for multiple entities, reading "If A or B are
on". With "or" English uses singular agreement: "If A or B is on". The
match "all" case joins with "and" and correctly stays plural.
Nest a select on a new matchAny flag inside the multiple-entities plural
branch so the verb agrees with the join. Other languages keep their
count-based plural (the extra argument is ignored). Add a test that renders
the actual en.json string to lock in the grammar.