fix: close CODE-REVIEW-44-r1 M1-M2

M1: the room-climate cache keys on the STORED exclusion array reference
(stable across renders; _excluded builds a fresh Set per call), so
saving new Discovery filters recomputes climate immediately instead of
waiting for an unrelated hass tick.
M2: a device without a platform-bearing entity takes its integration
name from the identifier domain — the excluded reason can no longer
render a raw {integration} placeholder (if the exclusion matched,
at least one of the two names exists).

User-Visible: no
Issue: #44
This commit is contained in:
Codex
2026-08-30 14:29:19 +03:00
parent 34778d81e4
commit e4e1e3705f
22 changed files with 167 additions and 153 deletions
+11 -3
View File
@@ -11849,7 +11849,9 @@ export class HouseplanCard extends LitElement {
return key ? this._climate().get(key)?.hum ?? null : null;
}
private _climateCache: { h: any; r: any; mk: any; m: Map<string, AreaClimate> } | null = null;
private _climateCache: {
h: any; r: any; mk: any; ex: string[] | undefined; m: Map<string, AreaClimate>;
} | null = null;
/**
* Climate for every HA Area and explicitly placed local room, computed ONCE
@@ -11865,9 +11867,15 @@ export class HouseplanCard extends LitElement {
const mk = this._serverCfg?.markers;
const planHass = this._renderPlanHass;
const c = this._climateCache;
if (c && c.h === planHass && c.r === this._iconRules && c.mk === mk) return c.m;
// #44 r1-M1: the exclusion list is part of the key — saving new Discovery
// filters replaces the settings object, and room climate must follow the
// new exclusions immediately, not on the next unrelated hass tick. The
// STORED array reference is the key (stable across renders); _excluded
// itself builds a fresh Set per call when a list is present.
const ex = this._settings.exclude_integrations;
if (c && c.h === planHass && c.r === this._iconRules && c.mk === mk && c.ex === ex) return c.m;
const m = roomClimateMap(planHass, this._iconRules, mk, this._excluded);
this._climateCache = { h: planHass, r: this._iconRules, mk, m };
this._climateCache = { h: planHass, r: this._iconRules, mk, ex, m };
return m;
}
+6
View File
@@ -7637,6 +7637,12 @@ public _deviceInboxRows(): DeviceInboxRow[] {
const platforms = devicePlatforms.get(deviceId) || new Set<string>();
const identifierDomain = Array.isArray(device?.identifiers?.[0])
? String(device.identifiers[0][0] || '') : '';
// #44 r1-M2: a device without a single platform-bearing entity still has
// an integration name — its identifier domain. Without this fallback the
// reason text would render the raw {integration} placeholder.
if (identifierDomain && !integrationByBinding[binding]) {
integrationByBinding[binding] = identifierDomain;
}
const excluded = [identifierDomain, ...platforms].some((domain) => this.host._excluded.has(domain));
if (device?.entry_type === 'service') reasonByBinding[binding] = 'service_entry';
else if (excluded) reasonByBinding[binding] = 'excluded_integration';