Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0188f2f
Mark is_on property as mandatory in binary sensors and toggle entitie…
epenet Feb 19, 2026
9e87fa7
Mark entity capability/state attribute type hints as mandatory (#163300)
epenet Feb 19, 2026
7fa5111
Update Anthropic repair flow (#163303)
Shulyaka Feb 19, 2026
05f9e25
Pump pyliebherrhomeapi to 0.3.0 (#163450)
mettolen Feb 19, 2026
77159e6
Improve error handling in Uptime Kuma (#163477)
tr4nt0r Feb 19, 2026
eb7e003
Fixing minor case errors in strings for systemnexa2 (#163567)
konsulten Feb 19, 2026
a3fd2f6
Add switch platform to Indevolt integration (#163522)
Xirt Feb 19, 2026
e6dbed0
Use shorthand attributes in geonetnz_quakes (#163568)
epenet Feb 19, 2026
865ec96
Add notify platform to HTML5 integration (#163229)
tr4nt0r Feb 19, 2026
05abe7e
Add callback inline keyboard tests for Telegram bot (#163328)
hanwg Feb 19, 2026
36c560b
Add flow rate (stat_rate) tracking for gas and water (#163274)
MindFreeze Feb 19, 2026
6f49f9a
NRGkick: do not update vehicle connected timestamp when vehicle is no…
andijakl Feb 19, 2026
2055082
Handle Mastodon auth fail in coordinator (#163234)
andrew-codechimp Feb 19, 2026
b2679dd
Update json fixture to reflect response from current LHM versions (#1…
Sab44 Feb 19, 2026
3c9a505
Handle gateway issues during setup in EnOcean integration (#163168)
CFenner Feb 19, 2026
882a44a
Fix touchline_sl zone availability when alarm state is set (#163338)
molsmadsen Feb 19, 2026
6b395b2
Add test for device_class inheritance in the min/max integration (#16…
JannisPohle Feb 19, 2026
c647ab1
Add proper ImplementationUnvailable handling to onedrive for business…
zweckj Feb 19, 2026
43dccf1
Add room correction intensity to Cambridge Audio (#163306)
noahhusby Feb 19, 2026
e009440
Mark action-setup quality scale rule as done for Advantage Air (#163208)
Bre77 Feb 19, 2026
7f35835
Combine matter snapshot tests (#162695)
epenet Feb 19, 2026
03d9c2c
Add Trane Local integration (#163301)
bdraco Feb 19, 2026
e8885de
add number platform to Velux integration for ExteriorHeating nodes (#…
wollew Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions homeassistant/brands/american_standard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"domain": "american_standard",
"name": "American Standard",
"integrations": ["nexia", "trane"]
}
5 changes: 5 additions & 0 deletions homeassistant/brands/trane.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"domain": "trane",
"name": "Trane",
"integrations": ["nexia", "trane"]
}
4 changes: 1 addition & 3 deletions homeassistant/components/advantage_air/quality_scale.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
rules:
# Bronze
action-setup:
status: todo
comment: https://developers.home-assistant.io/blog/2025/09/25/entity-services-api-changes/
action-setup: done
appropriate-polling: done
brands: done
common-modules: done
Expand Down
7 changes: 0 additions & 7 deletions homeassistant/components/anthropic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

from .const import (
CONF_CHAT_MODEL,
DATA_REPAIR_DEFER_RELOAD,
DEFAULT_CONVERSATION_NAME,
DEPRECATED_MODELS,
DOMAIN,
Expand All @@ -34,7 +33,6 @@

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Anthropic."""
hass.data.setdefault(DOMAIN, {}).setdefault(DATA_REPAIR_DEFER_RELOAD, set())
await async_migrate_integration(hass)
return True

Expand Down Expand Up @@ -85,11 +83,6 @@ async def async_update_options(
hass: HomeAssistant, entry: AnthropicConfigEntry
) -> None:
"""Update options."""
defer_reload_entries: set[str] = hass.data.setdefault(DOMAIN, {}).setdefault(
DATA_REPAIR_DEFER_RELOAD, set()
)
if entry.entry_id in defer_reload_entries:
return
await hass.config_entries.async_reload(entry.entry_id)


Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/anthropic/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
CONF_WEB_SEARCH_COUNTRY = "country"
CONF_WEB_SEARCH_TIMEZONE = "timezone"

DATA_REPAIR_DEFER_RELOAD = "repair_defer_reload"

DEFAULT = {
CONF_CHAT_MODEL: "claude-haiku-4-5",
CONF_MAX_TOKENS: 3000,
Expand Down
5 changes: 1 addition & 4 deletions homeassistant/components/anthropic/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ rules:
Integration does not subscribe to events.
entity-unique-id: done
has-entity-name: done
runtime-data:
status: todo
comment: |
To redesign deferred reloading.
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
Expand Down
183 changes: 44 additions & 139 deletions homeassistant/components/anthropic/repairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@
from homeassistant.config_entries import ConfigEntryState, ConfigSubentry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
SelectSelectorConfig,
)

from .config_flow import get_model_list
from .const import (
CONF_CHAT_MODEL,
DATA_REPAIR_DEFER_RELOAD,
DEFAULT,
DEPRECATED_MODELS,
DOMAIN,
)
from .const import CONF_CHAT_MODEL, DEFAULT, DEPRECATED_MODELS, DOMAIN

if TYPE_CHECKING:
from . import AnthropicConfigEntry
Expand All @@ -33,42 +31,40 @@ class ModelDeprecatedRepairFlow(RepairsFlow):
_subentry_iter: Iterator[tuple[str, str]] | None
_current_entry_id: str | None
_current_subentry_id: str | None
_reload_pending: set[str]
_pending_updates: dict[str, dict[str, str]]
_model_list_cache: dict[str, list[SelectOptionDict]] | None

def __init__(self) -> None:
"""Initialize the flow."""
super().__init__()
self._subentry_iter = None
self._current_entry_id = None
self._current_subentry_id = None
self._reload_pending = set()
self._pending_updates = {}
self._model_list_cache = None

async def async_step_init(
self, user_input: dict[str, str] | None = None
self, user_input: dict[str, str]
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
previous_entry_id: str | None = None
if user_input is not None:
previous_entry_id = self._async_update_current_subentry(user_input)
self._clear_current_target()
"""Handle the steps of a fix flow."""
if user_input.get(CONF_CHAT_MODEL):
self._async_update_current_subentry(user_input)

target = await self._async_next_target()
next_entry_id = target[0].entry_id if target else None
if previous_entry_id and previous_entry_id != next_entry_id:
await self._async_apply_pending_updates(previous_entry_id)
if target is None:
await self._async_apply_all_pending_updates()
return self.async_create_entry(data={})

entry, subentry, model = target
client = entry.runtime_data
model_list = [
model_option
for model_option in await get_model_list(client)
if not model_option["value"].startswith(tuple(DEPRECATED_MODELS))
]
if self._model_list_cache is None:
self._model_list_cache = {}
if entry.entry_id in self._model_list_cache:
model_list = self._model_list_cache[entry.entry_id]
else:
client = entry.runtime_data
model_list = [
model_option
for model_option in await get_model_list(client)
if not model_option["value"].startswith(tuple(DEPRECATED_MODELS))
]
self._model_list_cache[entry.entry_id] = model_list

if "opus" in model:
suggested_model = "claude-opus-4-5"
Expand Down Expand Up @@ -124,6 +120,8 @@ async def _async_next_target(
except StopIteration:
return None

# Verify that the entry/subentry still exists and the model is still
# deprecated. This may have changed since we started the repair flow.
entry = self.hass.config_entries.async_get_entry(entry_id)
if entry is None:
continue
Expand All @@ -132,46 +130,38 @@ async def _async_next_target(
if subentry is None:
continue

model = self._pending_model(entry_id, subentry_id)
if model is None:
model = subentry.data.get(CONF_CHAT_MODEL)
model = subentry.data.get(CONF_CHAT_MODEL)
if not model or not model.startswith(tuple(DEPRECATED_MODELS)):
continue

self._current_entry_id = entry_id
self._current_subentry_id = subentry_id
return entry, subentry, model

def _async_update_current_subentry(self, user_input: dict[str, str]) -> str | None:
def _async_update_current_subentry(self, user_input: dict[str, str]) -> None:
"""Update the currently selected subentry."""
if not self._current_entry_id or not self._current_subentry_id:
return None

entry = self.hass.config_entries.async_get_entry(self._current_entry_id)
if entry is None:
return None

subentry = entry.subentries.get(self._current_subentry_id)
if subentry is None:
return None
if (
self._current_entry_id is None
or self._current_subentry_id is None
or (
entry := self.hass.config_entries.async_get_entry(
self._current_entry_id
)
)
is None
or (subentry := entry.subentries.get(self._current_subentry_id)) is None
):
raise HomeAssistantError("Subentry not found")

updated_data = {
**subentry.data,
CONF_CHAT_MODEL: user_input[CONF_CHAT_MODEL],
}
if updated_data == subentry.data:
return entry.entry_id
self._queue_pending_update(
entry.entry_id,
subentry.subentry_id,
updated_data[CONF_CHAT_MODEL],
self.hass.config_entries.async_update_subentry(
entry,
subentry,
data=updated_data,
)
return entry.entry_id

def _clear_current_target(self) -> None:
"""Clear current target tracking."""
self._current_entry_id = None
self._current_subentry_id = None

def _format_subentry_type(self, subentry_type: str) -> str:
"""Return a user-friendly subentry type label."""
Expand All @@ -181,91 +171,6 @@ def _format_subentry_type(self, subentry_type: str) -> str:
return "AI task"
return subentry_type

def _queue_pending_update(
self, entry_id: str, subentry_id: str, model: str
) -> None:
"""Store a pending model update for a subentry."""
self._pending_updates.setdefault(entry_id, {})[subentry_id] = model

def _pending_model(self, entry_id: str, subentry_id: str) -> str | None:
"""Return a pending model update if one exists."""
return self._pending_updates.get(entry_id, {}).get(subentry_id)

def _mark_entry_for_reload(self, entry_id: str) -> None:
"""Prevent reload until repairs are complete for the entry."""
self._reload_pending.add(entry_id)
defer_reload_entries: set[str] = self.hass.data.setdefault(
DOMAIN, {}
).setdefault(DATA_REPAIR_DEFER_RELOAD, set())
defer_reload_entries.add(entry_id)

async def _async_reload_entry(self, entry_id: str) -> None:
"""Reload an entry once all repairs are completed."""
if entry_id not in self._reload_pending:
return

entry = self.hass.config_entries.async_get_entry(entry_id)
if entry is not None and entry.state is not ConfigEntryState.LOADED:
self._clear_defer_reload(entry_id)
self._reload_pending.discard(entry_id)
return

if entry is not None:
await self.hass.config_entries.async_reload(entry_id)

self._clear_defer_reload(entry_id)
self._reload_pending.discard(entry_id)

def _clear_defer_reload(self, entry_id: str) -> None:
"""Remove entry from the deferred reload set."""
defer_reload_entries: set[str] = self.hass.data.setdefault(
DOMAIN, {}
).setdefault(DATA_REPAIR_DEFER_RELOAD, set())
defer_reload_entries.discard(entry_id)

async def _async_apply_pending_updates(self, entry_id: str) -> None:
"""Apply pending subentry updates for a single entry."""
updates = self._pending_updates.pop(entry_id, None)
if not updates:
return

entry = self.hass.config_entries.async_get_entry(entry_id)
if entry is None or entry.state is not ConfigEntryState.LOADED:
return

changed = False
for subentry_id, model in updates.items():
subentry = entry.subentries.get(subentry_id)
if subentry is None:
continue

updated_data = {
**subentry.data,
CONF_CHAT_MODEL: model,
}
if updated_data == subentry.data:
continue

if not changed:
self._mark_entry_for_reload(entry_id)
changed = True

self.hass.config_entries.async_update_subentry(
entry,
subentry,
data=updated_data,
)

if not changed:
return

await self._async_reload_entry(entry_id)

async def _async_apply_all_pending_updates(self) -> None:
"""Apply all pending updates across entries."""
for entry_id in list(self._pending_updates):
await self._async_apply_pending_updates(entry_id)


async def async_create_fix_flow(
hass: HomeAssistant,
Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/cambridge_audio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

from .const import CONNECT_TIMEOUT, DOMAIN, STREAM_MAGIC_EXCEPTIONS

PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.SELECT, Platform.SWITCH]
PLATFORMS: list[Platform] = [
Platform.MEDIA_PLAYER,
Platform.NUMBER,
Platform.SELECT,
Platform.SWITCH,
]

_LOGGER = logging.getLogger(__name__)

Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/cambridge_audio/icons.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"entity": {
"number": {
"room_correction_intensity": {
"default": "mdi:home-sound-out"
}
},
"select": {
"audio_output": {
"default": "mdi:audio-input-stereo-minijack"
Expand Down
Loading
Loading