* Add activity detail dialog
* Apply review fixes to activity detail dialog
* Resolve previous state from history in logbook detail dialog
* Disable text selection on clickable logbook rows
* Document run discovery as a fallback for missing context ids
* Scope the detail chain to the subject's cause path
* Replace clickable logbook rows with explicit detail affordances
* Show the cause badge on automation and integration rows
* Map scheduled and Home Assistant causes to their trigger icons
* Replace logbook detail affordances with full-width rows
* Frame the activity detail dialog with ha-grouped-list
* Add missing import
* Fix milliseconds placement in activity chain times
* Lay out inline features in two columns
* Detect stacked features from the slot
* Rename tile container layout classes
* Rename the below features in the layout helper
* Keep area card features below at full width
* Stretch the area card features below the inline one
* Keep area card features compact outside the compact type
* Revert "Keep area card features compact outside the compact type"
This reverts commit 48268cb4e2.
* Reapply "Keep area card features compact outside the compact type"
This reverts commit e10bb27e2f.
* Keep the inline feature compact on condensed cards
* 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>
* Show remaining tile card features below when position is inline
Previously the tile card silently dropped every feature after the first
one when "features_position" was set to "inline". The first feature is
now rendered next to the name as before, and any additional features are
stacked underneath, the same way they are in "bottom" position.
The tile container gained a "features-bottom" slot for this, and the
card size and grid options now account for the extra rows.
* Add tests for hui-tile-card size and grid options calculations
* Enhance hui-tile-card to support inline feature layout and improve feature counting logic
* reduce vertical gap for inline features
---------
Co-authored-by: Claude Opus 5 <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>
* 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>
* Unify back navigation across subpages
* Address back navigation review findings
* Fix state
* Add missing back path
* Remove back path from editors to fix unsaved changes prompt
* Use more specific back fallback paths
* 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 target humidity feature to climate entity
* Use target_humidity_step if any instead of fixed step
* Add target humidity for climate in suggestd tile card
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.
Leaflet computes the zoom level that fits a set of bounds from the current
size of the map container. When ha-map's fit runs before the browser has
laid out the container (a race that hits when the Leaflet chunks are
already cached), the container measures 0x0, the computed zoom collapses
to the minimum, and the map shows the entire world. The later
invalidateSize() from the resize observer fixes the size but keeps the
zoom, so the map stays on the world view.
Defer fitting while the container has no size and run the pending fit
once the resize observer reports a usable size.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Wrap dashboard loading spinner in a delayed fade in component
* Expose initial view render completion
* Wait for dashboard initial readiness
* Signal initial Lovelace view readiness
* Aggregate dashboard view readiness
* Wait for generated panel readiness
* Test dashboard initial readiness
* Add fade in delay to correct dashboard loading
* Delay generated panel readiness until content
* Test generated dashboard initial readiness
* Guard component spread against undefined for unknown panels
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
* Wait for calendar and media browser readiness
* Test calendar and media browser readiness
* Wait for media panel host readiness
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@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
* Skip empty cells on the narrow data table secondary line
In narrow mode the main column renders every other visible column on a
secondary line, but the dot separator was inserted based on the column
index instead of whether the cell rendered anything. A row whose extra
columns are all empty showed a secondary line consisting only of dots,
and an empty column between two filled ones produced a double dot.
Filter empty cells out before joining, and return `nothing` instead of
`html`${nothing}`` for missing timestamps so those cells are detectably
empty too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Extract / clarify data table column visibility
* Fix unbound method and add all-empty secondary line test
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
* Fix prettier formatting in ha-data-table test
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
---------
Co-authored-by: marcinbauer85 <marcinbauer85@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Editing only the battery capacity left the dirty state untouched, so
Save stayed disabled and the change could not be saved. Add the missing
dirty state update.
The Save button also read power config validity off the
ha-energy-power-config child element. Lit evaluates every binding in
render() before committing any of them, so disabled was computed from
the child's pre-update state and lagged one render behind. Derive
validity from the dialog's own power type and config instead, through a
shared helper extracted alongside the other pure power config helpers,
and cover that helper with unit tests.
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bramkragten <5662298+bramkragten@users.noreply.github.com>
Fix race in demo e2e sidebar helper on narrow viewports
openDemoSidebar decided whether the sidebar was behind a modal drawer by
sampling ha-menu-button visibility. That element renders nothing until its
Lit contexts resolve, so right after load it reads as hidden on the narrow
layout too. The helper then took the wide-layout branch, never opened the
drawer, and the following panel assertion waited out its timeout on an
attached-but-hidden #sidebar-panel-map.
Read the layout from ha-drawer's type instead, which home-assistant-main
sets while committing its first render, and wait for the drawer to report
open after clicking the menu button.
* Implement new svg animation
* Fix and speed up fade out transition
* Cleanup
* Remove unintentional scale in
* Match 250ms fade in and out
* Revert
* Fix small layout jump
* Pass localise as context isnt avaliable to a sibling (not a child of ha)
* Update ohf loading logomark
* Fix alt
* Update logomark again
* Reduce gap
* Font changes
* Load font for launch screen
* Smooth launch screen text loading
* Restore launch screen migration line breaks