mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef0efd5855 | ||
|
|
c706f8a744 | ||
|
|
bdba9f48ff | ||
|
|
dbda64e89d | ||
|
|
cba13621c8 | ||
|
|
4190db54ab | ||
|
|
a2cdd360bc | ||
|
|
f383d8fd87 | ||
|
|
8a75c53eca | ||
|
|
d1091b6d47 | ||
|
|
a6a9757b6d | ||
|
|
d8e67f530c | ||
|
|
f3c32fb203 | ||
|
|
58df908db2 | ||
|
|
17a1c10bef | ||
|
|
431b462a16 | ||
|
|
6559679b63 | ||
|
|
58efb6a1b2 | ||
|
|
d97bf63feb | ||
|
|
260af7bfd2 | ||
|
|
34853ba32a | ||
|
|
972b0e0c0a | ||
|
|
2c4dd3f638 | ||
|
|
7cf0105d62 | ||
|
|
6f8ce48e5b | ||
|
|
8b9548775f | ||
|
|
bdf53dc482 | ||
|
|
1a8a51f2d9 | ||
|
|
ee678352c7 | ||
|
|
c3eb225c8f | ||
|
|
f71de89975 | ||
|
|
b87e99f9a1 | ||
|
|
4cabcbe828 | ||
|
|
cc09e8f827 | ||
|
|
a6eb3d6cfa | ||
|
|
223951c042 | ||
|
|
9d1e5fdbba | ||
|
|
8013f7d052 | ||
|
|
ac99a2ff08 | ||
|
|
587c20699c |
@@ -51,7 +51,7 @@ SCHEDULED_GRACE_S = 30 * 24 * 3600
|
||||
FILES_DIR = "houseplan/files"
|
||||
ASSETS_DIR = "houseplan/assets"
|
||||
CONF_ADMIN_ONLY = "admin_only"
|
||||
VERSION = "1.71.0-beta.2"
|
||||
VERSION = "1.71.0-beta.1"
|
||||
DECOR_ASSETS_API_VERSION = 1
|
||||
|
||||
# #43: the support transport is deliberately not configurable. A user supplied
|
||||
|
||||
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "a1c6b7fa80c55b69f45f0b0975bcde7eb67d53a02f75f5b7c61d66f220e78ed9",
|
||||
"fingerprint": "aa228c5ecf6e913fe9a0058b57b44a53616b73c0bef1c20f84f88f7b56814274",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js",
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 291175,
|
||||
"initialViewGzipBytes": 291067,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/de-B-zT3Bet.js",
|
||||
"houseplan-assets/editor-DJZS-SMT.js",
|
||||
"houseplan-assets/fr-DhlZhAWs.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/de-RKtXul6Y.js",
|
||||
"houseplan-assets/editor-D6wVlv9q.js",
|
||||
"houseplan-assets/fr-Cp6oi90d.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BENrZzdh.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js"
|
||||
],
|
||||
"lazyGzipBytes": 215559,
|
||||
"lazyGzipBytes": 215506,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/editor-DJZS-SMT.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/editor-D6wVlv9q.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BENrZzdh.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 159876,
|
||||
"lazyEditorGzipBytes": 159815,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14133,
|
||||
"lazyOnboardingGzipBytes": 14131,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-B-zT3Bet.js",
|
||||
"houseplan-assets/fr-DhlZhAWs.js"
|
||||
"houseplan-assets/de-RKtXul6Y.js",
|
||||
"houseplan-assets/fr-Cp6oi90d.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 48727,
|
||||
"lazyLocaleGzipBytes": 48737,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"sha256": "b556cef2823ea5e4f7c142ee5f4cee8dbbed71311dd101291faf30a0d38d6805",
|
||||
"path": "houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"sha256": "46e13a967a84ce24f3e31e01f3e51733efa61631b42e0ff3f94ed3daecac9ac2",
|
||||
"rawBytes": 20996,
|
||||
"gzipBytes": 7177,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-B-zT3Bet.js",
|
||||
"sha256": "2ee98742a517ac754434e5627f958a20def03cd1c3907dd7ec3e394104c850a1",
|
||||
"path": "houseplan-assets/de-RKtXul6Y.js",
|
||||
"sha256": "957316cc1ca9c874768c511bb282d4c603cea1eebb1af75170367d1288e1578d",
|
||||
"rawBytes": 81632,
|
||||
"gzipBytes": 24635,
|
||||
"gzipBytes": 24640,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-DJZS-SMT.js",
|
||||
"sha256": "3cda88a32916d4a3256cb0f76570d72f58308ad9ee6a28c9496d5c9e2c613aa4",
|
||||
"path": "houseplan-assets/editor-D6wVlv9q.js",
|
||||
"sha256": "304f38472f09805a10d812a4cd606b70d591bafae73885821c3b2c37a5cec49e",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1580,
|
||||
"gzipBytes": 1581,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-DhlZhAWs.js",
|
||||
"sha256": "f37aecbd6a5aaa1fbc5c3be0854464b949cbdcefd453906b268c46d76760772d",
|
||||
"path": "houseplan-assets/fr-Cp6oi90d.js",
|
||||
"sha256": "06efef049665b3f0caa94674f840f04768aca4bede2e9b715cb8cdd26cba028d",
|
||||
"rawBytes": 83754,
|
||||
"gzipBytes": 24092,
|
||||
"gzipBytes": 24097,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-D9kogu9G.js",
|
||||
"sha256": "65622583b77e3ff11a34ff2c2ceb65748cb1f607412794141375841fcea818bb",
|
||||
"rawBytes": 1033646,
|
||||
"gzipBytes": 290380,
|
||||
"path": "houseplan-assets/houseplan-card-BF6i5sqv.js",
|
||||
"sha256": "4a9deb476f6b6c2e0a074f42902603386cffde4205b3c734b989f439d8e3e74b",
|
||||
"rawBytes": 1033268,
|
||||
"gzipBytes": 290270,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-B-zT3Bet.js",
|
||||
"houseplan-assets/editor-DJZS-SMT.js",
|
||||
"houseplan-assets/fr-DhlZhAWs.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js"
|
||||
"houseplan-assets/de-RKtXul6Y.js",
|
||||
"houseplan-assets/editor-D6wVlv9q.js",
|
||||
"houseplan-assets/fr-Cp6oi90d.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BENrZzdh.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js",
|
||||
"sha256": "c1935afb94bfe01cc7ee1302492c6c9a76faac8bd60ddc1db98eded52c7d9d65",
|
||||
"rawBytes": 576162,
|
||||
"gzipBytes": 151119,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BENrZzdh.js",
|
||||
"sha256": "a3a99a6b8c994d09499a8506c94a70e7fe9d6127d71c3edaa3240a830234b26c",
|
||||
"rawBytes": 576032,
|
||||
"gzipBytes": 151057,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js",
|
||||
"sha256": "20b122261b8ce7484806fbaaf17aafb5f59c1864648b1a31fa226dc861d7cbd2",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js",
|
||||
"sha256": "006ccd4f8214f255f0009d5c4894f7bd6d07a6f6997a231515eb7ebb03977090",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6956,
|
||||
"gzipBytes": 6954,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "ac14b26a8d45a1747833be382ab3b0b816332156a59ed7125fa3eba598cd940d",
|
||||
"sha256": "c9808b3cea6e687c2dd48e069809e35255f82db260183416256d1e6c70c97c11",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 795,
|
||||
"gzipBytes": 797,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,14 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="a1c6b7fa80c55b69f45f0b0975bcde7eb67d53a02f75f5b7c61d66f220e78ed9";import{b as e,l as o,t,di as s,A as a,dj as i,dk as l,dl as n,E as r,c}from"./houseplan-card-D9kogu9G.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="aa228c5ecf6e913fe9a0058b57b44a53616b73c0bef1c20f84f88f7b56814274";import{b as e,l as o,t,dg as s,A as a,dh as i,di as l,dj as n,E as r,c}from"./houseplan-card-BF6i5sqv.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}
|
||||
.computeLabel=${e=>f[e.name]||e.name}
|
||||
.computeLabel=${e=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${d(h.slice(0,3))}
|
||||
${d(f.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(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);
|
||||
${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);
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+18
-18
File diff suppressed because one or more lines are too long
+74
-74
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="a1c6b7fa80c55b69f45f0b0975bcde7eb67d53a02f75f5b7c61d66f220e78ed9";try{await import("./houseplan-assets/houseplan-card-D9kogu9G.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__="aa228c5ecf6e913fe9a0058b57b44a53616b73c0bef1c20f84f88f7b56814274";try{await import("./houseplan-assets/houseplan-card-BF6i5sqv.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)}
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
||||
"requirements": [],
|
||||
"single_config_entry": true,
|
||||
"version": "1.71.0-beta.2"
|
||||
"version": "1.71.0-beta.1"
|
||||
}
|
||||
|
||||
@@ -26,68 +26,6 @@ const result = await page.evaluate(async () => {
|
||||
await card.updateComplete;
|
||||
out.defaultRoomTip = card._tip?.room === true && !!root().querySelector('.tip');
|
||||
|
||||
const originalCallWS = card.hass.callWS;
|
||||
const beforeRejectedSave = structuredClone(card._serverCfg);
|
||||
const beforeRejectedFingerprint = card._cfgContentFingerprint;
|
||||
card._openSettingsDialog();
|
||||
card._settingsDialog = {
|
||||
...card._settingsDialog,
|
||||
colors: {
|
||||
...card._settingsDialog.colors,
|
||||
light_on: { c: '#123456', a: 0.4 },
|
||||
},
|
||||
glowRadius: 4,
|
||||
bgColor: '#234567',
|
||||
northDeg: 45,
|
||||
bgMode: 'static',
|
||||
sunRays: true,
|
||||
showRoomTooltip: false,
|
||||
};
|
||||
card.hass = { ...card.hass, callWS: async (message) => {
|
||||
if (message.type === 'houseplan/config/set') throw new Error('offline');
|
||||
return originalCallWS(message);
|
||||
} };
|
||||
await card._saveSettingsDialog();
|
||||
await card.updateComplete;
|
||||
out.rejectedSettingsRolledBack = JSON.stringify(card._serverCfg)
|
||||
=== JSON.stringify(beforeRejectedSave);
|
||||
out.rejectedFingerprintRolledBack = card._cfgContentFingerprint
|
||||
=== beforeRejectedFingerprint;
|
||||
out.rejectedDraftKept = card._settingsDialog?.showRoomTooltip === false
|
||||
&& card._settingsDialog?.bgColor === '#234567'
|
||||
&& card._settingsDialog?.colors.light_on.c === '#123456';
|
||||
out.rejectedBusyCleared = card._settingsDialog?.busy === false;
|
||||
card._tip = null;
|
||||
room().dispatchEvent(mouse('pointermove', 185, 185));
|
||||
await card.updateComplete;
|
||||
out.rejectedRuntimeUsesServerSettings = card._tip?.room === true;
|
||||
|
||||
const authoritative = structuredClone(beforeRejectedSave);
|
||||
authoritative.settings = { ...authoritative.settings, bg_color: '#345678' };
|
||||
const authoritativeRev = card._cfgRev + 5;
|
||||
card._openSettingsDialog();
|
||||
card._settingsDialog = {
|
||||
...card._settingsDialog, bgColor: '#abcdef', showRoomTooltip: false,
|
||||
};
|
||||
card.hass = { ...card.hass, callWS: async (message) => {
|
||||
if (message.type === 'houseplan/config/set') {
|
||||
throw Object.assign(new Error('conflict'), { code: 'conflict' });
|
||||
}
|
||||
if (message.type === 'houseplan/config/get') {
|
||||
return { config: structuredClone(authoritative), rev: authoritativeRev, can_write: true };
|
||||
}
|
||||
return originalCallWS(message);
|
||||
} };
|
||||
await card._saveSettingsDialog();
|
||||
await card.updateComplete;
|
||||
out.conflictKeepsAuthoritative = card._serverCfg.settings.bg_color === '#345678'
|
||||
&& !Object.hasOwn(card._serverCfg.settings, 'show_room_tooltip')
|
||||
&& card._cfgRev === authoritativeRev;
|
||||
out.conflictDraftKept = card._settingsDialog?.bgColor === '#abcdef'
|
||||
&& card._settingsDialog?.showRoomTooltip === false
|
||||
&& card._settingsDialog?.busy === false;
|
||||
card.hass = { ...card.hass, callWS: originalCallWS };
|
||||
|
||||
card._openSettingsDialog();
|
||||
card._settingsDialog = { ...card._settingsDialog, showRoomTooltip: false };
|
||||
await card._saveSettingsDialog();
|
||||
@@ -132,13 +70,6 @@ checkAll(result, {
|
||||
defaultOn: true,
|
||||
cancelKeepsAbsentDefault: true,
|
||||
defaultRoomTip: true,
|
||||
rejectedSettingsRolledBack: true,
|
||||
rejectedFingerprintRolledBack: true,
|
||||
rejectedDraftKept: true,
|
||||
rejectedBusyCleared: true,
|
||||
rejectedRuntimeUsesServerSettings: true,
|
||||
conflictKeepsAuthoritative: true,
|
||||
conflictDraftKept: true,
|
||||
falsePersists: true,
|
||||
visibleRoomTipCleared: true,
|
||||
disabledRoomTip: true,
|
||||
|
||||
Vendored
+56
-56
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "a1c6b7fa80c55b69f45f0b0975bcde7eb67d53a02f75f5b7c61d66f220e78ed9",
|
||||
"fingerprint": "aa228c5ecf6e913fe9a0058b57b44a53616b73c0bef1c20f84f88f7b56814274",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js",
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 291175,
|
||||
"initialViewGzipBytes": 291067,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/de-B-zT3Bet.js",
|
||||
"houseplan-assets/editor-DJZS-SMT.js",
|
||||
"houseplan-assets/fr-DhlZhAWs.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/de-RKtXul6Y.js",
|
||||
"houseplan-assets/editor-D6wVlv9q.js",
|
||||
"houseplan-assets/fr-Cp6oi90d.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BENrZzdh.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js"
|
||||
],
|
||||
"lazyGzipBytes": 215559,
|
||||
"lazyGzipBytes": 215506,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/editor-DJZS-SMT.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/editor-D6wVlv9q.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BENrZzdh.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 159876,
|
||||
"lazyEditorGzipBytes": 159815,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14133,
|
||||
"lazyOnboardingGzipBytes": 14131,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-B-zT3Bet.js",
|
||||
"houseplan-assets/fr-DhlZhAWs.js"
|
||||
"houseplan-assets/de-RKtXul6Y.js",
|
||||
"houseplan-assets/fr-Cp6oi90d.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 48727,
|
||||
"lazyLocaleGzipBytes": 48737,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"sha256": "b556cef2823ea5e4f7c142ee5f4cee8dbbed71311dd101291faf30a0d38d6805",
|
||||
"path": "houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"sha256": "46e13a967a84ce24f3e31e01f3e51733efa61631b42e0ff3f94ed3daecac9ac2",
|
||||
"rawBytes": 20996,
|
||||
"gzipBytes": 7177,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-B-zT3Bet.js",
|
||||
"sha256": "2ee98742a517ac754434e5627f958a20def03cd1c3907dd7ec3e394104c850a1",
|
||||
"path": "houseplan-assets/de-RKtXul6Y.js",
|
||||
"sha256": "957316cc1ca9c874768c511bb282d4c603cea1eebb1af75170367d1288e1578d",
|
||||
"rawBytes": 81632,
|
||||
"gzipBytes": 24635,
|
||||
"gzipBytes": 24640,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-DJZS-SMT.js",
|
||||
"sha256": "3cda88a32916d4a3256cb0f76570d72f58308ad9ee6a28c9496d5c9e2c613aa4",
|
||||
"path": "houseplan-assets/editor-D6wVlv9q.js",
|
||||
"sha256": "304f38472f09805a10d812a4cd606b70d591bafae73885821c3b2c37a5cec49e",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1580,
|
||||
"gzipBytes": 1581,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-DhlZhAWs.js",
|
||||
"sha256": "f37aecbd6a5aaa1fbc5c3be0854464b949cbdcefd453906b268c46d76760772d",
|
||||
"path": "houseplan-assets/fr-Cp6oi90d.js",
|
||||
"sha256": "06efef049665b3f0caa94674f840f04768aca4bede2e9b715cb8cdd26cba028d",
|
||||
"rawBytes": 83754,
|
||||
"gzipBytes": 24092,
|
||||
"gzipBytes": 24097,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-D9kogu9G.js",
|
||||
"sha256": "65622583b77e3ff11a34ff2c2ceb65748cb1f607412794141375841fcea818bb",
|
||||
"rawBytes": 1033646,
|
||||
"gzipBytes": 290380,
|
||||
"path": "houseplan-assets/houseplan-card-BF6i5sqv.js",
|
||||
"sha256": "4a9deb476f6b6c2e0a074f42902603386cffde4205b3c734b989f439d8e3e74b",
|
||||
"rawBytes": 1033268,
|
||||
"gzipBytes": 290270,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-B-zT3Bet.js",
|
||||
"houseplan-assets/editor-DJZS-SMT.js",
|
||||
"houseplan-assets/fr-DhlZhAWs.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js"
|
||||
"houseplan-assets/de-RKtXul6Y.js",
|
||||
"houseplan-assets/editor-D6wVlv9q.js",
|
||||
"houseplan-assets/fr-Cp6oi90d.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BENrZzdh.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-S0bYVW5I.js",
|
||||
"sha256": "c1935afb94bfe01cc7ee1302492c6c9a76faac8bd60ddc1db98eded52c7d9d65",
|
||||
"rawBytes": 576162,
|
||||
"gzipBytes": 151119,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BENrZzdh.js",
|
||||
"sha256": "a3a99a6b8c994d09499a8506c94a70e7fe9d6127d71c3edaa3240a830234b26c",
|
||||
"rawBytes": 576032,
|
||||
"gzipBytes": 151057,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DJriHHug.js",
|
||||
"sha256": "20b122261b8ce7484806fbaaf17aafb5f59c1864648b1a31fa226dc861d7cbd2",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B0I6_T_V.js",
|
||||
"sha256": "006ccd4f8214f255f0009d5c4894f7bd6d07a6f6997a231515eb7ebb03977090",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6956,
|
||||
"gzipBytes": 6954,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-BEQcDs80.js",
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/backdrop-pick-DeLnU0B5.js",
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "ac14b26a8d45a1747833be382ab3b0b816332156a59ed7125fa3eba598cd940d",
|
||||
"sha256": "c9808b3cea6e687c2dd48e069809e35255f82db260183416256d1e6c70c97c11",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 795,
|
||||
"gzipBytes": 797,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-D9kogu9G.js"
|
||||
"houseplan-assets/houseplan-card-BF6i5sqv.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,14 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="a1c6b7fa80c55b69f45f0b0975bcde7eb67d53a02f75f5b7c61d66f220e78ed9";import{b as e,l as o,t,di as s,A as a,dj as i,dk as l,dl as n,E as r,c}from"./houseplan-card-D9kogu9G.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="aa228c5ecf6e913fe9a0058b57b44a53616b73c0bef1c20f84f88f7b56814274";import{b as e,l as o,t,dg as s,A as a,dh as i,di as l,dj as n,E as r,c}from"./houseplan-card-BF6i5sqv.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}
|
||||
.computeLabel=${e=>f[e.name]||e.name}
|
||||
.computeLabel=${e=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${d(h.slice(0,3))}
|
||||
${d(f.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(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);
|
||||
${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);
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+18
-18
File diff suppressed because one or more lines are too long
+74
-74
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="a1c6b7fa80c55b69f45f0b0975bcde7eb67d53a02f75f5b7c61d66f220e78ed9";try{await import("./houseplan-assets/houseplan-card-D9kogu9G.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__="aa228c5ecf6e913fe9a0058b57b44a53616b73c0bef1c20f84f88f7b56814274";try{await import("./houseplan-assets/houseplan-card-BF6i5sqv.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)}
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- If Home Assistant rejects a General settings save, the plan now returns to
|
||||
the last server-confirmed state instead of displaying an unsaved local draft;
|
||||
the entered values remain in the open dialog for another attempt
|
||||
([#439](https://github.com/Matysh/houseplan-card/issues/439)).
|
||||
|
||||
## v1.71.0-beta.2 — 2026-09-03
|
||||
|
||||
- Custom background images now recover cleanly after interrupted uploads,
|
||||
consume quota consistently, retry after configuration changes and remain
|
||||
compatible with older integrations; language switching and failed support
|
||||
|
||||
@@ -8,13 +8,6 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Если Home Assistant отклоняет сохранение общих настроек, план снова показывает
|
||||
последнее подтверждённое сервером состояние вместо несохранённого локального
|
||||
черновика; введённые значения остаются в открытом диалоге для повторной
|
||||
попытки ([#439](https://github.com/Matysh/houseplan-card/issues/439)).
|
||||
|
||||
## v1.71.0-beta.2 — 2026-09-03
|
||||
|
||||
- Пользовательские картинки подложки теперь корректно восстанавливаются после
|
||||
прерванной загрузки, всегда учитываются в лимите, повторно проверяются после
|
||||
изменения конфигурации и совместимы со старыми версиями интеграции; смена
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<!-- release: v1.71.0-beta.2 -->
|
||||
<!-- release: v1.71.0-beta.1 -->
|
||||
|
||||
## Основное
|
||||
|
||||
- Пользовательские картинки подложки теперь безопасно уменьшаются, восстанавливаются после прерванной загрузки и отсутствующего файла, а их координаты остаются стабильными после сохранения и оптимизации плана ([#427](https://github.com/Matysh/houseplan-card/issues/427), [#428](https://github.com/Matysh/houseplan-card/issues/428), [#431](https://github.com/Matysh/houseplan-card/issues/431), [#434](https://github.com/Matysh/houseplan-card/issues/434)).
|
||||
- Доступ к сохранённым картинкам подложки стал безопаснее и быстрее: произвольный поиск файлов закрыт, а повторные загрузки используют ограниченную кэшированную проверку целостности ([#432](https://github.com/Matysh/houseplan-card/issues/432), [#434](https://github.com/Matysh/houseplan-card/issues/434)).
|
||||
- В общих настройках теперь можно отключить информационное окно комнаты при наведении, сохранив подсветку комнаты и подсказки устройств ([#426](https://github.com/Matysh/houseplan-card/issues/426)).
|
||||
- В редакторе подложки теперь можно загружать и повторно использовать закрытые PNG, JPEG, WebP и безопасные SVG, а затем размещать и редактировать их как мебель ([#51](https://github.com/Matysh/houseplan-card/issues/51)).
|
||||
- «Помощь и обратная связь» и диалоги опасных действий стали надёжнее при смене языка, пространства или версии, повторной отправке и некорректном конфиге ([#417](https://github.com/Matysh/houseplan-card/issues/417), [#418](https://github.com/Matysh/houseplan-card/issues/418), [#420](https://github.com/Matysh/houseplan-card/issues/420), [#423](https://github.com/Matysh/houseplan-card/issues/423)).
|
||||
- Автоматическая привязка к зонам Home Assistant больше не забывает устройства из-за временно пустого или неполного реестра ([#419](https://github.com/Matysh/houseplan-card/issues/419)).
|
||||
- Мелкие исправления и улучшения.
|
||||
|
||||
## Highlights
|
||||
|
||||
- Custom background images can now be safely reduced, recovered after interrupted uploads or missing files, and keep stable coordinates across saves and plan optimization ([#427](https://github.com/Matysh/houseplan-card/issues/427), [#428](https://github.com/Matysh/houseplan-card/issues/428), [#431](https://github.com/Matysh/houseplan-card/issues/431), [#434](https://github.com/Matysh/houseplan-card/issues/434)).
|
||||
- Access to saved background images is safer and faster: arbitrary file lookup is blocked, while repeated loads reuse a bounded cached integrity check ([#432](https://github.com/Matysh/houseplan-card/issues/432), [#434](https://github.com/Matysh/houseplan-card/issues/434)).
|
||||
- General settings can now hide the room information window on hover while keeping room highlighting and device tooltips ([#426](https://github.com/Matysh/houseplan-card/issues/426)).
|
||||
- The Background editor can now upload and reuse private PNG, JPEG, WebP and safe SVG images, then place and edit them like furniture ([#51](https://github.com/Matysh/houseplan-card/issues/51)).
|
||||
- Help & feedback and dangerous-action dialogs are now more reliable across language, space and version changes, edited retries, and malformed configuration ([#417](https://github.com/Matysh/houseplan-card/issues/417), [#418](https://github.com/Matysh/houseplan-card/issues/418), [#420](https://github.com/Matysh/houseplan-card/issues/420), [#423](https://github.com/Matysh/houseplan-card/issues/423)).
|
||||
- Home Assistant Area relocation tracking no longer forgets devices because the registry is temporarily empty or incomplete ([#419](https://github.com/Matysh/houseplan-card/issues/419)).
|
||||
- Small fixes and improvements.
|
||||
|
||||
[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.71.0-beta.2/docs/CHANGELOG.ru.md)
|
||||
· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.71.0-beta.2/docs/CHANGELOG.md)
|
||||
[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.71.0-beta.1/docs/CHANGELOG.ru.md)
|
||||
· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.71.0-beta.1/docs/CHANGELOG.md)
|
||||
|
||||
+3
-3
@@ -17,12 +17,12 @@ change must pass through a published beta/RC before stable. Stable release
|
||||
commits are promotion-only (versions, generated bundles and release/changelog
|
||||
metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
||||
|
||||
## Snapshot (2026-09-03)
|
||||
## Snapshot (2026-09-02)
|
||||
|
||||
| Item | State |
|
||||
|---|---|
|
||||
| Version | **v1.71.0-beta.2** everywhere (manifest, const.py, package.json, CARD_VERSION) |
|
||||
| Current local cycle | v1.71.0-beta.2 hardens custom Background images across oversized sources, interrupted uploads, missing-file export/import, coordinate canonicalization, bounded integrity checks and old-integration compatibility (#427, #428, #431, #432, #434). It also adds the option to hide the room information window without disabling room highlighting or device tooltips (#426). Publish only after exact-SHA Validate is green. |
|
||||
| Version | **v1.71.0-beta.1** everywhere (manifest, const.py, package.json, CARD_VERSION) |
|
||||
| Current local cycle | v1.71.0-beta.1 is the first prerelease candidate after stable v1.70.0. It adds reusable private raster/SVG images to the Background editor (#51), hardens confirmation and Help & feedback races/privacy/compatibility (#417, #418, #420, #423), and protects HA Area relocation provenance from incomplete registries (#419). Test and capture gates #421, #422, #424 and #425 ship in the same candidate. Publish only after exact-SHA Validate is green. |
|
||||
| Hidden Labs Stage | #89 Stage 1 ships in v1.63.0-beta.1. #122 Stage 2 ships in v1.64.0 and evolves the same hidden, expiring `iso` experiment with matte walls, a low exterior floor edge, restrained shared shadows and live vertical door/window/gate panels. Flat remains default; editors and `houseplan-space-card` remain flat; live floor effects and HA actions remain unchanged. Public activation remains a separate task. |
|
||||
| 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) |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"fixture": "synthetic-only",
|
||||
"chromium": "151.0.7922.34",
|
||||
"oxipng": "oxipng 10.2.0",
|
||||
"sourceFingerprint": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceFingerprint": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"captureScriptSha256": "cadb8e1bcab9f1dcdd7d75b3b90ddcbaaeb2b8c2a098f575a21f39ff70f5c59c",
|
||||
"command": "npm run build && node demo/docs/capture.mjs",
|
||||
"scenarios": {
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "80a70361dc18dd0461568df332062e6482c633af5d280954f8b675701418a76d"
|
||||
},
|
||||
"view-touch": {
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "4106cc28847047505f46921ff95765d8abdf5b382d9d17d4c5e4ad129dd8f6be"
|
||||
},
|
||||
"space-create": {
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "617b51b3648498787b5039980c9f3eceb75ba56ed63a1a20e616bc05bc304362"
|
||||
},
|
||||
"room-contour-close": {
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "1dab6cc3b9d1bf7d8c40f0e5137f8c688683c9b7eabc5167da99d41ecfdd5b79"
|
||||
},
|
||||
"plan-context-tray": {
|
||||
@@ -59,7 +59,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "94ef50762753c0ac6ddc84d2521c4232a3f9ecd89843810d2df61c517592bed7"
|
||||
},
|
||||
"device-editor": {
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "7a601769de38aa19c2e280f6ff4cf3695b854b1d1c6d1f8de4e6b5e8f7eb1559"
|
||||
},
|
||||
"device-display-preview": {
|
||||
@@ -81,7 +81,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "0939875f8631694f4de0ef4fd01032010ede8c7d49fb7c0a857612d4b3afff93"
|
||||
},
|
||||
"background-editor": {
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "054170fd9ef45762b602b4d5c9c3b9ea9724858be61af137970c246f485c13bb"
|
||||
},
|
||||
"room-card": {
|
||||
@@ -103,7 +103,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "2ae4a58853d98e10d12456b2078ec2f6a0b597722c8310d7b016abd2bc42561e"
|
||||
},
|
||||
"device-info": {
|
||||
@@ -114,7 +114,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "b352add35375f89faf34bb618d758fd2e8e11964dc850c2a3c4035afc676258d",
|
||||
"sourceSha256": "1169cf86d01e9ff27d4fd4e55f114d299eb094aa84799c395b45ad914cffc24b",
|
||||
"imageSha256": "cb37f7eefd936f98ef44969b21dfe8e40ee27d1f558abe8d53c0223f6488b8a6"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
# CODE-REVIEW-439-r1
|
||||
|
||||
Issue: #439 — «Отказ сохранения настроек пространства оставляет применённым то,
|
||||
чего на сервере нет»
|
||||
Ветка: `issue/439-settings-save-rollback`
|
||||
SHA на момент ревью: `3883352f` (родитель `f0574c1f`)
|
||||
Трек: `trivial` (короткий, ТЗ в теле issue). Заход r1, блокирующих циклов
|
||||
израсходовано 0 из 2.
|
||||
|
||||
## Скоуп
|
||||
|
||||
Диалог общих настроек пространства применял новый `settings` к
|
||||
`_serverCfg` **оптимистично**, до подтверждения записи сервером
|
||||
(`houseplan/config/set`). При обычном отказе записи (не `conflict`) откат не
|
||||
происходил: карточка продолжала показывать непринятые сервером настройки, а
|
||||
`show_room_tooltip` (и любая другая опция) вела себя так, будто запись
|
||||
прошла — при том что ТЗ #426 объявляло этот риск снятым.
|
||||
|
||||
Три AC короткого трека:
|
||||
- **AC1** — обычный отказ: `_serverCfg.settings` и его content fingerprint
|
||||
откатываются к снимку до нажатия «Сохранить»; диалог остаётся открыт с
|
||||
введённым draft, `busy=false`, тост ошибки.
|
||||
- **AC2** — конфликт: авторитетный `config/get` после `conflict` не
|
||||
перезаписывается локальным rollback-снимком.
|
||||
- **AC3** — успех: не регрессирует.
|
||||
|
||||
Изменения: `src/serialized-write-queue.ts` (+49, новые
|
||||
`optimisticAttempt`/`rollbackOptimistic`), `src/houseplan-editor-runtime.ts`
|
||||
(точка вызова в `_saveSettingsDialog`, ~10 строк), новый браузерный smoke
|
||||
`demo/smoke_room_tooltip_toggle.mjs` (+69), юнит-тесты на сам механизм,
|
||||
оба changelog, собранный бандл (класс D), принятый fingerprint
|
||||
документационных скриншотов.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
### Чтение кода (доказательство по AC)
|
||||
|
||||
- **AC1.** `_saveSettingsDialog` (`src/houseplan-editor-runtime.ts:10228+`)
|
||||
до оптимистичного присваивания сохраняет `attempt =
|
||||
optimisticAttempt(cfg, nextConfig, _cfgContentFingerprint, _cfgRev,
|
||||
contentFingerprint)` — глубокий клон предыдущего `_serverCfg` плюс его
|
||||
fingerprint и текущую ревизию. В `catch` вызывается `rollbackOptimistic`,
|
||||
которая переписывает `_serverCfg`/`_cfgContentFingerprint` обратно и
|
||||
дёргает `requestUpdate()`, **только если** `host._cfgRev` не сдвинулась и
|
||||
текущий `_serverCfg` либо тот же объект, либо содержательно совпадает с
|
||||
неудавшейся попыткой. `_cfgRev` в `_sendConfigCandidate`
|
||||
(`houseplan-card.ts:7677-7683`) обновляется **только после** успешного
|
||||
`callWS` — на отказе строка `this._cfgRev = response?.rev …` не
|
||||
выполняется, значит guard пропускает откат. Проверено чтением и
|
||||
подтверждено smoke (ниже).
|
||||
- **AC2.** Конфликтная ветка `_saveConfigNow`
|
||||
(`houseplan-editor-runtime.ts:8930-8941`) уже до правки перечитывала
|
||||
конфиг через `_reloadConfigOnly()` и **до** повторного throw. Ревизия из
|
||||
ответа `config/get` монотонно больше ожидавшейся (иначе `conflict` не
|
||||
возник бы), поэтому к моменту, когда внешний `catch` в
|
||||
`_saveSettingsDialog` вызывает `rollbackOptimistic`, `host._cfgRev !==
|
||||
attempt.revision` истинно и функция возвращает `false`, не трогая уже
|
||||
авторитетный `_serverCfg`. Проверено чтением и smoke.
|
||||
- **AC3.** Успешный путь `attempt` не читает вовсе — ветка `try` не
|
||||
изменена в части закрытия диалога/применения настроек. Регрессия
|
||||
подтверждена существующими и новыми smoke-проверками.
|
||||
|
||||
### Гейты, которые прогнал сам (на SHA `3883352f`, зелёного Validate на нём не найдено)
|
||||
|
||||
| Гейт | Команда | Результат |
|
||||
|---|---|---|
|
||||
| Тайпчек | `npx tsc --noEmit` | чисто, без вывода |
|
||||
| Юнит-тесты | `npm test` | 1814 passed, 0 failed, 1 skipped — совпадает с тем, что заявил автор |
|
||||
| Сборка + сверка 3 копий бандла | `npm run build`, затем `npm run bundle:sync` | сборка чистая; `git status --porcelain` после обеих команд пуст — `dist/`, `custom_components/.../frontend/`, `demo/srv/assets` уже в актуальном состоянии, commit не разошёлся с исходником |
|
||||
| Документационный фингерпринт | `node scripts/check-docs.mjs` (diff трогает `src/**`) | «Documentation checks passed (7 files, 10 external links)» — подтверждает принятый в `3883352f` fingerprint корректен |
|
||||
| Named smoke (AC) | `node demo/smoke_room_tooltip_toggle.mjs` | все 21 проверка `true`, `OK` |
|
||||
| Дисциплина «тест умеет падать» | временно заменил `if (attempt) rollbackOptimistic(...)` на no-op, пересобрал бандл, перезапустил тот же smoke | покраснели ровно 3 проверки: `rejectedSettingsRolledBack`, `rejectedFingerprintRolledBack`, `rejectedRuntimeUsesServerSettings` — то есть именно AC1. Дерево восстановлено (`cp` бэкапа + `bundle:sync`), `git status --porcelain` снова пуст |
|
||||
| Бюджет бандла (дёшево, для полноты) | `npm run bundle:budget` | initial View 291175 B gzip / 300000 бюджет, есть предупреждение о запасе <15000 Б (#367) — это существующее давление на бюджет всего проекта, не следствие 40 строк этого диффа; не блокирует |
|
||||
|
||||
### Выбор смоков по дельте
|
||||
|
||||
`node scripts/smoke-select.mjs --base origin/dev --head HEAD`:
|
||||
изменено файлов `src/**`: 2, символов на изменённых строках: 9, матрица 216
|
||||
смоков (`ls demo/smoke_*.mjs | wc -l` = 216).
|
||||
|
||||
**Прямое совпадение (4)** — прогнал все:
|
||||
- `demo/smoke_room_tooltip_toggle.mjs` — назван в AC, см. выше.
|
||||
- `demo/smoke_area_relocation_safety.mjs` — зелёный, все 11 проверок `true`.
|
||||
- `demo/smoke_v8_draft_write.mjs` — зелёный, все 16 проверок `true`.
|
||||
- `demo/smoke_optimize_coordinate_canonicalization.mjs` — зелёный, все 17
|
||||
проверок `true`.
|
||||
|
||||
**Слабая связь — одно распространённое имя (20, `_cfgRev`)** — не прогонял.
|
||||
Решение: диф ограничен единственной точкой входа —
|
||||
`catch`-блоком `_saveSettingsDialog`, который не переиспользуется другими
|
||||
диалогами/потоками записи; `_cfgRev` в остальных 20 смоках фигурирует по
|
||||
несвязанным путям (drag, layout, virtual light, discovery-фильтры и т.д.),
|
||||
ни один из них не проходит через `_saveSettingsDialog`. Считаю прогон
|
||||
избыточным для этой дельты.
|
||||
|
||||
Golden/perf/backend: не прогонял — diff не меняет геометрию, стили или слои
|
||||
в успешном пути (единственное видимое отличие — поведение при уже
|
||||
подтверждённом отказе записи, вне сценариев golden-капчура); backend
|
||||
(`custom_components/**/*.py`) не тронут; perf не назван в AC. Инварианты
|
||||
модели (`npm run invariants`) не прогонял — diff не трогает рёбра комнат,
|
||||
записи толщины, `layout`, `marker.space`, `open_spans`.
|
||||
|
||||
## Находки
|
||||
|
||||
Ни одной блокирующей (High) или Medium-в-скоупе находки. Одна Low,
|
||||
снимается с записью ниже — без отдельного issue (в скоуп не выходит).
|
||||
|
||||
**Low — обещанный в самом ТЗ второй сдвиг `_cfgEpoch` не реализован.**
|
||||
Раздел «Реализация и границы» в теле issue: «Одновременно восстановить
|
||||
`_cfgContentFingerprint`, сдвинуть cache epoch и запросить render.»
|
||||
`rollbackOptimistic` (`src/serialized-write-queue.ts`) восстанавливает
|
||||
`_serverCfg` и `_cfgContentFingerprint`, дёргает `requestUpdate()`, но
|
||||
**не трогает `_cfgEpoch`** — тип `host` в её сигнатуре его даже не
|
||||
перечисляет. `_cfgEpoch` бампается один раз в начале `_saveConfigNow`
|
||||
(строка 8931) и второй раз после отката не сдвигается.
|
||||
|
||||
Разобрал, порождает ли это видимый дефект: `_cfgEpoch` участвует в ключах
|
||||
кэшей геометрии/предпросмотра (контуры комнат, объединение стен, солнечные
|
||||
лучи) и в редакторских кэшах декора — но геометрию/декор этот диалог не
|
||||
меняет, а единственный settings-зависимый кэш, ключующийся по эпохе
|
||||
(солнечные лучи, `houseplan-card.ts:10488`), включает `north`/значения
|
||||
режима **прямо в строку ключа**, а не полагается на эпоху как на
|
||||
единственный признак изменения. Поэтому даже кэш-промах после несостоявшегося
|
||||
второго бампа не может отдать неверные лучи для другого `north` — ключ
|
||||
и так другой; а для того же `north` результат совпадает по построению.
|
||||
Цвета/glow/фон читаются из `_serverCfg.settings` в рендере без отдельного
|
||||
эпоха-кэша. Видимого дефекта не нашёл ни рассуждением, ни smoke (который
|
||||
как раз проверяет наблюдаемое поведение runtime после отката —
|
||||
`rejectedRuntimeUsesServerSettings`).
|
||||
|
||||
Снимаю без правки: разрыв между текстом ТЗ и кодом реален, но не создаёт
|
||||
наблюдаемого расхождения при нынешней архитектуре кэшей. Если в будущем
|
||||
появится settings-зависимый эпоха-кэш без значения в ключе (например,
|
||||
для `bg_color`/`glow_radius_cm`), тот же паттерн уже будет тихо ломаться —
|
||||
стоит иметь в виду при следующей работе с `rollbackOptimistic`, но заводить
|
||||
это отдельным issue сейчас избыточно: одно наблюдение без текущего
|
||||
пользовательского эффекта.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- Идентити-guard (`_cfgRev` + ссылочное/содержательное сравнение
|
||||
`_serverCfg`) корректно различает «наш неудавшийся кандидат» и
|
||||
«авторитетный конфликтный reload» / «более новую правку» — подтверждено
|
||||
и чтением кода, и двумя юнит-тестами
|
||||
(`test/serialized-write-queue.test.mjs`), и smoke AC2.
|
||||
`_cfgRev` меняется исключительно после успешного ответа сервера
|
||||
(`_sendConfigCandidate`), поэтому на обычном отказе guard всегда
|
||||
пропускает откат — структурно, а не по счастливой случайности с таймингом.
|
||||
- Глубокий клон снимка (`JSON.parse(JSON.stringify(previous))` в
|
||||
`optimisticAttempt`) — восстановленный `_serverCfg` не делит ссылку с
|
||||
последующими мутациями; проверено юнит-тестом
|
||||
(`assert.notEqual(host._serverCfg, previous, ...)`).
|
||||
- draft в диалоге не трогается веткой rollback — `_settingsDialog` в
|
||||
`catch` только теряет `busy`; введённые значения остаются, что и требует
|
||||
AC1/AC2 (smoke `rejectedDraftKept`, `conflictDraftKept`).
|
||||
- `_saveConfigNow`'s conflict-ветка (`await
|
||||
this.host._reloadConfigOnly()` до повторного throw) не переписана этим
|
||||
диффом — новый код добавлен строго в внешнем `catch`, не пересекается с
|
||||
существующей веткой rollback для физической геометрии
|
||||
(`physicalGeometryRolledBack`, `_reloadRejectedPhysicalWrite`), которая
|
||||
этот диф тоже не трогает.
|
||||
- Оба changelog в том же коммите (`f0574c1f`), формулировка соответствует
|
||||
фактическому поведению (AC1), не переобещает про AC2 (внутренняя защита,
|
||||
не новое видимое поведение) — текст `docs/CHANGELOG.md` /
|
||||
`docs/CHANGELOG.ru.md` сверен построчно.
|
||||
- Трейлеры `Issue: #439` и `User-Visible: yes|no` на обоих коммитах на
|
||||
месте; `User-Visible: yes` в `f0574c1f` сопровождается правкой обоих
|
||||
changelog в этом же коммите.
|
||||
- Классификация `trivial`: один диалог/модуль, 3 AC, ожидаемое поведение
|
||||
зафиксировано контрактом #426 — критерии §5.1 выполнены, спор не вижу.
|
||||
- Единственный источник числа/значения: диф не вводит новое отображаемое
|
||||
значение — только восстанавливает существующий источник (`_serverCfg`)
|
||||
к его прежнему состоянию; `test/single-source-numbers.test.mjs` в
|
||||
прогоне `npm test` зелёный без изменений в его области.
|
||||
|
||||
## Чего не проверял и почему
|
||||
|
||||
- Полный набор из 216 смоков и `golden:verify`/`performance_smoke` — diff
|
||||
не расширяется за пределы одного диалога, это предрелизная обязанность
|
||||
(§8), не гейт этого ревью.
|
||||
- 20 смоков со слабой связью по `_cfgRev` — см. обоснование выше.
|
||||
- `npm run invariants` — diff не касается геометрии/толщины/`layout`.
|
||||
- `python -m pytest tests_backend` — `custom_components/**/*.py` не
|
||||
тронут.
|
||||
- Ручное тестирование в браузере (вне smoke-харнесса) — не запускал,
|
||||
цикл ревью не предполагает ручной прогон; smoke дублирует ровно тот же
|
||||
DOM/hass-стенд.
|
||||
- Кросс-языковая идентичность канонизации координат (JS↔Python,
|
||||
`LATTICE_GRID_N=240`, `LATTICE_NOISE_STEPS=1e-4`) как потенциальный
|
||||
источник unstable fingerprint после `canonicalizeConfigGeometry` внутри
|
||||
`_writeConfig` — рассмотрел теоретически (если бы бэкенд не гарантировал
|
||||
фиксированную точку, guard мог бы пропустить откат для конфигов с
|
||||
«грязной» геометрией). Не нашёл сценария: backend
|
||||
(`custom_components/houseplan/websocket_api.py`) канонизирует конфиг на
|
||||
каждом `config/get`/`config/set` тем же алгоритмом, поэтому входящий
|
||||
`_serverCfg` уже канонический и повторная канонизация — no-op по
|
||||
построению. Не свежий риск этого диффа (канонизация всего конфига при
|
||||
любой записи settings существовала и до #439); не заводил как находку.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Все три AC доказаны: AC1/AC3 существующим+новым smoke и юнит-тестами,
|
||||
AC2 — smoke плюс структурным чтением guard'а (ревизия монотонна только
|
||||
вверх и обновляется исключительно на успехе). Единственная находка — Low,
|
||||
снята с запиской, без наблюдаемого дефекта. Гейты, которые обязаны быть
|
||||
зелёными на этом заходе, зелёные (пересчитано мной, не только заявлено
|
||||
автором). Trivial-классификация обоснована.
|
||||
|
||||
**Вердикт: зелёный.**
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/439-settings-save-rollback`, коммит `3883352f43e7` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `39c0bc135ef59643339eb2d2fbee5a52e4ad6b3f`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 39c0bc135ef5
|
||||
```
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "houseplan-card",
|
||||
"version": "1.71.0-beta.2",
|
||||
"version": "1.71.0-beta.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "houseplan-card",
|
||||
"version": "1.71.0-beta.2",
|
||||
"version": "1.71.0-beta.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lit": "^3.1.3",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "houseplan-card",
|
||||
"version": "1.71.0-beta.2",
|
||||
"version": "1.71.0-beta.1",
|
||||
"description": "Interactive house plan Lovelace card for Home Assistant",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -378,7 +378,7 @@ import {
|
||||
} from './space-order';
|
||||
import { applyOpeningMoves, mergeCollinearPartitions, spaceMergeGeometry } from './wall-merge';
|
||||
|
||||
const CARD_VERSION = '1.71.0-beta.2';
|
||||
const CARD_VERSION = '1.71.0-beta.1';
|
||||
const ENTRY_BUILD_FINGERPRINT = '__HOUSEPLAN_SOURCE_FINGERPRINT__';
|
||||
const EDITOR_RETRY_ASSET = '__HOUSEPLAN_EDITOR_RETRY_ASSET__';
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ import {
|
||||
canonicalizePosition,
|
||||
formatLatticeShiftCm,
|
||||
} from './coordinate-canonicalization';
|
||||
import { enqueueSerializedWrite, optimisticAttempt, rollbackOptimistic, type OptimisticAttempt } from './serialized-write-queue';
|
||||
import { enqueueSerializedWrite } from './serialized-write-queue';
|
||||
import { hasTranslation, langOf, t, type I18nKey } from './i18n';
|
||||
import { supportT, type SupportI18nKey } from './i18n/support';
|
||||
import {
|
||||
@@ -358,7 +358,7 @@ import {
|
||||
} from './space-order';
|
||||
import { applyOpeningMoves, mergeCollinearPartitions, spaceMergeGeometry } from './wall-merge';
|
||||
|
||||
const CARD_VERSION = '1.71.0-beta.2';
|
||||
const CARD_VERSION = '1.71.0-beta.1';
|
||||
|
||||
type ResizeLiveLabel = {
|
||||
kind: 'length';
|
||||
@@ -10228,7 +10228,6 @@ public _updateDecorStyle(next: DecorStyle): void {
|
||||
const d = this.host._settingsDialog;
|
||||
if (!d || d.busy) return;
|
||||
this.host._settingsDialog = { ...d, busy: true };
|
||||
let attempt: OptimisticAttempt<ServerConfig> | null = null;
|
||||
try {
|
||||
const cfg = this.host._serverCfg!;
|
||||
const isDefault = JSON.stringify(d.colors) === JSON.stringify(DEFAULT_FILL_COLORS);
|
||||
@@ -10240,7 +10239,8 @@ public _updateDecorStyle(next: DecorStyle): void {
|
||||
else delete settings.glow_radius_cm;
|
||||
if (d.bgColor) settings.bg_color = d.bgColor;
|
||||
else delete settings.bg_color;
|
||||
// Legacy absence means static; new installations/spaces materialise daynight.
|
||||
// Sun background compatibility is explicit: legacy absence means static,
|
||||
// while new installations and spaces materialise daynight.
|
||||
if (d.northDeg !== null && Number.isInteger(d.northDeg) && d.northDeg >= 0 && d.northDeg <= 359)
|
||||
settings.north_deg = d.northDeg;
|
||||
else delete settings.north_deg;
|
||||
@@ -10249,22 +10249,21 @@ public _updateDecorStyle(next: DecorStyle): void {
|
||||
else delete settings.sun_rays;
|
||||
if (d.showRoomTooltip) delete settings.show_room_tooltip;
|
||||
else settings.show_room_tooltip = false;
|
||||
// Old configs may still contain this accepted field, but weather no
|
||||
// longer affects sunlight; saving general settings cleans it up.
|
||||
// Legacy compatibility: old configs may still contain this accepted
|
||||
// field, but weather no longer affects sunlight and the UI no longer
|
||||
// exposes it. Saving general settings cleans the obsolete value up.
|
||||
delete settings.weather_entity;
|
||||
const nextConfig = { ...cfg, settings };
|
||||
attempt = optimisticAttempt(cfg, nextConfig, this.host._cfgContentFingerprint,
|
||||
this.host._cfgRev, contentFingerprint);
|
||||
this.host._serverCfg = nextConfig;
|
||||
this.host._serverCfg = { ...cfg, settings };
|
||||
await this._saveConfigNow();
|
||||
if (!d.showRoomTooltip && this.host._tip?.room) this.host._tip = null;
|
||||
this.host._settingsDialog = null;
|
||||
this.host.requestUpdate();
|
||||
this.host._showToast(this.host._t('gs.saved'));
|
||||
} catch (e: any) {
|
||||
// Esc may close the dialog in flight; the toast must still fire (audit L3).
|
||||
// Never overwrite an authoritative conflict reload or newer edit (#439).
|
||||
if (attempt) rollbackOptimistic(this.host, attempt, contentFingerprint);
|
||||
// audit L3: the dialog may have been closed (Esc) while the save was
|
||||
// in flight — spreading null yields a truthy husk and the renderer
|
||||
// then crashes, blanking the whole card. The toast below is the
|
||||
// only remaining signal, so it must still fire.
|
||||
if (this.host._settingsDialog) this.host._settingsDialog = { ...this.host._settingsDialog, busy: false };
|
||||
this.host._showToast(this.host._t('toast.error', { err: this.host._errText(e) }));
|
||||
}
|
||||
|
||||
@@ -9,52 +9,3 @@ export function enqueueSerializedWrite(
|
||||
): Promise<void> {
|
||||
return previous.catch(() => undefined).then(write);
|
||||
}
|
||||
|
||||
export interface OptimisticAttempt<T> {
|
||||
previous: T;
|
||||
previousFingerprint: string;
|
||||
revision: number;
|
||||
attempted: T;
|
||||
attemptedFingerprint: string;
|
||||
}
|
||||
|
||||
/** Capture the server-backed value before exposing a write candidate locally. */
|
||||
export function optimisticAttempt<T>(
|
||||
previous: T,
|
||||
attempted: T,
|
||||
previousFingerprint: string,
|
||||
revision: number,
|
||||
fingerprint: (value: T) => string,
|
||||
): OptimisticAttempt<T> {
|
||||
return {
|
||||
previous: JSON.parse(JSON.stringify(previous)) as T,
|
||||
previousFingerprint,
|
||||
revision,
|
||||
attempted,
|
||||
attemptedFingerprint: fingerprint(attempted),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll back only the failed candidate represented by this attempt. A conflict
|
||||
* reload or a newer edit owns a different revision/content and must win.
|
||||
*/
|
||||
export function rollbackOptimistic<T>(
|
||||
host: {
|
||||
_serverCfg: T | null;
|
||||
_cfgRev: number;
|
||||
_cfgContentFingerprint: string;
|
||||
requestUpdate: () => unknown;
|
||||
},
|
||||
attempt: OptimisticAttempt<T>,
|
||||
fingerprint: (value: T) => string,
|
||||
): boolean {
|
||||
const current = host._serverCfg;
|
||||
if (!current || host._cfgRev !== attempt.revision
|
||||
|| (current !== attempt.attempted
|
||||
&& fingerprint(current) !== attempt.attemptedFingerprint)) return false;
|
||||
host._serverCfg = attempt.previous;
|
||||
host._cfgContentFingerprint = attempt.previousFingerprint;
|
||||
host.requestUpdate();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
enqueueSerializedWrite,
|
||||
optimisticAttempt,
|
||||
rollbackOptimistic,
|
||||
} from '../test-build/serialized-write-queue.js';
|
||||
import { enqueueSerializedWrite } from '../test-build/serialized-write-queue.js';
|
||||
|
||||
function deferred() {
|
||||
let resolve;
|
||||
@@ -55,48 +51,3 @@ test('a failed write does not poison the next queued edit (#224)', async () => {
|
||||
await recovered;
|
||||
assert.deepEqual(seen, ['failed', 'recovered']);
|
||||
});
|
||||
|
||||
test('a rejected optimistic write restores only its own candidate (#439)', () => {
|
||||
const fingerprint = (value) => JSON.stringify(value);
|
||||
const previous = { settings: { enabled: true } };
|
||||
const attempted = { settings: { enabled: false } };
|
||||
const attempt = optimisticAttempt(previous, attempted, 'server-fingerprint', 7, fingerprint);
|
||||
let updates = 0;
|
||||
const host = {
|
||||
_serverCfg: attempted,
|
||||
_cfgRev: 7,
|
||||
_cfgContentFingerprint: fingerprint(attempted),
|
||||
requestUpdate: () => { updates += 1; },
|
||||
};
|
||||
|
||||
assert.equal(rollbackOptimistic(host, attempt, fingerprint), true);
|
||||
assert.deepEqual(host._serverCfg, previous);
|
||||
assert.notEqual(host._serverCfg, previous, 'the rollback snapshot is isolated from later mutations');
|
||||
assert.equal(host._cfgContentFingerprint, 'server-fingerprint');
|
||||
assert.equal(updates, 1);
|
||||
});
|
||||
|
||||
test('a conflict reload or newer mutation wins over a rejected candidate (#439)', () => {
|
||||
const fingerprint = (value) => JSON.stringify(value);
|
||||
const previous = { settings: { value: 'server-before' } };
|
||||
const attempted = { settings: { value: 'draft' } };
|
||||
const attempt = optimisticAttempt(previous, attempted, 'before', 3, fingerprint);
|
||||
const authoritative = { settings: { value: 'server-after' } };
|
||||
let updates = 0;
|
||||
const host = {
|
||||
_serverCfg: authoritative,
|
||||
_cfgRev: 4,
|
||||
_cfgContentFingerprint: fingerprint(authoritative),
|
||||
requestUpdate: () => { updates += 1; },
|
||||
};
|
||||
|
||||
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
|
||||
assert.equal(host._serverCfg, authoritative);
|
||||
assert.equal(updates, 0);
|
||||
|
||||
host._cfgRev = 3;
|
||||
host._serverCfg = { settings: { value: 'newer-local-edit' } };
|
||||
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
|
||||
assert.equal(host._serverCfg.settings.value, 'newer-local-edit');
|
||||
assert.equal(updates, 0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user