Compare commits

...

8 Commits

Author SHA1 Message Date
Claude 2ad86643ab Keep supervisor feature flags typed
Flags are temporary by design, so a union is worth more here than a plain
string key: when one is retired from the Supervisor, removing it from this
type surfaces every place in the frontend still reading it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-31 09:44:17 +00:00
Claude 1c4c7c5192 Keep iot_class and the ZHA radio type as unions
Both are stable enumerations rather than growing registries, so a closed
union is the better trade. Retain the members that were genuinely missing:
"calculated" on iot_class and ziggurat on the radio type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-31 06:30:14 +00:00
Claude 8479efdc66 Do not model open backend value sets as closed unions
Several types enumerated values that come from a backend list or mapping
rather than a fixed protocol enum. Those unions go stale silently whenever
the backend gains an entry, and none of them are narrowed on in the frontend.

- Thread router brand is looked up in the backend's vendor-name-to-brand map,
  which grows per vendor. It is only passed to brandsUrl as a domain.
- The ZHA radio type grows whenever a new radio library is supported; this
  session already had to add ziggurat to it. It is only rendered as text.
- Supervisor feature flags are development toggles that come and go, and are
  only read as a record of booleans.
- iot_class comes from a hand-maintained hassfest list that had already gained
  "calculated" without the frontend following. Every consumer only tests
  startsWith("cloud_").

installation_type is a genuinely closed set, so it stays a union, but the
literals were wrong: the backend emits "Home Assistant OS", not "Home
Assistant Operating System", and "Unsupported Third Party Container" was
missing. The onboarding supervisor check already tested for the correct
value, so it was comparing against a string the declared type said was
impossible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-31 06:17:16 +00:00
Claude ec3bba3469 Correct media browsing, TTS and STT response types
- BrowseMedia.as_dict always emits children_media_class, search_media_classes
  and thumbnail, nullable rather than absent. Only not_shown and children are
  conditional, and those are already optional.
- The tts engine list only sets deprecated for legacy providers shadowed by an
  entity-based engine, and supported_languages is always present.
- The stt engine list never sets deprecated at all, so the picker's filter on
  it is currently inert. Kept the field, marked optional to match the wire.

Verified as already correct: conversation agents, wake word info, media source
resolution, and the media_player browse payload.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-29 05:56:03 +00:00
Claude 299514d04f Sync cloud status and preference types with the backend
- The strict_connection preference no longer exists anywhere in the cloud
  integration. It was still modelled, still offered as an update parameter
  (which the update_prefs schema would now reject), and still gated an
  unreachable drop-connection warning in the remote panel.
- google_domains is likewise not part of the account payload any more, while
  google_local_connected is sent and was not modelled.
- The preferences payload also carries alexa_default_expose and
  google_default_expose, both nullable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-29 05:49:12 +00:00
Claude 21754d40d1 Verify ZHA, Matter and Z-Wave types against their client libraries
Installed zha 2.0.1, matter-python-client 1.3.0 and zwave-js-server-python
0.72.0 to read the dataclasses and enums these integrations serialize,
rather than inferring their shapes from the core wrappers.

ZHA:
- lqi and rssi come from zigpy as int | None; rssi was typed as a string
- manufacturer_code is nullable, and exposes_features and endpoint_names
  were missing from the device payload entirely
- Neighbor only modelled 5 of the 9 keys the topology payload sends
- Network backups carry a version, and ziggurat was missing from the radio
  type union

Matter:
- dataclass_to_dict uses asdict without dropping None, so network_name,
  mac_address, fabric_label and vendor_name are always present and nullable
  rather than optional
- NetworkType, NodeType and the commissioning parameters already matched

Z-Wave JS:
- controller status is the ControllerStatus enum (ready/unresponsive/jammed),
  now modelled instead of a bare number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-29 05:43:49 +00:00
Claude 30eb330cfb Fix request payload and remaining response type mismatches
Extracted every websocket command schema from core (466 commands, plus the
57 storage-collection CRUD call sites) and compared them against every
callWS/sendMessagePromise/subscribeMessage payload the frontend sends. The
command schemas extend BASE_COMMAND_MESSAGE_SCHEMA, which is PREVENT_EXTRA,
so an unknown key is a hard validation error rather than a silent no-op.

Request payloads:
- Assist pipeline conversation_language was sent as null when the selected
  conversation agent reports no supported languages, but the backend schema
  requires a string. Send the "*" match-all value the backend already uses,
  and type the field as non-nullable on both the pipeline and its mutable
  params.
- The backup config schedule days had a null branch the backend schema does
  not accept. The frontend already sends [] to clear it.

Responses:
- Z-Wave JS network status reports client.server_logging_enabled and
  controller.status, neither of which were modelled.
- Thread router addresses is a list, not a one-element tuple, brand covers
  every vendor in the backend's brand map and can be null, and the router
  removal event carries no data, so model the discovery event as a
  discriminated union.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-29 05:35:34 +00:00
Claude aa10b9b715 Align frontend API types with the backend
Audited the TypeScript types under src/data against the core websocket/REST
serializers and the Supervisor REST API, and corrected the mismatches.

Core:
- ConfigEntry: add the unload_in_progress state, and created_at/modified_at
- SubEntry.unique_id and DeviceRegistryEntry config_entry_id/config_subentry_id
- removeConfigEntryFromDevice can resolve to null
- config_entries/flow/subscribe: removed messages carry no flow, so model the
  message as a discriminated union
- Entity registry: original_name/translation_key are always sent (nullable),
  options is never null, hidden_by is only user/integration, and the extended
  entry's capabilities/device_class/original_device_class/original_icon are
  always present and nullable
- CategoryRegistryEntry gains the registry timestamps
- updateFloorRegistryEntry returned an area entry
- search/related can return floor, label and person results
- Manifest zeroconf entries can be objects, and integration_type can be virtual
- LoggedError.message holds up to five deduplicated messages
- StatisticsMetaData: add display_unit_of_measurement and has_mean, name is
  always present
- Backup: add the blocked manager state, the event reason, the core_restart
  restore state, nullable homeassistant_version/addon names, and the
  agent_errors on backup details
- Energy validation placeholders are nullable
- Entity feature flags: add MEDIA_ANNOUNCE/MEDIA_ENQUEUE/SEARCH_MEDIA,
  water heater ON_OFF and assist satellite START_CONVERSATION, and drop the
  removed climate AUX_HEAT along with the stale aux_heat attribute

Supervisor:
- Host info nullability, plus the fields the API reports that were missing
- OS info gains boot_slots/version_pending, data_disk is nullable
- Resolution issues and suggestions are objects, not strings, and checks was
  missing entirely
- Supervisor info: addons and addons_repositories are object lists, plus
  auto_update/country/detect_blocking_io/feature_flags
- Add-on details: services and uart were named services_role and auto_uart,
  and boot_config/dns/host_uts/ingress_port/udev/usb/video were missing
- Store add-on details apparmor is a profile name, long_description is nullable
- Mounts: the state enum mirrors the systemd unit state, read_only and
  user_path were missing, and port is optional

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKb38CdFmbwAwfy8xbci8S
2026-07-28 13:19:46 +00:00
70 changed files with 401 additions and 181 deletions
+6 -2
View File
@@ -151,7 +151,9 @@ export class HaDemo extends HomeAssistantAppEl {
entity_category: null,
has_entity_name: false,
unique_id: "co2_intensity",
options: null,
original_name: null,
translation_key: null,
options: {},
created_at: 0,
modified_at: 0,
},
@@ -172,7 +174,9 @@ export class HaDemo extends HomeAssistantAppEl {
entity_category: null,
has_entity_name: false,
unique_id: "grid_fossil_fuel_percentage",
options: null,
original_name: null,
translation_key: null,
options: {},
created_at: 0,
modified_at: 0,
},
+3 -2
View File
@@ -43,7 +43,7 @@ const cloudStatus: CloudStatusLoggedIn = {
email: "demo@home-assistant.io",
google_registered: true,
google_entities: emptyFilter(),
google_domains: ["light", "switch", "climate", "cover"],
google_local_connected: true,
alexa_registered: true,
alexa_entities: emptyFilter(),
remote_domain: "demo-instance.ui.nabu.casa",
@@ -64,7 +64,8 @@ const cloudStatus: CloudStatusLoggedIn = {
alexa_enabled: true,
remote_enabled: true,
remote_allow_remote_enable: true,
strict_connection: "disabled",
alexa_default_expose: null,
google_default_expose: null,
google_secure_devices_pin: undefined,
cloudhooks: {},
alexa_report_state: true,
+2
View File
@@ -21,6 +21,8 @@ const baseEntry = {
reason: null,
error_reason_translation_key: null,
error_reason_translation_placeholders: null,
created_at: 0,
modified_at: 0,
};
// Each entry is tagged with its integration type so we can honor the
+2
View File
@@ -14,6 +14,8 @@ const baseDevice = {
name_by_user: null,
disabled_by: null,
configuration_url: null,
config_entry_id: null,
config_subentry_id: null,
created_at: 0,
modified_at: 0,
};
+8 -1
View File
@@ -16,7 +16,14 @@ export const mockEntityRegistry = (
for (const entityId of msg.entity_ids) {
const entry = data.find((e) => e.entity_id === entityId);
if (entry) {
result[entityId] = { ...entry, capabilities: {}, aliases: [] };
result[entityId] = {
...entry,
capabilities: {},
original_icon: null,
device_class: null,
original_device_class: null,
aliases: [],
};
}
}
return result;
+31 -2
View File
@@ -114,13 +114,14 @@ const addonDetails = (addon: HassioAddonInfo): HassioAddonDetails => ({
audio_output: null,
audio: false,
auth_api: false,
auto_uart: false,
auto_update: false,
boot: "auto",
boot_config: "auto",
changelog: false,
devices: [],
devicetree: false,
discovery: [],
dns: [],
docker_api: false,
documentation: false,
full_access: false,
@@ -133,8 +134,10 @@ const addonDetails = (addon: HassioAddonInfo): HassioAddonDetails => ({
host_ipc: false,
host_network: false,
host_pid: false,
host_uts: false,
ingress_entry: null,
ingress_panel: false,
ingress_port: null,
ingress_url: null,
ingress: false,
ip_address: "172.30.33.2",
@@ -148,13 +151,17 @@ const addonDetails = (addon: HassioAddonInfo): HassioAddonDetails => ({
protected: true,
rating: 6,
schema: CONFIG_SCHEMAS[addon.slug] ?? null,
services_role: [],
services: [],
signed: false,
startup: "application",
stdin: false,
system_managed: false,
system_managed_config_entry: null,
translations: {},
uart: false,
udev: false,
usb: false,
video: false,
watchdog: true,
webui: null,
});
@@ -212,6 +219,13 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
debug: false,
debug_block: false,
diagnostics: true,
auto_update: true,
country: "NL",
detect_blocking_io: false,
feature_flags: {
supervisor_v2_api: false,
supervisor_websocket_v2_api: false,
},
addons: DEMO_ADDONS as any,
addons_repositories: [
"https://github.com/music-assistant/home-assistant-addon",
@@ -264,6 +278,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
hostname: "homeassistant",
logging: "info",
machine: "green",
machine_id: "0123456789abcdef0123456789abcdef",
state: "running",
operating_system: "Home Assistant OS 18.2",
supervisor: "2026.07.1",
@@ -277,6 +292,9 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
if (msg.endpoint === "/host/info") {
const data: HassioHostInfo = {
agent_version: "1.8.0",
apparmor_version: "3.0.7",
broadcast_llmnr: true,
broadcast_mdns: true,
chassis: "embedded",
cpe: "cpe:2.3:o:home-assistant:haos:18.2:*:production:*:*:*:aarch64:*",
deployment: "production",
@@ -284,12 +302,18 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
disk_free: 22.3,
disk_total: 31.2,
disk_used: 8.9,
dt_synchronized: true,
dt_utc: "2026-07-28T10:00:00+00:00",
features: ["reboot", "shutdown", "network", "hostname", "os_agent"],
hostname: "homeassistant",
kernel: "6.12.48-haos",
llmnr_hostname: "homeassistant",
operating_system: "Home Assistant OS 18.2",
boot_timestamp: 1751932800000000,
startup_time: 12.4,
timezone: "Europe/Amsterdam",
use_ntp: true,
virtualization: "",
};
return data;
}
@@ -298,9 +322,14 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
const data: HassioHassOSInfo = {
board: "green",
boot: "A",
boot_slots: {
A: { state: "booted", status: "good", version: "18.2" },
B: { state: "inactive", status: "good", version: "18.1" },
},
update_available: false,
version: "18.2",
version_latest: "18.2",
version_pending: null,
data_disk: "Home Assistant Green (mmcblk0)",
};
return data;
+6
View File
@@ -69,6 +69,8 @@ const DEVICES: DeviceRegistryEntry[] = [
configuration_url: null,
config_entries: ["config_entry_1"],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
disabled_by: null,
entry_type: null,
@@ -93,6 +95,8 @@ const DEVICES: DeviceRegistryEntry[] = [
configuration_url: null,
config_entries: ["config_entry_2"],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
disabled_by: null,
entry_type: null,
@@ -117,6 +121,8 @@ const DEVICES: DeviceRegistryEntry[] = [
configuration_url: null,
config_entries: ["config_entry_3"],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
disabled_by: null,
entry_type: null,
@@ -82,6 +82,8 @@ const DEVICES: DeviceRegistryEntry[] = [
configuration_url: null,
config_entries: ["config_entry_1"],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
disabled_by: null,
entry_type: null,
@@ -106,6 +108,8 @@ const DEVICES: DeviceRegistryEntry[] = [
configuration_url: null,
config_entries: ["config_entry_2"],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
disabled_by: null,
entry_type: null,
@@ -130,6 +134,8 @@ const DEVICES: DeviceRegistryEntry[] = [
configuration_url: null,
config_entries: ["config_entry_3"],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
disabled_by: null,
entry_type: null,
+7 -1
View File
@@ -29,6 +29,8 @@ const createConfigEntry = (
title,
source: "zeroconf",
state: "loaded",
created_at: 0,
modified_at: 0,
supports_options: false,
supports_remove_device: false,
supports_unload: true,
@@ -204,7 +206,9 @@ const createEntityRegistryEntries = (
platform: "updater",
has_entity_name: false,
unique_id: "updater",
options: null,
original_name: null,
translation_key: null,
options: {},
labels: [],
categories: {},
created_at: 0,
@@ -219,6 +223,8 @@ const createDeviceRegistryEntries = (
entry_type: null,
config_entries: [item.entry_id],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
manufacturer: "ESPHome",
model: "Mock Device",
+2
View File
@@ -187,6 +187,8 @@ export class HaPictureUpload extends LitElement {
can_play: true,
can_expand: false,
can_search: false,
children_media_class: null,
search_media_classes: null,
thumbnail: generateImageThumbnailUrl(media.id, 256),
} as const;
const navigateIds = [
@@ -94,6 +94,8 @@ const MANUAL_ITEM_BASE: Omit<MediaPlayerItem, "title"> = {
can_play: false,
can_search: false,
children_media_class: "",
search_media_classes: null,
thumbnail: null,
media_class: "app",
media_content_id: MANUAL_MEDIA_SOURCE_PREFIX,
media_content_type: "",
+2 -2
View File
@@ -7,7 +7,7 @@ export interface AssistPipeline {
name: string;
language: string;
conversation_engine: string;
conversation_language: string | null;
conversation_language: string;
prefer_local_intents?: boolean;
stt_engine: string | null;
stt_language: string | null;
@@ -27,7 +27,7 @@ export interface AssistPipelineMutableParams {
name: string;
language: string;
conversation_engine: string;
conversation_language: string | null;
conversation_language: string;
prefer_local_intents?: boolean;
stt_engine: string | null;
stt_language: string | null;
+1
View File
@@ -5,6 +5,7 @@ import { UNAVAILABLE } from "./entity/entity";
export enum AssistSatelliteEntityFeature {
ANNOUNCE = 1,
START_CONVERSATION = 2,
}
export interface WakeWordInterceptMessage {
+8 -9
View File
@@ -81,7 +81,8 @@ export interface BackupMutableConfig {
schedule?: {
recurrence: BackupScheduleRecurrence;
time?: string | null;
days?: BackupDay[] | null;
// The backend schema has no null branch for days, send [] to clear
days?: BackupDay[];
};
agents?: BackupAgentsConfig;
}
@@ -121,22 +122,19 @@ export interface BackupContent {
"supervisor.addon_update"?: string;
"supervisor.app_update"?: string;
};
with_automatic_settings: boolean;
with_automatic_settings: boolean | null;
}
export interface BackupData {
addons: BackupAddon[];
database_included: boolean;
folders: string[];
homeassistant_version: string;
// null when homeassistant_included is false
homeassistant_version: string | null;
homeassistant_included: boolean;
}
export interface BackupAddon {
name: string;
slug: string;
version: string;
}
export type BackupAddon = AddonInfo;
export interface BackupContentExtended extends BackupContent, BackupData {}
@@ -152,7 +150,8 @@ export interface BackupInfo {
}
export interface BackupDetails {
backup: BackupContentExtended;
backup: BackupContentExtended | null;
agent_errors: Record<string, string>;
}
export interface BackupAgentsInfo {
+16 -5
View File
@@ -1,7 +1,7 @@
import type { HomeAssistant } from "../types";
export type BackupManagerState =
"idle" | "create_backup" | "receive_backup" | "restore_backup";
"idle" | "blocked" | "create_backup" | "receive_backup" | "restore_backup";
export type CreateBackupStage =
| "addon_repositories"
@@ -39,26 +39,34 @@ export type RestoreBackupStage =
| "remove_delta_addons"
| "remove_delta_apps";
export type RestoreBackupState = "completed" | "failed" | "in_progress";
export type RestoreBackupState =
"completed" | "core_restart" | "failed" | "in_progress";
interface IdleEvent {
manager_state: "idle";
}
interface BlockedEvent {
manager_state: "blocked";
}
interface CreateBackupEvent {
manager_state: "create_backup";
reason: string | null;
stage: CreateBackupStage | null;
state: CreateBackupState;
}
interface ReceiveBackupEvent {
manager_state: "receive_backup";
reason: string | null;
stage: ReceiveBackupStage | null;
state: ReceiveBackupState;
}
interface RestoreBackupEvent {
manager_state: "restore_backup";
reason: string | null;
stage: RestoreBackupStage | null;
state: RestoreBackupState;
}
@@ -70,11 +78,14 @@ export interface UploadBackupEvent {
total_bytes: number;
}
export type ManagerState =
"idle" | "create_backup" | "receive_backup" | "restore_backup";
export type ManagerState = BackupManagerState;
export type ManagerStateEvent =
IdleEvent | CreateBackupEvent | ReceiveBackupEvent | RestoreBackupEvent;
| IdleEvent
| BlockedEvent
| CreateBackupEvent
| ReceiveBackupEvent
| RestoreBackupEvent;
export type BackupSubscriptionEvent = ManagerStateEvent | UploadBackupEvent;
+2 -1
View File
@@ -4,8 +4,9 @@ import type { Store } from "home-assistant-js-websocket/dist/store";
import { stringCompare } from "../common/string/compare";
import type { HomeAssistant } from "../types";
import { debounce } from "../common/util/debounce";
import type { RegistryEntry } from "./registry";
export interface CategoryRegistryEntry {
export interface CategoryRegistryEntry extends RegistryEntry {
category_id: string;
name: string;
icon: string | null;
-1
View File
@@ -64,7 +64,6 @@ export type ClimateEntity = HassEntityBase & {
swing_modes?: string[];
swing_horizontal_mode?: string;
swing_horizontal_modes?: string[];
aux_heat?: "on" | "off";
};
};
+3 -5
View File
@@ -1,8 +1,6 @@
import type { EntityDomainFilter } from "../common/entity/entity_domain_filter";
import type { HomeAssistant } from "../types";
type StrictConnectionMode = "disabled" | "guard_page" | "drop_connection";
interface CloudStatusNotLoggedIn {
logged_in: false;
cloud: "disconnected" | "connecting" | "connected";
@@ -21,7 +19,8 @@ export interface CloudPreferences {
alexa_enabled: boolean;
remote_enabled: boolean;
remote_allow_remote_enable: boolean;
strict_connection: StrictConnectionMode;
alexa_default_expose: string[] | null;
google_default_expose: string[] | null;
google_secure_devices_pin: string | undefined;
cloudhooks: Record<string, CloudWebhook>;
alexa_report_state: boolean;
@@ -42,7 +41,7 @@ export interface CloudStatusLoggedIn {
email: string;
google_registered: boolean;
google_entities: EntityDomainFilter;
google_domains: string[];
google_local_connected: boolean;
alexa_registered: boolean;
alexa_entities: EntityDomainFilter;
prefs: CloudPreferences;
@@ -173,7 +172,6 @@ export const updateCloudPref = (
google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"];
tts_default_voice?: CloudPreferences["tts_default_voice"];
remote_allow_remote_enable?: CloudPreferences["remote_allow_remote_enable"];
strict_connection?: CloudPreferences["strict_connection"];
cloud_ice_servers_enabled?: CloudPreferences["cloud_ice_servers_enabled"];
}
) =>
+5 -3
View File
@@ -1,8 +1,9 @@
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../types";
import type { IntegrationType } from "./integration";
import type { RegistryEntry } from "./registry";
export interface ConfigEntry {
export interface ConfigEntry extends RegistryEntry {
entry_id: string;
domain: string;
title: string;
@@ -14,7 +15,8 @@ export interface ConfigEntry {
| "setup_retry"
| "not_loaded"
| "failed_unload"
| "setup_in_progress";
| "setup_in_progress"
| "unload_in_progress";
supports_options: boolean;
supports_remove_device: boolean;
supports_unload: boolean;
@@ -33,7 +35,7 @@ export interface SubEntry {
subentry_id: string;
subentry_type: string;
title: string;
unique_id: string;
unique_id: string | null;
}
export const getSubEntries = (hass: HomeAssistant, entry_id: string) =>
+10 -2
View File
@@ -91,12 +91,20 @@ export const fetchConfigFlowInProgress = (
type: "config_entries/flow/progress",
});
export interface ConfigFlowInProgressMessage {
type: null | "added" | "removed";
interface ConfigFlowInProgressAddedMessage {
type: null | "added";
flow_id: string;
flow: DataEntryFlowProgress;
}
interface ConfigFlowInProgressRemovedMessage {
type: "removed";
flow_id: string;
}
export type ConfigFlowInProgressMessage =
ConfigFlowInProgressAddedMessage | ConfigFlowInProgressRemovedMessage;
export const subscribeConfigFlowInProgress = (
hass: HomeAssistant,
onChange: (update: ConfigFlowInProgressMessage[]) => void
+3 -1
View File
@@ -18,6 +18,8 @@ export interface DeviceRegistryEntry extends RegistryEntry {
id: string;
config_entries: string[];
config_entries_subentries: Record<string, (string | null)[]>;
config_entry_id: string | null;
config_subentry_id: string | null;
connections: [string, string][];
identifiers: [string, string][];
manufacturer: string | null;
@@ -87,7 +89,7 @@ export const removeConfigEntryFromDevice = (
deviceId: string,
configEntryId: string
) =>
hass.callWS<DeviceRegistryEntry>({
hass.callWS<DeviceRegistryEntry | null>({
type: "config/device_registry/remove_config_entry",
device_id: deviceId,
config_entry_id: configEntryId,
+1 -1
View File
@@ -243,7 +243,7 @@ export interface EnergyInfo {
export interface EnergyValidationIssue {
type: string;
affected_entities: [string, unknown][];
translation_placeholders: Record<string, string>;
translation_placeholders: Record<string, string> | null;
}
export interface EnergyPreferencesValidation {
+9 -9
View File
@@ -37,7 +37,7 @@ export interface EntityRegistryDisplayEntryResponse {
ec?: number;
en?: string;
ic?: string;
pl?: string;
pl: string;
tk?: string;
hb?: boolean;
dp?: number;
@@ -58,21 +58,21 @@ export interface EntityRegistryEntry extends RegistryEntry {
area_id: string | null;
labels: string[];
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
hidden_by: "user" | "integration" | null;
entity_category: EntityCategory | null;
has_entity_name: boolean;
original_name?: string;
original_name: string | null;
unique_id: string;
translation_key?: string;
options: EntityRegistryOptions | null;
translation_key: string | null;
options: EntityRegistryOptions;
categories: Record<string, string>;
}
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
capabilities: Record<string, unknown>;
original_icon?: string;
device_class?: string;
original_device_class?: string;
capabilities: Record<string, unknown> | null;
original_icon: string | null;
device_class: string | null;
original_device_class: string | null;
aliases: (string | null)[];
}
@@ -5,7 +5,6 @@ export enum ClimateEntityFeature {
FAN_MODE = 8,
PRESET_MODE = 16,
SWING_MODE = 32,
AUX_HEAT = 64,
TURN_OFF = 128,
TURN_ON = 256,
SWING_HORIZONTAL_MODE = 512,
@@ -19,4 +19,7 @@ export enum MediaPlayerEntityFeature {
BROWSE_MEDIA = 131072,
REPEAT_SET = 262144,
GROUPING = 524288,
MEDIA_ANNOUNCE = 1048576,
MEDIA_ENQUEUE = 2097152,
SEARCH_MEDIA = 4194304,
}
+1 -1
View File
@@ -35,7 +35,7 @@ export const updateFloorRegistryEntry = (
floorId: string,
updates: Partial<FloorRegistryEntryMutableParams>
) =>
hass.callWS<AreaRegistryEntry>({
hass.callWS<FloorRegistryEntry>({
type: "config/floor_registry/update",
floor_id: floorId,
...updates,
+12 -4
View File
@@ -17,6 +17,7 @@ export type AddonCapability = Exclude<
>;
export type AddonStage = "stable" | "experimental" | "deprecated";
export type AddonAppArmour = "disable" | "default" | "profile";
export type AddonBootConfig = "auto" | "manual" | "manual_only";
export type AddonRole = "default" | "homeassistant" | "manager" | "admin";
export type AddonStartup =
"initialize" | "system" | "services" | "application" | "once";
@@ -63,13 +64,14 @@ export interface HassioAddonDetails extends HassioAddonInfo {
audio_output: null | string;
audio: boolean;
auth_api: boolean;
auto_uart: boolean;
auto_update: boolean;
boot: "auto" | "manual";
boot_config: AddonBootConfig;
changelog: boolean;
devices: string[];
devicetree: boolean;
discovery: string[];
dns: string[];
docker_api: boolean;
documentation: boolean;
full_access: boolean;
@@ -82,22 +84,24 @@ export interface HassioAddonDetails extends HassioAddonInfo {
host_ipc: boolean;
host_network: boolean;
host_pid: boolean;
host_uts: boolean;
ingress_entry: null | string;
ingress_panel: boolean;
ingress_port: number | null;
ingress_url: null | string;
ingress: boolean;
ip_address: string;
kernel_modules: boolean;
long_description: null | string;
machine: any;
machine: string[];
network_description: null | Record<string, string>;
network: null | Record<string, number>;
options: Record<string, unknown>;
privileged: any;
privileged: string[];
protected: boolean;
rating: number;
schema: HaFormSchema[] | null;
services_role: string[];
services: string[];
signed: boolean;
slug: string;
startup: AddonStartup;
@@ -105,6 +109,10 @@ export interface HassioAddonDetails extends HassioAddonInfo {
system_managed: boolean;
system_managed_config_entry: string | null;
translations: Record<string, AddonTranslations>;
uart: boolean;
udev: boolean;
usb: boolean;
video: boolean;
watchdog: null | boolean;
webui: null | string;
}
+27 -10
View File
@@ -1,29 +1,46 @@
import type { HomeAssistant } from "../../types";
export interface HassioHostInfo {
agent_version: string;
chassis: string;
cpe: string;
deployment: string;
agent_version: string | null;
apparmor_version: string | null;
boot_timestamp: number | null;
broadcast_llmnr: boolean | null;
broadcast_mdns: boolean | null;
chassis: string | null;
cpe: string | null;
deployment: string | null;
disk_life_time: number | null;
disk_free: number;
disk_total: number;
disk_used: number;
dt_synchronized: boolean | null;
dt_utc: string;
features: string[];
hostname: string;
kernel: string;
operating_system: string;
boot_timestamp: number;
startup_time: number;
hostname: string | null;
kernel: string | null;
llmnr_hostname: string | null;
operating_system: string | null;
startup_time: number | null;
timezone: string | null;
use_ntp: boolean | null;
virtualization: string | null;
}
export interface HassioHassOSBootSlot {
state: string | null;
status: string | null;
version: string | null;
}
export interface HassioHassOSInfo {
board: string | null;
boot: string | null;
boot_slots: Record<string, HassioHassOSBootSlot>;
update_available: boolean;
version_latest: string | null;
version_pending: string | null;
version: string | null;
data_disk: string;
data_disk: string | null;
}
export interface Datadisk {
+20 -2
View File
@@ -1,10 +1,28 @@
import type { HomeAssistant, TranslationDict } from "../../types";
export interface ResolutionIssue {
type: string;
context: string;
reference: string | null;
reference_extra: Record<string, unknown> | null;
uuid: string;
}
export interface ResolutionSuggestion extends ResolutionIssue {
auto: boolean;
}
export interface ResolutionCheck {
enabled: boolean;
slug: string;
}
export interface HassioResolution {
unsupported: (keyof TranslationDict["ui"]["dialogs"]["unsupported"]["reasons"])[];
unhealthy: (keyof TranslationDict["ui"]["dialogs"]["unhealthy"]["reasons"])[];
issues: string[];
suggestions: string[];
issues: ResolutionIssue[];
suggestions: ResolutionSuggestion[];
checks: ResolutionCheck[];
}
export const fetchHassioResolution = async (
+35 -5
View File
@@ -1,5 +1,6 @@
import type { HomeAssistant, PanelInfo } from "../../types";
import type { SupervisorArch } from "../supervisor/supervisor";
import type { AddonState } from "./addon";
import type { HassioResponse } from "./common";
export interface HassioHomeAssistantInfo {
@@ -19,14 +20,41 @@ export interface HassioHomeAssistantInfo {
watchdog: boolean;
}
export interface HassioSupervisorAddonInfo {
name: string;
slug: string;
version: string | null;
version_latest: string;
update_available: boolean;
state: AddonState;
repository: string;
icon: boolean;
}
export interface HassioSupervisorRepositoryInfo {
name: string;
slug: string;
}
/**
* Supervisor development toggles. Kept as a union on purpose: flags are
* temporary, so removing one here surfaces every place still reading it.
*/
export type SupervisorFeatureFlag =
"supervisor_v2_api" | "supervisor_websocket_v2_api";
export interface HassioSupervisorInfo {
addons: string[];
addons_repositories: string[];
addons: HassioSupervisorAddonInfo[];
addons_repositories: HassioSupervisorRepositoryInfo[];
arch: SupervisorArch;
auto_update: boolean;
channel: string;
country: string | null;
debug: boolean;
debug_block: boolean;
detect_blocking_io: boolean;
diagnostics: boolean | null;
feature_flags: Record<SupervisorFeatureFlag, boolean>;
healthy: boolean;
ip_address: string;
logging: string;
@@ -35,6 +63,7 @@ export interface HassioSupervisorInfo {
update_available: boolean;
version: string;
version_latest: string;
/** @deprecated No longer used by the Supervisor */
wait_boot: number;
}
@@ -43,11 +72,12 @@ export interface HassioInfo {
channel: string;
docker: string;
features: string[];
hassos: null;
hassos: string | null;
homeassistant: string;
hostname: string;
hostname: string | null;
logging: string;
machine: string;
machine_id: string | null;
state:
| "initialize"
| "setup"
@@ -57,7 +87,7 @@ export interface HassioInfo {
| "shutdown"
| "stopping"
| "close";
operating_system: string;
operating_system: string | null;
supervisor: string;
supported: boolean;
supported_arch: SupervisorArch[];
+9 -2
View File
@@ -20,6 +20,12 @@ export const integrationsWithPanel = {
export type IntegrationType =
"device" | "helper" | "hub" | "service" | "hardware" | "entity" | "system";
/**
* `integration_type` as reported by manifests and brand data. Virtual
* integrations only exist as metadata, they never have config entries.
*/
export type ManifestIntegrationType = IntegrationType | "virtual";
export type DomainManifestLookup = Record<string, IntegrationManifest>;
export interface IntegrationManifest {
@@ -35,9 +41,9 @@ export interface IntegrationManifest {
codeowners?: string[];
requirements?: string[];
ssdp?: { manufacturer?: string; modelName?: string; st?: string }[];
zeroconf?: string[];
zeroconf?: (string | Record<string, string>)[];
homekit?: { models: string[] };
integration_type?: IntegrationType;
integration_type?: ManifestIntegrationType;
loggers?: string[];
quality_scale?:
| "bronze"
@@ -50,6 +56,7 @@ export interface IntegrationManifest {
| "custom";
iot_class:
| "assumed_state"
| "calculated"
| "cloud_polling"
| "cloud_push"
| "local_polling"
+2 -2
View File
@@ -1,10 +1,10 @@
import type { HomeAssistant } from "../types";
import type { IntegrationType } from "./integration";
import type { ManifestIntegrationType } from "./integration";
export type IotStandards = "zwave" | "zigbee" | "homekit" | "matter";
export interface Integration {
integration_type: IntegrationType;
integration_type: ManifestIntegrationType;
name?: string;
config_flow?: boolean;
iot_standards?: IotStandards[];
+4 -4
View File
@@ -27,17 +27,17 @@ export interface MatterFabricData {
fabric_id: number;
vendor_id: number;
fabric_index: number;
fabric_label?: string;
vendor_name?: string;
fabric_label: string | null;
vendor_name: string | null;
}
export interface MatterNodeDiagnostics {
node_id: number;
network_type: NetworkType;
node_type: NodeType;
network_name?: string;
network_name: string | null;
ip_adresses: string[];
mac_address?: string;
mac_address: string | null;
available: boolean;
active_fabrics: MatterFabricData[];
active_fabric_index: number;
+3 -3
View File
@@ -182,14 +182,14 @@ export interface MediaPlayerItem {
media_content_type: string;
media_content_id: string;
media_class: keyof TranslationDict["ui"]["components"]["media-browser"]["class"];
children_media_class?: string | null;
children_media_class: string | null;
can_play: boolean;
can_expand: boolean;
can_search: boolean;
search_media_classes?:
search_media_classes:
| (keyof TranslationDict["ui"]["components"]["media-browser"]["class"])[]
| null;
thumbnail?: string;
thumbnail: string | null;
iconPath?: string;
children?: MediaPlayerItem[];
not_shown?: number;
+2 -1
View File
@@ -4,10 +4,11 @@ import type { CloudStatus } from "./cloud";
export interface InstallationType {
installation_type:
| "Home Assistant Operating System"
| "Home Assistant OS"
| "Home Assistant Container"
| "Home Assistant Supervised"
| "Home Assistant Core"
| "Unsupported Third Party Container"
| "Unknown";
}
+4 -1
View File
@@ -47,9 +47,12 @@ export enum StatisticMeanType {
export interface StatisticsMetaData {
statistics_unit_of_measurement: string | null;
display_unit_of_measurement: string | null;
statistic_id: string;
source: string;
name?: string | null;
name: string | null;
/** @deprecated Use `mean_type` instead, removed in HA Core 2026.4 */
has_mean: boolean;
has_sum: boolean;
mean_type: StatisticMeanType;
unit_class: string | null;
+5
View File
@@ -7,8 +7,11 @@ export interface RelatedResult {
config_entry?: string[];
device?: string[];
entity?: string[];
floor?: string[];
group?: string[];
integration?: string[];
label?: string[];
person?: string[];
scene?: string[];
script?: string[];
script_blueprint?: string[];
@@ -29,7 +32,9 @@ export type ItemType =
| "entity"
| "floor"
| "group"
| "integration"
| "label"
| "person"
| "scene"
| "script"
| "automation_blueprint"
+4 -2
View File
@@ -12,9 +12,11 @@ export interface SpeechMetadata {
export interface STTEngine {
engine_id: string;
supported_languages?: string[];
supported_languages: string[];
/** Only set for legacy providers. */
name?: string;
deprecated: boolean;
/** Not currently set by stt/engine/list, unlike the tts equivalent. */
deprecated?: boolean;
}
export const listSTTEngines = (
+27 -12
View File
@@ -12,49 +12,64 @@ export enum SupervisorMountUsage {
SHARE = "share",
}
/** Mirrors the systemd unit active state the Supervisor reports. */
export enum SupervisorMountState {
ACTIVE = "active",
ACTIVATING = "activating",
DEACTIVATING = "deactivating",
FAILED = "failed",
UNKNOWN = "unknown",
INACTIVE = "inactive",
MAINTENANCE = "maintenance",
RELOADING = "reloading",
}
interface MountOptions {
default_backup_mount?: string | null;
}
/** `auto` is a UI-only value, it is stripped before the mount is submitted. */
export type CIFSVersion = "auto" | "1.0" | "2.0";
interface SupervisorMountBase {
name: string;
usage: SupervisorMountUsage;
type: SupervisorMountType;
read_only?: boolean;
server: string;
port: number;
port?: number;
}
export interface SupervisorMountResponse extends SupervisorMountBase {
state: SupervisorMountState | null;
}
export interface SupervisorNFSMount extends SupervisorMountResponse {
interface SupervisorNFSMountBase extends SupervisorMountBase {
type: SupervisorMountType.NFS;
path: string;
}
export interface SupervisorCIFSMount extends SupervisorMountResponse {
interface SupervisorCIFSMountBase extends SupervisorMountBase {
type: SupervisorMountType.CIFS;
share: string;
version?: CIFSVersion;
version?: CIFSVersion | null;
}
/** Fields the Supervisor adds to a configured mount when reporting it. */
export interface SupervisorMountResponse {
read_only: boolean;
state: SupervisorMountState | null;
user_path: string | null;
}
export type SupervisorNFSMount = SupervisorNFSMountBase &
SupervisorMountResponse;
export type SupervisorCIFSMount = SupervisorCIFSMountBase &
SupervisorMountResponse;
export type SupervisorMount = SupervisorNFSMount | SupervisorCIFSMount;
export type SupervisorNFSMountRequestParams = SupervisorNFSMount;
export type SupervisorNFSMountRequestParams = SupervisorNFSMountBase;
export interface SupervisorCIFSMountRequestParams extends SupervisorCIFSMount {
export interface SupervisorCIFSMountRequestParams extends SupervisorCIFSMountBase {
username?: string;
password?: string;
version?: CIFSVersion;
}
export type SupervisorMountRequestParams =
+9 -6
View File
@@ -1,13 +1,15 @@
import type { HomeAssistant } from "../../types";
import type { AddonRole, AddonStage } from "../hassio/addon";
import type { AddonAppArmour, AddonRole, AddonStage } from "../hassio/addon";
import { supervisorApiCall } from "./common";
import type { SupervisorArch } from "./supervisor";
export interface StoreAddon {
advanced: boolean;
arch: SupervisorArch[];
available: boolean;
build: boolean;
description: string;
documentation: boolean;
homeassistant: string | null;
icon: boolean;
installed: boolean;
@@ -17,19 +19,20 @@ export interface StoreAddon {
slug: string;
stage: AddonStage;
update_available: boolean;
url: string;
url: string | null;
version_latest: string;
// The Supervisor reports the installed version here, but the frontend only
// ever loads store details for add-ons that are not installed. Kept as `null`
// so it discriminates `StoreAddon` from `HassioAddonInfo`.
version: null;
}
export interface StoreAddonDetails extends StoreAddon {
apparmor: boolean;
arch: SupervisorArch[];
apparmor: AddonAppArmour;
auth_api: boolean;
changelog: boolean;
detached: boolean;
docker_api: boolean;
documentation: boolean;
full_access: boolean;
hassio_api: boolean;
hassio_role: AddonRole;
@@ -37,7 +40,7 @@ export interface StoreAddonDetails extends StoreAddon {
host_network: boolean;
host_pid: boolean;
ingress: boolean;
long_description: string;
long_description: string | null;
rating: number;
signed: boolean;
}
+2 -1
View File
@@ -5,7 +5,8 @@ export type SystemLogLevel =
export interface LoggedError {
name: string;
message: [string];
// The backend keeps up to 5 deduplicated messages per entry
message: string[];
level: SystemLogLevel;
source: [string, number];
exception: string;
+7 -7
View File
@@ -2,9 +2,10 @@ import type { HomeAssistant } from "../types";
export interface ThreadRouter {
instance_name: string;
addresses: [string];
addresses: string[];
border_agent_id: string | null;
brand: "google" | "apple" | "homeassistant";
/** Integration domain the backend maps the router's vendor name to. */
brand: string | null;
extended_address: string;
extended_pan_id: string;
model_name: string | null;
@@ -28,11 +29,10 @@ export interface ThreadDataSet {
source: string;
}
export interface ThreadRouterDiscoveryEvent {
key: string;
type: "router_discovered" | "router_removed";
data: ThreadRouter;
}
export type ThreadRouterDiscoveryEvent =
| { type: "router_discovered"; key: string; data: ThreadRouter }
// No data is sent when a router disappears
| { type: "router_removed"; key: string };
class DiscoveryStream {
routers: Record<string, ThreadRouter>;
+4 -2
View File
@@ -2,9 +2,11 @@ import type { HomeAssistant } from "../types";
export interface TTSEngine {
engine_id: string;
supported_languages?: string[];
supported_languages: string[];
/** Only set for legacy providers superseded by an entity-based engine. */
deprecated?: boolean;
/** Only set for legacy providers. */
name?: string;
deprecated: boolean;
}
export interface TTSVoice {
+1
View File
@@ -7,6 +7,7 @@ export enum WaterHeaterEntityFeature {
TARGET_TEMPERATURE = 1,
OPERATION_MODE = 2,
AWAY_MODE = 4,
ON_OFF = 8,
}
export const OPERATION_MODES = [
+17 -9
View File
@@ -12,33 +12,40 @@ export interface ZHADevice {
name: string;
ieee: string;
nwk: number;
lqi: number;
rssi: string;
lqi: number | null;
rssi: number | null;
last_seen: string;
manufacturer: string;
model: string;
quirk_applied: boolean;
quirk_class: string;
exposes_features: string[];
entities: ZHAEntityReference[];
manufacturer_code: number;
endpoint_names: { name: string }[];
manufacturer_code: number | null;
device_reg_id: string;
user_given_name?: string;
user_given_name?: string | null;
power_source?: string;
area_id?: string;
area_id?: string | null;
device_type: string;
active_coordinator: boolean;
signature: any;
signature: Record<string, unknown>;
neighbors: Neighbor[];
routes: Route[];
/** Only sent on device_joined / device_fully_initialized gateway events. */
pairing_status?: string;
}
export interface Neighbor {
device_type: string;
rx_on_when_idle: string;
relationship: string;
extended_pan_id: string;
ieee: string;
nwk: string;
lqi: string;
permit_joining: string;
depth: string;
relationship: string;
lqi: string;
}
export interface Route {
@@ -182,6 +189,7 @@ export interface ZHANetworkBackupNetworkInfo {
}
export interface ZHANetworkBackup {
version: number;
backup_time: string;
network_info: ZHANetworkBackupNetworkInfo;
node_info: ZHANetworkBackupNodeInfo;
@@ -195,7 +203,7 @@ export interface ZHADeviceSettings {
export interface ZHANetworkSettings {
settings: ZHANetworkBackup;
radio_type: "ezsp" | "znp" | "deconz" | "zigate" | "xbee";
radio_type: "ezsp" | "znp" | "deconz" | "ziggurat" | "zigate" | "xbee";
device: ZHADeviceSettings;
}
+11
View File
@@ -15,6 +15,15 @@ export enum InclusionState {
SmartStart,
}
export enum ControllerStatus {
/** The controller is ready to accept commands and transmit. */
Ready,
/** The controller is unresponsive. */
Unresponsive,
/** The controller is unable to transmit because the airwaves are jammed. */
Jammed,
}
export const enum InclusionStrategy {
/**
* Always uses Security S2 if supported, otherwise uses Security S0 for certain devices which don't work without encryption and uses no encryption otherwise.
@@ -195,6 +204,7 @@ export interface ZWaveJSClient {
ws_server_url: string;
server_version: string;
driver_version: string;
server_logging_enabled: boolean;
}
export interface ZWaveJSController {
@@ -218,6 +228,7 @@ export interface ZWaveJSController {
supports_timers: boolean;
is_rebuilding_routes: boolean;
inclusion_state: InclusionState;
status: ControllerStatus;
nodes: ZWaveJSNodeStatus[];
supports_long_range: boolean;
}
-13
View File
@@ -619,19 +619,6 @@ export const demoServices: HassServices = {
},
},
climate: {
set_aux_heat: {
description: "Turn auxiliary heater on/off for climate device.",
fields: {
entity_id: {
description: "Name(s) of entities to change.",
example: "climate.kitchen",
},
aux_heat: {
description: "New value of axillary heater.",
example: "true",
},
},
},
set_temperature: {
description: "Set target temperature of climate device.",
fields: {
-10
View File
@@ -100,11 +100,6 @@ export class MockClimateEntity extends MockBaseEntity {
return;
}
if (service === "set_aux_heat") {
this.update({ attributes: { aux_heat: data.aux_heat } });
return;
}
super.handleService(domain, service, data);
}
@@ -227,11 +222,6 @@ export class MockClimateEntity extends MockBaseEntity {
: (attrs.swing_horizontal_mode ?? null);
}
// Aux heat
if (supportsFeatureFromAttributes(attrs, ClimateEntityFeature.AUX_HEAT)) {
stateAttrs.aux_heat = isOff ? null : (attrs.aux_heat ?? null);
}
return stateAttrs;
}
@@ -91,7 +91,7 @@ export class HaBackupDataPicker extends LitElement {
`ui.panel.${this.translationKeyPanel}.data_picker.${data.database_included ? "settings_and_history" : "settings"}`
),
id: "config",
version: data.homeassistant_version,
version: data.homeassistant_version ?? undefined,
});
}
items.push(
@@ -133,9 +133,9 @@ export class HaBackupDataPicker extends LitElement {
addonIcons: Record<string, boolean>
) =>
data.addons.map<BackupAddonItem>((addon) => ({
name: addon.name,
name: addon.name || addon.slug,
slug: addon.slug,
version: addon.version,
version: addon.version ?? undefined,
icon: addonIcons[addon.slug],
}))
);
@@ -85,7 +85,7 @@ export class HaBackupOverviewProgress extends LitElement {
private get _heading() {
const managerState = this.manager.manager_state;
if (managerState === "idle") {
if (managerState === "idle" || managerState === "blocked") {
return "";
}
return this.hass.localize(
@@ -94,7 +94,10 @@ export class HaBackupOverviewProgress extends LitElement {
}
private get _isUploadStage(): boolean {
if (this.manager.manager_state === "idle") {
if (
this.manager.manager_state === "idle" ||
this.manager.manager_state === "blocked"
) {
return false;
}
return this.manager.stage === "upload_to_agents";
@@ -314,7 +314,7 @@ class HaConfigBackupDetails extends LitElement {
try {
const response = await fetchBackupDetails(this.hass, this.backupId);
this._backup = response.backup;
this._agents = computeAgents(response.backup);
this._agents = response.backup ? computeAgents(response.backup) : [];
} catch (err: any) {
this._error =
err?.message ||
@@ -39,7 +39,7 @@ export class CloudRemotePref extends LitElement {
return nothing;
}
const { remote_enabled, remote_allow_remote_enable, strict_connection } =
const { remote_enabled, remote_allow_remote_enable } =
this.cloudStatus.prefs;
const {
@@ -120,17 +120,7 @@ export class CloudRemotePref extends LitElement {
)}
></ha-alert>
`
: strict_connection === "drop_connection"
? html`<ha-alert
alert-type="warning"
.title=${this.hass.localize(
`ui.panel.config.cloud.account.remote.drop_connection_warning_title`
)}
>${this.hass.localize(
`ui.panel.config.cloud.account.remote.drop_connection_warning`
)}</ha-alert
>`
: nothing
: nothing
}
<p>
${this.hass.localize(
@@ -1206,7 +1206,7 @@ ${rejected
dismissText: this.hass.localize("ui.common.cancel"),
destructive: true,
confirm: async () => {
const proms: Promise<DeviceRegistryEntry>[] = [];
const proms: Promise<DeviceRegistryEntry | null>[] = [];
this._selectedCanDelete.forEach((deviceId) => {
const entries = this.hass!.devices[deviceId]?.config_entries;
entries.forEach((entryId) => {
@@ -27,7 +27,7 @@ class EnergyValidationMessage extends LitElement {
>
${this.hass.localize(
`component.energy.issues.${issue.type}.description`,
issue.translation_placeholders
issue.translation_placeholders ?? undefined
)}
${
issue.type === "recorder_untracked"
@@ -264,7 +264,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
this._name = this.entry.name || "";
this._icon = this.entry.icon || "";
this._deviceClass =
this.entry.device_class || this.entry.original_device_class;
this.entry.device_class || this.entry.original_device_class || undefined;
this._origEntityId = this.entry.entity_id;
this._areaId = this.entry.area_id;
this._labels = this.entry.labels;
@@ -1231,6 +1231,8 @@ export class HaConfigEntities extends LitElement {
selectable: false,
entity_category: null,
has_entity_name: false,
original_name: null,
translation_key: null,
options: this._getExposedEntitySettingsAsOptions(entityId),
labels: [],
categories: {},
@@ -54,6 +54,7 @@ import {
import type {
IntegrationLogInfo,
IntegrationManifest,
IntegrationType,
} from "../../../data/integration";
import {
LogSeverity,
@@ -166,6 +167,14 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
private _subEntriesFetchId = 0;
/** Virtual integrations never have config entries, so they have no strings. */
private get _configEntryIntegrationType(): IntegrationType | undefined {
const integrationType = this._manifest?.integration_type;
return integrationType && integrationType !== "virtual"
? integrationType
: undefined;
}
@state() private _domainEntities: Record<string, string[]> = {};
@queryAll("ha-config-entry-row")
@@ -683,9 +692,9 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
this.hass.localize(
`component.${this.domain}.config.initiate_flow.user`
) ||
(this._manifest?.integration_type
(this._configEntryIntegrationType
? this.hass.localize(
`ui.panel.config.integrations.integration_page.add_${this._manifest.integration_type}`
`ui.panel.config.integrations.integration_page.add_${this._configEntryIntegrationType}`
)
: this.hass.localize(
`ui.panel.config.integrations.integration_page.add_entry`
@@ -834,9 +843,9 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
<div class="section">
<h3 class="section-header">
${
this._manifest?.integration_type
this._configEntryIntegrationType
? this.hass.localize(
`ui.panel.config.integrations.integration_page.entries_${this._manifest.integration_type}`
`ui.panel.config.integrations.integration_page.entries_${this._configEntryIntegrationType}`
)
: this.hass.localize(
`ui.panel.config.integrations.integration_page.entries`
@@ -251,6 +251,9 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
reason: null,
error_reason_translation_key: null,
error_reason_translation_placeholders: null,
// Synthetic entry for a YAML-only integration, it has no registry timestamps
created_at: 0,
modified_at: 0,
})
);
@@ -252,13 +252,13 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
slot="graphic"
.src=${brandsUrl(
{
domain: router.brand,
domain: router.brand ?? "",
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
)}
alt=${router.brand}
alt=${router.brand ?? ""}
crossorigin="anonymous"
referrerpolicy="no-referrer"
@error=${this._onImageError}
@@ -32,7 +32,7 @@ export class HassioHostname extends LitElement {
private async _fetchHostInfo() {
const hostInfo = await fetchHassioHostInfo(this.hass);
this._hostname = hostInfo.hostname;
this._hostname = hostInfo.hostname ?? undefined;
}
protected render() {
@@ -37,7 +37,7 @@ interface MoveDatadiskFormState {
const calculateMoveTime = memoizeOne((hostInfo: HassioHostInfo): number => {
// Assume a speed of 30 MB/s.
const moveTime = (hostInfo.disk_used * 1000) / 60 / 30;
const rebootTime = (hostInfo.startup_time * 4) / 60;
const rebootTime = ((hostInfo.startup_time ?? 0) * 4) / 60;
return Math.ceil((moveTime + rebootTime) / 10) * 10;
});
@@ -746,10 +746,13 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
this._data.push({
statistic_id: statisticId,
statistics_unit_of_measurement: "",
display_unit_of_measurement: "",
source: "",
name: null,
state: this._states[statisticId],
issues: issues[statisticId],
mean_type: StatisticMeanType.NONE,
has_mean: false,
has_sum: false,
unit_class: null,
});
@@ -121,10 +121,11 @@ export class AssistPipelineDetailConversation extends LitElement {
// wait for update of conversation_engine
setTimeout(() => {
const value = { ...this.data };
// The backend requires a language, "*" is its match-all value
if (this._supportedLanguages === "*") {
value.conversation_language = "*";
} else {
value.conversation_language = this._supportedLanguages?.[0] ?? null;
value.conversation_language = this._supportedLanguages?.[0] ?? "*";
}
fireEvent(this, "value-changed", { value });
}, 0);
@@ -293,7 +293,7 @@ export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
name: data.name!,
language: data.language!,
conversation_engine: data.conversation_engine!,
conversation_language: data.conversation_language ?? null,
conversation_language: data.conversation_language ?? "*",
prefer_local_intents: data.prefer_local_intents ?? true,
stt_engine: data.stt_engine ?? null,
stt_language: data.stt_language ?? null,
@@ -94,7 +94,7 @@ export class BrowserMediaPlayer {
: "playing";
stateObj.attributes = {
media_title: this.item.title,
entity_picture: this.item.thumbnail,
entity_picture: this.item.thumbnail ?? undefined,
volume_level: this.player.volume,
supported_features:
// eslint-disable-next-line no-bitwise
+2 -1
View File
@@ -7170,7 +7170,8 @@
"setup_retry": "Failed setup, will retry",
"not_loaded": "Not loaded",
"failed_unload": "Failed to unload",
"setup_in_progress": "Initializing"
"setup_in_progress": "Initializing",
"unload_in_progress": "Unloading"
},
"open_configuration_url": "Visit device",
"bronze_quality": "Bronze quality",
@@ -22,8 +22,10 @@ const buildMetadata = (
{
statistic_id: id,
statistics_unit_of_measurement: unit,
display_unit_of_measurement: unit,
source: "recorder",
name: null,
has_mean: !hasSum,
has_sum: hasSum,
mean_type: hasSum
? StatisticMeanType.NONE
+5 -1
View File
@@ -47,7 +47,9 @@ export const mockEntityEntry = (
has_entity_name: false,
unique_id: "",
id: "",
options: null,
original_name: null,
translation_key: null,
options: {},
categories: {},
created_at: 0,
modified_at: 0,
@@ -60,6 +62,8 @@ export const mockDevice = (
id: "",
config_entries: [],
config_entries_subentries: {},
config_entry_id: null,
config_subentry_id: null,
connections: [],
identifiers: [],
manufacturer: null,
@@ -28,8 +28,10 @@ const buildMetadata = (
{
statistic_id: id,
statistics_unit_of_measurement: unit,
display_unit_of_measurement: unit,
source: "recorder",
name: null,
has_mean: !hasSum,
has_sum: hasSum,
mean_type: hasSum
? StatisticMeanType.NONE
@@ -48,7 +48,8 @@ const makePrefs = (
alexa_enabled: true,
remote_enabled: true,
remote_allow_remote_enable: true,
strict_connection: "disabled",
alexa_default_expose: null,
google_default_expose: null,
google_secure_devices_pin: undefined,
cloudhooks: {},
alexa_report_state: true,
@@ -70,7 +71,7 @@ const makeStatus = (
email: "test@home-assistant.io",
google_registered: true,
google_entities: emptyFilter(),
google_domains: [],
google_local_connected: false,
alexa_registered: true,
alexa_entities: emptyFilter(),
prefs: makePrefs(prefsOverrides),