fix: remove isometric overlay plates

Keep the raised overlay footprint as calculation-only geometry for collision, nudge and fit while removing its painted SVG surface and texture. Update Stage 3 contracts, smokes, golden harness rules and synchronized bundles.

Issue: #471
User-Visible: no
This commit is contained in:
Matysh
2026-09-06 11:23:51 +00:00
committed by claude[bot]
parent 73a2fc66d3
commit be5eeb41c3
48 changed files with 561 additions and 651 deletions
@@ -1,215 +1,215 @@
{
"schema": 1,
"fingerprint": "d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2",
"fingerprint": "ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 299658,
"initialViewGzipBytes": 299568,
"lazyFiles": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/de-BROjEISN.js",
"houseplan-assets/editor-ossC-C6x.js",
"houseplan-assets/fr-Bz-zlHx6.js",
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js",
"houseplan-assets/hp-zigbee-topology-overlay-DOdvYP0_.js",
"houseplan-assets/iso-scene-render-Hhd22t6P.js",
"houseplan-assets/live-interaction-runtime-NEe-ve-s.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js",
"houseplan-assets/zigbee-topology-runtime-uRnFDovO.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/de-BJ59SqHd.js",
"houseplan-assets/editor-DCvR7dOu.js",
"houseplan-assets/fr-DNZtgC-A.js",
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js",
"houseplan-assets/hp-zigbee-topology-overlay-DTCmHov1.js",
"houseplan-assets/iso-scene-render-JRXPmIjZ.js",
"houseplan-assets/live-interaction-runtime-C10l_L0Y.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js",
"houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js"
],
"lazyGzipBytes": 264827,
"lazyGzipBytes": 264740,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/editor-ossC-C6x.js",
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/editor-DCvR7dOu.js",
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js"
],
"lazyEditorGzipBytes": 186876,
"lazyEditorGzipBytes": 186891,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js"
],
"lazyOnboardingGzipBytes": 14324,
"lazyOnboardingGzipBytes": 14327,
"lazyLocaleFiles": [
"houseplan-assets/de-BROjEISN.js",
"houseplan-assets/fr-Bz-zlHx6.js"
"houseplan-assets/de-BJ59SqHd.js",
"houseplan-assets/fr-DNZtgC-A.js"
],
"lazyLocaleGzipBytes": 50136,
"lazyLocaleGzipBytes": 50160,
"lazyIsometricFiles": [
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/iso-scene-render-Hhd22t6P.js"
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/iso-scene-render-JRXPmIjZ.js"
],
"lazyIsometricGzipBytes": 13598,
"lazyIsometricGzipBytes": 13468,
"files": [
{
"path": "houseplan-assets/backdrop-pick-DXO50Xjr.js",
"sha256": "a95a50bd1717ee8c212a523b0a3ad23117715bc92c85f8656c9a0a70a3a67e38",
"path": "houseplan-assets/backdrop-pick-DtelLDb5.js",
"sha256": "43c706bd470dcc62916e9c5fafaed4d5cbef5328be3c98c20187a0a16227a060",
"rawBytes": 21592,
"gzipBytes": 7356,
"gzipBytes": 7360,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-BROjEISN.js",
"sha256": "ef1806e714d881be8a81e949fb85c62238b930c80c0d62e598cc41e90ee2ef69",
"path": "houseplan-assets/de-BJ59SqHd.js",
"sha256": "31e73b1364053770ba079e518648a42a9e751e47ebef2e1c6907f1cd4cb8abf8",
"rawBytes": 84232,
"gzipBytes": 25345,
"gzipBytes": 25357,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-ossC-C6x.js",
"sha256": "acb30c823934597cdaafbad7aba3cb79866edee3b82a3874e1810af70e165473",
"path": "houseplan-assets/editor-DCvR7dOu.js",
"sha256": "7e3fb571635b5e8e85b02158a98ca9dc826122a3df5daaa290720ad19468558b",
"rawBytes": 3837,
"gzipBytes": 1588,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-Bz-zlHx6.js",
"sha256": "c0b2878d0888219a8ea8dab93f3a0e88c184fde89edf4000675d71b0c5b71588",
"path": "houseplan-assets/fr-DNZtgC-A.js",
"sha256": "cdd9839ec611a780733d997c5605d8d1a0740d89c9e626cd1c5da29a9d4baa34",
"rawBytes": 86273,
"gzipBytes": 24791,
"gzipBytes": 24803,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/guard-CuNKoi-q.js",
"sha256": "f1cc95898e0dcaf4439a8954182c1b5e85c23d526ca9033a24da231a6f849871",
"path": "houseplan-assets/guard-DDFB6K1x.js",
"sha256": "5aaf1212a318c9d9f7491e46444d7cb9650a57836ac336226e8ca99bec359bc1",
"rawBytes": 518,
"gzipBytes": 369,
"gzipBytes": 370,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-EoBMuWV_.js",
"sha256": "16bd931aec1720da86f7c19eae868e7da4c410ccbcf589c5994a31d73508d52d",
"rawBytes": 1072739,
"gzipBytes": 298863,
"path": "houseplan-assets/houseplan-card-BotpFSfJ.js",
"sha256": "f58ee9b09efdd7b905b824767aeb1a06f6056d2d481e6948715cb2e7ea84a906",
"rawBytes": 1072024,
"gzipBytes": 298771,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-BROjEISN.js",
"houseplan-assets/editor-ossC-C6x.js",
"houseplan-assets/fr-Bz-zlHx6.js",
"houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js",
"houseplan-assets/hp-zigbee-topology-overlay-DOdvYP0_.js",
"houseplan-assets/iso-scene-render-Hhd22t6P.js",
"houseplan-assets/live-interaction-runtime-NEe-ve-s.js"
"houseplan-assets/de-BJ59SqHd.js",
"houseplan-assets/editor-DCvR7dOu.js",
"houseplan-assets/fr-DNZtgC-A.js",
"houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js",
"houseplan-assets/hp-zigbee-topology-overlay-DTCmHov1.js",
"houseplan-assets/iso-scene-render-JRXPmIjZ.js",
"houseplan-assets/live-interaction-runtime-C10l_L0Y.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"sha256": "26020f0bffb48c7f8f92b50416eb64944337b84cb23150455cd57424fca760fe",
"path": "houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"sha256": "1148dc0e63fc468409d20189f01ec55ddb84fdc4ce370de1f4229cb585be4064",
"rawBytes": 639230,
"gzipBytes": 170136,
"gzipBytes": 170143,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js"
],
"dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-uRnFDovO.js"
"houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js"
]
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js",
"sha256": "143bbf50347cdc6f7835af793cb24a3e3c9050caea66cd41d8e518cf6e966222",
"path": "houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js",
"sha256": "767c9f0db979b669d28476f10408576426fb5c8c2f3b82b99a2c4df240760e6a",
"rawBytes": 28071,
"gzipBytes": 6968,
"gzipBytes": 6967,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/hp-zigbee-topology-overlay-DOdvYP0_.js",
"sha256": "9ba691ef912aa8575529247df0783ebc415dd28f67e6dcccad4e643aef2fda01",
"path": "houseplan-assets/hp-zigbee-topology-overlay-DTCmHov1.js",
"sha256": "6531f4ca200c1a8f6c29735ce02aad05fc80b93ff1540a524fee0a28c7ba3c51",
"rawBytes": 11034,
"gzipBytes": 3731,
"gzipBytes": 3732,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js",
"houseplan-assets/zigbee-topology-runtime-uRnFDovO.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js",
"houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/iso-scene-render-Hhd22t6P.js",
"sha256": "5391f91da2cb984f1086f1eb6f9a4bfe0df67f2739233f6a2a1e5346863f6f94",
"rawBytes": 39365,
"gzipBytes": 13229,
"path": "houseplan-assets/iso-scene-render-JRXPmIjZ.js",
"sha256": "703309837539c51a0fd5a7be9038ce007c7a1a4ebadb123edc45a30fce70e95e",
"rawBytes": 38593,
"gzipBytes": 13098,
"isEntry": false,
"imports": [
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/live-interaction-runtime-NEe-ve-s.js",
"sha256": "9ba797a5ea95a3b78cb6cc5ddd8068721192664e745bf26f7c7d914c99666160",
"path": "houseplan-assets/live-interaction-runtime-C10l_L0Y.js",
"sha256": "0005d5b3c867a686cb0c46306b6b5e6d287e55aeaa2156bf907c4c3f2eea5310",
"rawBytes": 5196,
"gzipBytes": 2125,
"gzipBytes": 2128,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-CZt15Mjr.js",
"sha256": "67f179bb23ef3b95ccae913de88612507a441f28832fa643a7d1783c7cee7dfe",
"path": "houseplan-assets/zigbee-topology-B9AIr-UU.js",
"sha256": "3d30830f5cbf87605340e98f2cd0773625418b2f79dccf2ea4a603bff91dbf11",
"rawBytes": 18961,
"gzipBytes": 7427,
"gzipBytes": 7430,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-runtime-uRnFDovO.js",
"sha256": "d6638fea70906f9e20d61f49498681950be289c4df9413bff9b6e3078b29a942",
"path": "houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js",
"sha256": "9c67889471882d864ffffb65d60bf4ce519896f1e6be6acbb4ddb13cb15f86a4",
"rawBytes": 3941,
"gzipBytes": 1762,
"gzipBytes": 1764,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "4d7292ec38b5b7e39a12f9c62ec77be62b48e3ed1fe6b3c6dc5e1f0db35d64f6",
"sha256": "2ea16018a3996968eee180625db1e039a119144fb01de931336bbf5d2fbfbeb6",
"rawBytes": 1183,
"gzipBytes": 795,
"gzipBytes": 797,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";import{t as e,l as o,x as t,dK as s,A as a,dL as i,dM as l,dN as n,dJ as r,b as c}from"./houseplan-card-EoBMuWV_.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";import{t as e,l as o,x as t,dK as s,A as a,dL as i,dM as l,dN as n,dJ as r,b as c}from"./houseplan-card-BotpFSfJ.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";import{dH as r,dI as t,dJ as e}from"./houseplan-card-EoBMuWV_.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";import{dH as r,dI as t,dJ as e}from"./houseplan-card-BotpFSfJ.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";import{n as t,b as n}from"./zigbee-topology-CZt15Mjr.js";import{F as e}from"./houseplan-card-EoBMuWV_.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__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";import{n as t,b as n}from"./zigbee-topology-B9AIr-UU.js";import{F as e}from"./houseplan-card-BotpFSfJ.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};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";try{await import("./houseplan-assets/houseplan-card-EoBMuWV_.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__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";try{await import("./houseplan-assets/houseplan-card-BotpFSfJ.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)}
+6 -5
View File
@@ -1205,12 +1205,13 @@ export async function prepareGoldenScenario(page, scenario) {
if (contract.requireNoMaterialDefs && materialDefs !== 0) {
throw new Error(`Stage 3 solid fallback retained material definitions: ${scenario.id}`);
}
if (contract.requireSolidCues) {
const plates = [...card.renderRoot.querySelectorAll('.iso-overlay-plate')];
if (contract.requireTetherCues) {
const tethers = [...card.renderRoot.querySelectorAll('.iso-overlay-tether')];
if (plates.length < raisedRoots.length || !tethers.length
|| plates.some((plate) => getComputedStyle(plate).fill === 'none')) {
throw new Error(`Stage 3 golden lost solid plate/tether cues: ${scenario.id}`);
const paintedFootprint = card.renderRoot.querySelector(
'.iso-overlay-plate, .iso-overlay-plate-texture, #hp-iso-overlay-texture',
);
if (paintedFootprint || !tethers.length) {
throw new Error(`Stage 3 golden lost invisible-footprint/tether contract: ${scenario.id}`);
}
}
if (contract.requireGrounding
+3 -3
View File
@@ -283,7 +283,7 @@ export const GOLDEN_SCENARIOS = Object.freeze([
requireMaterialDefs: true, maxMaterialDefs: 12,
requireVacuumFloor: theme === 'dark',
requireDenseFacets: true,
requireSolidCues: true,
requireTetherCues: true,
requireGrounding: true,
requireLiveLayers: theme === 'dark',
},
@@ -337,7 +337,7 @@ export const GOLDEN_SCENARIOS = Object.freeze([
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
stage3Golden: {
requiredKinds: stage3RequiredOverlays, requireNudged: true,
requireNoMaterialDefs: true, requireSolidCues: true,
requireNoMaterialDefs: true, requireTetherCues: true,
},
layoutOverrides: stage3DenseLayout,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
@@ -347,7 +347,7 @@ export const GOLDEN_SCENARIOS = Object.freeze([
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
stage3Golden: {
requiredKinds: stage3RequiredOverlays, requireNudged: true,
requireNoMaterialDefs: true, requireSolidCues: true,
requireNoMaterialDefs: true, requireTetherCues: true,
},
layoutOverrides: stage3DenseLayout,
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
+13 -17
View File
@@ -135,7 +135,7 @@ const out = await page.evaluate(async () => {
const requiredDefs = [
'hp-iso-wall-side', 'hp-iso-wall-top', 'hp-iso-wall-texture',
'hp-iso-floor-texture', 'hp-iso-ambient-shadow', 'hp-iso-contact-shadow',
'hp-iso-leaf-shadow', 'hp-iso-overlay-ground', 'hp-iso-overlay-texture',
'hp-iso-leaf-shadow', 'hp-iso-overlay-ground',
];
result.stage3DefinitionsBounded = materialDefs.length >= requiredDefs.length
&& materialDefs.length <= 16
@@ -178,9 +178,12 @@ const out = await page.evaluate(async () => {
&& raisedRoot(roomLabel, 'room-label')
&& raisedRoot(openingLock, 'opening-lock')
&& ['device', 'room-label', 'opening-lock'].every((kind) => root().querySelector(
`[data-hp="iso-raised-overlays"] [data-hp-iso-overlay-kind="${kind}"]`
+ '[data-hp-iso-raised="true"]',
`[data-hp="iso-raised-overlays"] .iso-overlay-tether`
+ `[data-hp-iso-overlay-kind="${kind}"]`,
));
result.raisedFootprintsStayInvisible = !root().querySelector(
'.iso-overlay-plate, .iso-overlay-plate-texture, #hp-iso-overlay-texture',
);
result.preferenceStored = JSON.parse(localStorage.getItem('houseplan_card_view_v1') || '{}').f1 === 'iso';
result.anchorsFinite = [device, roomLabel, openingLock].every((node) => node
&& center(node).every((value) => Number.isFinite(value)));
@@ -192,19 +195,9 @@ const out = await page.evaluate(async () => {
const kind = node.getAttribute('data-hp-iso-overlay-kind');
const id = node.getAttribute('data-id');
const suffix = id ? `[data-id="${CSS.escape(id)}"]` : '';
const group = root().querySelector(
`[data-hp="iso-raised-overlays"] .iso-overlay-plates > [data-hp-iso-overlay-kind="${kind}"]${suffix}`,
) || root().querySelector(
`[data-hp="iso-raised-overlays"] .iso-overlay-plates > [data-hp-iso-overlay-kind="${kind}"]`,
);
const plate = group?.querySelector('.iso-overlay-plate');
const points = (plate?.getAttribute('points') || '').trim().split(/\s+/)
.map((pair) => pair.split(',').map(Number));
const visual = (node.getAttribute('data-hp-iso-visual') || '').split(',').map(Number);
const floorPoint = (node.getAttribute('data-hp-iso-floor') || '').split(',').map(Number);
if (!group || points.length !== 4 || visual.length !== 2 || floorPoint.length !== 2) return false;
const centroid = [points.reduce((sum, point) => sum + point[0], 0) / 4,
points.reduce((sum, point) => sum + point[1], 0) / 4];
if (visual.length !== 2 || floorPoint.length !== 2) return false;
const expected = sceneToClient(visual), actual = center(node);
const ground = root().querySelector(
`[data-hp="iso-overlay-grounds"] [data-hp-iso-overlay-kind="${kind}"]${suffix}`,
@@ -217,10 +210,13 @@ const out = await page.evaluate(async () => {
`[data-hp="iso-raised-overlays"] .iso-overlay-tether[data-hp-iso-overlay-kind="${kind}"]`,
);
return Math.hypot(actual[0] - expected[0], actual[1] - expected[1]) <= 1
&& Math.hypot(centroid[0] - visual[0], centroid[1] - visual[1]) <= 1e-6
&& !!ground && Math.hypot(Number(ground.getAttribute('cx')) - floorPoint[0],
Number(ground.getAttribute('cy')) - floorPoint[1]) <= 1e-6
&& (group.getAttribute('data-hp-iso-nudged') !== 'true' || !!tether);
&& !!tether
&& Math.hypot(Number(tether.getAttribute('x1')) - floorPoint[0],
Number(tether.getAttribute('y1')) - floorPoint[1]) <= 1e-6
&& Math.hypot(Number(tether.getAttribute('x2')) - visual[0],
Number(tether.getAttribute('y2')) - visual[1]) <= 1e-6;
};
result.globalFitContainsRaisedFootprints = raisedNodes.length >= 3
&& raisedNodes.every((node) => inside(root().querySelector('.stage'), node));
@@ -387,7 +383,7 @@ const out = await page.evaluate(async () => {
&& card._isoFallback.size === fallbackCountBeforeCapabilityProbe
&& !!root().querySelector('[data-hp="iso-walls"] .iso-wall-top')
&& !!root().querySelector('[data-hp="iso-openings"] .iso-opening-panel')
&& !!root().querySelector('[data-hp="iso-raised-overlays"] .iso-overlay-plate')
&& !root().querySelector('.iso-overlay-plate, .iso-overlay-plate-texture')
&& !!root().querySelector('[data-hp="iso-raised-overlays"] .iso-overlay-tether')
&& !root().querySelector('[data-hp-iso-material-def]')
&& !root().querySelector('.iso-ambient-shadow, .iso-contact-shadow,'
+3 -6
View File
@@ -365,10 +365,6 @@ const out = await page.evaluate(async () => {
await original.updateComplete;
const fallbackNudged = root(original).querySelector('[data-hp-iso-nudged="true"][data-id]');
const fallbackId = fallbackNudged?.getAttribute('data-id');
const fallbackPlate = fallbackId && root(original).querySelector(
`.iso-overlay-plate[data-id="${CSS.escape(fallbackId)}"],`
+ `.iso-overlay-plates [data-id="${CSS.escape(fallbackId)}"] .iso-overlay-plate`,
);
const fallbackTether = fallbackId && root(original).querySelector(
`.iso-overlay-tether[data-id="${CSS.escape(fallbackId)}"]`,
);
@@ -378,8 +374,9 @@ const out = await page.evaluate(async () => {
&& !!root(original).querySelector('[data-hp="iso-openings"]');
result.unsupportedDecorationDropsUnsupportedEffects = !root(original).querySelector('[data-hp="iso-shadows"]')
&& root(original).querySelectorAll('[data-hp-iso-material-def]').length === 0;
result.unsupportedDecorationKeepsSolidPlate = !!fallbackPlate
&& getComputedStyle(fallbackPlate).fill !== 'none';
result.unsupportedDecorationKeepsFootprintInvisible = !root(original).querySelector(
'.iso-overlay-plate, .iso-overlay-plate-texture, #hp-iso-overlay-texture',
);
result.unsupportedDecorationKeepsTether = !!fallbackTether;
result.unsupportedDecorationKeepsActions = fallbackCalls.length === 1;
CSS.supports = supports;
+101 -101
View File
@@ -1,215 +1,215 @@
{
"schema": 1,
"fingerprint": "d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2",
"fingerprint": "ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 299658,
"initialViewGzipBytes": 299568,
"lazyFiles": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/de-BROjEISN.js",
"houseplan-assets/editor-ossC-C6x.js",
"houseplan-assets/fr-Bz-zlHx6.js",
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js",
"houseplan-assets/hp-zigbee-topology-overlay-DOdvYP0_.js",
"houseplan-assets/iso-scene-render-Hhd22t6P.js",
"houseplan-assets/live-interaction-runtime-NEe-ve-s.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js",
"houseplan-assets/zigbee-topology-runtime-uRnFDovO.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/de-BJ59SqHd.js",
"houseplan-assets/editor-DCvR7dOu.js",
"houseplan-assets/fr-DNZtgC-A.js",
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js",
"houseplan-assets/hp-zigbee-topology-overlay-DTCmHov1.js",
"houseplan-assets/iso-scene-render-JRXPmIjZ.js",
"houseplan-assets/live-interaction-runtime-C10l_L0Y.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js",
"houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js"
],
"lazyGzipBytes": 264827,
"lazyGzipBytes": 264740,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/editor-ossC-C6x.js",
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/editor-DCvR7dOu.js",
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js"
],
"lazyEditorGzipBytes": 186876,
"lazyEditorGzipBytes": 186891,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js"
],
"lazyOnboardingGzipBytes": 14324,
"lazyOnboardingGzipBytes": 14327,
"lazyLocaleFiles": [
"houseplan-assets/de-BROjEISN.js",
"houseplan-assets/fr-Bz-zlHx6.js"
"houseplan-assets/de-BJ59SqHd.js",
"houseplan-assets/fr-DNZtgC-A.js"
],
"lazyLocaleGzipBytes": 50136,
"lazyLocaleGzipBytes": 50160,
"lazyIsometricFiles": [
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/iso-scene-render-Hhd22t6P.js"
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/iso-scene-render-JRXPmIjZ.js"
],
"lazyIsometricGzipBytes": 13598,
"lazyIsometricGzipBytes": 13468,
"files": [
{
"path": "houseplan-assets/backdrop-pick-DXO50Xjr.js",
"sha256": "a95a50bd1717ee8c212a523b0a3ad23117715bc92c85f8656c9a0a70a3a67e38",
"path": "houseplan-assets/backdrop-pick-DtelLDb5.js",
"sha256": "43c706bd470dcc62916e9c5fafaed4d5cbef5328be3c98c20187a0a16227a060",
"rawBytes": 21592,
"gzipBytes": 7356,
"gzipBytes": 7360,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-BROjEISN.js",
"sha256": "ef1806e714d881be8a81e949fb85c62238b930c80c0d62e598cc41e90ee2ef69",
"path": "houseplan-assets/de-BJ59SqHd.js",
"sha256": "31e73b1364053770ba079e518648a42a9e751e47ebef2e1c6907f1cd4cb8abf8",
"rawBytes": 84232,
"gzipBytes": 25345,
"gzipBytes": 25357,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-ossC-C6x.js",
"sha256": "acb30c823934597cdaafbad7aba3cb79866edee3b82a3874e1810af70e165473",
"path": "houseplan-assets/editor-DCvR7dOu.js",
"sha256": "7e3fb571635b5e8e85b02158a98ca9dc826122a3df5daaa290720ad19468558b",
"rawBytes": 3837,
"gzipBytes": 1588,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-Bz-zlHx6.js",
"sha256": "c0b2878d0888219a8ea8dab93f3a0e88c184fde89edf4000675d71b0c5b71588",
"path": "houseplan-assets/fr-DNZtgC-A.js",
"sha256": "cdd9839ec611a780733d997c5605d8d1a0740d89c9e626cd1c5da29a9d4baa34",
"rawBytes": 86273,
"gzipBytes": 24791,
"gzipBytes": 24803,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/guard-CuNKoi-q.js",
"sha256": "f1cc95898e0dcaf4439a8954182c1b5e85c23d526ca9033a24da231a6f849871",
"path": "houseplan-assets/guard-DDFB6K1x.js",
"sha256": "5aaf1212a318c9d9f7491e46444d7cb9650a57836ac336226e8ca99bec359bc1",
"rawBytes": 518,
"gzipBytes": 369,
"gzipBytes": 370,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-EoBMuWV_.js",
"sha256": "16bd931aec1720da86f7c19eae868e7da4c410ccbcf589c5994a31d73508d52d",
"rawBytes": 1072739,
"gzipBytes": 298863,
"path": "houseplan-assets/houseplan-card-BotpFSfJ.js",
"sha256": "f58ee9b09efdd7b905b824767aeb1a06f6056d2d481e6948715cb2e7ea84a906",
"rawBytes": 1072024,
"gzipBytes": 298771,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-BROjEISN.js",
"houseplan-assets/editor-ossC-C6x.js",
"houseplan-assets/fr-Bz-zlHx6.js",
"houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js",
"houseplan-assets/hp-zigbee-topology-overlay-DOdvYP0_.js",
"houseplan-assets/iso-scene-render-Hhd22t6P.js",
"houseplan-assets/live-interaction-runtime-NEe-ve-s.js"
"houseplan-assets/de-BJ59SqHd.js",
"houseplan-assets/editor-DCvR7dOu.js",
"houseplan-assets/fr-DNZtgC-A.js",
"houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js",
"houseplan-assets/hp-zigbee-topology-overlay-DTCmHov1.js",
"houseplan-assets/iso-scene-render-JRXPmIjZ.js",
"houseplan-assets/live-interaction-runtime-C10l_L0Y.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-DX8Qa14m.js",
"sha256": "26020f0bffb48c7f8f92b50416eb64944337b84cb23150455cd57424fca760fe",
"path": "houseplan-assets/houseplan-editor-runtime-00uay_9V.js",
"sha256": "1148dc0e63fc468409d20189f01ec55ddb84fdc4ce370de1f4229cb585be4064",
"rawBytes": 639230,
"gzipBytes": 170136,
"gzipBytes": 170143,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js"
],
"dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-uRnFDovO.js"
"houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js"
]
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-SsTouhSI.js",
"sha256": "143bbf50347cdc6f7835af793cb24a3e3c9050caea66cd41d8e518cf6e966222",
"path": "houseplan-assets/houseplan-onboarding-runtime-Ca87v6jq.js",
"sha256": "767c9f0db979b669d28476f10408576426fb5c8c2f3b82b99a2c4df240760e6a",
"rawBytes": 28071,
"gzipBytes": 6968,
"gzipBytes": 6967,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DXO50Xjr.js",
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/backdrop-pick-DtelLDb5.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/hp-zigbee-topology-overlay-DOdvYP0_.js",
"sha256": "9ba691ef912aa8575529247df0783ebc415dd28f67e6dcccad4e643aef2fda01",
"path": "houseplan-assets/hp-zigbee-topology-overlay-DTCmHov1.js",
"sha256": "6531f4ca200c1a8f6c29735ce02aad05fc80b93ff1540a524fee0a28c7ba3c51",
"rawBytes": 11034,
"gzipBytes": 3731,
"gzipBytes": 3732,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js",
"houseplan-assets/zigbee-topology-runtime-uRnFDovO.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js",
"houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/iso-scene-render-Hhd22t6P.js",
"sha256": "5391f91da2cb984f1086f1eb6f9a4bfe0df67f2739233f6a2a1e5346863f6f94",
"rawBytes": 39365,
"gzipBytes": 13229,
"path": "houseplan-assets/iso-scene-render-JRXPmIjZ.js",
"sha256": "703309837539c51a0fd5a7be9038ce007c7a1a4ebadb123edc45a30fce70e95e",
"rawBytes": 38593,
"gzipBytes": 13098,
"isEntry": false,
"imports": [
"houseplan-assets/guard-CuNKoi-q.js",
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/guard-DDFB6K1x.js",
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/live-interaction-runtime-NEe-ve-s.js",
"sha256": "9ba797a5ea95a3b78cb6cc5ddd8068721192664e745bf26f7c7d914c99666160",
"path": "houseplan-assets/live-interaction-runtime-C10l_L0Y.js",
"sha256": "0005d5b3c867a686cb0c46306b6b5e6d287e55aeaa2156bf907c4c3f2eea5310",
"rawBytes": 5196,
"gzipBytes": 2125,
"gzipBytes": 2128,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-CZt15Mjr.js",
"sha256": "67f179bb23ef3b95ccae913de88612507a441f28832fa643a7d1783c7cee7dfe",
"path": "houseplan-assets/zigbee-topology-B9AIr-UU.js",
"sha256": "3d30830f5cbf87605340e98f2cd0773625418b2f79dccf2ea4a603bff91dbf11",
"rawBytes": 18961,
"gzipBytes": 7427,
"gzipBytes": 7430,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-runtime-uRnFDovO.js",
"sha256": "d6638fea70906f9e20d61f49498681950be289c4df9413bff9b6e3078b29a942",
"path": "houseplan-assets/zigbee-topology-runtime-ZZkqg-Gu.js",
"sha256": "9c67889471882d864ffffb65d60bf4ce519896f1e6be6acbb4ddb13cb15f86a4",
"rawBytes": 3941,
"gzipBytes": 1762,
"gzipBytes": 1764,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js",
"houseplan-assets/zigbee-topology-CZt15Mjr.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js",
"houseplan-assets/zigbee-topology-B9AIr-UU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "4d7292ec38b5b7e39a12f9c62ec77be62b48e3ed1fe6b3c6dc5e1f0db35d64f6",
"sha256": "2ea16018a3996968eee180625db1e039a119144fb01de931336bbf5d2fbfbeb6",
"rawBytes": 1183,
"gzipBytes": 795,
"gzipBytes": 797,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-EoBMuWV_.js"
"houseplan-assets/houseplan-card-BotpFSfJ.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";import{t as e,l as o,x as t,dK as s,A as a,dL as i,dM as l,dN as n,dJ as r,b as c}from"./houseplan-card-EoBMuWV_.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";import{t as e,l as o,x as t,dK as s,A as a,dL as i,dM as l,dN as n,dJ as r,b as c}from"./houseplan-card-BotpFSfJ.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";import{dH as r,dI as t,dJ as e}from"./houseplan-card-EoBMuWV_.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";import{dH as r,dI as t,dJ as e}from"./houseplan-card-BotpFSfJ.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";import{n as t,b as n}from"./zigbee-topology-CZt15Mjr.js";import{F as e}from"./houseplan-card-EoBMuWV_.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__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";import{n as t,b as n}from"./zigbee-topology-B9AIr-UU.js";import{F as e}from"./houseplan-card-BotpFSfJ.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};
+1 -1
View File
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d6a3471afc7a85dfa1eb3d7363deca18dabe72bbc653ed884c7fd849259888c2";try{await import("./houseplan-assets/houseplan-card-EoBMuWV_.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__="ab077e4fb68320e583ac75418056196bdb941d9751fbe28c49e91f8310519e28";try{await import("./houseplan-assets/houseplan-card-BotpFSfJ.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)}
+4 -3
View File
@@ -716,15 +716,16 @@ uses the Stage 1 latched Flat fallback. Details and fixed ratios are recorded in
Stage 3 keeps the Stage 2 structural scene and moves the fixed camera authority
to `rotDeg=4`, `tiltDeg=20`. `isoPlaneMatrix()` is shared by floor content,
raised plate corners, point projection and inverse floor hit mapping. The
invisible raised-footprint corners, point projection and inverse floor hit mapping. The
projected frame includes the floor edge, wall/opening tops and the raised
height; blur/shadow extents never enter camera fit.
`src/iso-overlays.ts` is the pure boundary between floor-bound and raised
presentation. Device roots, room-label/card roots and opening-lock roots keep
an immutable floor anchor and receive one computed raised visual anchor. Their
floor-parallel SVG plate, grounding cue and tether are inert; the existing
screen-facing HTML root remains the sole hit, focus, tooltip and action target.
conservative floor-parallel footprint stays calculation-only for collision and
fit; only the grounding cue and tether are inert SVG. The existing screen-facing
HTML root remains the sole hit, focus, tooltip and action target.
Vacuum, lighting, fills, backdrop and decor continue to consume `z=0`.
Wall collision consumes projected top and visible-side silhouettes produced
+9 -6
View File
@@ -200,15 +200,17 @@ decisions are in `docs/adr/160-isometric-stage3-overlays.md`.
The current fixed camera is orthographic `rotDeg=4`, `tiltDeg=20`, with the
same `[500,500]` pivot, scale and scale-aware 64-unit wall height. The four
degree turn is part of `ISO_CAMERA`; floor SVG, point projection, inverse hit
mapping, raised plates and fit bounds therefore use one affine authority.
mapping, invisible raised footprints and fit bounds therefore use one affine
authority.
Device markers (including their badges), room labels/cards and opening lock
badges use a raised plane at the scale-aware wall height plus a nominal four
visual units. Vacuum puck/trail, Glow/spill, sunlight, room fills/hover,
decor/furniture/backdrop and every persisted coordinate stay on the floor
plane. Plates are floor-parallel, while glyphs, values and text remain
screen-facing on the existing HTML roots and retain their actions and minimum
touch targets.
plane. A conservative floor-parallel footprint remains internal calculation
geometry for collision, nudge and fit, but is never painted. Glyphs, values and
text remain screen-facing on the existing HTML roots and retain their actions
and minimum touch targets.
Each raised item keeps separate immutable floor and visual anchors. A grounding
cue stays at the floor anchor. Collision is tested against cached projected wall
@@ -234,8 +236,9 @@ semantics.
One bounded set of shared gradients, patterns and filters provides low-amplitude
theme-aware texture and a fixed visual-light direction. Generated walls,
floor-edge surfaces, opening volume and raised plates may use it. User floor
content, room fills, Glow, sunlight, decor, glyph/text and vacuum never do.
floor-edge surfaces and opening volume may use it. The invisible raised
footprint and user floor content, room fills, Glow, sunlight, decor, glyph/text
and vacuum never do.
Neither theme nor HA Sun state enters the structural fingerprint.
Forced colours and missing filter support remove texture/soft shadow nuance,
+2 -2
View File
@@ -22,8 +22,8 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
| Item | State |
|---|---|
| Version | **v1.73.0-beta.1** everywhere (manifest, const.py, package.json, CARD_VERSION) |
| Current local cycle | v1.73.0-beta.1 keeps active Zigbee topology readable above room names and unrelated markers while preserving linked-device markers on top (#464). The same candidate carries hidden isometric Stage 3 (#160) without public enablement or changelog entry. Publish from `dev` only after exact-SHA Validate is green; `main` remains at v1.72.0. |
| Hidden Alpha Stage | #89 Stage 1 ships in v1.63.0-beta.1, #122 Stage 2 in v1.64.0 and #160 Stage 3 in v1.73.0-beta.1. The same hidden `iso` view now uses the fixed 4° camera, raised/tethered device-room-lock overlays, deeper openings and bounded theme materials. Since #448 the experiment is enabled only through the single indefinite browser-local `hp_alpha` gate; it is not expiring and has no per-stage key. Flat remains default; editors, `houseplan-space-card`, floor effects, stored coordinates and HA actions remain unchanged. Stage 3 stays internal and is absent from public changelog/user documentation. |
| Current local cycle | v1.73.0-beta.1 keeps active Zigbee topology readable above room names and unrelated markers while preserving linked-device markers on top (#464). The following hidden-alpha correction removes painted rectangular plates around raised device, room and lock overlays while retaining their invisible collision/fit footprint (#471). Publish from `dev` only after exact-SHA Validate is green; `main` remains at v1.72.0. |
| Hidden Alpha Stage | #89 Stage 1 ships in v1.63.0-beta.1, #122 Stage 2 in v1.64.0 and #160 Stage 3 in v1.73.0-beta.1. The same hidden `iso` view uses the fixed 4° camera, raised/tethered device-room-lock overlays, deeper openings and bounded theme materials; #471 removes the overlay plates from paint while retaining their safety geometry. Since #448 the experiment is enabled only through the single indefinite browser-local `hp_alpha` gate; it is not expiring and has no per-stage key. Flat remains default; editors, `houseplan-space-card`, floor effects, stored coordinates and HA actions remain unchanged. Stage 3 stays internal and is absent from public changelog/user documentation. |
| Workflow | Superseded 2026-08-12: the pre-1.62 rule of "local edits without tests or commits" is **dead** — since release 1.62 every product change follows `PROCESS.md` (issue in `S5-ready`+, branch `issue/<NN>-slug`, trailers on every commit, review pipeline; `AGENTS.md` is the summary). Release mechanics below remain current. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the GitHub Release uses `prerelease=false`. Release bodies are short and bilingual (Russian first); every bullet links its GitHub issue (#NN) so the #328 rules stay machine-checkable. A STABLE body aggregates the changelog since the PREVIOUS STABLE release (never since the last beta): features/fixes described across the line's beta changelogs must appear, while bugs that were introduced and fixed strictly inside the beta line (never shipped in any stable) are excluded — draft with `npm run release:notes -- <tag>`, curate by hand, then `npm run release:notes -- <tag> --verify` must pass. `Мелкие исправления и улучшения` / `Small fixes and improvements` is allowed only when the range really contains user-visible work not itemised in the body; a single-issue hotfix ships without it (the verifier enforces this). Every body ends with separate links to the Russian and English changelogs. Open or partially delivered issues are never presented as shipped. Telegram announcements are sent only for stable releases; beta and RC publication is silent. `docs/RELEASE-NOTES.md` is the current canonical body instance; `npm run release:prerelease -- <tag> --issues=… --yes` is the primary local publication path and the manual `Publish prerelease` workflow is its GitHub-only equivalent once present on `main`. Nothing is copied to the home instance by hand |
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records; their labels carry priority and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
| CI | Prerelease publication requires a green exact-SHA Validate: frontend/backend, smoke (including the #73 rAF frame sampler), golden, HACS/Hassfest and a short absolute-ceiling performance smoke. Obsolete same-ref Validate runs are cancelled. Full seven-sample base/candidate performance moved to `performance.yml` (`main` push, weekly, manual); stable release assets fail closed unless Validate and Full Performance are green for the exact tagged SHA and the stable-only CDP compositor screencast finds no empty/black presented frame. |
+16 -10
View File
@@ -5,6 +5,9 @@
- Date: 2026-09-05
- Normative spec: `docs/specs/160-isometric-stage3.md`
- Predecessor: `docs/adr/122-isometric-stage2-composition.md`
- Amended by: #471 (`docs/specs/471-isometric-overlay-white-plates.md`) removes
visible raised plates while retaining their geometry as an invisible safety
footprint.
## Context
@@ -47,12 +50,13 @@ Each raised item has four distinct pieces:
1. an immutable logical floor point;
2. its projected floor grounding point;
3. a floor-parallel plate and raised visual point;
3. an invisible floor-parallel safety footprint and raised visual point;
4. the existing screen-facing HTML root.
The SVG grounding, tether and plate roots are pointer-, focus- and ARIA-inert.
The HTML root moves to the same visual point and remains the single owner of
hover, focus, tooltip/dialog and click/context actions. It keeps its existing
The SVG grounding and tether roots are pointer-, focus- and ARIA-inert. The
safety footprint is calculation-only and produces no SVG/HTML surface. The HTML
root moves to the same visual point and remains the single owner of hover,
focus, tooltip/dialog and click/context actions. It keeps its existing
axis-aligned minimum 44 CSS-pixel target. Zigbee topology continues reading the
rendered marker position and gains no second projector.
@@ -60,11 +64,11 @@ rendered marker position and gains no second projector.
Wall proximity is tested against canonical physical-wall silhouettes cached in
the structural scene, including projected top faces and visible side quads. The
plate is expanded by a four CSS-pixel safety gap. If it intersects, a pure
footprint is expanded by a four CSS-pixel safety gap. If it intersects, a pure
resolver searches toward a proven room-safe point for the minimum clearing
displacement, with a 48 CSS-pixel cap. The complete straight candidate path must
remain strictly inside the owner and outside island holes. The displacement is
screen-space stable and applies only to the visual point and plate; the floor
screen-space stable and applies only to the visual point and footprint; the floor
point, config, layout, storage and export remain unchanged.
Device ownership first accepts its explicit room only when that room strictly
@@ -98,8 +102,8 @@ semantics.
### Materials, light and degradation
Generated wall faces, exterior floor edge, opening surfaces and raised plates
may receive a low-amplitude deterministic texture. User floor/backdrop, room
Generated wall faces, exterior floor edge and opening surfaces may receive a
low-amplitude deterministic texture. Invisible raised footprints and user floor/backdrop, room
fills, live lighting, decor/furniture, glyphs/text and vacuum may not. One
bounded set of shared gradients, patterns and filters is emitted per card; no
definition is allocated per face, opening or marker.
@@ -111,7 +115,7 @@ keys.
Forced colours use solid system surfaces and omit material texture and soft
shadows. A runtime without filter paint does the same while retaining solid
opening/raised geometry, tether and actions. These are decoration capability
opening geometry, invisible footprint calculations, tether and actions. These are decoration capability
paths, not reasons for Flat fallback.
### Cache, no-borders and evidence
@@ -124,7 +128,7 @@ filter/forced-colors capability. Live opening projection remains O(O); overlay
placement consumes cached wall silhouettes and does no per-marker union.
`show_borders:false` is an exact no-volume path. It emits no wall/opening/floor
edge or raised plate/ground/tether roots. Markers, labels and locks return to
edge or raised ground/tether roots. Markers, labels and locks return to
their `z=0` anchors but still share the real rotated floor matrix. The frame
does not include invisible raised bounds.
@@ -149,6 +153,8 @@ measures this graph separately from the initial View graph.
- The fixed 4° camera changes every legitimate Iso baseline; it must not change
a Flat baseline.
- Existing live floor effects remain one layer and are not textured or raised.
- Since #471, the conservative raised footprint remains in collision/nudge/fit
calculations but is never painted as a plate or texture surface.
- Unsupported decoration loses nuance rather than silently changing projection.
- Stage 3 remains an internal `hp_alpha` capability with Flat as the default and
immediate rollback; it is not announced in public documentation or changelog.
+11 -6
View File
@@ -1,5 +1,9 @@
# Issue #160 — Isometric Stage 3: глубина сцены, материалы и пространственные overlays
> **Уточнение #471:** видимая raised plate из исходного контракта удалена.
> Её геометрия сохранена только как невидимый footprint для collision, nudge и
> fit. Нормативная дельта: `docs/specs/471-isometric-overlay-white-plates.md`.
- **Issue:** https://github.com/Matysh/houseplan-card/issues/160
- **Предшественники:** #89 (Stage 1), #122 (Stage 2), #448 (единый
бессрочный alpha-переключатель)
@@ -228,7 +232,8 @@ Stage 3 развивает существующий Stage 2 wall cut и live ope
- сгенерированному floor edge/base underlay, не пользовательскому floor
content;
- opening panels, jamb/reveal, frame/sill;
- raised plates device/label/lock.
- невидимый safety footprint device/label/lock; он не получает texture и не
выводится как SVG/HTML-поверхность (#471).
Texture не применяется к:
@@ -370,19 +375,19 @@ geometry и могут кэшироваться в structural scene. Запре
3. существующий floor SVG со всеми live слоями;
4. wall contact/opening shadows;
5. canonical wall sides/top и opening volume;
6. grounding cues и floor-parallel raised plates;
6. grounding/tether cues; floor-parallel footprint существует только в расчётах;
7. screen-facing marker/label/lock content;
8. tooltip/dialog/system overlays.
Grounding/plate SVG geometry pointer-, focus- и ARIA-inert. Интерактивность
остаётся на существующих HTML roots.
Grounding/tether SVG geometry pointer-, focus- и ARIA-inert. Footprint вообще
не попадает в DOM. Интерактивность остаётся на существующих HTML roots.
### 7.4 <code>show_borders:false</code>
Это принятый Stage 1 no-volume branch:
- wall/opening/floor-edge Stage 2/3 structural roots отсутствуют;
- raised plates, grounding shadows, tethers и nudge отсутствуют;
- raised placement, grounding shadows, tethers, footprint и nudge отсутствуют;
- весь floor/live слой всё равно использует настоящую каноническую affine-
матрицу камеры `+4°`; прежняя аппроксимация через два угла `viewBox`
запрещена, потому что при ненулевом повороте она искажает projection и hit
@@ -721,7 +726,7 @@ Backend не меняется. Отдельных backend tests нет; Linux Va
| W3 | Поднять vacuum puck/trail | floor-bound unit + combined browser smoke |
| W4 | Убрать <code>max(44px,...)</code> у raised root | coarse-pointer target smoke |
| W5 | При nudge изменить layout/config floor coordinate | write-spy unit/smoke |
| W6 | Показывать raised plates при <code>show_borders:false</code> | capability unit + no-borders golden |
| W6 | Вернуть painted raised plate либо raised footprint при <code>show_borders:false</code> | capability unit + no-borders golden |
| W7 | Включить HA Sun state в fingerprint/light vector | fingerprint unit + HA-only rebuild smoke |
| W8 | Создать pattern/filter на каждую face или marker | definition-count contract/smoke |
| W9 | Перепутать hinge/flip одной door/gate leaf | exact opening-basis unit + opening golden |
+35 -33
View File
@@ -55,8 +55,8 @@ export interface IsoOverlayPlacementInput extends IsoOverlayOwnerInput {
wallSilhouettes: readonly IsoWallSilhouette[];
/** Internal fast path for silhouettes produced by the cached structural scene. */
wallGeometryValidated?: boolean;
/** Half-size of the floor-parallel plate in plan units. */
plateHalfSize: PlanPoint;
/** Half-size of the invisible floor-parallel safety footprint in plan units. */
footprintHalfSize: PlanPoint;
wallHeight?: number;
visualOffset?: number;
/** Uniform viewBox units represented by one CSS pixel at the current viewport. */
@@ -91,7 +91,8 @@ export interface IsoOverlayPlacement {
floorScene: ScenePoint;
raisedScene: ScenePoint;
visualScene: ScenePoint;
plate: readonly ScenePoint[];
/** Invisible collision/fit footprint; never render it as a surface. */
footprint: readonly ScenePoint[];
nudgeScene: ScenePoint;
nudgeCss: ScenePoint;
nudgeDistanceCss: number;
@@ -266,7 +267,7 @@ export function isoOverlayPlane(kind: IsoOverlayKind, showBorders: boolean): Iso
? 'raised' : 'floor';
}
export function buildIsoPlatePolygon(
export function buildIsoFootprintPolygon(
center: PlanPoint,
halfSize: PlanPoint,
zUnits: number,
@@ -275,7 +276,7 @@ export function buildIsoPlatePolygon(
): readonly ScenePoint[] {
if (!finitePoint(center) || !finitePoint(halfSize) || halfSize[0] < 0 || halfSize[1] < 0
|| !Number.isFinite(zUnits) || !finitePoint(sceneOffset))
throw new Error('invalid isometric overlay plate');
throw new Error('invalid isometric overlay footprint');
return ([
[center[0] - halfSize[0], center[1] - halfSize[1]],
[center[0] + halfSize[0], center[1] - halfSize[1]],
@@ -353,25 +354,25 @@ function segmentBoundsNear(
&& Math.max(a[1], b[1]) >= bounds[1] - gap;
}
function plateNearSilhouette(
plate: readonly ScenePoint[], plateBounds: Bounds,
function footprintNearSilhouette(
footprint: readonly ScenePoint[], footprintBounds: Bounds,
silhouette: IsoWallSilhouette, gap: number, cacheBounds: boolean,
): boolean {
const wallBounds = silhouetteBounds(silhouette, cacheBounds);
if (!wallBounds || !boundsNear(plateBounds, wallBounds, gap)) return false;
if (plate.some((point) => pointInSilhouette(point, silhouette))) return true;
if (!wallBounds || !boundsNear(footprintBounds, wallBounds, gap)) return false;
if (footprint.some((point) => pointInSilhouette(point, silhouette))) return true;
if (silhouette.outer.some((point) =>
point[0] >= plateBounds[0] && point[0] <= plateBounds[2]
&& point[1] >= plateBounds[1] && point[1] <= plateBounds[3]
&& pointInRing(point, plate))) return true;
point[0] >= footprintBounds[0] && point[0] <= footprintBounds[2]
&& point[1] >= footprintBounds[1] && point[1] <= footprintBounds[3]
&& pointInRing(point, footprint))) return true;
for (const wallRing of [silhouette.outer, ...(silhouette.holes || [])]) {
for (let plateIndex = 0; plateIndex < plate.length; plateIndex++) {
const plateNext = (plateIndex + 1) % plate.length;
for (let footprintIndex = 0; footprintIndex < footprint.length; footprintIndex++) {
const footprintNext = (footprintIndex + 1) % footprint.length;
for (let wallIndex = 0; wallIndex < wallRing.length; wallIndex++) {
const wallNext = (wallIndex + 1) % wallRing.length;
if (!segmentBoundsNear(plateBounds, wallRing[wallIndex], wallRing[wallNext], gap))
if (!segmentBoundsNear(footprintBounds, wallRing[wallIndex], wallRing[wallNext], gap))
continue;
if (segmentDistance(plate[plateIndex], plate[plateNext],
if (segmentDistance(footprint[footprintIndex], footprint[footprintNext],
wallRing[wallIndex], wallRing[wallNext]) <= gap + EPS) return true;
}
}
@@ -414,8 +415,8 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
const unitsPerPixel = input.sceneUnitsPerCssPixel ?? 1;
const safetyGap = input.safetyGapCssPx ?? ISO_OVERLAY_SAFETY_GAP_CSS_PX;
const maxNudge = input.maxNudgeCssPx ?? ISO_OVERLAY_MAX_NUDGE_CSS_PX;
if (!finitePoint(input.floorAnchor) || !finitePoint(input.plateHalfSize)
|| input.plateHalfSize[0] < 0 || input.plateHalfSize[1] < 0
if (!finitePoint(input.floorAnchor) || !finitePoint(input.footprintHalfSize)
|| input.footprintHalfSize[0] < 0 || input.footprintHalfSize[1] < 0
|| !Number.isFinite(wallHeight) || wallHeight < 0
|| !Number.isFinite(visualOffset) || visualOffset < 0
|| !Number.isFinite(unitsPerPixel) || unitsPerPixel <= 0
@@ -430,7 +431,7 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
const tether = tetherGeometry(floorScene, floorScene, false);
return {
plane, owner: null, floorAnchor, floorScene, raisedScene: floorScene,
visualScene: floorScene, plate: [], nudgeScene: [0, 0], nudgeCss: [0, 0],
visualScene: floorScene, footprint: [], nudgeScene: [0, 0], nudgeCss: [0, 0],
nudgeDistanceCss: 0, nudged: false, nearWallBefore: false, nearWallAfter: false,
cleared: true, capped: false,
grounding: { center: floorScene, visible: false }, tether,
@@ -445,14 +446,15 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
: resolveIsoOverlayOwner(input);
const geometryValid = input.wallGeometryValidated ?? input.wallSilhouettes.every(validSilhouette);
const gapUnits = safetyGap * unitsPerPixel;
const basePlate = buildIsoPlatePolygon(floorAnchor, input.plateHalfSize,
const baseFootprint = buildIsoFootprintPolygon(floorAnchor, input.footprintHalfSize,
raisedHeight, camera);
const isNear = (plate: readonly ScenePoint[]): boolean => {
const bounds = ringBounds(plate);
const isNear = (footprint: readonly ScenePoint[]): boolean => {
const bounds = ringBounds(footprint);
return !!bounds && input.wallSilhouettes.some((wall) =>
plateNearSilhouette(plate, bounds, wall, gapUnits, input.wallGeometryValidated === true));
footprintNearSilhouette(footprint, bounds, wall, gapUnits,
input.wallGeometryValidated === true));
};
const nearWallBefore = geometryValid ? isNear(basePlate) : true;
const nearWallBefore = geometryValid ? isNear(baseFootprint) : true;
let distanceCss = 0;
let nudgeScene: ScenePoint = [0, 0];
@@ -487,7 +489,7 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
floorAnchor[1] + (owner.safePoint[1] - floorAnchor[1]) * searchRatio,
];
// The structural scene may contain hundreds of faces. Only silhouettes
// intersecting the complete swept plate envelope can affect this nudge;
// intersecting the complete swept footprint envelope can affect this nudge;
// filter them once instead of repeating the whole-scene scan for every
// coarse and binary probe.
const maxOffset: ScenePoint = [
@@ -495,8 +497,8 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
uy * searchLimitCss * unitsPerPixel,
];
const sweptBounds = ringBounds([
...basePlate,
...basePlate.map((point) => [
...baseFootprint,
...baseFootprint.map((point) => [
point[0] + maxOffset[0], point[1] + maxOffset[1],
] as ScenePoint),
]);
@@ -511,11 +513,11 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
const collidesAt = (candidateCss: number): boolean => {
const offset: ScenePoint = [ux * candidateCss * unitsPerPixel,
uy * candidateCss * unitsPerPixel];
const plate = basePlate.map((point) =>
const footprint = baseFootprint.map((point) =>
[point[0] + offset[0], point[1] + offset[1]] as ScenePoint);
const bounds = ringBounds(plate);
const bounds = ringBounds(footprint);
return !!bounds && nearbyWalls.some((wall) =>
plateNearSilhouette(plate, bounds, wall, gapUnits,
footprintNearSilhouette(footprint, bounds, wall, gapUnits,
input.wallGeometryValidated === true));
};
const hint = input.nudgeHintCss;
@@ -576,14 +578,14 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
}
const visualScene: ScenePoint = [raisedScene[0] + nudgeScene[0], raisedScene[1] + nudgeScene[1]];
const plate = buildIsoPlatePolygon(floorAnchor, input.plateHalfSize,
const footprint = buildIsoFootprintPolygon(floorAnchor, input.footprintHalfSize,
raisedHeight, camera, nudgeScene);
const nearWallAfter = geometryValid ? knownNearWallAfter ?? isNear(plate) : true;
const nearWallAfter = geometryValid ? knownNearWallAfter ?? isNear(footprint) : true;
const nudged = distanceCss > EPS;
const tetherVisible = nudged || nearWallBefore || nearWallAfter
|| !!input.hovered || !!input.focused || !!input.selected;
return {
plane, owner, floorAnchor, floorScene, raisedScene, visualScene, plate,
plane, owner, floorAnchor, floorScene, raisedScene, visualScene, footprint,
nudgeScene,
nudgeCss: [nudgeScene[0] / unitsPerPixel, nudgeScene[1] / unitsPerPixel],
nudgeDistanceCss: distanceCss,
+15 -32
View File
@@ -113,8 +113,8 @@ export type IsoOverlayRenderScene = {
};
/**
* CSS-derived, deliberately conservative footprint constants. The raised SVG
* plate has no DOM measurement loop, so every value is an upper bound in
* CSS-derived, deliberately conservative footprint constants. The invisible
* footprint has no DOM measurement loop, so every value is an upper bound in
* multiples of the device core / room base-font rather than a sampled pixel
* size. Keep these in one place beside the pure footprint resolver whenever
* the shared face/room-label CSS changes.
@@ -188,7 +188,7 @@ function halfSizeOf(bounds: RelativeBounds, padding: number): PlanPoint {
];
}
/** Conservative raised-plate footprint for every supported HTML overlay. */
/** Conservative invisible footprint for every supported raised HTML overlay. */
export function isoRaisedOverlayHalfSize(input: IsoRaisedFootprintInput): PlanPoint {
if (input.kind === 'opening-lock') {
const size = Number.isFinite(input.size) && input.size > 0 ? input.size : 0;
@@ -601,12 +601,12 @@ const overlayEntryPoints = (
const placement = entry.placement;
const center = final ? placement.visualScene : placement.raisedScene;
const [halfX, halfY] = entry.screenHalfSize;
const plate = final ? placement.plate : placement.plate.map((point) => [
const footprint = final ? placement.footprint : placement.footprint.map((point) => [
point[0] - placement.nudgeScene[0], point[1] - placement.nudgeScene[1],
] as ScenePoint);
return [
placement.floorScene,
...plate,
...footprint,
[center[0] - halfX, center[1] - halfY],
[center[0] + halfX, center[1] - halfY],
[center[0] + halfX, center[1] + halfY],
@@ -619,7 +619,7 @@ const selectedOverlayEntries = (
): readonly IsoOverlayRenderEntry[] => ownerId === undefined ? entries
: entries.filter((entry) => entry.placement.owner?.id === ownerId);
/** Exact AABB of the final screen-facing roots and their raised plates. */
/** Exact AABB of the final screen-facing roots and their invisible footprints. */
export function isoOverlaySceneBounds(
scene: IsoOverlayRenderScene | null, ownerId?: string,
): Rect | null {
@@ -796,13 +796,14 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
kind: IsoRaisedOverlayKind,
id: string,
floorAnchor: PlanPoint,
plateHalfSize: PlanPoint,
footprintHalfSize: PlanPoint,
preferredRoomId?: string | null,
selected = false,
): IsoOverlayPlacement => {
const collisionMode = input.resolveCollisions === false ? 'fit' : 'live';
const cacheKey = `${collisionMode}\u0000${kind}\u0000${id}`;
const shapeSignature = [floorAnchor[0], floorAnchor[1], plateHalfSize[0], plateHalfSize[1],
const shapeSignature = [floorAnchor[0], floorAnchor[1],
footprintHalfSize[0], footprintHalfSize[1],
preferredRoomId || '', wallHeight, visualOffset,
input.layers.shadows ? 1 : 0, selected ? 1 : 0].join('|');
const signature = `${shapeSignature}|${unitsPerPixel}`;
@@ -814,8 +815,8 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
const distanceAtNewScale = previous.nudgeDistanceCss
* cached.value.unitsPerPixel / unitsPerPixel;
// Zooming in only shrinks the required scene-unit safety gap. An
// unchanged plate that was clear therefore stays clear; an unchanged
// non-near plate stays non-near. Reuse the immutable scene placement so
// unchanged footprint that was clear therefore stays clear; an unchanged
// non-near footprint stays non-near. Reuse the immutable scene placement so
// Lit also keeps the raised SVG subtree. Once its CSS displacement would
// exceed the public cap, fall through to the exact resolver.
if (!previous.nearWallBefore
@@ -847,7 +848,7 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
showBorders: true,
wallSilhouettes: input.resolveCollisions === false ? [] : input.wallSilhouettes,
wallGeometryValidated: true,
plateHalfSize,
footprintHalfSize,
wallHeight,
visualOffset,
sceneUnitsPerCssPixel: unitsPerPixel,
@@ -1066,7 +1067,7 @@ export function resolveIsoFramePresentation(input: {
shadows: renderIsoShadows(renderLayers, panels, input.scene?.geometry, input.cellCm),
walls: renderIsoWalls(input.projection, renderLayers, input.scene, panels, input.cellCm),
grounds: renderIsoOverlayGrounds(overlays, renderLayers, input.cellCm),
raised: renderIsoRaisedOverlays(overlays, renderLayers),
raised: renderIsoRaisedOverlays(overlays),
});
try { return render(layers); } catch (error) {
// A material/shadow presentation failure loses only decorative nuance.
@@ -1126,12 +1127,6 @@ function renderIsoDefs(
<filter id="hp-iso-overlay-ground" data-hp-iso-material-def x="-45%" y="-80%" width="190%" height="260%">
<feGaussianBlur stdDeviation="${3 * visualScale}"></feGaussianBlur>
</filter>` : nothing}
${root === 'overlays' && layers.materialNuance ? svg`
<pattern id="hp-iso-overlay-texture" data-hp-iso-material-def patternUnits="userSpaceOnUse"
width="${10 * visualScale}" height="${10 * visualScale}">
<circle class="iso-texture-mark" cx="${2.5 * visualScale}" cy="${2.5 * visualScale}"
r="${0.45 * visualScale}"></circle>
</pattern>` : nothing}
</defs>` as unknown as TemplateResult;
}
@@ -1146,7 +1141,7 @@ export function renderIsoOverlayGrounds(
return `${entry.kind}\u0000${entry.id}\u0000${grounding.center[0]}\u0000${grounding.center[1]}`
+ `\u0000${grounding.visible ? 1 : 0}\u0000${entry.groundRadius}`;
}).join('\u0001');
return guard([signature, layers.shadows, layers.materialNuance, cellCm], () => svg`
return guard([signature, layers.shadows, cellCm], () => svg`
${renderIsoDefs(layers, 'overlays', cellCm)}
<g class="iso-overlay-grounds" data-hp="iso-overlay-grounds"
aria-hidden="true" pointer-events="none">${overlays.entries.map((entry) => {
@@ -1161,10 +1156,9 @@ export function renderIsoOverlayGrounds(
export function renderIsoRaisedOverlays(
overlays: IsoOverlayRenderScene | null,
layers: IsoDecorationLayers,
): TemplateResult {
if (!overlays) return emptySvg();
return guard([overlays, layers.materialNuance], () => svg`
return guard([overlays], () => svg`
<g class="iso-raised-overlays" data-hp="iso-raised-overlays"
aria-hidden="true" pointer-events="none">
<g class="iso-overlay-tethers">${overlays.entries.map((entry) => {
@@ -1174,17 +1168,6 @@ export function renderIsoRaisedOverlays(
x1=${tether.from[0]} y1=${tether.from[1]}
x2=${tether.to[0]} y2=${tether.to[1]}></line>` : nothing;
})}</g>
<g class="iso-overlay-plates">${overlays.entries.map((entry) => {
const points = entry.placement.plate.map((point) => `${point[0]},${point[1]}`).join(' ');
if (!points) return nothing;
return svg`<g data-hp-iso-overlay-kind=${entry.kind} data-id=${entry.id}
data-hp-iso-raised="true" data-hp-iso-nudged=${String(entry.placement.nudged)}>
<polygon class="iso-overlay-plate iso-overlay-${entry.kind}" points=${points}></polygon>
${layers.materialNuance
? svg`<polygon class="iso-overlay-plate-texture" points=${points}></polygon>`
: nothing}
</g>`;
})}</g>
</g>`) as unknown as TemplateResult;
}
+3 -27
View File
@@ -335,19 +335,6 @@ export const planStyles = css`
vector-effect: non-scaling-stroke;
pointer-events: none;
}
.iso-overlay-plate {
fill: rgba(248, 249, 247, 0.9);
stroke: rgba(123, 132, 136, 0.58);
stroke-width: 0.8;
vector-effect: non-scaling-stroke;
pointer-events: none;
}
.iso-overlay-plate-texture {
fill: url(#hp-iso-overlay-texture);
opacity: 0.11;
stroke: none;
pointer-events: none;
}
.iso-ambient-shadow {
fill: rgba(15, 21, 25, 0.22);
filter: url(#hp-iso-ambient-shadow);
@@ -392,10 +379,6 @@ export const planStyles = css`
.stage.theme-dark .iso-texture-line { stroke: rgba(255, 255, 255, 0.18); }
.stage.theme-dark .iso-overlay-ground { fill: rgba(0, 0, 0, 0.42); }
.stage.theme-dark .iso-overlay-tether { stroke: rgba(224, 232, 235, 0.5); }
.stage.theme-dark .iso-overlay-plate {
fill: rgba(68, 77, 81, 0.92);
stroke: rgba(183, 195, 200, 0.52);
}
@media (prefers-color-scheme: dark) {
/* HA's explicit theme wins over the operating-system preference. */
.stage:not(.theme-light) .iso-side-hi { stop-color: #4c555a; }
@@ -421,27 +404,20 @@ export const planStyles = css`
.stage:not(.theme-light) .iso-texture-line { stroke: rgba(255, 255, 255, 0.18); }
.stage:not(.theme-light) .iso-overlay-ground { fill: rgba(0, 0, 0, 0.42); }
.stage:not(.theme-light) .iso-overlay-tether { stroke: rgba(224, 232, 235, 0.5); }
.stage:not(.theme-light) .iso-overlay-plate {
fill: rgba(68, 77, 81, 0.92);
stroke: rgba(183, 195, 200, 0.52);
}
}
@media (forced-colors: active) {
.iso-wall-side, .iso-wall-top, .iso-floor-side, .iso-opening-panel,
.iso-overlay-plate {
.iso-wall-side, .iso-wall-top, .iso-floor-side, .iso-opening-panel {
fill: Canvas;
stroke: CanvasText;
forced-color-adjust: auto;
}
.iso-ambient-shadow, .iso-contact-shadow, .iso-leaf-shadow,
.iso-overlay-ground, .iso-material-texture,
.iso-overlay-plate-texture { display: none; }
.iso-overlay-ground, .iso-material-texture { display: none; }
.iso-overlay-tether { stroke: CanvasText; }
}
@supports not (filter: blur(1px)) {
.iso-ambient-shadow, .iso-contact-shadow, .iso-leaf-shadow,
.iso-overlay-ground, .iso-material-texture,
.iso-overlay-plate-texture { display: none; }
.iso-overlay-ground, .iso-material-texture { display: none; }
}
/* Opaque plan paper (owner 2026-08-03): the scene bg_color / daynight sky
shows ONLY around the plan, never through it. The colour is the
+3 -3
View File
@@ -782,7 +782,7 @@ test('issue 160 Stage 3 goldens cover raised ownership, openings and solid fallb
assert.deepEqual(new Set(overlays.map((item) => item.theme)), new Set(['light', 'dark']));
assert.equal(overlays.every((item) => item.stage3Golden.requireNudged
&& item.stage3Golden.requireDenseFacets
&& item.stage3Golden.requireSolidCues
&& item.stage3Golden.requireTetherCues
&& item.stage3Golden.requireGrounding
&& item.stage3Golden.requiredKinds.includes('device')
&& item.stage3Golden.requiredKinds.includes('room-label')
@@ -823,8 +823,8 @@ test('issue 160 Stage 3 goldens cover raised ownership, openings and solid fallb
const noFilter = scenarios.find((item) => item.disableIsoFilters);
assert.equal(forced.stage3Golden.requireNoMaterialDefs, true);
assert.equal(noFilter.stage3Golden.requireNoMaterialDefs, true);
assert.equal(forced.stage3Golden.requireSolidCues, true);
assert.equal(noFilter.stage3Golden.requireSolidCues, true);
assert.equal(forced.stage3Golden.requireTetherCues, true);
assert.equal(noFilter.stage3Golden.requireTetherCues, true);
const noBorders = GOLDEN_SCENARIOS.find((item) => item.id === 'isometric-no-borders-dark');
assert.equal(noBorders.stage3Golden.noBorders, true);
+26 -19
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import {
ISO_OVERLAY_MAX_NUDGE_CSS_PX,
ISO_OVERLAY_SAFETY_GAP_CSS_PX,
buildIsoPlatePolygon,
buildIsoFootprintPolygon,
isoOverlayPlane,
isoRoomSafePoint,
resolveIsoOverlayOwner,
@@ -39,7 +39,7 @@ const placement = (overrides = {}) => resolveIsoOverlayPlacement({
preferredRoomId: 'room',
showBorders: true,
wallSilhouettes: [],
plateHalfSize: [4, 4],
footprintHalfSize: [4, 4],
...overrides,
});
@@ -124,7 +124,7 @@ test('a degenerate room has no safe point and placement degrades without throwin
const result = placement({
rooms: [degenerate],
preferredRoomId: degenerate.id,
wallSilhouettes: [{ outer: buildIsoPlatePolygon([50, 50], [10, 10], ISO_WALL_HEIGHT) }],
wallSilhouettes: [{ outer: buildIsoFootprintPolygon([50, 50], [10, 10], ISO_WALL_HEIGHT) }],
});
assert.equal(result.owner, null);
assert.equal(result.status, 'degraded');
@@ -132,25 +132,26 @@ test('a degenerate room has no safe point and placement degrades without throwin
assert.equal(result.nudged, false);
});
test('plate corners use the same affine camera on the raised plane', () => {
const plate = buildIsoPlatePolygon([50, 50], [10, 5], ISO_RAISED_OVERLAY_HEIGHT);
test('footprint corners use the same affine camera on the raised plane', () => {
const footprint = buildIsoFootprintPolygon([50, 50], [10, 5], ISO_RAISED_OVERLAY_HEIGHT);
const logical = [[40, 45], [60, 45], [60, 55], [40, 55]];
logical.forEach((point, index) => {
const projected = projectPlanPoint(point, ISO_RAISED_OVERLAY_HEIGHT);
close(plate[index][0], projected[0]);
close(plate[index][1], projected[1]);
close(footprint[index][0], projected[0]);
close(footprint[index][1], projected[1]);
});
assert.notEqual(plate[0][1], plate[1][1], 'the +4° plate is a projected parallelogram');
assert.notEqual(footprint[0][1], footprint[1][1],
'the +4° footprint is a projected parallelogram');
});
test('free raised overlay separates floor, raised, plate and optional tether geometry', () => {
test('free raised overlay separates floor, raised, footprint and optional tether geometry', () => {
const normal = placement();
assert.equal(normal.plane, 'raised');
assert.deepEqual(normal.floorAnchor, [50, 50]);
assert.deepEqual(normal.floorScene, projectPlanPoint([50, 50], 0));
assert.deepEqual(normal.raisedScene, projectPlanPoint([50, 50], ISO_RAISED_OVERLAY_HEIGHT));
assert.deepEqual(normal.visualScene, normal.raisedScene);
assert.equal(normal.plate.length, 4);
assert.equal(normal.footprint.length, 4);
assert.equal(normal.grounding.visible, true);
assert.equal(normal.tether.visible, false);
assert.equal(normal.status, 'ok');
@@ -163,7 +164,7 @@ test('free raised overlay separates floor, raised, plate and optional tether geo
test('wall-aware nudge is deterministic, minimal, inward and never changes the floor anchor', () => {
const wall = {
outer: buildIsoPlatePolygon([0, 50], [4, 60], ISO_WALL_HEIGHT),
outer: buildIsoFootprintPolygon([0, 50], [4, 60], ISO_WALL_HEIGHT),
};
const input = {
floorAnchor: [5, 50],
@@ -191,7 +192,9 @@ test('wall-aware nudge is deterministic, minimal, inward and never changes the f
});
test('shared-wall and corner fixtures nudge only toward the selected room', () => {
const verticalWall = { outer: buildIsoPlatePolygon([50, 50], [3, 60], ISO_WALL_HEIGHT) };
const verticalWall = {
outer: buildIsoFootprintPolygon([50, 50], [3, 60], ISO_WALL_HEIGHT),
};
const shared = placement({
floorAnchor: [49, 50],
rooms: [
@@ -209,8 +212,8 @@ test('shared-wall and corner fixtures nudge only toward the selected room', () =
rooms: [square('corner', 0, 0, 100, 100, [50, 50])],
preferredRoomId: 'corner',
wallSilhouettes: [
{ outer: buildIsoPlatePolygon([0, 50], [4, 60], ISO_WALL_HEIGHT) },
{ outer: buildIsoPlatePolygon([50, 0], [60, 4], ISO_WALL_HEIGHT) },
{ outer: buildIsoFootprintPolygon([0, 50], [4, 60], ISO_WALL_HEIGHT) },
{ outer: buildIsoFootprintPolygon([50, 0], [60, 4], ISO_WALL_HEIGHT) },
],
});
assert.equal(corner.nearWallBefore, true);
@@ -252,7 +255,7 @@ test('nudge never crosses an island hole or a concave-room boundary', () => {
rooms: [fixture.room],
preferredRoomId: fixture.room.id,
wallSilhouettes: [obstacle],
plateHalfSize: [1, 1],
footprintHalfSize: [1, 1],
wallHeight: 0,
visualOffset: 0,
sceneUnitsPerCssPixel: 1,
@@ -273,7 +276,9 @@ test('nudge never crosses an island hole or a concave-room boundary', () => {
});
test('cap and ambiguous ownership fail safe with a visible tether', () => {
const largeWall = { outer: buildIsoPlatePolygon([25, 50], [30, 30], ISO_WALL_HEIGHT) };
const largeWall = {
outer: buildIsoFootprintPolygon([25, 50], [30, 30], ISO_WALL_HEIGHT),
};
const capped = placement({
floorAnchor: [25, 50], wallSilhouettes: [largeWall], maxNudgeCssPx: 2,
});
@@ -286,7 +291,9 @@ test('cap and ambiguous ownership fail safe with a visible tether', () => {
const ownerless = placement({
floorAnchor: [150, 50], rooms: [], preferredRoomId: null,
wallSilhouettes: [{ outer: buildIsoPlatePolygon([150, 50], [10, 10], ISO_WALL_HEIGHT) }],
wallSilhouettes: [{
outer: buildIsoFootprintPolygon([150, 50], [10, 10], ISO_WALL_HEIGHT),
}],
});
assert.equal(ownerless.owner, null);
assert.equal(ownerless.nudged, false);
@@ -305,7 +312,7 @@ test('malformed collision input degrades without a guessed move', () => {
assert.equal(result.tether.visible, true);
});
test('show_borders:false is exact no-volume: floor anchor, no plate/nudge/cues', () => {
test('show_borders:false is exact no-volume: floor anchor, no footprint/nudge/cues', () => {
const result = placement({
showBorders: false,
wallSilhouettes: [{ outer: [[0, 0], [Number.NaN, 1], [2, 2]] }],
@@ -313,7 +320,7 @@ test('show_borders:false is exact no-volume: floor anchor, no plate/nudge/cues',
});
assert.equal(result.plane, 'floor');
assert.deepEqual(result.visualScene, result.floorScene);
assert.deepEqual(result.plate, []);
assert.deepEqual(result.footprint, []);
assert.equal(result.nudged, false);
assert.equal(result.grounding.visible, false);
assert.equal(result.tether.visible, false);
+11 -9
View File
@@ -23,7 +23,7 @@ import { ISO_OPENING_GEOMETRY_POLICY } from '../test-build/iso-openings.js';
import { buildIsoWallGeometry } from '../test-build/iso-walls.js';
import { wallKey } from '../test-build/wall-thickness.js';
import {
buildIsoPlatePolygon,
buildIsoFootprintPolygon,
resolveIsoOverlayOwner,
resolveIsoOverlayPlacement,
} from '../test-build/iso-overlays.js';
@@ -76,7 +76,7 @@ test('device footprint contains value capsule, bottom badge and displaced LQI ro
+ ISO_RAISED_FOOTPRINT.deviceLqiFontSize
+ ISO_RAISED_FOOTPRINT.devicePadding) * core;
assert.equal(halfSize[1], lqiBottom,
'bottom LQI position, line box and conservative plate padding are all included');
'bottom LQI position, line box and conservative footprint padding are all included');
assert.ok(halfSize[0] > core * 1.6,
'the expanding value core is not reduced to the shell diameter');
});
@@ -122,7 +122,7 @@ test('overlay bounds use final screen footprint and canonical owner filtering',
owner: { id: owner }, floorScene: [center[0] - 5, center[1]],
raisedScene: [center[0] - 4, center[1]], visualScene: center,
nudgeScene: [4, 0],
plate: [[center[0] - 2, center[1] - 1], [center[0] + 2, center[1] - 1],
footprint: [[center[0] - 2, center[1] - 1], [center[0] + 2, center[1] - 1],
[center[0] + 2, center[1] + 1], [center[0] - 2, center[1] + 1]],
});
const scene = { entries: [
@@ -141,7 +141,7 @@ test('stable overlay fit contains a maximum final nudge without zoom feedback',
placement: {
owner: { id: 'room' }, floorScene: [95, 50], raisedScene: [95, 50],
visualScene: [95, 50], nudgeScene: [0, 0],
plate: [[90, 46], [100, 46], [100, 54], [90, 54]],
footprint: [[90, 46], [100, 46], [100, 54], [90, 54]],
},
};
const stageSize = { width: 320, height: 180 };
@@ -164,7 +164,7 @@ test('stable overlay fit contains a maximum final nudge without zoom feedback',
const dx = 48 * unitsPerPixel;
const actual = { entries: [{ ...entry, placement: {
...entry.placement, visualScene: [95 + dx, 50], nudgeScene: [dx, 0],
plate: entry.placement.plate.map((point) => [point[0] + dx, point[1]]),
footprint: entry.placement.footprint.map((point) => [point[0] + dx, point[1]]),
} }] };
const final = isoOverlaySceneBounds(actual);
assert.ok(final.x >= fitted.bounds.x - 1e-7
@@ -345,7 +345,7 @@ test('Stage 3 reuses pure overlay placements and fit probes skip collision searc
rooms: [owner], wall_segments: [], room_drafts: [], partitions: [], wall_columns: [],
};
const wallSilhouettes = [{
outer: buildIsoPlatePolygon([0, 50], [4, 60], ISO_WALL_HEIGHT),
outer: buildIsoFootprintPolygon([0, 50], [4, 60], ISO_WALL_HEIGHT),
}];
const input = {
space,
@@ -439,7 +439,7 @@ test('visible wall side quads participate in overlay collision', () => {
rooms: [{ id: owner.id, outer: owner.poly, safePoint: owner.safePoint }],
preferredRoomId: owner.id,
showBorders: true,
plateHalfSize: [0.05, 0.05],
footprintHalfSize: [0.05, 0.05],
wallHeight: ISO_WALL_HEIGHT,
visualOffset: ISO_OVERLAY_VISUAL_OFFSET,
sceneUnitsPerCssPixel: 1,
@@ -452,8 +452,10 @@ test('visible wall side quads participate in overlay collision', () => {
const complete = resolveIsoOverlayPlacement({
...collisionInput, wallSilhouettes: scene.wallSilhouettes,
});
assert.equal(topOnly.nearWallBefore, false, 'raised plate does not touch the top footprint');
assert.equal(complete.nearWallBefore, true, 'the same plate intersects a rendered vertical side');
assert.equal(topOnly.nearWallBefore, false,
'raised footprint does not touch the wall-top footprint');
assert.equal(complete.nearWallBefore, true,
'the same raised footprint intersects a rendered vertical side');
});
test('orphan hosted openings never become phantom Stage 3 volumes', () => {
+5 -4
View File
@@ -101,7 +101,8 @@ test('Stage 3 composes ordered inert SVG surfaces below screen-facing HTML', ()
assert.match(sceneRender, /class="iso-wall-top"/);
assert.match(sceneRender, /class="iso-wall-side"/);
assert.match(sceneRender, /class="iso-opening-panel iso-\$\{surface\.type\} iso-opening-\$\{surface\.kind\} iso-material-\$\{surface\.material\}"/);
assert.match(sceneRender, /class="iso-overlay-plate iso-overlay-\$\{entry\.kind\}"/);
assert.doesNotMatch(sceneRender, /iso-overlay-plate|hp-iso-overlay-texture/,
'the invisible safety footprint must never become painted SVG geometry');
assert.match(sceneRender, /class="iso-overlay-tether"/);
assert.match(sceneRender, /class="iso-overlay-ground"/);
assert.match(sceneRender, /const depthQueue = buildIsoWallDepthQueue\(scene\.geometry, openingSurfaces\)/);
@@ -242,7 +243,7 @@ test('raised footprints participate in global and room fit without entering the
assert.match(sceneRender, /export function isoOverlaySceneBounds/);
assert.match(sceneRender, /export function resolveIsoOverlayFitEnvelope/);
assert.match(sceneRender, /const center = final \? placement\.visualScene : placement\.raisedScene/);
assert.match(sceneRender, /const plate = final \? placement\.plate/);
assert.match(sceneRender, /const footprint = final \? placement\.footprint/);
assert.match(sceneRender, /const \[halfX, halfY\] = entry\.screenHalfSize/);
const scene = section(card, 'private _isoScene(', 'private _latchIsoFallback');
assert.match(scene, /resolveIsoScene\([\s\S]*?buildIsoOverlayRenderScene|resolveIsoScene\([\s\S]*?_isoOverlayScene/);
@@ -283,9 +284,9 @@ test('Stage 3 materials and shadows are bounded, theme-aware and capability-safe
`expected a bounded shared definition set, got ${materialIds.length}`);
assert.equal(new Set(materialIds).size, materialIds.length, 'material ids must be unique');
assert.match(styles, /\.stage\.theme-dark \.iso-wall-side/);
assert.match(styles, /\.stage\.theme-dark \.iso-overlay-plate/);
assert.match(styles, /\.stage:not\(\.theme-light\) \.iso-wall-side/);
assert.match(styles, /@media \(forced-colors: active\)[\s\S]*?\.iso-overlay-plate[\s\S]*?fill:\s*Canvas/);
assert.doesNotMatch(styles, /iso-overlay-plate|hp-iso-overlay-texture/,
'themes and forced colors must not recreate a painted footprint');
assert.match(styles, /@supports not \(filter: blur\(1px\)\)[\s\S]*?\.iso-overlay-ground[\s\S]*?display:\s*none/);
const rendering = section(sceneRender, 'function renderIsoDefs(', 'export function resolveIsoDecorationLayers');
assert.match(sceneRender, /return `translate\(\$\{gridVisualUnits\(4, cellCm\)\} \$\{gridVisualUnits\(8, cellCm\)\}\)`/);