fix: settle live editor frames deterministically

Issue: #460
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-09-05 12:10:26 +03:00
parent 570da3dd1a
commit 5763c6aec0
38 changed files with 562 additions and 378 deletions
@@ -1,182 +1,182 @@
{
"schema": 1,
"fingerprint": "55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1",
"fingerprint": "f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 297496,
"initialViewGzipBytes": 297481,
"lazyFiles": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/de-DA2iaksI.js",
"houseplan-assets/editor-SYPHCziy.js",
"houseplan-assets/fr-CDr9xD9w.js",
"houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js",
"houseplan-assets/hp-zigbee-topology-overlay-Ck5d7CEt.js",
"houseplan-assets/live-interaction-runtime-DOFtpMsL.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js",
"houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/de-ikYRfVaQ.js",
"houseplan-assets/editor-D3hGLWnj.js",
"houseplan-assets/fr-CztCSf8d.js",
"houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js",
"houseplan-assets/hp-zigbee-topology-overlay-GnSX-nxk.js",
"houseplan-assets/live-interaction-runtime-DBSJOtiD.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js",
"houseplan-assets/zigbee-topology-runtime-eieh242A.js"
],
"lazyGzipBytes": 247833,
"lazyGzipBytes": 247974,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/editor-SYPHCziy.js",
"houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/editor-D3hGLWnj.js",
"houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js"
],
"lazyEditorGzipBytes": 183806,
"lazyEditorGzipBytes": 183988,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js"
],
"lazyOnboardingGzipBytes": 14315,
"lazyOnboardingGzipBytes": 14312,
"lazyLocaleFiles": [
"houseplan-assets/de-DA2iaksI.js",
"houseplan-assets/fr-CDr9xD9w.js"
"houseplan-assets/de-ikYRfVaQ.js",
"houseplan-assets/fr-CztCSf8d.js"
],
"lazyLocaleGzipBytes": 49943,
"lazyLocaleGzipBytes": 49914,
"files": [
{
"path": "houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"sha256": "5d7733f874ba4237cdb80efd6ab67d2114b11434008ff114db58ffdb4c4b2b4d",
"path": "houseplan-assets/backdrop-pick-BlzFtfHW.js",
"sha256": "15fde4dd87aa1039573fae46a9dc05bd4190eabec121686fe323192019345efe",
"rawBytes": 21592,
"gzipBytes": 7356,
"gzipBytes": 7357,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-DA2iaksI.js",
"sha256": "80c50906e30e4602ef6aca02e8324ab80d8a819339b93bb305c0cad5dff27928",
"path": "houseplan-assets/de-ikYRfVaQ.js",
"sha256": "2afe4ba96c9b4779665af9ec1c286c246d7cf4fb0b0f5d55c9e1f8bce22e2403",
"rawBytes": 83907,
"gzipBytes": 25246,
"gzipBytes": 25232,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-SYPHCziy.js",
"sha256": "3fb7e84aaead43e8c867e4754aa49acfaf5f988951e67ebd17cb30dc300eacff",
"path": "houseplan-assets/editor-D3hGLWnj.js",
"sha256": "016215135309933c3655320b4332d415995ad2ecf831014bd82c963b968f0700",
"rawBytes": 3831,
"gzipBytes": 1582,
"gzipBytes": 1579,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-CDr9xD9w.js",
"sha256": "971d9a485e2b73f09e3b544044f90fbcf237fab3c08c962080d03df8624e24ce",
"path": "houseplan-assets/fr-CztCSf8d.js",
"sha256": "b0c22cda12f5f09379b0900b670afe10f6cc456988fbb5433204b321cc817f2f",
"rawBytes": 85967,
"gzipBytes": 24697,
"gzipBytes": 24682,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-bl8WhyFc.js",
"sha256": "51f8c9ea339569f6eecefe3f54fdb644ecddbef4ef862c506740163aa5cbd4a0",
"path": "houseplan-assets/houseplan-card--Yu_3uZU.js",
"sha256": "cf3e3c01dc56cd44b3cdae00fc38ef54ddcf47fc5cb02531ee222f764818732e",
"rawBytes": 1058176,
"gzipBytes": 296701,
"gzipBytes": 296688,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-DA2iaksI.js",
"houseplan-assets/editor-SYPHCziy.js",
"houseplan-assets/fr-CDr9xD9w.js",
"houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js",
"houseplan-assets/hp-zigbee-topology-overlay-Ck5d7CEt.js",
"houseplan-assets/live-interaction-runtime-DOFtpMsL.js"
"houseplan-assets/de-ikYRfVaQ.js",
"houseplan-assets/editor-D3hGLWnj.js",
"houseplan-assets/fr-CztCSf8d.js",
"houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js",
"houseplan-assets/hp-zigbee-topology-overlay-GnSX-nxk.js",
"houseplan-assets/live-interaction-runtime-DBSJOtiD.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"sha256": "f2750ae81b2a32eb05bd35fc8ab3bfa610d78fa6285e9b32197a93ce7eddd89f",
"rawBytes": 631052,
"gzipBytes": 167443,
"path": "houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"sha256": "1309a63497de61690f0f86cc862902c57b9cb4cde841e0948494a2c074b15dc4",
"rawBytes": 631744,
"gzipBytes": 167631,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js"
],
"dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js"
"houseplan-assets/zigbee-topology-runtime-eieh242A.js"
]
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js",
"sha256": "f72e38d21a23609a34dc02cb6197e0a5cab03254fd5c6f0540fa582fdb8dc56b",
"path": "houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js",
"sha256": "a71b2f5339068aee09b9222503b734f0c91bf31b46c3b74337ee6d49ea444634",
"rawBytes": 28088,
"gzipBytes": 6959,
"gzipBytes": 6955,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/hp-zigbee-topology-overlay-Ck5d7CEt.js",
"sha256": "425c2b05175b2aa86d6fa281f85d216a51be4b1fbd3d2ee91156709c9b689bbb",
"path": "houseplan-assets/hp-zigbee-topology-overlay-GnSX-nxk.js",
"sha256": "18fbb64b96dbdb48569355909b6d70e7ee3f369c761304329e5d28382a10863e",
"rawBytes": 8626,
"gzipBytes": 3236,
"gzipBytes": 3234,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js",
"houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js",
"houseplan-assets/zigbee-topology-runtime-eieh242A.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/live-interaction-runtime-DOFtpMsL.js",
"sha256": "4d4c634e9a2494f1830e83ef24947e551ddc9aa0a6cf9fc95bdec7584715a20b",
"path": "houseplan-assets/live-interaction-runtime-DBSJOtiD.js",
"sha256": "e2188ee62c129161ead60af08a613353ff6e6b5fea0209d88c60a00795293f47",
"rawBytes": 5196,
"gzipBytes": 2126,
"gzipBytes": 2122,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-DM3CehUC.js",
"sha256": "ec2feed9e608719f48086c0ce577a88591ceaf63190f53169389b0cb5345d825",
"path": "houseplan-assets/zigbee-topology-CYMZJB0I.js",
"sha256": "3dbdda4a3c8decf229325193447a7e8498ac79ee9671e5fa923e1afab15fda78",
"rawBytes": 18961,
"gzipBytes": 7425,
"gzipBytes": 7421,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js",
"sha256": "95e9d3960b3199ac3b686265449b6fa4c27253a4631ba66826716a4464e1312e",
"path": "houseplan-assets/zigbee-topology-runtime-eieh242A.js",
"sha256": "9226ed8ab8996a3139630492336d1c669077ca6442f60e85b84068b94165754e",
"rawBytes": 3941,
"gzipBytes": 1763,
"gzipBytes": 1761,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "244f5722538f5419860ec757832f44161714476e58bf4da5bbe82747bed28377",
"sha256": "fa974652b479dbe9381b5862de0feb250083eda438065263b15754ffb069696a",
"rawBytes": 1183,
"gzipBytes": 795,
"gzipBytes": 793,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
@@ -1,14 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card-bl8WhyFc.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__="f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card--Yu_3uZU.js";class f 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,f={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")},h=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}
.computeLabel=${e=>h[e.name]||e.name}
.computeLabel=${e=>f[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(f.slice(0,3))}
${d(h.slice(0,3))}
${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a}
${d(f.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
${d(h.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1";import{n as t,b as n}from"./zigbee-topology-DM3CehUC.js";import{I as e}from"./houseplan-card-bl8WhyFc.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__="f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7";import{n as t,b as n}from"./zigbee-topology-CYMZJB0I.js";import{I as e}from"./houseplan-card--Yu_3uZU.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 f(t){if(!0!==t?.user?.is_admin)throw a("permission")}function p(n){const e=i(n);return e?c(e,"zha",async()=>{if(f(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(f(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,p=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{p=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 f=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&&p?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof f&&b.push(f),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{p as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1";try{await import("./houseplan-assets/houseplan-card-bl8WhyFc.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__="f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7";try{await import("./houseplan-assets/houseplan-card--Yu_3uZU.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)}
+1 -5
View File
@@ -10,11 +10,7 @@ const res = await page.evaluate(async () => {
while (c._modeTransitionBusy && performance.now() - started < 1500);
await c.updateComplete;
};
const settleLive = async () => {
await Promise.resolve();
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
await c.updateComplete;
};
const settleLive = () => c._editorRuntime?._whenLiveEditorSettled() ?? c.updateComplete;
const esc = async () => { window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await c.updateComplete; };
// 1) третья вкладка есть
const tabs = [...sr().querySelectorAll('.modetab')];
+1 -5
View File
@@ -27,11 +27,7 @@ const res = await page.evaluate(async () => {
while (c._modeTransitionBusy && performance.now() - started < 1500);
await c.updateComplete;
};
const settleLive = async () => {
await Promise.resolve();
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
await c.updateComplete;
};
const settleLive = () => c._editorRuntime?._whenLiveEditorSettled() ?? c.updateComplete;
// null-safe: на сборке ДО этих правок у надписи нет ни data-id, ни tspan —
// смок должен показать список провалов, а не упасть с исключением
const el = (id) => sr().querySelector(`[data-hp-live-editor] .decorlayer text.dtext[data-id="${id}"]`)
+19 -6
View File
@@ -23,11 +23,7 @@ const res = await page.evaluate(async () => {
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const settleLive = async () => {
await Promise.resolve();
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
await c.updateComplete;
};
const settleLive = () => c._editorRuntime?._whenLiveEditorSettled() ?? c.updateComplete;
const stageEl = () => sr().querySelector('.stage');
const el = (id) => (id ? sr().querySelector(`[data-hp-live-editor] .decorlayer [data-id="${id}"]`)
|| sr().querySelector(`.decorlayer [data-id="${id}"]`) : null);
@@ -54,7 +50,23 @@ const res = await page.evaluate(async () => {
const pe = (id) => { const e = el(id); return e ? getComputedStyle(e).pointerEvents : null; };
const box = () => c._decorBoxOf(sofaNow());
const reducedMotion = c._reducedMotion;
c._reducedMotion = true;
sr().querySelectorAll('.modetab')[2].click(); await c.updateComplete;
await new Promise((resolve) => {
const observer = new ResizeObserver(() => {
observer.disconnect();
resolve();
});
observer.observe(stageEl());
});
while (c._modeTransitionBusy || c._refitRaf || c._pendingRefitSize) {
await new Promise(requestAnimationFrame);
}
c.requestUpdate();
await c.updateComplete;
c._reducedMotion = reducedMotion;
out.initialModeSettled = !c._modeTransitionBusy && !c._refitRaf && !c._pendingRefitSize;
c._curSpaceCfg.decor = [];
c._decorTool = 'select'; c._decorSel = null; c._furnPalette = null; c._furnCategory = null;
c._cfgEpoch++; c.requestUpdate(); await c.updateComplete;
@@ -147,7 +159,8 @@ const res = await page.evaluate(async () => {
out.selectedItemIsMarked = !!item('sofa')?.classList.contains('on');
// ================= 3. точное превью без мутации ==========================
const ghost = () => sr().querySelector('.furniture-placement-preview');
const ghost = () => sr().querySelector('[data-hp-live-editor] .furniture-placement-preview')
|| sr().querySelector('.furniture-placement-preview');
const beforePreview = {
decor: c._decorList.length,
epoch: c._cfgEpoch,
+84 -84
View File
@@ -1,182 +1,182 @@
{
"schema": 1,
"fingerprint": "55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1",
"fingerprint": "f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 297496,
"initialViewGzipBytes": 297481,
"lazyFiles": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/de-DA2iaksI.js",
"houseplan-assets/editor-SYPHCziy.js",
"houseplan-assets/fr-CDr9xD9w.js",
"houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js",
"houseplan-assets/hp-zigbee-topology-overlay-Ck5d7CEt.js",
"houseplan-assets/live-interaction-runtime-DOFtpMsL.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js",
"houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/de-ikYRfVaQ.js",
"houseplan-assets/editor-D3hGLWnj.js",
"houseplan-assets/fr-CztCSf8d.js",
"houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js",
"houseplan-assets/hp-zigbee-topology-overlay-GnSX-nxk.js",
"houseplan-assets/live-interaction-runtime-DBSJOtiD.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js",
"houseplan-assets/zigbee-topology-runtime-eieh242A.js"
],
"lazyGzipBytes": 247833,
"lazyGzipBytes": 247974,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/editor-SYPHCziy.js",
"houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/editor-D3hGLWnj.js",
"houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js"
],
"lazyEditorGzipBytes": 183806,
"lazyEditorGzipBytes": 183988,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js"
],
"lazyOnboardingGzipBytes": 14315,
"lazyOnboardingGzipBytes": 14312,
"lazyLocaleFiles": [
"houseplan-assets/de-DA2iaksI.js",
"houseplan-assets/fr-CDr9xD9w.js"
"houseplan-assets/de-ikYRfVaQ.js",
"houseplan-assets/fr-CztCSf8d.js"
],
"lazyLocaleGzipBytes": 49943,
"lazyLocaleGzipBytes": 49914,
"files": [
{
"path": "houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"sha256": "5d7733f874ba4237cdb80efd6ab67d2114b11434008ff114db58ffdb4c4b2b4d",
"path": "houseplan-assets/backdrop-pick-BlzFtfHW.js",
"sha256": "15fde4dd87aa1039573fae46a9dc05bd4190eabec121686fe323192019345efe",
"rawBytes": 21592,
"gzipBytes": 7356,
"gzipBytes": 7357,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-DA2iaksI.js",
"sha256": "80c50906e30e4602ef6aca02e8324ab80d8a819339b93bb305c0cad5dff27928",
"path": "houseplan-assets/de-ikYRfVaQ.js",
"sha256": "2afe4ba96c9b4779665af9ec1c286c246d7cf4fb0b0f5d55c9e1f8bce22e2403",
"rawBytes": 83907,
"gzipBytes": 25246,
"gzipBytes": 25232,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-SYPHCziy.js",
"sha256": "3fb7e84aaead43e8c867e4754aa49acfaf5f988951e67ebd17cb30dc300eacff",
"path": "houseplan-assets/editor-D3hGLWnj.js",
"sha256": "016215135309933c3655320b4332d415995ad2ecf831014bd82c963b968f0700",
"rawBytes": 3831,
"gzipBytes": 1582,
"gzipBytes": 1579,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-CDr9xD9w.js",
"sha256": "971d9a485e2b73f09e3b544044f90fbcf237fab3c08c962080d03df8624e24ce",
"path": "houseplan-assets/fr-CztCSf8d.js",
"sha256": "b0c22cda12f5f09379b0900b670afe10f6cc456988fbb5433204b321cc817f2f",
"rawBytes": 85967,
"gzipBytes": 24697,
"gzipBytes": 24682,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-bl8WhyFc.js",
"sha256": "51f8c9ea339569f6eecefe3f54fdb644ecddbef4ef862c506740163aa5cbd4a0",
"path": "houseplan-assets/houseplan-card--Yu_3uZU.js",
"sha256": "cf3e3c01dc56cd44b3cdae00fc38ef54ddcf47fc5cb02531ee222f764818732e",
"rawBytes": 1058176,
"gzipBytes": 296701,
"gzipBytes": 296688,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-DA2iaksI.js",
"houseplan-assets/editor-SYPHCziy.js",
"houseplan-assets/fr-CDr9xD9w.js",
"houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js",
"houseplan-assets/hp-zigbee-topology-overlay-Ck5d7CEt.js",
"houseplan-assets/live-interaction-runtime-DOFtpMsL.js"
"houseplan-assets/de-ikYRfVaQ.js",
"houseplan-assets/editor-D3hGLWnj.js",
"houseplan-assets/fr-CztCSf8d.js",
"houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js",
"houseplan-assets/hp-zigbee-topology-overlay-GnSX-nxk.js",
"houseplan-assets/live-interaction-runtime-DBSJOtiD.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-DkbTjh7i.js",
"sha256": "f2750ae81b2a32eb05bd35fc8ab3bfa610d78fa6285e9b32197a93ce7eddd89f",
"rawBytes": 631052,
"gzipBytes": 167443,
"path": "houseplan-assets/houseplan-editor-runtime-QXAvNIKY.js",
"sha256": "1309a63497de61690f0f86cc862902c57b9cb4cde841e0948494a2c074b15dc4",
"rawBytes": 631744,
"gzipBytes": 167631,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js"
],
"dynamicImports": [
"houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js"
"houseplan-assets/zigbee-topology-runtime-eieh242A.js"
]
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-BCwiKHSu.js",
"sha256": "f72e38d21a23609a34dc02cb6197e0a5cab03254fd5c6f0540fa582fdb8dc56b",
"path": "houseplan-assets/houseplan-onboarding-runtime-DoCgwkP3.js",
"sha256": "a71b2f5339068aee09b9222503b734f0c91bf31b46c3b74337ee6d49ea444634",
"rawBytes": 28088,
"gzipBytes": 6959,
"gzipBytes": 6955,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Bx8Wbqtd.js",
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/backdrop-pick-BlzFtfHW.js",
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/hp-zigbee-topology-overlay-Ck5d7CEt.js",
"sha256": "425c2b05175b2aa86d6fa281f85d216a51be4b1fbd3d2ee91156709c9b689bbb",
"path": "houseplan-assets/hp-zigbee-topology-overlay-GnSX-nxk.js",
"sha256": "18fbb64b96dbdb48569355909b6d70e7ee3f369c761304329e5d28382a10863e",
"rawBytes": 8626,
"gzipBytes": 3236,
"gzipBytes": 3234,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js",
"houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js",
"houseplan-assets/zigbee-topology-runtime-eieh242A.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/live-interaction-runtime-DOFtpMsL.js",
"sha256": "4d4c634e9a2494f1830e83ef24947e551ddc9aa0a6cf9fc95bdec7584715a20b",
"path": "houseplan-assets/live-interaction-runtime-DBSJOtiD.js",
"sha256": "e2188ee62c129161ead60af08a613353ff6e6b5fea0209d88c60a00795293f47",
"rawBytes": 5196,
"gzipBytes": 2126,
"gzipBytes": 2122,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-DM3CehUC.js",
"sha256": "ec2feed9e608719f48086c0ce577a88591ceaf63190f53169389b0cb5345d825",
"path": "houseplan-assets/zigbee-topology-CYMZJB0I.js",
"sha256": "3dbdda4a3c8decf229325193447a7e8498ac79ee9671e5fa923e1afab15fda78",
"rawBytes": 18961,
"gzipBytes": 7425,
"gzipBytes": 7421,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-runtime-dzqvHl6K.js",
"sha256": "95e9d3960b3199ac3b686265449b6fa4c27253a4631ba66826716a4464e1312e",
"path": "houseplan-assets/zigbee-topology-runtime-eieh242A.js",
"sha256": "9226ed8ab8996a3139630492336d1c669077ca6442f60e85b84068b94165754e",
"rawBytes": 3941,
"gzipBytes": 1763,
"gzipBytes": 1761,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js",
"houseplan-assets/zigbee-topology-DM3CehUC.js"
"houseplan-assets/houseplan-card--Yu_3uZU.js",
"houseplan-assets/zigbee-topology-CYMZJB0I.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "244f5722538f5419860ec757832f44161714476e58bf4da5bbe82747bed28377",
"sha256": "fa974652b479dbe9381b5862de0feb250083eda438065263b15754ffb069696a",
"rawBytes": 1183,
"gzipBytes": 795,
"gzipBytes": 793,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-bl8WhyFc.js"
"houseplan-assets/houseplan-card--Yu_3uZU.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,14 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card-bl8WhyFc.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__="f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7";import{v as e,l as o,z as t,dt as s,A as a,du as i,dv as l,dw as n,E as r,c}from"./houseplan-card--Yu_3uZU.js";class f 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,f={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")},h=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}
.computeLabel=${e=>h[e.name]||e.name}
.computeLabel=${e=>f[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(f.slice(0,3))}
${d(h.slice(0,3))}
${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a}
${d(f.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
${d(h.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
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__="55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1";import{n as t,b as n}from"./zigbee-topology-DM3CehUC.js";import{I as e}from"./houseplan-card-bl8WhyFc.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__="f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7";import{n as t,b as n}from"./zigbee-topology-CYMZJB0I.js";import{I as e}from"./houseplan-card--Yu_3uZU.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 f(t){if(!0!==t?.user?.is_admin)throw a("permission")}function p(n){const e=i(n);return e?c(e,"zha",async()=>{if(f(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(f(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,p=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{p=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 f=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&&p?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof f&&b.push(f),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{p as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
+1 -1
View File
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="55f15b821c7086d2d1f7b16dbc290d54121aae8a0399897d252e46e1f717b2d1";try{await import("./houseplan-assets/houseplan-card-bl8WhyFc.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__="f5a5df099a166da3e233ffe681f86d9ee30cfedaac3da73518fcea0d0e4db9b7";try{await import("./houseplan-assets/houseplan-card--Yu_3uZU.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
View File
@@ -2,6 +2,10 @@
## Unreleased
- Furniture placement previews now clear reliably when the pointer leaves the
plan, including after a full editor redraw; the live editor also exposes a
deterministic paint barrier for browser checks
([#460](https://github.com/Matysh/houseplan-card/issues/460)).
- Zigbee topology links now show arrows towards the coordinator on hover;
incoming arrows identify devices behind a router, while a short bubble
truthfully ends the path when the next node is in another space or is not
+4
View File
@@ -8,6 +8,10 @@
## Не выпущено
- Превью размещения мебели теперь надёжно исчезает при уходе указателя с плана,
в том числе после полной перерисовки редактора; у живого редактора также
появилась детерминированная точка ожидания отрисовки для браузерных проверок
([#460](https://github.com/Matysh/houseplan-card/issues/460)).
- На линиях топологии Zigbee при наведении теперь видны стрелки в сторону
координатора; входящие стрелки показывают устройства за роутером, а если
следующий узел находится на другом плане или не размещён, короткая подпись
+9
View File
@@ -411,5 +411,14 @@ sets a non-zero exit code — including when the card threw during the run. The
suite runs in CI (`smoke` job) against a freshly built bundle; never test the
`demo/srv/assets/houseplan-card.js` copy, which `npm run bundle:sync` writes and which is not committed (#255).
`updateComplete` proves only that Lit finished its own update. Pointer-owned
editor state can be painted later by `live-editor`, outside that cycle. Browser
checks which read live editor DOM must await the lazy runtime's
`_whenLiveEditorSettled()` contract; a timeout, sleep or fixed number of RAFs is
not evidence that the latest projection was applied. If a smoke changes editor
mode before dispatching synthetic gestures, it must also wait for the observable
mode-transition and viewport-refit state to settle, because the stage can finish
its physical resize after the Lit update (#460).
When adding a checklist line marked `[auto: ...]` in docs/TESTING.md, add the
failing check in the same commit — that is what the marker now promises.
+14
View File
@@ -48,6 +48,18 @@ Issue: [#460](https://github.com/Matysh/houseplan-card/issues/460)
- Новое поведение touch/pen, View или kiosk.
- Изменение формата конфигурации, сохранённых данных или истории Undo/Redo.
## Затронутые файлы и модули
- `src/live-editor.ts` — ревизии, ожидания и terminal-null routing.
- `src/houseplan-editor-runtime.ts` — внутренний awaitable-метод для browser
harness.
- `test/live-editor.test.mjs` и `scripts/mutation-gate.mjs` — unit- и
mutation-доказательства.
- `demo/smoke_furniture.mjs`, `demo/smoke_decor.mjs`,
`demo/smoke_decor_text.mjs` — потребители контракта.
- `docs/DEVELOPMENT.md`, оба changelog и сгенерированные bundle-деревья —
обязательные сопутствующие артефакты.
## Контракт поведения
1. Вызов внутренней точки синхронизации сначала дожидается уже ожидавшегося
@@ -139,6 +151,8 @@ Unit-тест доказывает, что terminal-null hover требует п
- Одновременные записи в `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`.
- `docs/FURNITURE.md` уже содержит правильный пользовательский контракт очистки
preview и не требует изменения.
- `docs/DEVELOPMENT.md` фиксирует, что `updateComplete` не является барьером
live-editor и browser harness обязан использовать явный settlement contract.
- Стабильный screenshot/golden не меняется: исправляется transient-состояние,
которого после завершения жеста не должно существовать.
- Любое изменение `src/**` требует актуального Linux-артефакта `Docs
+9 -1
View File
@@ -115,8 +115,16 @@ export const LOW_HEADROOM_WARNING_BYTES = 15_000;
* состояние после pan/resize. После исправления initial-граф занимает
* 296 717 Б gzip: 1 283 Б сверху и 717 Б до нижней границы полосы. Общий
* бюджет 300 000 Б не меняется; долг ленивого выноса #367 остаётся видимым.
*
* 2026-09-05, #460: полоса перецентрирована 298 000 → 298 500 без изменения
* общего бюджета. Продуктовый код задачи находится в ленивом editor-графе,
* однако новые content hash и имя динамического чанка сдвинули initial gzip
* 297 496 → 297 503 Б. Старый факт уже оставлял всего 504 Б до верхней границы,
* то есть следующий семибайтный шум нарушил обязательный запас >500 Б.
* Новый центр оставляет 997 Б сверху и 1 003 Б снизу; бюджет 300 000 Б и
* предупреждение о малом запасе не меняются.
*/
export const INITIAL_VIEW_GZIP_CEILING = 298_000;
export const INITIAL_VIEW_GZIP_CEILING = 298_500;
export const INITIAL_VIEW_CEILING_BAND = 2_000;
/**
+22
View File
@@ -5981,6 +5981,28 @@ const MUTANT_DEFINITIONS = [
replace: '',
}],
},
{
id: 'live-editor-settlement-runs-ahead-of-paint',
guard: 'node --test --test-name-pattern="live editor settlement waits" test/live-editor.test.mjs',
because: 'the explicit live-editor barrier must resolve only after the scheduled DOM paint; '
+ 'an eager promise recreates the nondeterministic preview snapshot from #460',
patches: [{
file: 'src/live-editor.ts',
find: ' return new Promise((resolve) => state.waiters.push({ revision, resolve }));',
replace: ' return Promise.resolve();',
}],
},
{
id: 'live-editor-smoke-falls-back-to-double-raf',
guard: 'node --test --test-name-pattern="browser smokes use the explicit" test/live-editor.test.mjs',
because: 'a fixed number of animation frames is a timing guess, not evidence that the live '
+ 'editor applied its latest projection (#460)',
patches: [{
file: 'demo/smoke_furniture.mjs',
find: ' const settleLive = () => c._editorRuntime?._whenLiveEditorSettled() ?? c.updateComplete;',
replace: ' const settleLive = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));',
}],
},
{
id: 'pointer-move-queue-keeps-first-move',
guard: 'node --test test/live-editor.test.mjs',
+8 -1
View File
@@ -16,7 +16,7 @@ import {
} from './pointer-move-queue';
import {
commitHouseplanEditor, disposeHouseplanEditor, measureHouseplanDecorText,
routeHouseplanEditorUpdate,
routeHouseplanEditorUpdate, whenHouseplanEditorSettled,
} from './live-editor';
import './hp-dialog';
import type { HpDialog } from './hp-dialog';
@@ -1222,6 +1222,13 @@ public _routeLiveEditorUpdate(name?: PropertyKey, oldValue?: unknown): boolean {
}
public _commitLiveEditor(): void { commitHouseplanEditor(this.host); }
public _disposeLiveEditor(): void { disposeHouseplanEditor(this.host); }
public async _whenLiveEditorSettled(): Promise<void> {
// An already queued pointer calculation runs before this continuation.
// A complete Lit render may also supersede the lightweight frame, and its
// updated() hook commits that same state before updateComplete resolves.
await this.host.updateComplete;
await whenHouseplanEditorSettled(this.host);
}
public _queuePointerMove(key: string, run: () => void): void { queueHouseplanPointerMove(this.host, key, run); }
public _flushPointerMove(key: string): void { flushHouseplanPointerMove(this.host, key); }
public _cancelPointerMove(key?: string): void { cancelHouseplanPointerMove(this.host, key); }
+30 -1
View File
@@ -13,6 +13,9 @@ import { NORM_W } from './space-geometry';
interface LiveEditorState {
raf: number;
requestedRevision: number;
paintedRevision: number;
waiters: { revision: number; resolve: () => void }[];
hidden: HTMLElement[];
transparent: HTMLElement[];
dimmed: HTMLElement[];
@@ -84,12 +87,22 @@ const gestureProperties = new Set<PropertyKey>([
const stateOf = (host: object): LiveEditorState => {
let state = states.get(host);
if (!state) {
state = { raf: 0, hidden: [], transparent: [], dimmed: [] };
state = {
raf: 0, requestedRevision: 0, paintedRevision: 0, waiters: [],
hidden: [], transparent: [], dimmed: [],
};
states.set(host, state);
}
return state;
};
const finishRevision = (state: LiveEditorState, revision: number): void => {
state.paintedRevision = Math.max(state.paintedRevision, revision);
const ready = state.waiters.filter((waiter) => waiter.revision <= state.paintedRevision);
state.waiters = state.waiters.filter((waiter) => waiter.revision > state.paintedRevision);
for (const waiter of ready) waiter.resolve();
};
const activeEditorGesture = (host: LiveEditorHost): boolean => host._mode !== 'view' && (
!!host._deviceDrag || !!host._physicalDrag || !!host._physicalRotate
|| !!host._decorDraft || !!host._decorMove || !!host._dtDrag || !!host._bdDrag
@@ -120,6 +133,8 @@ export function routeHouseplanEditorUpdate(
return false;
}
if (name !== undefined && gestureProperties.has(name) && host[name] == null) return false;
if (name !== undefined && hoverProperties.has(name)
&& (host as unknown as Record<PropertyKey, unknown>)[name] == null) return false;
const route = (namedLive && (hoverProperties.has(name!) || activeEditorGesture(host)))
|| (name === undefined && activeEditorGesture(host));
if (!route) return false;
@@ -349,13 +364,25 @@ export function paintHouseplanEditor(value: object): void {
export function scheduleHouseplanEditor(value: object): void {
const state = stateOf(value);
state.requestedRevision++;
if (state.raf || typeof requestAnimationFrame !== 'function') return;
state.raf = requestAnimationFrame(() => {
state.raf = 0;
const revision = state.requestedRevision;
paintHouseplanEditor(value);
finishRevision(state, revision);
});
}
/** Await the latest live-editor frame which was scheduled before this call. */
export function whenHouseplanEditorSettled(value: object): Promise<void> {
const state = states.get(value);
if (!state || !state.raf || state.paintedRevision >= state.requestedRevision)
return Promise.resolve();
const revision = state.requestedRevision;
return new Promise((resolve) => state.waiters.push({ revision, resolve }));
}
/** Only text needs a DOM measurement; every box-like item owns its config box. */
export function measureHouseplanDecorText(value: object): void {
const host = value as LiveEditorHost;
@@ -399,6 +426,7 @@ export function commitHouseplanEditor(value: object): void {
const view = host._viewOr(host._baseVb());
render(host._renderLiveEditorMeasurements(view), htmlTarget);
}
finishRevision(state, state.requestedRevision);
}
export function disposeHouseplanEditor(host: object): void {
@@ -407,5 +435,6 @@ export function disposeHouseplanEditor(host: object): void {
if (!state) return;
if (state.raf && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(state.raf);
restore(state);
finishRevision(state, state.requestedRevision);
states.delete(host);
}
+83 -1
View File
@@ -1,9 +1,12 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import {
commitHouseplanEditor,
disposeHouseplanEditor,
routeHouseplanEditorUpdate,
whenHouseplanEditorSettled,
} from '../test-build/live-editor.js';
import {
cancelHouseplanPointerMove,
@@ -39,9 +42,13 @@ test('pointer-following editor hover routes while View remains reactive', () =>
const beforeCancel = globalThis.cancelAnimationFrame;
globalThis.requestAnimationFrame = () => 21;
globalThis.cancelAnimationFrame = () => {};
const host = { isConnected: true, _mode: 'plan' };
const host = { isConnected: true, _mode: 'plan', _cursorPt: [1, 2] };
try {
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', null), true);
const previous = host._cursorPt;
host._cursorPt = null;
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', previous), false);
host._cursorPt = [1, 2];
host._mode = 'view';
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', [1, 2]), false);
} finally {
@@ -75,3 +82,78 @@ test('pointer move queue is event-turn coalesced, last-wins and flushable', asyn
disposeHouseplanEditor(host);
}
});
test('live editor settlement waits for paint and releases on commit or dispose', async () => {
const beforeRaf = globalThis.requestAnimationFrame;
const beforeCancel = globalThis.cancelAnimationFrame;
const frames = new Map();
let nextFrame = 1;
globalThis.requestAnimationFrame = (callback) => {
const id = nextFrame++;
frames.set(id, callback);
return id;
};
globalThis.cancelAnimationFrame = (id) => frames.delete(id);
const root = { querySelector: () => null, querySelectorAll: () => [] };
const host = { isConnected: true, _mode: 'plan', renderRoot: root, _cursorPt: [0, 0] };
const runFrame = () => {
const [id, callback] = frames.entries().next().value || [];
assert.ok(id, 'a live frame is scheduled');
frames.delete(id);
callback(0);
};
try {
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', null), true);
const first = whenHouseplanEditorSettled(host);
let firstDone = false;
void first.then(() => { firstDone = true; });
await Promise.resolve();
assert.equal(firstDone, false, 'settlement cannot run ahead of the live paint');
runFrame();
await first;
assert.equal(firstDone, true);
const previousCursor = host._cursorPt;
host._cursorPt = null;
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', previousCursor), false,
'terminal hover null must reconcile the settled layer through Lit');
assert.equal(frames.size, 0, 'terminal hover null does not leave a live frame');
host._cursorPt = [1, 2];
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', [1, 2]), true);
host._cursorPt = [2, 3];
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', [2, 3]), true);
assert.equal(frames.size, 1, 'coalesced updates own one frame');
const coalesced = whenHouseplanEditorSettled(host);
runFrame();
await coalesced;
host._cursorPt = [3, 4];
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', [2, 3]), true);
const committed = whenHouseplanEditorSettled(host);
commitHouseplanEditor(host);
await committed;
assert.equal(frames.size, 0, 'a complete Lit commit cancels its superseded live frame');
host._cursorPt = [4, 5];
assert.equal(routeHouseplanEditorUpdate(host, '_cursorPt', [3, 4]), true);
const disposed = whenHouseplanEditorSettled(host);
disposeHouseplanEditor(host);
await disposed;
assert.equal(frames.size, 0, 'dispose leaves no frame or waiter behind');
} finally {
disposeHouseplanEditor(host);
globalThis.requestAnimationFrame = beforeRaf;
globalThis.cancelAnimationFrame = beforeCancel;
}
});
test('live editor browser smokes use the explicit settlement contract', () => {
for (const name of ['smoke_furniture.mjs', 'smoke_decor.mjs', 'smoke_decor_text.mjs']) {
const source = readFileSync(new URL(`../demo/${name}`, import.meta.url), 'utf8');
const helper = source.match(/const settleLive = ([^;]+);/)?.[0] || '';
assert.match(helper, /_whenLiveEditorSettled\(\)/, `${name}: explicit contract`);
assert.doesNotMatch(helper, /requestAnimationFrame|setTimeout|sleep|Promise\.resolve/,
`${name}: no timing heuristic`);
}
});