fix v1.33.4: device icon stays in place when rebinding or changing room

- rebinding migrates the layout position to the new marker id instead of
  recentering; room change within the same space keeps the position
  (owner's decision); new icons and cross-space moves still center
- smoke_marker_stay.mjs; TESTING/CHANGELOG same-commit
This commit is contained in:
Matysh
2026-07-22 15:29:54 +03:00
parent a90316c9f3
commit ac082569d7
10 changed files with 89 additions and 14 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory
FILES_URL = "/houseplan_files/files"
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.33.3"
VERSION = "1.33.4"
DEFAULT_CONFIG: dict = {
"spaces": [],
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -16,5 +16,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [],
"single_config_entry": true,
"version": "1.33.3"
"version": "1.33.4"
}
+46
View File
@@ -0,0 +1,46 @@
import { launch } from './serve.mjs';
const { page, browser } = await launch();
const res = await page.evaluate(async () => {
const out = {};
const c = window.__card;
c._setMode('devices'); await c.updateComplete;
const near = (a, b) => a && b && Math.abs(a.x - b.x) < 1e-6 && Math.abs(a.y - b.y) < 1e-6 && a.s === b.s;
// подопытный: авто-устройство с сохранённой позицией
const dev = c._devices.find((d) => !d.virtual && !d.marker && c._layout[d.id]);
out.hasDev = !!dev;
const pos0 = { ...c._layout[dev.id] };
// 1) смена комнаты (та же space) → позиция не меняется
const otherRoom = c._spaceModel(dev.space).rooms.find((r) => r.id && r.area && r.area !== dev.area);
out.hasOtherRoom = !!otherRoom;
c._openMarkerDialog(dev);
c._markerDialog = { ...c._markerDialog, room: dev.space + '#' + otherRoom.area };
await c._saveMarker(); await c.updateComplete;
out.stayAfterRoomChange = near(c._layout[dev.id], pos0);
// вернуть комнату назад
c._openMarkerDialog(c._devices.find((d) => d.id === dev.id));
c._markerDialog = { ...c._markerDialog, room: dev.space + '#' + dev.area };
await c._saveMarker(); await c.updateComplete;
// 2) смена привязки device -> entity (id меняется) → позиция мигрирует
const freeEnt = Object.keys(c.hass.states).find((e) => e.startsWith('sensor.') &&
!c._devices.some((d) => d.id === 'lg_' + e));
out.hasFreeEnt = !!freeEnt;
c._openMarkerDialog(c._devices.find((d) => d.id === dev.id));
c._markerDialog = { ...c._markerDialog, binding: 'entity:' + freeEnt };
await c._saveMarker(); await c.updateComplete;
const newId = 'lg_' + freeEnt;
out.migrated = near(c._layout[newId], pos0);
out.oldGone = !c._layout[dev.id];
// 3) новый виртуальный маркер по-прежнему центрируется в выбранной комнате
c._openMarkerDialog();
const room = c._spaceModel().rooms.find((r) => r.id && r.area);
c._markerDialog = { ...c._markerDialog, name: 'Тест', binding: 'virtual', room: c._space + '#' + room.area };
await c._saveMarker(); await c.updateComplete;
const vid = c._serverCfg.markers.find((m) => m.name === 'Тест')?.id;
const center = c._roomCenter(room);
const vpos = c._layout[vid];
const aspect = c._curSpaceCfg.aspect || 1;
out.newCentered = vpos && Math.abs(vpos.x * 1000 - center[0]) < 1 && Math.abs(vpos.y * (1000 / aspect) - center[1]) < 1;
return out;
});
console.log(JSON.stringify(res, null, 1));
await browser.close();
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+7
View File
@@ -1,5 +1,12 @@
# Changelog
## v1.33.4 — 2026-07-22
- Editing a device no longer makes its icon jump. Changing the HA binding
(which changes the marker id) migrates the saved position to the new id;
changing the room within the same space keeps the icon exactly where it
stands (previously it re-centered in the new room). Only a brand-new icon,
or a move to a room in a different space, is centered.
## v1.33.3 — 2026-07-22
- Device dialog: when no icon is set explicitly, the icon picker no longer
looks empty — it shows the **auto-derived icon** (from the icon rules /
+4
View File
@@ -140,6 +140,10 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
(explicit ripple color still wins); off/white lights unchanged [auto]
- [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any
display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static
- [ ] Icon stays on edit (v1.33.4): rebinding a device (HA device/entity) or
changing its room within the same space never moves the icon — the saved
or auto position migrates to the new marker id; only a brand-new icon or
a move to another space centers it in the target room [auto]
- [ ] Icon picker placeholder (v1.33.3): with no explicit icon the device
dialog's icon picker shows the auto-derived icon as its placeholder, plus
an "Auto: mdi:..." hint line with the icon preview; the hint disappears
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "houseplan-card",
"version": "1.33.3",
"version": "1.33.4",
"description": "Interactive house plan Lovelace card for Home Assistant",
"license": "MIT",
"type": "module",
+23 -5
View File
@@ -32,7 +32,7 @@ import './space-card';
import { cardStyles } from './styles';
import { langOf, t, type I18nKey } from './i18n';
const CARD_VERSION = '1.33.3';
const CARD_VERSION = '1.33.4';
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';
@@ -2218,11 +2218,29 @@ class HouseplanCard extends LitElement {
// remove the previous marker (by the old id and by the new id)
cfg.markers = cfg.markers.filter((m) => m.id !== id && m.id !== oldId);
cfg.markers.push(marker);
// position: a new icon OR the room changed → place it at the center of the room/space.
// Write POINT-WISE (layout/update), not the whole layout — a full layout/set overwrites
// positions changed in other windows (the v1.4.4 incident).
// Position rule (owner's decision, v1.33.4): editing an existing icon —
// rebinding it to another HA device/entity or to another room — must NOT
// move it. Its current position (saved or the ephemeral auto one) is
// migrated to the new marker id. Only two cases still center the icon:
// a truly NEW icon, and a move to a room in a DIFFERENT space (keeping
// the old coordinates there would be meaningless).
// Write POINT-WISE (layout/update), not the whole layout — a full layout/set
// overwrites positions changed in other windows (the v1.4.4 incident).
let newPos: { s: string; x: number; y: number } | null = null;
if (!this._layout[id] || roomChanged) {
const targetSpace = space || prevDev?.space || this._space;
const prevRec = oldId ? this._layout[oldId] : null;
const prevPos = prevRec
? { s: prevRec.s || prevDev?.space || this._space, x: prevRec.x, y: prevRec.y }
: oldId && prevDev && this._defPos[oldId]
? this._normPos(prevDev.space, this._defPos[oldId].x, this._defPos[oldId].y)
: null;
if (prevPos && prevPos.s === targetSpace) {
// stays in place; pin it under the (possibly new) id
if (id !== oldId || !this._layout[id] || roomChanged) {
newPos = { s: prevPos.s, x: prevPos.x, y: prevPos.y };
this._layout = { ...this._layout, [id]: newPos };
}
} else if (!this._layout[id] || roomChanged) {
const spm = this._spaceModel(space || undefined);
let cx = spm.vb[0] + spm.vb[2] / 2;
let cy = spm.vb[1] + spm.vb[3] / 2;