s`
${this._targetText(e.target)}
`)}
${d.remoteCount?s`
{let n=t.get(e);return n||(n={painted:null,pending:null,raf:0},t.set(e,n)),n},o=e=>`${e.x} ${e.y} ${e.w} ${e.h}`;const r=e=>0===e.translateXPercent&&0===e.translateYPercent&&1===e.scaleX&&1===e.scaleY,a=e=>[e.x,e.y,e.w,e.h].every(Number.isFinite)&&e.w>0&&e.h>0,s=(e,t)=>{if(!t)return e.style.removeProperty("transform"),e.style.removeProperty("transform-origin"),void e.style.removeProperty("will-change");e.style.transformOrigin="0 0",e.style.willChange="transform",e.style.transform=`translate(${t.translateXPercent}%,${t.translateYPercent}%) scale(${t.scaleX},${t.scaleY})`};function i(e,t,n){if(!a(t.view)||!a(n.view)||!a(n.floor))return;const i=o(n.view),l=o(n.floor);for(const t of e.querySelectorAll('[data-hp-live-viewbox="camera"]'))t.setAttribute("viewBox",i);for(const t of e.querySelectorAll('[data-hp-live-viewbox="floor"]'))t.setAttribute("viewBox",l);const c=(d=t.view,f=n.view,{translateXPercent:(d.x-f.x)/f.w*100,translateYPercent:(d.y-f.y)/f.h*100,scaleX:d.w/f.w,scaleY:d.h/f.h});var d,f;for(const t of e.querySelectorAll('[data-hp-live-layer="camera"]'))s(t,r(c)?null:c);const u=e.querySelector("[data-hp-live-zoom]");if(u){u.hidden=n.zoom<=1;const e=u.querySelector("[data-hp-live-zoom-value]");e&&(e.textContent=`${Math.round(100*n.zoom)}%`)}}const l=e=>{const t=e._viewOr(e._baseVb());return{view:{...t},floor:{...e._floorView(t)},zoom:e._zoom}};const c=new WeakMap,d=(e,t,n,o)=>{if(!n)return;const r=document.createElement("span");if(r.className="m",t){r.append(`${t} `);const e=document.createElement("b");e.textContent=n,o&&(e.style.color=o),r.append(e)}else r.textContent=n;e.append(r)},f=(e,t,n)=>{const o=e.querySelector(t);o&&(o.setAttribute("d",n),o.toggleAttribute("hidden",!n))};function u(t){const n=t,o=n.renderRoot;if(!o)return;((t,n)=>{const o=n.querySelector("[data-hp-live-tip]");if(!o)return;const r=t._tip;if(!r)return o.hidden=!0,void o.replaceChildren();o.style.left=`${r.x+12}px`,o.style.top=`${r.y+12}px`,o.replaceChildren();const a=document.createElement("b");a.textContent=r.title,o.append(a),d(o,"",r.meta),d(o,t._t("tip.temp_avg"),null==r.temp?"":`${r.temp}°`),d(o,t._t("tip.hum_avg"),null==r.hum?"":`${r.hum}%`),d(o,t._t("tip.lqi"),null==r.lqi?"":String(r.lqi),null==r.lqi?void 0:e(r.lqi)),o.hidden=!1})(n,o);const r=n._hoverRoom,a=c.get(t),s=r?.space||"",i=r?.room||null;if(a?.room===i&&a?.space===s)return;c.set(t,{room:i,space:s});const l=r&&n._spaceModel(),u=l?n._roomHoverPaths(l):null;f(o,'[data-hp-live-room-hover="fill"]',u?.fillD||""),f(o,'[data-hp-live-room-hover="halo"]',u?.outlineD||""),f(o,'[data-hp-live-room-hover="outline"]',u?.outlineD||"")}function m(e){c.delete(e)}class p{constructor(e){this.host=e,this.deferredHass=!1}hass(e,t,n,o){const r=function(e,t,n){if(e===t)return"none";if(!e||!t||!n)return"structural";if(e.connection!==t.connection||e.entities!==t.entities||e.devices!==t.devices||e.areas!==t.areas||e.themes!==t.themes||e.user!==t.user||e.config!==t.config||e.floors!==t.floors||e.services!==t.services||e.panels!==t.panels||e.language!==t.language||e.locale?.language!==t.locale?.language||e.locale?.number_format!==t.locale?.number_format||e.locale?.time_format!==t.locale?.time_format||e.locale?.date_format!==t.locale?.date_format)return"structural";const o=new Set(["states","connection","entities","devices","areas","themes","user","config","floors","services","panels","language","locale"]);for(const n of new Set([...Object.keys(e),...Object.keys(t)]))if(!o.has(n)&&e[n]!==t[n])return"structural";for(const o of n.entityIds)if(e.states?.[o]!==t.states?.[o])return"state";return"none"}(e,t,n),a="state"===r&&this.active();a&&(this.deferredHass=!0);const s="none"!==r&&!a;return s||this.host._renderLife.observe(e,t,n,o),s}clear(){this.deferredHass=!1}take(){const e=this.deferredHass;return this.deferredHass=!1,e}viewport(e=!1){!function(e,t=!1){const o=e,r=n(e),a=l(o);if(t)return r.raf&&"function"==typeof cancelAnimationFrame&&cancelAnimationFrame(r.raf),r.raf=0,r.pending=null,r.painted||(r.painted=a),void i(o.renderRoot,r.painted,a);r.pending=a,r.raf||"function"!=typeof requestAnimationFrame||(r.raf=requestAnimationFrame(()=>{r.raf=0;const e=r.pending;r.pending=null;const t=o.renderRoot;e&&t&&(r.painted||(r.painted=e),i(t,r.painted,e))}))}(this.host,e)}hover(){u(this.host)}active(){const e=this.host;return e._pointers.size>0||e._cameraTransition.active||!!e._deviceDrag||!!e._physicalDrag||!!e._physicalRotate||!!e._decorMove||!!e._decorDraft||!!e._dtDrag||!!e._bdDrag||!!e._opDrag||!!e._resize?.dragging}commit(){!function(e){const t=e,o=n(e);o.raf&&"function"==typeof cancelAnimationFrame&&cancelAnimationFrame(o.raf),o.raf=0,o.pending=null,o.painted=l(t);const r=t.renderRoot;if(r){for(const e of r.querySelectorAll('[data-hp-live-layer="camera"]'))s(e,null);i(r,o.painted,o.painted)}}(this.host),m(this.host),u(this.host)}dispose(){!function(e){const n=t.get(e);n?.raf&&"function"==typeof cancelAnimationFrame&&cancelAnimationFrame(n.raf),t.delete(e)}(this.host),m(this.host)}}export{p as LiveRuntime};
+globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="450863830ad6931b7698085deecbd39f6686a3e84f1b777008be5347426f4b21";import{dx as e}from"./houseplan-card-Dc1_eajM.js";const t=new WeakMap,n=e=>{let n=t.get(e);return n||(n={painted:null,pending:null,raf:0},t.set(e,n)),n},o=e=>`${e.x} ${e.y} ${e.w} ${e.h}`;const r=e=>0===e.translateXPercent&&0===e.translateYPercent&&1===e.scaleX&&1===e.scaleY,a=e=>[e.x,e.y,e.w,e.h].every(Number.isFinite)&&e.w>0&&e.h>0,s=(e,t)=>{if(!t)return e.style.removeProperty("transform"),e.style.removeProperty("transform-origin"),void e.style.removeProperty("will-change");e.style.transformOrigin="0 0",e.style.willChange="transform",e.style.transform=`translate(${t.translateXPercent}%,${t.translateYPercent}%) scale(${t.scaleX},${t.scaleY})`};function i(e,t,n){if(!a(t.view)||!a(n.view)||!a(n.floor))return;const i=o(n.view),l=o(n.floor);for(const t of e.querySelectorAll('[data-hp-live-viewbox="camera"]'))t.setAttribute("viewBox",i);for(const t of e.querySelectorAll('[data-hp-live-viewbox="floor"]'))t.setAttribute("viewBox",l);const c=(f=t.view,u=n.view,{translateXPercent:(f.x-u.x)/u.w*100,translateYPercent:(f.y-u.y)/u.h*100,scaleX:f.w/u.w,scaleY:f.h/u.h});var f,u;for(const t of e.querySelectorAll('[data-hp-live-layer="camera"]'))s(t,r(c)?null:c);const d=e.querySelector("[data-hp-live-zoom]");if(d){d.hidden=n.zoom<=1;const e=d.querySelector("[data-hp-live-zoom-value]");e&&(e.textContent=`${Math.round(100*n.zoom)}%`)}}const l=e=>{const t=e._viewOr(e._baseVb());return{view:{...t},floor:{...e._floorView(t)},zoom:e._zoom}};const c=new WeakMap,f=(e,t,n,o)=>{if(!n)return;const r=document.createElement("span");if(r.className="m",t){r.append(`${t} `);const e=document.createElement("b");e.textContent=n,o&&(e.style.color=o),r.append(e)}else r.textContent=n;e.append(r)},u=(e,t,n)=>{const o=e.querySelector(t);o&&(o.setAttribute("d",n),o.toggleAttribute("hidden",!n))};function d(t){const n=t,o=n.renderRoot;if(!o)return;((t,n)=>{const o=n.querySelector("[data-hp-live-tip]");if(!o)return;const r=t._tip;if(!r)return o.hidden=!0,void o.replaceChildren();o.style.left=`${r.x+12}px`,o.style.top=`${r.y+12}px`,o.replaceChildren();const a=document.createElement("b");a.textContent=r.title,o.append(a),f(o,"",r.meta),f(o,t._t("tip.temp_avg"),null==r.temp?"":`${r.temp}°`),f(o,t._t("tip.hum_avg"),null==r.hum?"":`${r.hum}%`),f(o,t._t("tip.lqi"),null==r.lqi?"":String(r.lqi),null==r.lqi?void 0:e(r.lqi)),o.hidden=!1})(n,o);const r=n._hoverRoom,a=c.get(t),s=r?.space||"",i=r?.room||null;if(a?.room===i&&a?.space===s)return;c.set(t,{room:i,space:s});const l=r&&n._spaceModel(),d=l?n._roomHoverPaths(l):null;u(o,'[data-hp-live-room-hover="fill"]',d?.fillD||""),u(o,'[data-hp-live-room-hover="halo"]',d?.outlineD||""),u(o,'[data-hp-live-room-hover="outline"]',d?.outlineD||"")}function m(e){c.delete(e)}class p{constructor(e){this.host=e,this.deferredHass=!1}hass(e,t,n,o){const r=function(e,t,n){if(e===t)return"none";if(!e||!t||!n)return"structural";if(e.connection!==t.connection||e.entities!==t.entities||e.devices!==t.devices||e.areas!==t.areas||e.themes!==t.themes||e.user!==t.user||e.config!==t.config||e.floors!==t.floors||e.services!==t.services||e.panels!==t.panels||e.language!==t.language||e.locale?.language!==t.locale?.language||e.locale?.number_format!==t.locale?.number_format||e.locale?.time_format!==t.locale?.time_format||e.locale?.date_format!==t.locale?.date_format)return"structural";const o=new Set(["states","connection","entities","devices","areas","themes","user","config","floors","services","panels","language","locale"]);for(const n of new Set([...Object.keys(e),...Object.keys(t)]))if(!o.has(n)&&e[n]!==t[n])return"structural";for(const o of n.entityIds)if(e.states?.[o]!==t.states?.[o])return"state";return"none"}(e,t,n),a="state"===r&&this.active();a&&(this.deferredHass=!0);const s="none"!==r&&!a;return s||this.host._renderLife.observe(e,t,n,o),s}clear(){this.deferredHass=!1}take(){const e=this.deferredHass;return this.deferredHass=!1,e}viewport(e=!1){!function(e,t=!1){const o=e,r=n(e),a=l(o);if(t)return r.raf&&"function"==typeof cancelAnimationFrame&&cancelAnimationFrame(r.raf),r.raf=0,r.pending=null,r.painted||(r.painted=a),void i(o.renderRoot,r.painted,a);r.pending=a,r.raf||"function"!=typeof requestAnimationFrame||(r.raf=requestAnimationFrame(()=>{r.raf=0;const e=r.pending;r.pending=null;const t=o.renderRoot;e&&t&&(r.painted||(r.painted=e),i(t,r.painted,e))}))}(this.host,e)}hover(){d(this.host)}active(){const e=this.host;return e._pointers.size>0||e._cameraTransition.active||!!e._deviceDrag||!!e._physicalDrag||!!e._physicalRotate||!!e._decorMove||!!e._decorDraft||!!e._dtDrag||!!e._bdDrag||!!e._opDrag||!!e._resize?.dragging}commit(){!function(e){const t=e,o=n(e);o.raf&&"function"==typeof cancelAnimationFrame&&cancelAnimationFrame(o.raf),o.raf=0,o.pending=null,o.painted=l(t);const r=t.renderRoot;if(r){for(const e of r.querySelectorAll('[data-hp-live-layer="camera"]'))s(e,null);i(r,o.painted,o.painted)}}(this.host),m(this.host),d(this.host)}dispose(){!function(e){const n=t.get(e);n?.raf&&"function"==typeof cancelAnimationFrame&&cancelAnimationFrame(n.raf),t.delete(e)}(this.host),m(this.host)}}export{p as LiveRuntime};
diff --git a/dist/houseplan-assets/zigbee-topology-CQHQu2NY.js b/dist/houseplan-assets/zigbee-topology-B2eQAXzT.js
similarity index 95%
rename from dist/houseplan-assets/zigbee-topology-CQHQu2NY.js
rename to dist/houseplan-assets/zigbee-topology-B2eQAXzT.js
index dec5b30e..fde1d08c 100644
--- a/dist/houseplan-assets/zigbee-topology-CQHQu2NY.js
+++ b/dist/houseplan-assets/zigbee-topology-B2eQAXzT.js
@@ -1 +1 @@
-globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d1cd3ecb18d8a872c7e231ce86e766515a15d278c981181d134e7be3d1076518";import{s as e,I as n}from"./houseplan-card-CmhTStvj.js";var t={title:"Zigbee links",toggle:"Show Zigbee links when hovering over a device",hint:"Shows observed direct neighbours only. Nothing is fetched while you hover.",help:"Shows which Zigbee devices each device talks to directly. Line colour is link quality (LQI) on the same scale as the device's LQI reading: red at 40 and below, green at 180 and above. A dashed line means quality was not reported. An arrow points to the next device on the way to the coordinator: one arrow leads out, incoming arrows are the devices that route through this one. A line without an arrow is a spare neighbour. This is the route tree House Plan builds, not the path a packet takes right now: a label at the end of an arrow means its target is not on this plan, and no arrow at all means the route is unknown. Data comes from the last load and goes stale; links are shown with a mouse only.",help_aria:"Help: Zigbee links",admin_only:"Topology data is available to Home Assistant administrators only.",save_first:"Save this setting before loading topology data.",zha:"ZHA",zha_read:"Read ZHA data",zha_hint:"Reads ZHA's cached topology; it does not start a radio scan.",z2m:"Zigbee2MQTT",z2m_topics:"Base topics (one per line)",z2m_update:"Update map",z2m_warning:"A network-map scan can take 10 seconds to 2 minutes and may temporarily reduce Zigbee responsiveness.",status_idle:"Data not loaded",status_loading:"Loading…",status_ready:"Received {time}",status_stale:"Received {time} · stale",status_partial:"Received {time} · some nodes were omitted",status_no_links:"Received {time} · no observed links found",error_permission:"Administrator permission is required.",error_unsupported:"This provider or Home Assistant API is unavailable.",error_timeout:"The provider did not respond in time.",error_invalid_topic:"Check the Zigbee2MQTT base topic.",error_invalid_payload:"The provider returned unsupported topology data.",error_provider:"Topology data could not be loaded.",remote_count:"+{n} in other spaces",route_device_not_on_plan:"device is not on the plan",route_coordinator_not_on_plan:"coordinator is not on the plan",route_other_space:"another space"};const i={en:t,ru:{title:"Связи Zigbee",toggle:"Показывать связи Zigbee при наведении на устройство",hint:"Показываются только наблюдаемые прямые соседи. Наведение ничего не запрашивает.",help:"Показывает, с какими устройствами Zigbee каждое связано напрямую. Цвет линии — качество связи (LQI) по той же шкале, что у показателя LQI устройства: красный при 40 и ниже, зелёный при 180 и выше. Пунктир — качество не сообщено. Стрелка ведёт к следующему устройству по пути к координатору: исходящая одна, входящие — те, кто ходит через это устройство. Линия без стрелки — запасной сосед. Это дерево маршрутов, которое строит House Plan, а не путь пакета в эту секунду: подпись на конце стрелки значит, что цель не на этом плане, а отсутствие стрелки — что путь неизвестен. Данные из последней загрузки и устаревают, связи видны только при работе мышью.",help_aria:"Справка: связи Zigbee",admin_only:"Данные топологии доступны только администраторам Home Assistant.",save_first:"Сохраните настройку перед загрузкой данных топологии.",zha:"ZHA",zha_read:"Прочитать данные ZHA",zha_hint:"Читает сохранённую топологию ZHA и не запускает радио-сканирование.",z2m:"Zigbee2MQTT",z2m_topics:"Базовые топики (по одному в строке)",z2m_update:"Обновить карту",z2m_warning:"Сканирование карты сети занимает от 10 секунд до 2 минут и временно может снизить отзывчивость Zigbee.",status_idle:"Данные не загружены",status_loading:"Загрузка…",status_ready:"Получено: {time}",status_stale:"Получено: {time} · данные устарели",status_partial:"Получено: {time} · часть узлов не показана",status_no_links:"Получено: {time} · наблюдаемые связи не найдены",error_permission:"Нужны права администратора.",error_unsupported:"Провайдер или API Home Assistant недоступен.",error_timeout:"Провайдер не ответил вовремя.",error_invalid_topic:"Проверьте базовый топик Zigbee2MQTT.",error_invalid_payload:"Провайдер вернул неподдерживаемые данные топологии.",error_provider:"Не удалось загрузить данные топологии.",remote_count:"+{n} в других пространствах",route_device_not_on_plan:"устройства нет на плане",route_coordinator_not_on_plan:"координатора нет на плане",route_other_space:"другое пространство"},de:{title:"Zigbee-Verbindungen",toggle:"Zigbee-Verbindungen beim Darüberfahren über ein Gerät anzeigen",hint:"Es werden nur beobachtete direkte Nachbarn angezeigt. Beim Darüberfahren werden keine Daten abgerufen.",help:"Zeigt, mit welchen Zigbee-Geräten jedes Gerät direkt verbunden ist. Die Linienfarbe ist die Verbindungsqualität (LQI) auf derselben Skala wie der LQI-Wert des Geräts: rot bei 40 und darunter, grün bei 180 und darüber. Eine gestrichelte Linie bedeutet, dass die Qualität nicht gemeldet wurde. Ein Pfeil zeigt auf das nächste Gerät auf dem Weg zum Koordinator: ein Pfeil führt hinaus, eingehende Pfeile sind die Geräte, die über dieses Gerät routen. Eine Linie ohne Pfeil ist ein Ersatznachbar. Das ist der Routenbaum, den House Plan bildet, nicht der Weg eines Pakets in diesem Moment: eine Beschriftung am Pfeilende bedeutet, dass das Ziel nicht auf diesem Plan liegt, und ein fehlender Pfeil, dass die Route unbekannt ist. Die Daten stammen aus dem letzten Laden und veralten; Verbindungen sind nur mit der Maus sichtbar.",help_aria:"Hilfe: Zigbee-Verbindungen",admin_only:"Topologiedaten sind nur für Home-Assistant-Administratoren verfügbar.",save_first:"Speichere diese Einstellung, bevor Topologiedaten geladen werden.",zha:"ZHA",zha_read:"ZHA-Daten lesen",zha_hint:"Liest den ZHA-Topologiecache; es wird kein Funkscan gestartet.",z2m:"Zigbee2MQTT",z2m_topics:"Basistopics (eines pro Zeile)",z2m_update:"Karte aktualisieren",z2m_warning:"Ein Netzwerkscan kann 10 Sekunden bis 2 Minuten dauern und Zigbee vorübergehend verlangsamen.",status_idle:"Daten nicht geladen",status_loading:"Wird geladen…",status_ready:"Empfangen: {time}",status_stale:"Empfangen: {time} · veraltet",status_partial:"Empfangen: {time} · einige Knoten fehlen",status_no_links:"Empfangen: {time} · keine beobachteten Verbindungen gefunden",error_permission:"Administratorrechte sind erforderlich.",error_unsupported:"Dieser Anbieter oder die Home-Assistant-API ist nicht verfügbar.",error_timeout:"Der Anbieter hat nicht rechtzeitig geantwortet.",error_invalid_topic:"Prüfe das Zigbee2MQTT-Basistopic.",error_invalid_payload:"Der Anbieter hat nicht unterstützte Topologiedaten geliefert.",error_provider:"Topologiedaten konnten nicht geladen werden.",remote_count:"+{n} in anderen Bereichen",route_device_not_on_plan:"Gerät ist nicht im Plan",route_coordinator_not_on_plan:"Koordinator ist nicht im Plan",route_other_space:"anderer Bereich"},fr:{title:"Liens Zigbee",toggle:"Afficher les liens Zigbee au survol d’un appareil",hint:"Seuls les voisins directs observés sont affichés. Le survol ne lance aucune requête.",help:"Montre avec quels appareils Zigbee chaque appareil communique directement. La couleur de la ligne est la qualité du lien (LQI) sur la même échelle que l'indicateur LQI de l'appareil : rouge à 40 et en dessous, vert à 180 et au-dessus. Une ligne pointillée signifie que la qualité n'a pas été communiquée. Une flèche pointe vers l'appareil suivant sur le chemin du coordinateur : une flèche part, les flèches entrantes sont les appareils qui passent par celui-ci. Une ligne sans flèche est un voisin de secours. C'est l'arbre de routes que construit House Plan, pas le trajet d'un paquet à cet instant : une étiquette au bout d'une flèche signifie que sa cible n'est pas sur ce plan, et l'absence de flèche que la route est inconnue. Les données proviennent du dernier chargement et vieillissent ; les liens ne sont visibles qu'à la souris.",help_aria:"Aide : liens Zigbee",admin_only:"Les données de topologie sont réservées aux administrateurs Home Assistant.",save_first:"Enregistrez ce réglage avant de charger les données de topologie.",zha:"ZHA",zha_read:"Lire les données ZHA",zha_hint:"Lit la topologie en cache de ZHA sans lancer d’analyse radio.",z2m:"Zigbee2MQTT",z2m_topics:"Topics de base (un par ligne)",z2m_update:"Actualiser la carte",z2m_warning:"L’analyse de la carte réseau peut durer de 10 secondes à 2 minutes et réduire temporairement la réactivité de Zigbee.",status_idle:"Données non chargées",status_loading:"Chargement…",status_ready:"Reçu : {time}",status_stale:"Reçu : {time} · obsolète",status_partial:"Reçu : {time} · certains nœuds sont omis",status_no_links:"Reçu : {time} · aucun lien observé trouvé",error_permission:"Les droits administrateur sont requis.",error_unsupported:"Ce fournisseur ou l’API Home Assistant n’est pas disponible.",error_timeout:"Le fournisseur n’a pas répondu à temps.",error_invalid_topic:"Vérifiez le topic de base Zigbee2MQTT.",error_invalid_payload:"Le fournisseur a renvoyé des données de topologie non prises en charge.",error_provider:"Impossible de charger les données de topologie.",remote_count:"+{n} dans d’autres espaces",route_device_not_on_plan:"appareil absent du plan",route_coordinator_not_on_plan:"coordinateur absent du plan",route_other_space:"autre espace"}};function r(e,n){const r=i[e]?.[n]??t[n];return"string"==typeof r&&r.trim().length>0}function o(n,r,o){return e(i[n]?.[r]??t[r]??r,o)}const s=3e5,a=2097152,d=6e3;function l(e){return null!==e&&"object"==typeof e?e:null}function u(e){if("string"!=typeof e&&"number"!=typeof e)return null;let n=String(e).trim().toLowerCase();return n=/^0x[0-9a-f]{16}$/.test(n)?n.slice(2):n.replace(/[:-]/g,""),/^[0-9a-f]{16}$/.test(n)?n:null}function c(e,n,t){return`${e}:${n}:${t}`}function p(e){const n=String(e||"").toLowerCase().replace(/[ _-]/g,"");return n.includes("coordinator")?"coordinator":n.includes("router")?"router":n.includes("enddevice")||"end"===n?"end":"unknown"}const h=["parent","child","sibling","none","previous_child"];function g(e){const n=l(e),t=function(e){const n=Number(e);return Number.isFinite(n)&&n>=0&&n<=255?Math.round(n):void 0}(n?.lqi??n?.linkquality??n?.link_quality),i=function(e){if("number"==typeof e)return h[e];if("string"!=typeof e)return;const n=e.trim().toLowerCase().replace(/[\s_-]+/g,"");return"previouschild"===n?"previous_child":n.slice(0,40)||void 0}(n?.relationship);return{...void 0===t?{}:{lqi:t},...i?{relationship:i}:{}}}function f(e,n){return Array.isArray(e)&&e.length<=n?e:null}function _(e){if("string"==typeof e)return e.length<=a;try{return JSON.stringify(e).length<=a}catch{return!1}}function m(e,n,t,i,r){if(t===i)return void n.push({code:"self_link",nodeKey:t});const o=t=d){t.push({code:"invalid_payload"});break}const n=l(e),i=u(n?.ieee??n?.ieee_address);if(!i){t.push({code:"invalid_payload",nodeKey:s});continue}const a=c("zha","zha",i);r.has(a)||r.set(a,{key:a,ieee:i,role:p(n?.device_type)}),m(o,t,s,a,e)}else null!=n?.neighbors&&t.push({code:"invalid_payload",nodeKey:s})}return{provider:"zha",instanceId:"zha",obtainedAt:n,freshness:"provider-cache",nodes:[...r.values()],links:[...o.values()],warnings:t}}function v(e,n){const t=l(e),i=u(t?.ieeeAddr??t?.ieee_address??t?.ieee??e);if(i)return i;for(const i of[t?.id,t?.networkAddress,t?.network_address,e])if(null!=i){const e=n.get(String(i));if(e)return e}return null}function y(e,t,i=Date.now()){const r=[],o=n(t)||"invalid",s=_(e)?function(e){let n=e;if("string"==typeof n){if(n.length>a)return null;try{n=JSON.parse(n)}catch{return null}}const t=l(n),i=l(t?.data);if(n=i?.value??t?.value??t?.data??n,"string"==typeof n){if(n.length>a)return null;try{n=JSON.parse(n)}catch{return null}}return n}(e):null,h=l(s),g=f(h?.nodes,1e3),b=f(h?.links,d);if(!g||!b)return{provider:"z2m",instanceId:o,obtainedAt:i,freshness:"fresh-scan",nodes:[],links:[],warnings:[{code:"invalid_payload"}]};const y=new Map,k=new Map;for(const e of g){const n=l(e),t=u(n?.ieeeAddr??n?.ieee_address??n?.ieee);if(!t){r.push({code:"invalid_payload"});continue}const i=c("z2m",o,t);y.set(i,{key:i,ieee:t,role:p(n?.type??n?.device_type),..."boolean"==typeof n?.failed?{available:!n.failed}:{}});for(const e of[n?.id,n?.networkAddress,n?.network_address])null!=e&&k.set(String(e),t)}const w=new Map;for(const e of b){const n=l(e),t=u(n?.sourceIeeeAddr)??v(n?.source,k),i=u(n?.targetIeeeAddr)??v(n?.target,k);if(!t||!i){r.push({code:"invalid_payload"});continue}const s=c("z2m",o,t),a=c("z2m",o,i);y.has(s)||y.set(s,{key:s,ieee:t,role:"unknown"}),y.has(a)||y.set(a,{key:a,ieee:i,role:"unknown"}),m(w,r,s,a,e)}return{provider:"z2m",instanceId:o,obtainedAt:i,freshness:"fresh-scan",nodes:[...y.values()],links:[...w.values()],warnings:r}}function k(e){if("string"!=typeof e)return null;const n=u(e);if(n)return n;const t=e.toLowerCase().match(/^zigbee2mqtt(?:_bridge)?_(.+)$/);return t?u(t[1]):null}function w(e,n){if(e.deviceId&&n.devices[e.deviceId])return[e.deviceId];const t=new Set;for(const[i,r]of Object.entries(n.devices)){const n=l(r);(Array.isArray(n?.identifiers)?n.identifiers:[]).some(n=>Array.isArray(n)&&k(n[1])===e.ieee)&&t.add(i)}if(t.size)return[...t];for(const i of Object.values(n.entities)){const n=l(i);"string"==typeof n?.device_id&&"string"==typeof n.unique_id&&(k(n.unique_id)===e.ieee&&t.add(n.device_id))}return[...t]}function z(e){return!e.hidden&&!e.virtual&&"ha_disabled"!==e.bindingStatus?.kind&&"orphaned"!==e.bindingStatus?.kind&&"unverified"!==e.bindingStatus?.kind}function A(e){const n=new Map,t=new Map,i=e.nodes.filter(e=>"coordinator"===e.role).map(e=>e.key).sort();if(1!==i.length)return{distances:n,parents:t};const r=i[0],o=function(e){const n=new Set(e.nodes.map(e=>e.key)),t=new Map;for(const e of[...n].sort())t.set(e,[]);for(const i of[...e.links].sort((e,n)=>`${e.a}\0${e.b}`.localeCompare(`${n.a}\0${n.b}`)))n.has(i.a)&&n.has(i.b)&&(t.get(i.a).push({neighborKey:i.b,observation:i.aToB}),t.get(i.b).push({neighborKey:i.a,observation:i.bToA}));for(const e of t.values())e.sort((e,n)=>e.neighborKey.localeCompare(n.neighborKey));return t}(e);if(!o.has(r))return{distances:n,parents:t};n.set(r,0);const s=[r];for(let e=0;en.get(e)===i-1).sort((e,n)=>{const t="parent"===e.observation?.relationship?1:0,i="parent"===n.observation?.relationship?1:0;if(t!==i)return i-t;const r=e.observation?.lqi??-1;return(n.observation?.lqi??-1)-r||e.neighborKey.localeCompare(n.neighborKey)});r[0]&&t.set(e,r[0].neighborKey)}return{coordinatorKey:r,distances:n,parents:t}}function Z(e,n,t){const i=new Map,r=[];for(const o of e.nodes){if(!1===o.available){r.push({code:"provider_scan_failure",nodeKey:o.key});continue}const e=w(o,t);if(1!==e.length){r.push({code:"unmatched_device",nodeKey:o.key});continue}const s=e[0];let a=n.filter(e=>z(e)&&"device"===e.bindingKind&&e.bindingRef===s);a.length||(a=n.filter(e=>z(e)&&"entity"===e.bindingKind&&!!e.bindingRef&&t.entities[e.bindingRef]?.device_id===s)),1===a.length?i.set(o.key,{markerId:a[0].id,space:a[0].space}):r.push({code:a.length>1?"ambiguous_placement":"unmatched_device",nodeKey:o.key})}return{placements:i,warnings:r}}function T(e,n,t){return e.map(e=>({topology:e,placements:Z(e,n,t).placements,routes:A(e)}))}function I(e,n,t){const i=new Map,r=new Set,o=new Map;let s=0;for(const{topology:a,placements:d,routes:l}of e){const e=new Set([...d].filter(([,e])=>e.markerId===t&&e.space===n).map(([e])=>e));if(e.size){for(const t of e){const e=l.parents.get(t);if(!e)continue;const i=d.get(e);if(i?.space===n)continue;let r;r=i?{kind:"remote-space",spaceId:i.space}:"coordinator"===a.nodes.find(n=>n.key===e)?.role?{kind:"unplaced-coordinator"}:{kind:"unplaced-device"};const s="remote-space"===r.kind?`${r.kind}:${r.spaceId}`:r.kind;o.set(s,r)}for(const o of a.links){const a=e.has(o.a),u=e.has(o.b);if(!a&&!u)continue;const c=a?o.a:o.b,p=a?o.b:o.a,h=d.get(p);if(!h||h.markerId===t){s++;continue}const g=l.parents.get(c)===p;if(h.space!==n){g||r.add(h.markerId);continue}const f=a?o.aToB:o.bToA,_=g?"toward-neighbor":l.parents.get(p)===c?"toward-origin":void 0,m=i.get(h.markerId);m?(void 0===m.line.lqi&&void 0!==f?.lqi&&(m.line.lqi=f.lqi),_&&m.directions.add(_)):i.set(h.markerId,{line:{neighborMarkerId:h.markerId,lqi:f?.lqi},directions:new Set(_?[_]:[])})}}}return{lines:[...i.values()].map(({line:e,directions:n})=>1===n.size?{...e,routeDirection:[...n][0]}:e),remoteCount:r.size,omittedCount:s,parentTargets:[...o.values()]}}export{s as T,T as a,y as b,r as h,Z as m,b as n,I as r,o as t};
+globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="450863830ad6931b7698085deecbd39f6686a3e84f1b777008be5347426f4b21";import{s as e,I as n}from"./houseplan-card-Dc1_eajM.js";var t={title:"Zigbee links",toggle:"Show Zigbee links when hovering over a device",hint:"Shows observed direct neighbours only. Nothing is fetched while you hover.",help:"Shows which Zigbee devices each device talks to directly. Line colour is link quality (LQI) on the same scale as the device's LQI reading: red at 40 and below, green at 180 and above. A dashed line means quality was not reported. An arrow points to the next device on the way to the coordinator: one arrow leads out, incoming arrows are the devices that route through this one. A line without an arrow is a spare neighbour. This is the route tree House Plan builds, not the path a packet takes right now: a label at the end of an arrow means its target is not on this plan, and no arrow at all means the route is unknown. Data comes from the last load and goes stale; links are shown with a mouse only.",help_aria:"Help: Zigbee links",admin_only:"Topology data is available to Home Assistant administrators only.",save_first:"Save this setting before loading topology data.",zha:"ZHA",zha_read:"Read ZHA data",zha_hint:"Reads ZHA's cached topology; it does not start a radio scan.",z2m:"Zigbee2MQTT",z2m_topics:"Base topics (one per line)",z2m_update:"Update map",z2m_warning:"A network-map scan can take 10 seconds to 2 minutes and may temporarily reduce Zigbee responsiveness.",status_idle:"Data not loaded",status_loading:"Loading…",status_ready:"Received {time}",status_stale:"Received {time} · stale",status_partial:"Received {time} · some nodes were omitted",status_no_links:"Received {time} · no observed links found",error_permission:"Administrator permission is required.",error_unsupported:"This provider or Home Assistant API is unavailable.",error_timeout:"The provider did not respond in time.",error_invalid_topic:"Check the Zigbee2MQTT base topic.",error_invalid_payload:"The provider returned unsupported topology data.",error_provider:"Topology data could not be loaded.",remote_count:"+{n} in other spaces",route_device_not_on_plan:"device is not on the plan",route_coordinator_not_on_plan:"coordinator is not on the plan",route_other_space:"another space"};const i={en:t,ru:{title:"Связи Zigbee",toggle:"Показывать связи Zigbee при наведении на устройство",hint:"Показываются только наблюдаемые прямые соседи. Наведение ничего не запрашивает.",help:"Показывает, с какими устройствами Zigbee каждое связано напрямую. Цвет линии — качество связи (LQI) по той же шкале, что у показателя LQI устройства: красный при 40 и ниже, зелёный при 180 и выше. Пунктир — качество не сообщено. Стрелка ведёт к следующему устройству по пути к координатору: исходящая одна, входящие — те, кто ходит через это устройство. Линия без стрелки — запасной сосед. Это дерево маршрутов, которое строит House Plan, а не путь пакета в эту секунду: подпись на конце стрелки значит, что цель не на этом плане, а отсутствие стрелки — что путь неизвестен. Данные из последней загрузки и устаревают, связи видны только при работе мышью.",help_aria:"Справка: связи Zigbee",admin_only:"Данные топологии доступны только администраторам Home Assistant.",save_first:"Сохраните настройку перед загрузкой данных топологии.",zha:"ZHA",zha_read:"Прочитать данные ZHA",zha_hint:"Читает сохранённую топологию ZHA и не запускает радио-сканирование.",z2m:"Zigbee2MQTT",z2m_topics:"Базовые топики (по одному в строке)",z2m_update:"Обновить карту",z2m_warning:"Сканирование карты сети занимает от 10 секунд до 2 минут и временно может снизить отзывчивость Zigbee.",status_idle:"Данные не загружены",status_loading:"Загрузка…",status_ready:"Получено: {time}",status_stale:"Получено: {time} · данные устарели",status_partial:"Получено: {time} · часть узлов не показана",status_no_links:"Получено: {time} · наблюдаемые связи не найдены",error_permission:"Нужны права администратора.",error_unsupported:"Провайдер или API Home Assistant недоступен.",error_timeout:"Провайдер не ответил вовремя.",error_invalid_topic:"Проверьте базовый топик Zigbee2MQTT.",error_invalid_payload:"Провайдер вернул неподдерживаемые данные топологии.",error_provider:"Не удалось загрузить данные топологии.",remote_count:"+{n} в других пространствах",route_device_not_on_plan:"устройства нет на плане",route_coordinator_not_on_plan:"координатора нет на плане",route_other_space:"другое пространство"},de:{title:"Zigbee-Verbindungen",toggle:"Zigbee-Verbindungen beim Darüberfahren über ein Gerät anzeigen",hint:"Es werden nur beobachtete direkte Nachbarn angezeigt. Beim Darüberfahren werden keine Daten abgerufen.",help:"Zeigt, mit welchen Zigbee-Geräten jedes Gerät direkt verbunden ist. Die Linienfarbe ist die Verbindungsqualität (LQI) auf derselben Skala wie der LQI-Wert des Geräts: rot bei 40 und darunter, grün bei 180 und darüber. Eine gestrichelte Linie bedeutet, dass die Qualität nicht gemeldet wurde. Ein Pfeil zeigt auf das nächste Gerät auf dem Weg zum Koordinator: ein Pfeil führt hinaus, eingehende Pfeile sind die Geräte, die über dieses Gerät routen. Eine Linie ohne Pfeil ist ein Ersatznachbar. Das ist der Routenbaum, den House Plan bildet, nicht der Weg eines Pakets in diesem Moment: eine Beschriftung am Pfeilende bedeutet, dass das Ziel nicht auf diesem Plan liegt, und ein fehlender Pfeil, dass die Route unbekannt ist. Die Daten stammen aus dem letzten Laden und veralten; Verbindungen sind nur mit der Maus sichtbar.",help_aria:"Hilfe: Zigbee-Verbindungen",admin_only:"Topologiedaten sind nur für Home-Assistant-Administratoren verfügbar.",save_first:"Speichere diese Einstellung, bevor Topologiedaten geladen werden.",zha:"ZHA",zha_read:"ZHA-Daten lesen",zha_hint:"Liest den ZHA-Topologiecache; es wird kein Funkscan gestartet.",z2m:"Zigbee2MQTT",z2m_topics:"Basistopics (eines pro Zeile)",z2m_update:"Karte aktualisieren",z2m_warning:"Ein Netzwerkscan kann 10 Sekunden bis 2 Minuten dauern und Zigbee vorübergehend verlangsamen.",status_idle:"Daten nicht geladen",status_loading:"Wird geladen…",status_ready:"Empfangen: {time}",status_stale:"Empfangen: {time} · veraltet",status_partial:"Empfangen: {time} · einige Knoten fehlen",status_no_links:"Empfangen: {time} · keine beobachteten Verbindungen gefunden",error_permission:"Administratorrechte sind erforderlich.",error_unsupported:"Dieser Anbieter oder die Home-Assistant-API ist nicht verfügbar.",error_timeout:"Der Anbieter hat nicht rechtzeitig geantwortet.",error_invalid_topic:"Prüfe das Zigbee2MQTT-Basistopic.",error_invalid_payload:"Der Anbieter hat nicht unterstützte Topologiedaten geliefert.",error_provider:"Topologiedaten konnten nicht geladen werden.",remote_count:"+{n} in anderen Bereichen",route_device_not_on_plan:"Gerät ist nicht im Plan",route_coordinator_not_on_plan:"Koordinator ist nicht im Plan",route_other_space:"anderer Bereich"},fr:{title:"Liens Zigbee",toggle:"Afficher les liens Zigbee au survol d’un appareil",hint:"Seuls les voisins directs observés sont affichés. Le survol ne lance aucune requête.",help:"Montre avec quels appareils Zigbee chaque appareil communique directement. La couleur de la ligne est la qualité du lien (LQI) sur la même échelle que l'indicateur LQI de l'appareil : rouge à 40 et en dessous, vert à 180 et au-dessus. Une ligne pointillée signifie que la qualité n'a pas été communiquée. Une flèche pointe vers l'appareil suivant sur le chemin du coordinateur : une flèche part, les flèches entrantes sont les appareils qui passent par celui-ci. Une ligne sans flèche est un voisin de secours. C'est l'arbre de routes que construit House Plan, pas le trajet d'un paquet à cet instant : une étiquette au bout d'une flèche signifie que sa cible n'est pas sur ce plan, et l'absence de flèche que la route est inconnue. Les données proviennent du dernier chargement et vieillissent ; les liens ne sont visibles qu'à la souris.",help_aria:"Aide : liens Zigbee",admin_only:"Les données de topologie sont réservées aux administrateurs Home Assistant.",save_first:"Enregistrez ce réglage avant de charger les données de topologie.",zha:"ZHA",zha_read:"Lire les données ZHA",zha_hint:"Lit la topologie en cache de ZHA sans lancer d’analyse radio.",z2m:"Zigbee2MQTT",z2m_topics:"Topics de base (un par ligne)",z2m_update:"Actualiser la carte",z2m_warning:"L’analyse de la carte réseau peut durer de 10 secondes à 2 minutes et réduire temporairement la réactivité de Zigbee.",status_idle:"Données non chargées",status_loading:"Chargement…",status_ready:"Reçu : {time}",status_stale:"Reçu : {time} · obsolète",status_partial:"Reçu : {time} · certains nœuds sont omis",status_no_links:"Reçu : {time} · aucun lien observé trouvé",error_permission:"Les droits administrateur sont requis.",error_unsupported:"Ce fournisseur ou l’API Home Assistant n’est pas disponible.",error_timeout:"Le fournisseur n’a pas répondu à temps.",error_invalid_topic:"Vérifiez le topic de base Zigbee2MQTT.",error_invalid_payload:"Le fournisseur a renvoyé des données de topologie non prises en charge.",error_provider:"Impossible de charger les données de topologie.",remote_count:"+{n} dans d’autres espaces",route_device_not_on_plan:"appareil absent du plan",route_coordinator_not_on_plan:"coordinateur absent du plan",route_other_space:"autre espace"}};function r(e,n){const r=i[e]?.[n]??t[n];return"string"==typeof r&&r.trim().length>0}function o(n,r,o){return e(i[n]?.[r]??t[r]??r,o)}const s=3e5,a=2097152,d=6e3;function l(e){return null!==e&&"object"==typeof e?e:null}function u(e){if("string"!=typeof e&&"number"!=typeof e)return null;let n=String(e).trim().toLowerCase();return n=/^0x[0-9a-f]{16}$/.test(n)?n.slice(2):n.replace(/[:-]/g,""),/^[0-9a-f]{16}$/.test(n)?n:null}function c(e,n,t){return`${e}:${n}:${t}`}function p(e){const n=String(e||"").toLowerCase().replace(/[ _-]/g,"");return n.includes("coordinator")?"coordinator":n.includes("router")?"router":n.includes("enddevice")||"end"===n?"end":"unknown"}const h=["parent","child","sibling","none","previous_child"];function g(e){const n=l(e),t=function(e){const n=Number(e);return Number.isFinite(n)&&n>=0&&n<=255?Math.round(n):void 0}(n?.lqi??n?.linkquality??n?.link_quality),i=function(e){if("number"==typeof e)return h[e];if("string"!=typeof e)return;const n=e.trim().toLowerCase().replace(/[\s_-]+/g,"");return"previouschild"===n?"previous_child":n.slice(0,40)||void 0}(n?.relationship);return{...void 0===t?{}:{lqi:t},...i?{relationship:i}:{}}}function f(e,n){return Array.isArray(e)&&e.length<=n?e:null}function _(e){if("string"==typeof e)return e.length<=a;try{return JSON.stringify(e).length<=a}catch{return!1}}function b(e,n,t,i,r){if(t===i)return void n.push({code:"self_link",nodeKey:t});const o=t=d){t.push({code:"invalid_payload"});break}const n=l(e),i=u(n?.ieee??n?.ieee_address);if(!i){t.push({code:"invalid_payload",nodeKey:s});continue}const a=c("zha","zha",i);r.has(a)||r.set(a,{key:a,ieee:i,role:p(n?.device_type)}),b(o,t,s,a,e)}else null!=n?.neighbors&&t.push({code:"invalid_payload",nodeKey:s})}return{provider:"zha",instanceId:"zha",obtainedAt:n,freshness:"provider-cache",nodes:[...r.values()],links:[...o.values()],warnings:t}}function v(e,n){const t=l(e),i=u(t?.ieeeAddr??t?.ieee_address??t?.ieee??e);if(i)return i;for(const i of[t?.id,t?.networkAddress,t?.network_address,e])if(null!=i){const e=n.get(String(i));if(e)return e}return null}function y(e,t,i=Date.now()){const r=[],o=n(t)||"invalid",s=_(e)?function(e){let n=e;if("string"==typeof n){if(n.length>a)return null;try{n=JSON.parse(n)}catch{return null}}const t=l(n),i=l(t?.data);if(n=i?.value??t?.value??t?.data??n,"string"==typeof n){if(n.length>a)return null;try{n=JSON.parse(n)}catch{return null}}return n}(e):null,h=l(s),g=f(h?.nodes,1e3),m=f(h?.links,d);if(!g||!m)return{provider:"z2m",instanceId:o,obtainedAt:i,freshness:"fresh-scan",nodes:[],links:[],warnings:[{code:"invalid_payload"}]};const y=new Map,k=new Map;for(const e of g){const n=l(e),t=u(n?.ieeeAddr??n?.ieee_address??n?.ieee);if(!t){r.push({code:"invalid_payload"});continue}const i=c("z2m",o,t);y.set(i,{key:i,ieee:t,role:p(n?.type??n?.device_type),..."boolean"==typeof n?.failed?{available:!n.failed}:{}});for(const e of[n?.id,n?.networkAddress,n?.network_address])null!=e&&k.set(String(e),t)}const w=new Map;for(const e of m){const n=l(e),t=u(n?.sourceIeeeAddr)??v(n?.source,k),i=u(n?.targetIeeeAddr)??v(n?.target,k);if(!t||!i){r.push({code:"invalid_payload"});continue}const s=c("z2m",o,t),a=c("z2m",o,i);y.has(s)||y.set(s,{key:s,ieee:t,role:"unknown"}),y.has(a)||y.set(a,{key:a,ieee:i,role:"unknown"}),b(w,r,s,a,e)}return{provider:"z2m",instanceId:o,obtainedAt:i,freshness:"fresh-scan",nodes:[...y.values()],links:[...w.values()],warnings:r}}function k(e){if("string"!=typeof e)return null;const n=u(e);if(n)return n;const t=e.toLowerCase().match(/^zigbee2mqtt(?:_bridge)?_(.+)$/);return t?u(t[1]):null}function w(e,n){if(e.deviceId&&n.devices[e.deviceId])return[e.deviceId];const t=new Set;for(const[i,r]of Object.entries(n.devices)){const n=l(r);(Array.isArray(n?.identifiers)?n.identifiers:[]).some(n=>Array.isArray(n)&&k(n[1])===e.ieee)&&t.add(i)}if(t.size)return[...t];for(const i of Object.values(n.entities)){const n=l(i);"string"==typeof n?.device_id&&"string"==typeof n.unique_id&&(k(n.unique_id)===e.ieee&&t.add(n.device_id))}return[...t]}function z(e){return!e.hidden&&!e.virtual&&"ha_disabled"!==e.bindingStatus?.kind&&"orphaned"!==e.bindingStatus?.kind&&"unverified"!==e.bindingStatus?.kind}function A(e){const n=new Map,t=new Map,i=e.nodes.filter(e=>"coordinator"===e.role).map(e=>e.key).sort();if(1!==i.length)return{distances:n,parents:t};const r=i[0],o=function(e){const n=new Set(e.nodes.map(e=>e.key)),t=new Map;for(const e of[...n].sort())t.set(e,[]);for(const i of[...e.links].sort((e,n)=>`${e.a}\0${e.b}`.localeCompare(`${n.a}\0${n.b}`)))n.has(i.a)&&n.has(i.b)&&(t.get(i.a).push({neighborKey:i.b,observation:i.aToB}),t.get(i.b).push({neighborKey:i.a,observation:i.bToA}));for(const e of t.values())e.sort((e,n)=>e.neighborKey.localeCompare(n.neighborKey));return t}(e);if(!o.has(r))return{distances:n,parents:t};n.set(r,0);const s=[r];for(let e=0;en.get(e)===i-1).sort((e,n)=>{const t="parent"===e.observation?.relationship?1:0,i="parent"===n.observation?.relationship?1:0;if(t!==i)return i-t;const r=e.observation?.lqi??-1;return(n.observation?.lqi??-1)-r||e.neighborKey.localeCompare(n.neighborKey)});r[0]&&t.set(e,r[0].neighborKey)}return{coordinatorKey:r,distances:n,parents:t}}function Z(e,n,t){const i=new Map,r=[];for(const o of e.nodes){if(!1===o.available){r.push({code:"provider_scan_failure",nodeKey:o.key});continue}const e=w(o,t);if(1!==e.length){r.push({code:"unmatched_device",nodeKey:o.key});continue}const s=e[0];let a=n.filter(e=>z(e)&&"device"===e.bindingKind&&e.bindingRef===s);a.length||(a=n.filter(e=>z(e)&&"entity"===e.bindingKind&&!!e.bindingRef&&t.entities[e.bindingRef]?.device_id===s)),1===a.length?i.set(o.key,{markerId:a[0].id,space:a[0].space}):r.push({code:a.length>1?"ambiguous_placement":"unmatched_device",nodeKey:o.key})}return{placements:i,warnings:r}}function T(e,n,t){return e.map(e=>({topology:e,placements:Z(e,n,t).placements,routes:A(e)}))}function I(e,n,t){const i=new Map,r=new Set,o=new Map;let s=0;for(const{topology:a,placements:d,routes:l}of e){const e=new Set([...d].filter(([,e])=>e.markerId===t&&e.space===n).map(([e])=>e));if(e.size){for(const t of e){const e=l.parents.get(t);if(!e)continue;const i=d.get(e);if(i?.space===n)continue;let r;r=i?{kind:"remote-space",spaceId:i.space}:"coordinator"===a.nodes.find(n=>n.key===e)?.role?{kind:"unplaced-coordinator"}:{kind:"unplaced-device"};const s="remote-space"===r.kind?`${r.kind}:${r.spaceId}`:r.kind;o.set(s,r)}for(const o of a.links){const a=e.has(o.a),u=e.has(o.b);if(!a&&!u)continue;const c=a?o.a:o.b,p=a?o.b:o.a,h=d.get(p);if(!h||h.markerId===t){s++;continue}const g=l.parents.get(c)===p;if(h.space!==n){g||r.add(h.markerId);continue}const f=a?o.aToB:o.bToA,_=g?"toward-neighbor":l.parents.get(p)===c?"toward-origin":void 0,b=i.get(h.markerId);b?(void 0===b.line.lqi&&void 0!==f?.lqi&&(b.line.lqi=f.lqi),_&&b.directions.add(_)):i.set(h.markerId,{line:{neighborMarkerId:h.markerId,lqi:f?.lqi},directions:new Set(_?[_]:[])})}}}return{lines:[...i.values()].map(({line:e,directions:n})=>1===n.size?{...e,routeDirection:[...n][0]}:e),remoteCount:r.size,omittedCount:s,parentTargets:[...o.values()]}}export{s as T,T as a,y as b,r as h,Z as m,m as n,I as r,o as t};
diff --git a/dist/houseplan-assets/zigbee-topology-runtime-B3IlOOs3.js b/dist/houseplan-assets/zigbee-topology-runtime-CSb-ImjM.js
similarity index 93%
rename from dist/houseplan-assets/zigbee-topology-runtime-B3IlOOs3.js
rename to dist/houseplan-assets/zigbee-topology-runtime-CSb-ImjM.js
index 916cc0ae..a5fefd1f 100644
--- a/dist/houseplan-assets/zigbee-topology-runtime-B3IlOOs3.js
+++ b/dist/houseplan-assets/zigbee-topology-runtime-CSb-ImjM.js
@@ -1 +1 @@
-globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d1cd3ecb18d8a872c7e231ce86e766515a15d278c981181d134e7be3d1076518";import{n as t,b as n}from"./zigbee-topology-CQHQu2NY.js";import{I as e}from"./houseplan-card-CmhTStvj.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
+globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="450863830ad6931b7698085deecbd39f6686a3e84f1b777008be5347426f4b21";import{n as t,b as n}from"./zigbee-topology-B2eQAXzT.js";import{I as e}from"./houseplan-card-Dc1_eajM.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
diff --git a/dist/houseplan-card.js b/dist/houseplan-card.js
index 79a375d3..656bacaa 100644
--- a/dist/houseplan-card.js
+++ b/dist/houseplan-card.js
@@ -1 +1 @@
-globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d1cd3ecb18d8a872c7e231ce86e766515a15d278c981181d134e7be3d1076518";try{await import("./houseplan-assets/houseplan-card-CmhTStvj.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale entry: the main chunk is unavailable",e)}
+globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="450863830ad6931b7698085deecbd39f6686a3e84f1b777008be5347426f4b21";try{await import("./houseplan-assets/houseplan-card-Dc1_eajM.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale entry: the main chunk is unavailable",e)}
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index ad552628..4ac27e18 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -124,9 +124,17 @@ the same profiler available between stable promotions.
falls back to the existing attention marker. Relocation invalidates only
Undo/Redo commands owned by the moved device. Limited registry snapshots
are read-only and never infer movement.
-5. **One modal contract.** Card modals render through `hp-dialog`. In Home
- Assistant it delegates surface semantics and trapping to `ha-dialog`; the
- standalone demo falls back to native `