fix: deduplicate entity markers from parent devices

Issue: #226
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-08-20 21:35:23 +03:00
parent d7c654d4c3
commit f151e70a27
14 changed files with 960 additions and 423 deletions
+6
View File
@@ -2,6 +2,12 @@
## Unreleased
- Placing an individual Home Assistant entity no longer leaves a duplicate
automatic marker of its complete parent device. The automatic marker now
contains only the remaining active visible entities and disappears when
none remain; two explicitly placed entity/device markers still coexist
([#226](https://github.com/Matysh/houseplan-card/issues/226)).
## v1.66.0 — 2026-08-20
- Text device markers once again use a capsule-shaped outer outline instead of
+7
View File
@@ -8,6 +8,13 @@
## Не выпущено
- Размещение отдельной сущности Home Assistant больше не оставляет рядом
дублирующий автоматический маркер всего родительского устройства. В
auto-marker теперь входят только оставшиеся активные видимые сущности, а при
пустом остатке он исчезает; две явно размещённые привязки entity/device
по-прежнему могут сосуществовать
([#226](https://github.com/Matysh/houseplan-card/issues/226)).
## v1.66.0 — 2026-08-20
- Текстовые маркеры устройств снова получают капсульную внешнюю обводку вместо
+10
View File
@@ -26,6 +26,12 @@ as the SEEDER of initial hidden flags.
automatic discovery cannot immediately recreate the deleted device. The
same binding remains available in Add; saving it again replaces the
tombstone and starts with a fresh position.
- A live `entity:X` marker owns X inside its automatic parent `device:D`. A
residual auto-device contains only active, HA-visible siblings not owned by
other entity markers and disappears when that set is empty. A user-hidden
live marker still owns X; an entity tombstone does not. An explicit
`device:D` remains complete and may intentionally coexist with explicit
entity markers.
- No marker — never evaluated by the seeder yet, or a plain physical device.
- `bindingStatus: ha_disabled` is runtime-only. It is derived from Home
Assistant's device/entity registries and is never written into a marker or
@@ -51,6 +57,10 @@ are never revisited. It fires on:
3. a new device appearing in a bound area — non-physical ones are hidden
silently (no red dot); physical ones keep the red-dot flow.
Entity-marker ownership uses the same residual projection here as in the
renderer. The seeder never turns an automatic parent with an empty or
HA-hidden-only residual into a persistent hidden `device:D` stub.
Until a config is seeded (`filter_seeded` absent), `buildDevices` applies the
LEGACY runtime filter, so a read-only client on an old config sees exactly
the old behaviour until an editing client materialises it.
+17
View File
@@ -676,6 +676,23 @@ separately promised workflows:
## Devices on the plan ★
- [ ] Auto devices appear only in rooms bound to their area [manual]
- [ ] **Entity/parent ownership (#226):** placing `entity:X` removes X from its
automatic parent. A visible unclaimed sibling keeps one residual parent;
an empty or HA-hidden-only residual removes it. State, primary/action,
`allEntities`, light/Glow and LQI cannot see X twice
[auto: unit `devices.test.mjs`; browser `smoke_entity_parent_dedup.mjs`].
- [ ] Two explicit markers `entity:X` + `device:D` coexist and the device stays
complete. A user-hidden live entity marker still owns X, while an entity
tombstone returns X to the parent; disabled entity ownership follows the
known full-registry relation [auto: unit `devices.test.mjs`].
- [ ] The #94 curtain boundary is deliberate: untouched hidden `cover.*` stays
cover-first, but after placing the only visible auxiliary switch the
hidden-only automatic remainder disappears. An explicit `device:D`
restores the complete curtain beside that entity marker
[auto: unit `devices.test.mjs`].
- [ ] Renderer and seeder cannot drift back to exact-binding-only ownership
[mutation: `entity-marker-kept-in-parent-device`,
`entity-marker-parent-seeded`].
- [ ] Filtering hides bridges/groups/scenes/excluded integrations; 👁 "show all" reveals [manual]
- [ ] Duplicate "name|area" numbered ("Lamp", "Lamp 2") [manual]
- [ ] Light groups fold their single lamps; `group_lights=false` unfolds [manual]
+10
View File
@@ -338,6 +338,16 @@ Newly discovered devices get a red dot until first opened in Device.
The same binding cannot be used by two markers.
When an exact HA entity belongs to a device, placing that entity gives its
channel to the entity marker. The automatic parent marker, if needed, contains
only the remaining active, HA-visible and unplaced entities; it disappears
when that residual is empty. HA-hidden siblings alone do not keep an automatic
parent on the plan. To show both the exact entity and the complete device,
place `entity:X` and `device:D` explicitly — two explicit markers are treated
as an intentional configuration. Deleting an entity marker returns the entity
to automatic parent discovery; its binding tombstone does not remove registry
data from the live HA device.
### Device editor
- drag a marker to save its server-side position;
+10
View File
@@ -573,6 +573,16 @@ House Plan читает реестры устройств, сущностей и
Одну и ту же привязку нельзя одновременно использовать в двух маркерах.
Если отдельная HA-сущность принадлежит устройству, после её размещения этот
канал принадлежит точному entity-marker. Автоматический родительский маркер,
если он ещё нужен, строится только из оставшихся активных, видимых в HA и не
размещённых отдельно сущностей; при пустом остатке он исчезает. Одни только
скрытые в HA siblings не удерживают auto-marker на плане. Чтобы одновременно
показать точную сущность и полное устройство, явно разместите и `entity:X`, и
`device:D` — два явных маркера считаются осознанной конфигурацией. После
удаления entity-marker сущность снова доступна автоматическому родителю:
binding tombstone не вырезает её из живого устройства HA.
### Редактор устройств
| Действие | Результат |
+11 -11
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"fixture": "synthetic-only",
"sourceFingerprint": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceFingerprint": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"captureScriptSha256": "34f2219790d46efd8250e7a1bd829cb8fc0b0547e1260635fefa52407551b41b",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -13,7 +13,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "2885f96e348b15ab7c696e56e99bddcd9bb2ee94218a883e5a2155f45f5042aa"
},
"view-touch": {
@@ -24,7 +24,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "f62d8af3617c00a5e99511bd765980d2e27badf25047a1be34046b64195abe6c"
},
"space-create": {
@@ -35,7 +35,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "c53db2e5c642a5549c13f3c93a5b359fed69bdb2621bf71a243a877ffcb95e6b"
},
"room-contour-close": {
@@ -46,7 +46,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "2f13ea645af306eee9cc7c52699c928377b72f412b08c1a3db6beb848ace1b51"
},
"plan-context-tray": {
@@ -57,7 +57,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "a6c526fede11bc3503fd2384bd4a3f6afa008f481c5c47578f06cae6c81c6f9b"
},
"device-editor": {
@@ -68,7 +68,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "9585b59add4d35b5a6f028ce5b720ef1b77a3f8b19436d192cd1a0e6fc637264"
},
"device-display-preview": {
@@ -79,7 +79,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "cfc317da4628d079a116ff71311fbf06b1eb3b181928a5ea7ba888ab936a5e8b"
},
"background-editor": {
@@ -90,7 +90,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "d7cfe70551d9260169df8efd832e32ed7df99c7f60b55d1fd4a8322bd4c47175"
},
"room-card": {
@@ -101,7 +101,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
},
"device-info": {
@@ -112,7 +112,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d2baf22d884daf235af39c2010d42f5fdefc4004ae30a45a6a4ce33115c52c8f",
"sourceSha256": "264380083a9a18bfc2305a6577368068d6839fabe82fe1a1f440a62867b4e589",
"imageSha256": "a06cbf83f09e2f67b3566d7c0b10e973db060c3d20786f26f74ada6a21937c3e"
}
}