Compare commits

...
Author SHA1 Message Date
claude[bot] 97d5e865ac docs: review document for #391
Issue: #391
User-Visible: no
2026-08-30 17:12:58 +00:00
claude[bot]andMatysh c3f38654f6 docs: review document for #391
Issue: #391
User-Visible: no
2026-08-30 20:05:29 +03:00
Matysh 7ae0f14d53 chore: refresh docs source fingerprint (#391)
Record the unchanged screenshot set against the typed editor runtime source after verifying the capture output and preserving the reviewed PNG bytes.

Issue: #391
User-Visible: no
2026-08-30 20:05:09 +03:00
Matysh 3bbc65aa28 refactor: type editor i18n keys (#391)
Remove legacy any casts from device inbox, marker, and geometry preflight translation calls. Refresh the moved preflight mutation anchor.

Issue: #391
User-Visible: no
2026-08-30 20:00:51 +03:00
23 changed files with 554 additions and 173 deletions
@@ -1,72 +1,72 @@
{
"schema": 1,
"fingerprint": "099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347",
"fingerprint": "cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-DzlQl4_k.js",
"houseplan-assets/houseplan-card-B3HePKmB.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 283094,
"initialViewGzipBytes": 283100,
"lazyFiles": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/de-Dq2fGpol.js",
"houseplan-assets/editor-DrQ9S89m.js",
"houseplan-assets/fr-Bq4OMxB4.js",
"houseplan-assets/houseplan-editor-runtime-C6fRvS15.js",
"houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/de-BmzESOTw.js",
"houseplan-assets/editor-DQVhIDEt.js",
"houseplan-assets/fr-Oc3CqcjD.js",
"houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js",
"houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js"
],
"lazyGzipBytes": 197612,
"lazyGzipBytes": 197619,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/editor-DrQ9S89m.js",
"houseplan-assets/houseplan-editor-runtime-C6fRvS15.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/editor-DQVhIDEt.js",
"houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js"
],
"lazyEditorGzipBytes": 143897,
"lazyEditorGzipBytes": 143903,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js"
],
"lazyOnboardingGzipBytes": 13789,
"lazyOnboardingGzipBytes": 13791,
"lazyLocaleFiles": [
"houseplan-assets/de-Dq2fGpol.js",
"houseplan-assets/fr-Bq4OMxB4.js"
"houseplan-assets/de-BmzESOTw.js",
"houseplan-assets/fr-Oc3CqcjD.js"
],
"lazyLocaleGzipBytes": 46997,
"lazyLocaleGzipBytes": 46996,
"files": [
{
"path": "houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"sha256": "383eea583e6ee787be10c7766420bbff1738f466827bcada4dbb2fbf1de24505",
"path": "houseplan-assets/backdrop-pick-DLodXdwD.js",
"sha256": "e2f816068519a0b6b7f03538230fd5ccd878e982ce4478f493743ac33a79131a",
"rawBytes": 20636,
"gzipBytes": 7071,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-Dq2fGpol.js",
"sha256": "17cca86ed13e257b06567609834707e975e9dacb38e495208a5bceedf4e7da8f",
"path": "houseplan-assets/de-BmzESOTw.js",
"sha256": "0ec4ce92fe2c75cc6821200b501b6222908a3e4928682a35f92f6b1ba7774611",
"rawBytes": 78310,
"gzipBytes": 23721,
"gzipBytes": 23720,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-DrQ9S89m.js",
"sha256": "0b137accab6d6ed51f44aed3d4d2b1ff444b5307ac9c11ff0cb9535cfd25bb2c",
"path": "houseplan-assets/editor-DQVhIDEt.js",
"sha256": "0a424827d20ac6dd1c9a82dca914a3d3eb4c0dca0ec3b047876fc799ec0e64a3",
"rawBytes": 3826,
"gzipBytes": 1582,
"gzipBytes": 1583,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-Bq4OMxB4.js",
"sha256": "da0d137d42243e36315ed6ad73197210ea12cf672cf14eb7cb6320594c9aec56",
"path": "houseplan-assets/fr-Oc3CqcjD.js",
"sha256": "04f590260b469a5dcfdb16862d30488f00ad88e4061fee133c0aabd493eca7ee",
"rawBytes": 80482,
"gzipBytes": 23276,
"isEntry": false,
@@ -74,52 +74,52 @@
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-DzlQl4_k.js",
"sha256": "ac14483abe54719e12506df7eef1f26f4fd570c92600ea781159e445249ecca9",
"path": "houseplan-assets/houseplan-card-B3HePKmB.js",
"sha256": "8915715da95372e342f48a6643e0bc91acd0fa176cc35df444f0ae804ee79c91",
"rawBytes": 1001346,
"gzipBytes": 282298,
"gzipBytes": 282303,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-Dq2fGpol.js",
"houseplan-assets/editor-DrQ9S89m.js",
"houseplan-assets/fr-Bq4OMxB4.js",
"houseplan-assets/houseplan-editor-runtime-C6fRvS15.js",
"houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js"
"houseplan-assets/de-BmzESOTw.js",
"houseplan-assets/editor-DQVhIDEt.js",
"houseplan-assets/fr-Oc3CqcjD.js",
"houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js",
"houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-C6fRvS15.js",
"sha256": "aeef2fb0c3cec33286d9aee59488c69713483da5e93d5a47b28b67e5637354e6",
"path": "houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js",
"sha256": "6676cdf3a5b2d1e95e8e08d8534a3a1c1d2898959fbc45311b56f907aeaf8283",
"rawBytes": 524015,
"gzipBytes": 135244,
"gzipBytes": 135249,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js",
"sha256": "d7aba6728494fdc25e17a614eba3e91bb8132ee8248d8970fe6030565ec8c36b",
"path": "houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js",
"sha256": "c99d452141ba30ce46fba8f7a10bb1a26ecd4a5c953c3fcb27d0a486d2f67cb2",
"rawBytes": 27120,
"gzipBytes": 6718,
"gzipBytes": 6720,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "2bdf546fd8a2b8fe97577f166d3dd1720dc7777201accb899856e1ea3e96c14d",
"sha256": "03f0c7029b85a070d1f123fe401566d3b529f886d195ca9beaf3a17177811446",
"rawBytes": 1183,
"gzipBytes": 796,
"gzipBytes": 797,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";import{b as e,l as o,t,d7 as s,A as a,d8 as i,d9 as l,da as n,E as r,c}from"./houseplan-card-DzlQl4_k.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__="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";import{b as e,l as o,t,d7 as s,A as a,d8 as i,d9 as l,da as n,E as r,c}from"./houseplan-card-B3HePKmB.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";import{l as t,ar as s,A as e,t as o,c as a,bV as l,bW as i,bX as h,bY as c,ao as n,bZ as p,c2 as r,b_ as _,b$ as d,c0 as u,c1 as g,cX as m,an as b,ap as $,cY as v}from"./houseplan-card-DzlQl4_k.js";import{i as f,c as y,e as w,r as D,a as S,b as k,s as C,t as M}from"./backdrop-pick-CJ3kZCi2.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";class I{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";import{l as t,ar as s,A as e,t as o,c as a,bV as l,bW as i,bX as h,bY as c,ao as n,bZ as p,c2 as r,b_ as _,b$ as d,c0 as u,c1 as g,cX as m,an as b,ap as $,cY as v}from"./houseplan-card-B3HePKmB.js";import{i as f,c as y,e as w,r as D,a as S,b as k,s as C,t as M}from"./backdrop-pick-DLodXdwD.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";class I{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
.text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,r="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:p(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:r,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=r(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...f(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:d,tempMax:_,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await y(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await w(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return D(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans">
${s.map(s=>{return a`
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";try{await import("./houseplan-assets/houseplan-card-DzlQl4_k.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__="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";try{await import("./houseplan-assets/houseplan-card-B3HePKmB.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)}
+53 -53
View File
@@ -1,72 +1,72 @@
{
"schema": 1,
"fingerprint": "099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347",
"fingerprint": "cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-DzlQl4_k.js",
"houseplan-assets/houseplan-card-B3HePKmB.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 283094,
"initialViewGzipBytes": 283100,
"lazyFiles": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/de-Dq2fGpol.js",
"houseplan-assets/editor-DrQ9S89m.js",
"houseplan-assets/fr-Bq4OMxB4.js",
"houseplan-assets/houseplan-editor-runtime-C6fRvS15.js",
"houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/de-BmzESOTw.js",
"houseplan-assets/editor-DQVhIDEt.js",
"houseplan-assets/fr-Oc3CqcjD.js",
"houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js",
"houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js"
],
"lazyGzipBytes": 197612,
"lazyGzipBytes": 197619,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/editor-DrQ9S89m.js",
"houseplan-assets/houseplan-editor-runtime-C6fRvS15.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/editor-DQVhIDEt.js",
"houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js"
],
"lazyEditorGzipBytes": 143897,
"lazyEditorGzipBytes": 143903,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js"
],
"lazyOnboardingGzipBytes": 13789,
"lazyOnboardingGzipBytes": 13791,
"lazyLocaleFiles": [
"houseplan-assets/de-Dq2fGpol.js",
"houseplan-assets/fr-Bq4OMxB4.js"
"houseplan-assets/de-BmzESOTw.js",
"houseplan-assets/fr-Oc3CqcjD.js"
],
"lazyLocaleGzipBytes": 46997,
"lazyLocaleGzipBytes": 46996,
"files": [
{
"path": "houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"sha256": "383eea583e6ee787be10c7766420bbff1738f466827bcada4dbb2fbf1de24505",
"path": "houseplan-assets/backdrop-pick-DLodXdwD.js",
"sha256": "e2f816068519a0b6b7f03538230fd5ccd878e982ce4478f493743ac33a79131a",
"rawBytes": 20636,
"gzipBytes": 7071,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-Dq2fGpol.js",
"sha256": "17cca86ed13e257b06567609834707e975e9dacb38e495208a5bceedf4e7da8f",
"path": "houseplan-assets/de-BmzESOTw.js",
"sha256": "0ec4ce92fe2c75cc6821200b501b6222908a3e4928682a35f92f6b1ba7774611",
"rawBytes": 78310,
"gzipBytes": 23721,
"gzipBytes": 23720,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-DrQ9S89m.js",
"sha256": "0b137accab6d6ed51f44aed3d4d2b1ff444b5307ac9c11ff0cb9535cfd25bb2c",
"path": "houseplan-assets/editor-DQVhIDEt.js",
"sha256": "0a424827d20ac6dd1c9a82dca914a3d3eb4c0dca0ec3b047876fc799ec0e64a3",
"rawBytes": 3826,
"gzipBytes": 1582,
"gzipBytes": 1583,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-Bq4OMxB4.js",
"sha256": "da0d137d42243e36315ed6ad73197210ea12cf672cf14eb7cb6320594c9aec56",
"path": "houseplan-assets/fr-Oc3CqcjD.js",
"sha256": "04f590260b469a5dcfdb16862d30488f00ad88e4061fee133c0aabd493eca7ee",
"rawBytes": 80482,
"gzipBytes": 23276,
"isEntry": false,
@@ -74,52 +74,52 @@
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-DzlQl4_k.js",
"sha256": "ac14483abe54719e12506df7eef1f26f4fd570c92600ea781159e445249ecca9",
"path": "houseplan-assets/houseplan-card-B3HePKmB.js",
"sha256": "8915715da95372e342f48a6643e0bc91acd0fa176cc35df444f0ae804ee79c91",
"rawBytes": 1001346,
"gzipBytes": 282298,
"gzipBytes": 282303,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-Dq2fGpol.js",
"houseplan-assets/editor-DrQ9S89m.js",
"houseplan-assets/fr-Bq4OMxB4.js",
"houseplan-assets/houseplan-editor-runtime-C6fRvS15.js",
"houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js"
"houseplan-assets/de-BmzESOTw.js",
"houseplan-assets/editor-DQVhIDEt.js",
"houseplan-assets/fr-Oc3CqcjD.js",
"houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js",
"houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-C6fRvS15.js",
"sha256": "aeef2fb0c3cec33286d9aee59488c69713483da5e93d5a47b28b67e5637354e6",
"path": "houseplan-assets/houseplan-editor-runtime-BwnLX8MM.js",
"sha256": "6676cdf3a5b2d1e95e8e08d8534a3a1c1d2898959fbc45311b56f907aeaf8283",
"rawBytes": 524015,
"gzipBytes": 135244,
"gzipBytes": 135249,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-HmSdEh0z.js",
"sha256": "d7aba6728494fdc25e17a614eba3e91bb8132ee8248d8970fe6030565ec8c36b",
"path": "houseplan-assets/houseplan-onboarding-runtime-DG2Ws9vn.js",
"sha256": "c99d452141ba30ce46fba8f7a10bb1a26ecd4a5c953c3fcb27d0a486d2f67cb2",
"rawBytes": 27120,
"gzipBytes": 6718,
"gzipBytes": 6720,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-CJ3kZCi2.js",
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/backdrop-pick-DLodXdwD.js",
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "2bdf546fd8a2b8fe97577f166d3dd1720dc7777201accb899856e1ea3e96c14d",
"sha256": "03f0c7029b85a070d1f123fe401566d3b529f886d195ca9beaf3a17177811446",
"rawBytes": 1183,
"gzipBytes": 796,
"gzipBytes": 797,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-DzlQl4_k.js"
"houseplan-assets/houseplan-card-B3HePKmB.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";import{b as e,l as o,t,d7 as s,A as a,d8 as i,d9 as l,da as n,E as r,c}from"./houseplan-card-DzlQl4_k.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__="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";import{b as e,l as o,t,d7 as s,A as a,d8 as i,d9 as l,da as n,E as r,c}from"./houseplan-card-B3HePKmB.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";import{l as t,ar as s,A as e,t as o,c as a,bV as l,bW as i,bX as h,bY as c,ao as n,bZ as p,c2 as r,b_ as _,b$ as d,c0 as u,c1 as g,cX as m,an as b,ap as $,cY as v}from"./houseplan-card-DzlQl4_k.js";import{i as f,c as y,e as w,r as D,a as S,b as k,s as C,t as M}from"./backdrop-pick-CJ3kZCi2.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";class I{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";import{l as t,ar as s,A as e,t as o,c as a,bV as l,bW as i,bX as h,bY as c,ao as n,bZ as p,c2 as r,b_ as _,b$ as d,c0 as u,c1 as g,cX as m,an as b,ap as $,cY as v}from"./houseplan-card-B3HePKmB.js";import{i as f,c as y,e as w,r as D,a as S,b as k,s as C,t as M}from"./backdrop-pick-DLodXdwD.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";class I{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?a`<hp-help data-help-key=${l}
.text=${o(h,l)} .ariaLabel=${o(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const o=l(e),a=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,r="none"===o.fill?{...a||h,a:0}:a;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:o.showBorders,showNames:o.showNames,zeroWallStyle:p(e),displayTouched:!0,hideDecor:o.hideDecor,hideOpenings:o.hideOpenings,roomColor:o.color,roomOpacity:o.opacity,fillMode:"none"===o.fill?"custom":o.fill,customFill:r,glowEnabled:o.glow,bgColor:o.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:o.tempMin,tempMax:o.tempMax,showLqi:o.showLqi??this.host._config?.show_signal??!0,cardFontScale:o.cardFontScale,labelTemp:o.labelTemp,labelHum:o.labelHum,labelLqi:o.labelLqi,labelLight:o.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=r(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...f(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:d,tempMax:_,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;s.value="";const o=await y(e);if("reject"===o.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===o.kind)return void(this.host._backdropGuard=o.state);const a=await w(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return D(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return a`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return a`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return a`<div class="savedplans">
${s.map(s=>{return a`
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
+1 -1
View File
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="099688ecae2a6f015325a6ffa40c7112bd2726309f7760aec7d6b2534ad2f347";try{await import("./houseplan-assets/houseplan-card-DzlQl4_k.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__="cd530b1a2b7fea73c9b39236f868bd23d8f35d404eec4c128453f2140d9288fb";try{await import("./houseplan-assets/houseplan-card-B3HePKmB.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)}
+11 -11
View File
@@ -3,7 +3,7 @@
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"oxipng": "oxipng 10.2.0",
"sourceFingerprint": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceFingerprint": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"captureScriptSha256": "9229c467130e1f3d3abd042e2dcabaca9d4d5a918d0d77eadc814848df597ba6",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -15,7 +15,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "9119ab87502038f787529f621c39e1e0d01f3bc3b0289051c3791a1886e97a6b"
},
"view-touch": {
@@ -26,7 +26,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "21c6fe7d79ff5c878ade8e98a64cc90dfd7bcc66338c864fb5a0bd411471313d"
},
"space-create": {
@@ -37,7 +37,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "fb32ea4a785fedb0e99b0bca6dbcf19022b32ced763ed875a57eb453be550bf5"
},
"room-contour-close": {
@@ -48,7 +48,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "75e0bc3dedff68e29be46531df0cc205f69988e86229580cc021017932049b34"
},
"plan-context-tray": {
@@ -59,7 +59,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "5fc29f4a71572ed43221131cd6e5f9a3da19fcc9bf63c879b12664bee98e292a"
},
"device-editor": {
@@ -70,7 +70,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "49e14ee30284b44bc914a67c368abef6d8ac26ef320079e985f1edd36c7985d7"
},
"device-display-preview": {
@@ -81,7 +81,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "7185c75cf0e3629c7bc880381d1eab1d1f67a2484ca3d17e86ad53e0ed6387c8"
},
"background-editor": {
@@ -92,7 +92,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "3d0d088f89da53a73efd13ef5bb5403f407011ab918e88671974459b5967ca25"
},
"room-card": {
@@ -103,7 +103,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "93d88ed84e2e3f9071e94e4b74cc3f3a86c7f898a76d1fb26bcc499e7dc4f538"
},
"device-info": {
@@ -114,7 +114,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "d27cbb20bea10b3d0a75f904be32de014d651e0c4e98a869e24c78fc0021a235",
"sourceSha256": "74378a150939498d42a00a1f52f941eed496e3ab2ed222b4b7b69664cf71d587",
"imageSha256": "4d2257e49b28d3be517ed382269569b0e3bc18cec9d84d56b5f407c505a9b398"
}
}
+189
View File
@@ -0,0 +1,189 @@
# CODE-REVIEW-391-r1
Issue: #391 «refactor: type editor i18n keys»
Этап: code (S7-code-review)
Заход: r1 (первый код-ревью раунд; спек-ревью прошло зелёным на r1 отдельно)
Ветка: `issue/391-i18n-key-casts`
SHA под ревью: `81a4278aa4236cec656e473e3ff3953a05c6c5d1` (пайплайн привёл ветку к `dev`
поверх коммита `f7f088fe`; после ребейза это другой код — разбор ниже полный).
Материал: `git diff origin/dev...HEAD` относительно локально закэшированного
`origin/dev` = `eb5aa2a0f8dea5514ee2fee2dbd609457dd0fb4d`.
## Скоуп
Класс A: `src/houseplan-editor-runtime.ts` — снятие `as any` с 33 вызовов
`this.host._t(...)` в трёх семействах ключей (`device_inbox.*` — 26,
`marker.*` — 6, `gs.preflight_reason_*` — 1) и замена вычисляемых ключей на
узкий `as I18nKey`, без изменения словарей, текстов, разметки и ветвления.
Класс B: `scripts/mutation-gate.mjs` — перенос mutation-anchor #295
(`preflight-reason-lost-in-dialog`) на новый файл/сигнатуру после того, как
lazy-load рефакторинг увёл вызов из `houseplan-card.ts` в
`houseplan-editor-runtime.ts` (без изменения `guard`/`because`).
Класс D: `dist/**`, `custom_components/houseplan/frontend/**` — пересборка.
`User-Visible: no`, трейлер `Issue: #391` на месте — оба корректны для
чисто типового рефакторинга без пользовательского эффекта; изменений в
`docs/CHANGELOG*.md` нет и не требуется.
## Как проверялось
Зелёного Validate на этом SHA не найдено, поэтому гейты прогнаны вручную на
чистом дереве этого коммита (Node 22, зависимости уже установлены):
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | `npx tsc --noEmit` | pass, без вывода |
| Unit/integration | `npm test` | 1644 pass / 0 fail / 1 skipped |
| No-new-any (#342) | `git diff origin/dev...HEAD -- src/houseplan-editor-runtime.ts \| node scripts/no-new-any.mjs --diff -` | «Проверено добавленных строк: 32 в 1 файл(ах). Новых any нет.» |
| Mutation-gate анchor (#295) | `node --test test/mutation-gate.test.mjs` | 10/10 pass |
| Build | `npm run build` | pass |
| Bundle sync | `npm run bundle:sync` | pass; `git status --porcelain` после пересборки — пусто (обе generated-копии побайтово совпадают с закоммиченными) |
| Bundle budget | `npm run bundle:budget` | initial View 283100 B / budget 300000 B; lazy editor 143903 B; lazy locale 46996 B — совпадает с числами автора |
| Inventory | `npm run inventory` | Node 1645 (было 1643 у автора; расхождение — от несвязанного коммита `eb5aa2a0`, легшего поверх после отправки на ревью, не дефект этой задачи) |
| Docs fingerprint | `node scripts/check-docs.mjs` | **FAIL** — см. находку M1 |
Diff не трогает геометрию/модель (нет правок `wall-thickness.ts`, `layout`,
`marker.space`, `open_spans`, узлов/рёбер) — `npm run invariants` не
запускался, это осознанный пропуск, а не мелочь.
Diff не трогает `custom_components/**/*.py` — `pytest tests_backend` не
запускался.
`golden:verify` не запускался: изменение не может повлиять на рендер —
это доказано не только заявлением автора, а тем, что все правки состоят
из удаления TypeScript-каста (`as any`/`as I18nKey`), который стирается на
этапе компиляции без остатка; визуальных/строковых/ветвящихся изменений в
diff нет.
### Браузерные смоки
`node scripts/smoke-select.mjs --base origin/dev --head HEAD`:
```
Изменено файлов src/**: 1 · символов проекта на изменённых строках: 4
Прямое совпадение (7): _showToast →
smoke_help_affordance, smoke_junction_limits,
smoke_optimize_coordinate_canonicalization, smoke_partition_openings,
smoke_room_resize, smoke_tap_ctx, smoke_zero_wall_migration_unblocked
```
Символ `_showToast` попал в выборку механически: он встречается на той же
строке, что и переименованный `_t('device_inbox.saved' as any)` →
`_t('device_inbox.saved')`, хотя сам вызов `_showToast` не менялся. Решение:
прогнать все 7 — дёшево, а инструмент прямо предупреждает не полагаться на
тематический отбор (регресс #234 был пойман смоком, чьё название не
подсказывало связь). Все 7 — `OK`. Дополнительно прогнан
`node demo/smoke_preflight_diagnostics.mjs` — это ровно guard-команда
mutation-anchor `preflight-reason-lost-in-dialog`, который правит diff;
`OK`. Итого 8 браузерных смоков, все зелёные. Полный набор (208 смоков) не
запускался — задача не задевает ничего, кроме уже перечисленного, это
предрелизная обязанность, а не гейт код-ревью.
## Находки
### M1 (Medium, в скоупе — правится в этой же задаче) — стал протухшим отпечаток скриншотов документации
`node scripts/check-docs.mjs` на SHA `81a4278a` падает:
```
ERROR screenshot source fingerprint is stale; run npm run build && node demo/docs/capture.mjs
```
Воспроизведение и причина, а не голословно: `visualFingerprint()`
(`scripts/source-fingerprint.mjs`) хэширует весь `src/**` плюс несколько
build-input файлов. Коммит правит `src/houseplan-editor-runtime.ts`, поэтому
отпечаток обязан был измениться — и изменился (проверено прямым вызовом:
на этом SHA `visualFingerprint('.')` = `74378a15…`, а
`docs/images/screenshots.json.sourceFingerprint`, оставшийся из `dev`, —
`d27cbb20…`; для контроля тот же расчёт на чистом клоне `origin/dev`
(`eb5aa2a0`, без диффа этой задачи) даёт согласованные `d27cbb20…` с обеих
сторон — несовпадение появляется ровно с этим коммитом, а не раньше).
Коммит не содержит правок `docs/images/**`, то есть шаг
`npm run build && node demo/docs/capture.mjs` не выполнялся.
Это не гипотетическая придирка: `docs` — один из перечисленных gate-джобов
Validate (AGENTS.md), и ровно этот пропуск уже дважды оставлял `dev` с
красным `docs` до отдельной последующей задачи (#230, #234 → почищено в
#237). Смысловых изменений отпечаток не несёт (правки — чистое удаление
типовых кастов, кадры почти наверняка выйдут пиксель-в-пиксель прежними),
но манифест обязан отражать актуальный `sourceFingerprint`, иначе `docs`
red на `dev` сразу после мержа.
Почему Medium, а не High: не портит пользовательское поведение, не ломает
контракт, чинится механически. Почему в скоупе этой задачи, а не отдельный
issue: находка — прямое следствие ровно этого diff (правка src/** этой
задачи сделала отпечаток невалидным), а не постороннего дефекта соседней
подсистемы — правило «Medium вне скоупа → отдельный issue» сюда не
применяется.
Что нужно для закрытия: `npm run build && node demo/docs/capture.mjs`,
проверить, что кадры не изменились (`imageSha256` в манифесте), закоммитить
обновлённый `docs/images/screenshots.json` (и PNG, если оптимизатор дал
другие байты) в той же ветке с трейлерами `Issue: #391` / `User-Visible: no`
— это не пользовательская документация, а служебный манифест, видимое
поведение не меняется.
## Что проверено и корректно
- Пересчитаны все 33 целевых вызова построчно (`grep` по
`_t(.*as any` до/после диффа): 26 `device_inbox.*` + 6 `marker.*` + 1
`gs.preflight_reason_*` — все убраны; ни один каст той же строки за
пределами трёх заявленных семейств не тронут (проверено: `tap.*`,
`fill.*`, `decor.*`, `junction.*`, `vac.*`, `run.*` и generic `message`/
`hintKey`/`labelKey` остаются с `as any` — корректно, они вне скоупа).
- Условный `readd`/`add` (строка ~12054) переписан на литеральный union без
каста на каждой ветке, как требует правило реализации №5 — подтверждено
диффом.
- `I18nKey` был импортирован и `_t` уже строго типизирован до этой правки
(строки 245, 1104 в текущем файле) — задача действительно восстанавливает
существующий контракт, а не создаёт новый.
- Ни `as never`, ни `as unknown as`, ни новых `any`, ни расширения
сигнатуры `_t`/`I18nKey` в diff нет — подтверждено чтением полного diff и
`no-new-any.mjs`.
- Ключи, тексты и ветвление визуально не изменились — единственные строки
diff это удаление/сужение каста; проверено чтением, не исполнением, для
всех 33 мест плюс подтверждено 8 зелёными браузерными смоками и `npm
test`, включая i18n parity/policy-тесты в общем прогоне.
- Mutation-anchor `preflight-reason-lost-in-dialog` (#295) актуализирован
корректно: `find`-строка патча совпадает буквально со строкой 9769
текущего файла, `guard`/`because` не изменены, `node --test
test/mutation-gate.test.mjs` зелёный (уникальность/применимость мутантов
проверяется этим набором), `node demo/smoke_preflight_diagnostics.mjs` —
`OK`.
- Generated bundle (класс D) синхронизирован и совпадает побайтово: после
`npm run build && npm run bundle:sync` `git status` пуст.
- Трейлеры коммита корректны: `Issue: #391`, `User-Visible: no`; changelog
оправданно не тронут.
- Ремарка спек-ревью про два «нечистых» вычисляемых ключа
(`marker.value_badge_attr_${source.attribute}`, где `attribute: string`,
и `.replace()/.replaceAll()`, теряющие литеральный тип) — не новый риск:
тот же профиль (произвольная строка, приведённая к `I18nKey`) был и до
правки под `as any`; ТЗ сознательно разрешает это как единственно
доступный вариант, когда компилятор сам не выводит ключ.
## Чего не проверял и почему
- `npm run invariants` — diff не касается геометрии/модели (нет правок
толщины стен, `layout`, `marker.space`, `open_spans`, рёбер решётки).
- `python -m pytest tests_backend` — diff не касается
`custom_components/**/*.py`.
- `npm run golden:verify` — правка стирается на этапе компиляции
(`as any`/`as I18nKey` не производят рантайм-кода), рендер не может
измениться; смоки подтверждают отсутствие видимых эффектов дешевле, чем
полный golden-прогон.
- Полный набор из 208 браузерных смоков — не оправдано ни AC, ни объёмом
diff (одна поверхность, 33 каста в одном файле); прогнаны все 7
«прямых совпадений» инструмента подбора плюс guard-смок мутанта — этого
достаточно для типовой правки такого размера.
- Performance-профили — не названы в AC, diff не касается чувствительных к
перфу путей.
- Ручное тестирование в браузере (UI) не проводилось — не требуется:
изменение не может изменить скомпилированную семантику, что подтверждено
выше техническим аргументом (стирание типовых кастов), а не только словом
автора.
## Вердикт
Единственная находка (M1) — Medium, в скоупе задачи, чинится в ней же:
`npm run build && node demo/docs/capture.mjs`, проверка, что кадры не
изменились, коммит обновлённого манифеста с теми же трейлерами. High
находок нет. Все AC из тела issue выполнены и подтверждены либо тестом,
либо построчным чтением с указанием, что именно проверено. Вердикт —
жёлтый.
+192
View File
@@ -0,0 +1,192 @@
# CODE-REVIEW-391-r2
Issue: #391 «Ещё 26 as any на ключах i18n в device inbox (и рядом marker/gs) — тот же паттерн, что #390»
Этап: code (S7-code-review)
Заход: r2 · блокирующих циклов израсходовано 1 из 2 (израсходован жёлтым вердиктом r1)
Ветка: `issue/391-i18n-key-casts`
SHA под ревью: `c3f38654f6833d95e3299393afad8084652e2bb8`
Материал: `git diff origin/dev...HEAD`, `origin/dev` = `a3dcee52418e41ee698089910731ce0101492a24`.
## Почему разбор полный, а не только по находке M1
Между r1 и r2 ветка сменила базу: r1 разбирал SHA `81a4278a`, привязанный к
`origin/dev = eb5aa2a0`; сейчас `origin/dev = a3dcee52` — на один коммит
дальше (`ci: move the harness to the current Home Assistant`, #392,
класс B: `.github/workflows/{mutation-gate,validate}.yml`,
`tests_backend/requirements.txt`). Это ребейз на ушедший вперёд `dev`, а
инструкция раунда прямо называет такой ребейз триггером полного разбора
(«после ребейза это другой код», PROCESS.md §7.2) — независимо от того,
пересекается ли новый коммит dev с файлами задачи.
Проверил пересечение и содержимое, а не только факт ребейза:
`a3dcee52` не касается ни одного файла этой задачи (`src/**`,
`docs/images/**`, `scripts/mutation-gate.mjs`, `dist/**`,
`custom_components/houseplan/frontend/**`); прямое построчное сравнение
диффа `3bbc65aa` (продуктовый коммит после ребейза) с фрагментами, которые
r1 цитировал (строки 7746, 9766, 11772, 11979, 12043, 12252, 12408, 12596,
13098 в текущем файле), показывает тот же самый текст патча — ребейз не
переписал ни одну правку. Поэтому разбор ниже полный по всем AC (гейты
прогнаны заново), но выводы r1 о построчной верности диффа переиспользованы
там, где я сам подтвердил их идентичность чтением, а не поверил на слово.
## Скоуп (без изменений к r1)
Класс A: `src/houseplan-editor-runtime.ts` — снятие `as any` с 33 вызовов
`this.host._t(...)` в трёх семействах ключей (`device_inbox.*` — 26,
`marker.*` — 6, `gs.preflight_reason_*` — 1); вычисляемые ключи — `as
I18nKey`; литеральные — без каста.
Класс B: `scripts/mutation-gate.mjs` — перенос mutation-anchor #295
(`preflight-reason-lost-in-dialog`) на `src/houseplan-editor-runtime.ts` /
строгую сигнатуру `_t`, `guard`/`because` не менялись.
Класс C: `docs/images/screenshots.json` — новый коммит `7ae0f14d`,
закрывающий находку M1 из r1 (см. таблицу ниже).
Класс D: `dist/**`, `custom_components/houseplan/frontend/**`,
`demo/srv/assets/**` — пересборка.
`User-Visible: no` на всех трёх коммитах (`3bbc65aa`, `7ae0f14d`,
`c3f38654`), трейлер `Issue: #391` на месте — оба верны: чистый типовой
рефакторинг плюс служебный манифест скриншотов, видимого пользователю
эффекта нет, changelog не требуется и не тронут.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| **M1** (Medium, в скоупе) — `node scripts/check-docs.mjs` красный: закоммиченный `sourceFingerprint` (`d27cbb20…`) устарел относительно текущего `src/**` (`74378a15…`), докапчур не выполнялся | Коммит `7ae0f14d` заменяет ровно 11 полей в `docs/images/screenshots.json`: 1×`sourceFingerprint` (манифест) + 10×`sourceSha256` (по одному на сценарий), оба `d27cbb20…` → `74378a15…`; `imageSha256` каждого из 10 сценариев и сам PNG-набор не тронуты (`git diff --stat` по `docs/images/` показывает только `screenshots.json`, без бинарников) | Перепрогнал `node scripts/check-docs.mjs` на текущем SHA сам — `Documentation checks passed (7 files, 10 external links)`. Скрипт не просто читает поле: строка 134 пересчитывает `visualFingerprint(ROOT)` и сверяет с манифестом, строка 145 пересчитывает `sha256` каждого PNG с диска и сверяет с `imageSha256`, строка 147 сверяет `sourceSha256` сценария с `sourceFingerprint` манифеста — все три проверки прошли на живом дереве, это не заявление автора |
## Как проверялось (гейты прогнаны заново на `c3f38654`)
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | `npx tsc --noEmit` | pass, без вывода |
| Unit/integration | `npm test` | 1644 pass / 0 fail / 1 skipped — совпадает с отчётом автора |
| No-new-any (#342) | `git diff origin/dev...HEAD -- src/houseplan-editor-runtime.ts \| node scripts/no-new-any.mjs --diff -` | «Проверено добавленных строк: 32 в 1 файл(ах). Новых any нет.» |
| Mutation-gate (#295/#332) | `node --test test/mutation-gate.test.mjs` | 10/10 pass |
| Build | `npm run build` | pass |
| Bundle sync | `npm run bundle:sync` | pass; `git status --porcelain` после — пусто (обе generated-копии побайтово совпадают с закоммиченными) |
| Bundle budget | `npm run bundle:budget` | initial View 283100 B / budget 300000 B; lazy editor 143903 B; lazy locale 46996 B — совпадает с числами автора и с r1 |
| Docs fingerprint | `node scripts/check-docs.mjs` | pass (7 files, 10 external links) — см. таблицу закрытия M1 |
| Inventory | `npm run inventory` | Node 1645, pure backend 221, HA-harness 154, browser smokes 208 — совпадает с отчётом автора, расхождение r1 (1643 vs 1645) объяснялось несвязанным коммитом `eb5aa2a0` и здесь уже не проявляется |
Diff не трогает геометрию/модель (нет правок толщины стен, `layout`,
`marker.space`, `open_spans`, узлов/рёбер решётки) — `npm run invariants`
не запускался, решение не изменилось относительно r1.
Diff не трогает `custom_components/**/*.py` — `pytest tests_backend` не
запускался.
`golden:verify` не запускался: правка src — чистое удаление/сужение
TypeScript-каста, стирается на компиляции; докс-манифест меняет только
хранимые хэши, не сами кадры (подтверждено самим `check-docs.mjs`, а не
только рассуждением).
### Браузерные смоки
`node scripts/smoke-select.mjs --base origin/dev --head HEAD` на этом SHA
выдал ту же выборку, что и на r1 (ожидаемо: `src/**`-часть диффа
байт-в-байт та же, база сместилась только по несвязанным CI-файлам):
```
Изменено файлов src/**: 1 · символов проекта на изменённых строках: 4
Прямое совпадение (7): _showToast →
smoke_help_affordance, smoke_junction_limits,
smoke_optimize_coordinate_canonicalization, smoke_partition_openings,
smoke_room_resize, smoke_tap_ctx, smoke_zero_wall_migration_unblocked
```
Решение: эти 7 не перегонял повторно — унаследовал из r1 (см. раздел
ниже), содержимое диффа, которое их выбрало, идентично проверенному в r1
(символ `_showToast` на той же строке, механическая связь, не поведенческая).
Отдельно перегнал сам, свежо на этом SHA:
`node demo/smoke_preflight_diagnostics.mjs` — это guard-команда
mutation-anchor `preflight-reason-lost-in-dialog`, который правит diff
напрямую (не механическая связь, а прямое совпадение с `guard` в
`scripts/mutation-gate.mjs:3258-3259`) — результат `OK`. Полный набор
(208 смоков) не запускался — не оправдано ни AC, ни объёмом дельты между
раундами (один файл манифеста скриншотов плюс несвязанный CI-коммит в
базе).
## Что проверено и корректно (реверифицировано на этом SHA, не унаследовано молча)
- Прочитан целиком дифф `3bbc65aa` (продуктовый коммит после ребейза) по
файлу `src/houseplan-editor-runtime.ts` — все 9 изменённых мест
(`_setInboxHidden`, `_renderAlignDialog`, `_renderDevicesBar`,
`_renderDeviceInbox` ×2 блока, `_toggleHintLines`,
`_valueBadgeCandidateLabel`, `_renderMarkerDialog` ×2) текстуально
совпадают с тем, что цитировал r1 — ребейз не исказил ни одну правку.
- `grep -n "as any" src/houseplan-editor-runtime.ts`, отфильтрованный по
`device_inbox\.|marker\.|gs\.preflight_reason_`, — пусто: ни одного
каста этих трёх семейств не осталось.
- Условный `readd`/`add` — литеральный union без каста на каждой ветке
(`row.category === 'readd' ? 'device_inbox.readd' : 'device_inbox.add'`,
без `as any` вообще) — подтверждено чтением и тем, что `tsc --noEmit`
зелёный без ослаблений типов.
- `scripts/mutation-gate.mjs`: анchor `preflight-reason-lost-in-dialog`
ссылается на `src/houseplan-editor-runtime.ts` и строку с `as I18nKey`
(не `as any`), `guard`/`because` не изменены — подтверждено чтением
`scripts/mutation-gate.mjs:3254-3266` и зелёным
`node demo/smoke_preflight_diagnostics.mjs`.
- Generated bundle (класс D) синхронизирован и совпадает побайтово после
`npm run build && npm run bundle:sync` — `git status` пуст.
- Трейлеры всех трёх коммитов раунда (`3bbc65aa`, `7ae0f14d`, `c3f38654`)
корректны: `Issue: #391`, `User-Visible: no`; changelog оправданно не
тронут ни в одном.
- M1 закрыта проверяемо, не на слово: см. таблицу «Закрытие раунда r1».
## Унаследовано из r1
Документ: `docs/reviews/CODE-REVIEW-391-r1.md`, SHA `81a4278a`
(зафиксирован в шапке того документа; на момент этого раунда сам SHA
недостижим в дереве — ветка ушла дальше по истории, но диффу это не
мешает: содержимое сверено текстуально, см. раздел выше).
Принято без повторной проверки в этом раунде:
- построчный пересчёт всех 33 целевых `as any` до правки и подтверждение,
что ни один каст `tap.*`, `fill.*`, `decor.*`, `junction.*`, `vac.*`,
`run.*` и generic `message`/`hintKey`/`labelKey` не тронут — r1 сверял
это по полному диффу, который в r2 текстуально идентичен (см. «Что
проверено» выше), пересчитывать заново нечего;
- 7 «прямых совпадений» браузерных смоков
(`smoke_help_affordance`, `smoke_junction_limits`,
`smoke_optimize_coordinate_canonicalization`, `smoke_partition_openings`,
`smoke_room_resize`, `smoke_tap_ctx`,
`smoke_zero_wall_migration_unblocked`) — прогнаны в r1 (8/8 OK вместе с
guard-смоком), диффа, который их выбрал, в r2 не изменился;
- решение не гонять `npm run invariants`, `pytest tests_backend`,
`golden:verify` и полный набор из 208 смоков — основания (diff не
касается геометрии/`.py`/рендера, объём не сопоставим с полным набором)
не изменились между раундами;
- оценка двух «нечистых» вычисляемых ключей
(`marker.value_badge_attr_${source.attribute}`, `.replace()`/
`.replaceAll()`) как принятого риска, а не новой находки — тот же
профиль был под `as any` до задачи, ТЗ разрешает `as I18nKey` как
единственный доступный вариант.
## Чего не проверял и почему
- `npm run invariants` — diff не касается геометрии/модели.
- `python -m pytest tests_backend` — diff не касается
`custom_components/**/*.py`.
- `npm run golden:verify` — подтверждено самим `check-docs.mjs`
(сверка `imageSha256` с диска), что кадры не изменились; полный
golden-прогон был бы дороже без нового вопроса, на который надо
ответить.
- 7 из 8 браузерных смоков (кроме guard-смока) — не перегонял повторно,
унаследованы из r1 (см. раздел выше), диффа, который их выбрал, между
раундами не изменился.
- Полный набор из 208 браузерных смоков — не оправдано ни AC, ни объёмом
дельты между раундами.
- Performance-профили — не названы в AC, diff не касается чувствительных
к перфу путей.
- Ручное тестирование в браузере (UI) — не требуется: изменение не может
изменить скомпилированную семантику (стирание типовых кастов), докс-
манифест не меняет кадры (подтверждено `check-docs.mjs`).
## Вердикт
Единственная находка r1 (M1) закрыта проверяемо — `check-docs.mjs`
пересчитывает и фингерпринт, и хэш каждого сохранённого PNG, и оба
совпадают на этом SHA, а не только на слово автора. Ребейз на ушедший
вперёд `dev` не задел ни один файл задачи и не исказил ни одну правку
(проверено текстуальным сравнением). Все дешёвые гейты зелены и совпадают
с отчётом автора. Новых находок нет. Все AC из тела issue выполнены и
подтверждены тестом либо построчным чтением с явной пометкой «проверено
чтением, не исполнением». Вердикт — зелёный.
+2 -2
View File
@@ -3259,8 +3259,8 @@ const MUTANT_DEFINITIONS = [
+ '&& node demo/smoke_preflight_diagnostics.mjs',
because: 'отказ preflight без причины недиагностируем — ровно исходный дефект #295',
patches: [{
file: 'src/houseplan-card.ts',
find: " ${failure.displayName}: ${this._t(`gs.preflight_reason_${failure.reason}` as any)}",
file: 'src/houseplan-editor-runtime.ts',
find: " ${failure.displayName}: ${this.host._t(`gs.preflight_reason_${failure.reason}` as I18nKey)}",
replace: " ${failure.displayName}",
}],
},
+32 -32
View File
@@ -7746,7 +7746,7 @@ public async _setInboxHidden(row: DeviceInboxRow, hidden: boolean): Promise<void
this.host._deviceInboxMemo = null;
this.host._maybeRebuildDevices();
if (this.host._deviceInbox) this.host._deviceInbox = { ...this.host._deviceInbox, busy: undefined };
this.host._showToast(this.host._t('device_inbox.saved' as any));
this.host._showToast(this.host._t('device_inbox.saved'));
} catch (error: any) {
if (this.host._serverCfg === cfg) cfg.markers = previous;
if (this.host._deviceInbox) this.host._deviceInbox = { ...this.host._deviceInbox, busy: undefined };
@@ -9766,7 +9766,7 @@ public _renderAlignDialog(): TemplateResult {
? html`
<p class="alignmsg">${this.host._t('gs.align_preflight_failed', { spaces, more })}</p>
${failures.slice(0, 10).map((failure) => html`<p class="alignmsg">
${failure.displayName}: ${this.host._t(`gs.preflight_reason_${failure.reason}` as any)}
${failure.displayName}: ${this.host._t(`gs.preflight_reason_${failure.reason}` as I18nKey)}
</p>`)}
${failures.length > 10 ? html`<p class="alignmsg">
${this.host._t('gs.align_preflight_more', { n: String(failures.length - 10) })}
@@ -11772,8 +11772,8 @@ public _renderDevicesBar(): TemplateResult {
<ha-icon icon="mdi:plus-box-outline"></ha-icon>${this.host._t('devbar.add')}
</button>
<button class="btn ${this.host._showAll ? 'on' : ''}" @click=${() => this._openDeviceInbox()}
title=${this.host._t('device_inbox.title' as any)}>
<ha-icon icon="mdi:devices"></ha-icon>${this.host._t('device_inbox.button' as any)}
title=${this.host._t('device_inbox.title')}>
<ha-icon icon="mdi:devices"></ha-icon>${this.host._t('device_inbox.button')}
</button>
<button class="btn" @click=${() => this._openRulesDialog()} title=${this.host._t('title.icon_rules')}>
<ha-icon icon="mdi:shape-plus-outline"></ha-icon>${this.host._t('devbar.rules')}
@@ -11979,8 +11979,8 @@ public _renderDeviceInbox(): TemplateResult {
rows, dialog.tab, dialog.search, dialog.tab === 'on_plan' && dialog.onlyNew,
);
const visible = filtered.slice(0, dialog.limit);
const tabLabel = (tab: DeviceInboxCategory) => this.host._t(`device_inbox.tab_${tab}` as any);
const emptyKey = `device_inbox.empty_${dialog.tab}` as any;
const tabLabel = (tab: DeviceInboxCategory) => this.host._t(`device_inbox.tab_${tab}` as I18nKey);
const emptyKey = `device_inbox.empty_${dialog.tab}` as I18nKey;
const openVirtual = () => {
this.host._deviceInboxReturn = { ...dialog };
this.host._deviceInbox = null;
@@ -11988,18 +11988,18 @@ public _renderDeviceInbox(): TemplateResult {
if (!this.host._markerDialog) this._closeMarkerDialog();
};
return html`<hp-dialog class="device-inbox-dialog" .hass=${this.host.hass}
.title=${this.host._t('device_inbox.title' as any)} icon="mdi:devices" wide
.title=${this.host._t('device_inbox.title')} icon="mdi:devices" wide
@hp-close=${() => (this.host._deviceInbox = null)}>
<div class="device-inbox" ?inert=${!!dialog.busy}>
<div class="device-inbox-head">
<input class="device-inbox-search" type="search" autofocus
placeholder=${this.host._t('device_inbox.search' as any)} .value=${dialog.search}
placeholder=${this.host._t('device_inbox.search')} .value=${dialog.search}
@input=${(event: Event) => (this.host._deviceInbox = {
...dialog, search: (event.target as HTMLInputElement).value, limit: 100,
})} />
<button type="button" class="btn" @click=${openVirtual}>
<ha-icon icon="mdi:map-marker-plus-outline"></ha-icon>
${this.host._t('device_inbox.add_virtual' as any)}
${this.host._t('device_inbox.add_virtual')}
</button>
</div>
<div class="device-inbox-tabs" role="tablist" @keydown=${(event: KeyboardEvent) => this._deviceInboxTabKey(event)}>
@@ -12015,7 +12015,7 @@ public _renderDeviceInbox(): TemplateResult {
<input type="checkbox" .checked=${dialog.onlyNew}
@change=${(event: Event) => (this.host._deviceInbox = {
...dialog, onlyNew: (event.target as HTMLInputElement).checked, limit: 100,
})} />${this.host._t('device_inbox.only_new' as any)}
})} />${this.host._t('device_inbox.only_new')}
</label>` : nothing}
${dialog.tab === 'available' ? html`<label>
<input type="checkbox" .checked=${dialog.showEntities}
@@ -12024,7 +12024,7 @@ public _renderDeviceInbox(): TemplateResult {
this.host._deviceInbox = {
...dialog, showEntities: (event.target as HTMLInputElement).checked, limit: 100,
};
}} />${this.host._t('device_inbox.show_entities' as any)}
}} />${this.host._t('device_inbox.show_entities')}
</label>` : nothing}
<span class="device-inbox-filter-help">
<label>
@@ -12043,24 +12043,24 @@ public _renderDeviceInbox(): TemplateResult {
${visible.length ? visible.map((row) => {
const primary = row.category === 'on_plan'
? row.canFind ? html`<button type="button" class="btn" @click=${() => this._findInboxDevice(row)}>
<ha-icon icon="mdi:crosshairs-gps"></ha-icon>${this.host._t('device_inbox.find' as any)}</button>`
<ha-icon icon="mdi:crosshairs-gps"></ha-icon>${this.host._t('device_inbox.find')}</button>`
: html`<button type="button" class="btn" @click=${() => this._openInboxMarker(row)}
?disabled=${!row.canEdit}>${this.host._t('device_inbox.edit' as any)}</button>`
?disabled=${!row.canEdit}>${this.host._t('device_inbox.edit')}</button>`
: row.category === 'hidden'
? html`<button type="button" class="btn" @click=${() => this._setInboxHidden(row, false)}
title=${row.canShow ? '' : this.host._t('device_inbox.show_disabled' as any)}
?disabled=${!row.canShow}>${this.host._t('device_inbox.show' as any)}</button>`
title=${row.canShow ? '' : this.host._t('device_inbox.show_disabled')}
?disabled=${!row.canShow}>${this.host._t('device_inbox.show')}</button>`
: html`<button type="button" class="btn" @click=${() => this._openInboxMarker(row, true)}
?disabled=${!row.canAdd}>${this.host._t(row.category === 'readd'
? 'device_inbox.readd' as any : 'device_inbox.add' as any)}</button>`;
? 'device_inbox.readd' : 'device_inbox.add')}</button>`;
const status = row.status.kind === 'active' ? ''
: this.host._t(`device_inbox.status_${row.status.kind}` as any);
: this.host._t(`device_inbox.status_${row.status.kind}` as I18nKey);
return html`<article class="device-inbox-row" data-binding=${row.binding}
data-category=${row.category} data-status=${row.status.kind}>
<ha-icon class="device-inbox-icon" .icon=${row.icon}></ha-icon>
<div class="device-inbox-copy">
<div class="device-inbox-name"><b>${row.name}</b>
${row.isNew ? html`<span class="device-inbox-new">${this.host._t('device_inbox.new' as any)}</span>` : nothing}
${row.isNew ? html`<span class="device-inbox-new">${this.host._t('device_inbox.new')}</span>` : nothing}
</div>
<div class="device-inbox-meta">
${[row.model, row.integration, row.spaceName, row.areaName].filter(Boolean).join(' · ')}
@@ -12079,22 +12079,22 @@ public _renderDeviceInbox(): TemplateResult {
${row.canEdit || row.canHide || row.category === 'available'
|| row.category === 'hidden' || this.host._bindingHasHaPage(row.binding) ? html`
<details class="device-inbox-menu">
<summary class="btn ghost" aria-label=${this.host._t('device_inbox.more_actions' as any)}
title=${this.host._t('device_inbox.more_actions' as any)}>
<summary class="btn ghost" aria-label=${this.host._t('device_inbox.more_actions')}
title=${this.host._t('device_inbox.more_actions')}>
<ha-icon icon="mdi:dots-vertical"></ha-icon>
</summary>
<div class="device-inbox-menu-items">
${row.canEdit && !(row.category === 'on_plan' && !row.canFind)
? html`<button type="button" class="btn ghost" @click=${() => this._openInboxMarker(row)}>
${this.host._t('device_inbox.edit' as any)}</button>` : nothing}
${this.host._t('device_inbox.edit')}</button>` : nothing}
${row.canHide ? html`<button type="button" class="btn ghost"
@click=${() => this._setInboxHidden(row, true)}>${this.host._t('device_inbox.hide' as any)}</button>` : nothing}
@click=${() => this._setInboxHidden(row, true)}>${this.host._t('device_inbox.hide')}</button>` : nothing}
${row.category === 'available' ? html`<button type="button" class="btn ghost"
@click=${() => this._setInboxHidden(row, true)}>${this.host._t('device_inbox.hide_available' as any)}</button>` : nothing}
@click=${() => this._setInboxHidden(row, true)}>${this.host._t('device_inbox.hide_available')}</button>` : nothing}
${row.category === 'hidden' ? html`<button type="button" class="btn ghost"
title=${row.canFind ? '' : this.host._t('device_inbox.find_hidden_hint' as any)}
title=${row.canFind ? '' : this.host._t('device_inbox.find_hidden_hint')}
?disabled=${!row.canFind} @click=${() => this._findInboxDevice(row)}>
<ha-icon icon="mdi:crosshairs-gps"></ha-icon>${this.host._t('device_inbox.find' as any)}</button>` : nothing}
<ha-icon icon="mdi:crosshairs-gps"></ha-icon>${this.host._t('device_inbox.find')}</button>` : nothing}
${this.host._bindingHasHaPage(row.binding) ? html`<button type="button" class="btn ghost"
@click=${() => this.host._openBindingInHa(row.binding)}>${this.host._t('btn.open_in_ha')}</button>` : nothing}
</div>
@@ -12105,7 +12105,7 @@ public _renderDeviceInbox(): TemplateResult {
</div>
${filtered.length > visible.length ? html`<button type="button" class="btn device-inbox-more"
@click=${() => (this.host._deviceInbox = { ...dialog, limit: dialog.limit + 100 })}>
${this.host._t('device_inbox.show_more' as any)} (${filtered.length - visible.length})
${this.host._t('device_inbox.show_more')} (${filtered.length - visible.length})
</button>` : nothing}
</div>
<div slot="footer" class="row">
@@ -12252,9 +12252,9 @@ public _toggleConfirmationLines(intent: ResolvedToggleIntent): string[] {
public _toggleHintLines(intent: ResolvedToggleIntent | null): string[] {
if (!intent) return [];
const effect = (value: ToggleNextEffect): string =>
this.host._t((`marker.toggle_effect_${value.replace('-', '_')}`) as any);
this.host._t(`marker.toggle_effect_${value.replace('-', '_')}` as I18nKey);
const skipReason = (value: ToggleSkipReason): string =>
this.host._t((`marker.toggle_skip_${value.replace('-', '_')}`) as any);
this.host._t(`marker.toggle_skip_${value.replace('-', '_')}` as I18nKey);
return formatToggleIntent(intent, {
single: (target) => {
if ('via' in target && target.via === 'virtual-light') {
@@ -12291,7 +12291,7 @@ public _toggleHintLines(intent: ResolvedToggleIntent | null): string[] {
}).join(', '),
}),
none: (reason: ToggleNoneReason) =>
this.host._t((`marker.toggle_none_${reason.replaceAll('-', '_')}`) as any),
this.host._t(`marker.toggle_none_${reason.replaceAll('-', '_')}` as I18nKey),
});
}
@@ -12408,7 +12408,7 @@ public _valueBadgeCandidateLabel(candidate: ValueBadgeCandidate): string {
}
const entityName = this.host.hass.states[source.entity_id]?.attributes?.friendly_name
|| this.host._fullRegistryHass.entities[source.entity_id]?.name || source.entity_id;
return this.host._t((`marker.value_badge_attr_${source.attribute}`) as any, { name: entityName });
return this.host._t(`marker.value_badge_attr_${source.attribute}` as I18nKey, { name: entityName });
}
public _controlCandidates(d: NonNullable<HouseplanEditorHostPort['_markerDialog']>): {
@@ -12596,7 +12596,7 @@ public _renderMarkerDialog(): TemplateResult {
${bindingStatus?.kind === 'ha_disabled'
? html`<div class="habindingbanner" role="status">
<ha-icon icon="mdi:power-plug-off-outline"></ha-icon>
<span>${this.host._t((`marker.ha_disabled_${bindingStatus.reason}`) as any)}</span>
<span>${this.host._t(`marker.ha_disabled_${bindingStatus.reason}` as I18nKey)}</span>
${canOpenBindingInHa
? html`<button class="btn ghost" type="button" @click=${() => this.host._openBindingInHa(d.binding)}>
<ha-icon icon="mdi:open-in-new"></ha-icon>${this.host._t('btn.open_in_ha')}
@@ -13098,7 +13098,7 @@ public _renderMarkerDialog(): TemplateResult {
})}>
${(['right', 'bottom', 'left', 'top'] as const).map((position) => html`
<option value=${position} ?selected=${position === effectiveBadgePosition}>
${this.host._t((`marker.value_badge_${position}`) as any)}
${this.host._t(`marker.value_badge_${position}` as I18nKey)}
</option>`)}
</select>
` : nothing}