v1.51.1: the v1.51.0 review (HP-1510-01, HP-1510-02)

- HP-1510-01: the static card's visibility filter had quietly become its
  aggregation filter — the same room showed different Zigbee health on the
  two cards. Two lists now: aggregation (room LQI, temp) sees every device
  of the space including hidden ones, rendering sees visible only. Light
  fill keeps excluding hidden through areaLights itself, so the contract
  stays exactly as agreed: hidden counts toward signal, casts no light.
- HP-1510-02: the ghost suppressed state colors but still painted value
  text, temperature, humidity, the LQI badge and the state-morphed icon.
  All live numbers are gated on d.hidden now — a ghost is the base icon and
  the name, nothing else.

smoke_hidden_flag grew both audit vectors: the 42 kW value-display ghost
renders no numbers, and a room whose only Zigbee devices are hidden paints
the identical lqi fill on the full and the static card.
This commit is contained in:
Matysh
2026-07-29 15:14:06 +03:00
parent 4e736d49a3
commit fc95a1f09b
14 changed files with 158 additions and 40 deletions
+73
View File
@@ -73,4 +73,77 @@ Object.assign(out, await page.evaluate(async () => {
return o;
}));
// --- HP-1510-02: призрак не показывает live-значения ----------------------
Object.assign(out, await page.evaluate(async () => {
const o = {};
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
// датчик с числом + display:value, скрыт
c.hass = { ...c.hass, states: { ...c.hass.states,
'sensor.power_meter': { state: '42', attributes: { unit_of_measurement: 'kW' } } } };
c._serverCfg.markers = c._serverCfg.markers || [];
c._serverCfg.markers.push({ id: 'pm', binding: 'entity:sensor.power_meter',
display: 'value', hidden: true, space: 'f1' });
c._cfgEpoch++; c._regSignature = '';
c._maybeRebuildDevices();
c._setMode('devices'); c._showHidden = true;
c.requestUpdate(); await c.updateComplete;
const ghosts = [...sr().querySelectorAll('.dev.ghost')];
const pm = ghosts.find((g) => g.textContent.includes('42')) || null;
o.ghostHidesValue = pm === null; // «42 kW» не отрисован
o.ghostNoLiveBadges = ghosts.every((g) =>
!g.querySelector('.valtext') && !g.querySelector('.tval') && !g.querySelector('.hval') && !g.querySelector('.lqi'));
o.ghostKeepsIcon = ghosts.every((g) => !!g.querySelector('ha-icon') || g.classList.contains('noicon'));
c._setMode('view'); c._showHidden = false;
return o;
}));
// --- HP-1510-01: LQI комнаты одинаков на полной и статичной карточке ------
Object.assign(out, await page.evaluate(async () => {
const o = {};
const c = window.__card;
await customElements.whenDefined('houseplan-space-card');
const cfg = JSON.parse(JSON.stringify(c._serverCfg));
const f1 = cfg.spaces.find((s) => s.id === 'f1');
f1.settings = { ...(f1.settings || {}), fill_mode: 'lqi', show_borders: true };
// прячем ВСЕ устройства зоны living_room: комнату красит только скрытое
for (const d of c._devices.filter((x) => x.area === 'living_room' && !x.virtual)) {
if (!cfg.markers.some((m) => m.id === d.id)) {
cfg.markers.push({ id: d.id, binding: d.bindingKind + ':' + d.bindingRef, hidden: true });
} else {
cfg.markers = cfg.markers.map((m) => (m.id === d.id ? { ...m, hidden: true } : m));
}
}
c._serverCfg = cfg; c._cfgEpoch++; c._regSignature = '';
c._maybeRebuildDevices(); c.requestUpdate(); await c.updateComplete;
const fullRoom = [...(c.shadowRoot || c.renderRoot).querySelectorAll('.room')]
.find((r) => (r.getAttribute('style') || '').includes('--room-fill'));
const fullFill = fullRoom ? (fullRoom.getAttribute('style').match(/--room-fill:([^;]+)/) || [])[1] : null;
const hass = { ...c.hass, callWS: async (m) => {
if (m.type === 'houseplan/config/get') return { config: cfg, rev: 1 };
if (m.type === 'houseplan/layout/get') return { layout: {}, rev: 1 };
return { ok: true };
} };
const host = document.createElement('div');
document.body.appendChild(host);
const card = document.createElement('houseplan-space-card');
card.setConfig({ type: 'custom:houseplan-space-card', space: 'f1' });
card.hass = hass;
host.appendChild(card);
const t0 = Date.now();
while (!card.renderRoot?.querySelector('.room') && Date.now() - t0 < 6000) {
await new Promise((r) => setTimeout(r, 60));
}
await card.updateComplete;
const stRoom = [...card.renderRoot.querySelectorAll('.room')]
.find((r) => (r.getAttribute('style') || '').includes('--room-fill'));
const stFill = stRoom ? (stRoom.getAttribute('style').match(/--room-fill:([^;]+)/) || [])[1] : null;
o.fullPaintsHiddenLqi = !!fullFill;
o.staticPaintsHiddenLqi = !!stFill;
o.lqiParity = !!fullFill && fullFill === stFill;
host.remove();
return o;
}));
await finish(browser, checkAll(out));