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
+10 -7
View File
@@ -36,7 +36,7 @@ import { cardStyles } from './styles';
import { fitInSquare, contentBounds, spaceModels } from './space-geometry';
import { langOf, t, type I18nKey } from './i18n';
const CARD_VERSION = '1.51.0';
const CARD_VERSION = '1.51.1';
const LS_KEY = 'houseplan_card_layout_v1';
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
const LS_ZOOM = 'houseplan_card_zoom_v1';
@@ -4206,17 +4206,20 @@ class HouseplanCard extends LitElement {
const p = this._pos(d);
const left = ((p.x - view.x) / view.w) * 100;
const top = ((p.y - view.y) / view.h) * 100;
// a ghost is configuration, not status: no yellow, no open, no unavail
// a ghost is configuration, not status: no yellow, no open, no unavail
// and no live numbers either (HP-1510-02): no value text, no temperature,
// no humidity, no LQI badge, no state-morphed icon. The base icon and the
// name stay — enough to recognise the device and open its dialog.
const cls = d.hidden ? '' : this._stateClass(d);
const temp = this._liveTemp(d);
const hum = this._liveHum(d);
const lqi = showLqi && !d.virtual ? lqiFor(this.hass, d.entities) : null;
const temp = d.hidden ? null : this._liveTemp(d);
const hum = d.hidden ? null : this._liveHum(d);
const lqi = showLqi && !d.virtual && !d.hidden ? lqiFor(this.hass, d.entities) : null;
const m = d.marker;
const disp = m?.display || 'badge';
const ripple = disp === 'ripple' || disp === 'icon_ripple';
// value-only display: the measurement IS the marker
const primarySt = d.primary ? this.hass.states[d.primary] : undefined;
const valText = disp === 'value'
const valText = disp === 'value' && !d.hidden
? (temp != null ? temp + '°'
: hum != null ? hum + '%'
: primarySt && !isNaN(parseFloat(primarySt.state))
@@ -4225,7 +4228,7 @@ class HouseplanCard extends LitElement {
: null;
// live state variants of the auto icon (doors, locks, bulbs), like core HA
const domain = d.primary ? d.primary.split('.')[0] : null;
const icon = this._config?.live_states
const icon = this._config?.live_states && !d.hidden
? stateIcon(d.icon, domain, primarySt?.attributes?.device_class, primarySt?.state, !!m?.icon)
: d.icon;
// RGB lights color the icon (and the ripple, unless a custom ripple color is set);
+10 -5
View File
@@ -65,8 +65,13 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
loc,
iconRules,
});
// the static card never shows hidden devices — there is no editor here
const devs = all.filter((d) => d.space === o.spaceId && !d.hidden);
// Two lists, two jobs (HP-1510-01): AGGREGATION sees every device of the
// space — hidden ones still count toward room LQI, same as the full card —
// while RENDERING sees only the visible ones (there is no editor here, so
// hidden devices are never drawn). Filtering one list for both jobs made
// the same room show different Zigbee health on the two cards.
const spaceDevs = all.filter((d) => d.space === o.spaceId);
const devs = spaceDevs.filter((d) => !d.hidden);
const defPos = defaultPositions(devs, space, iconPct);
const roomShapes = space.rooms
@@ -83,9 +88,9 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
const fillC = r.area
? roomFillStyle(
fill,
fill === 'lqi' ? areaLqi(o.hass, devs, r.area) : null,
fill === 'light' ? areaLights(o.hass, devs, r.area) : 'none',
fill === 'temp' ? areaTemp(o.hass, devs, r.area) : null,
fill === 'lqi' ? areaLqi(o.hass, spaceDevs, r.area) : null,
fill === 'light' ? areaLights(o.hass, spaceDevs, r.area) : 'none',
fill === 'temp' ? areaTemp(o.hass, spaceDevs, r.area) : null,
disp.tempMin,
disp.tempMax,
fillColorsOf(o.cfg?.settings),