Compare commits

...
Author SHA1 Message Date
claude[bot] af46d2a9b4 docs: review document for #366
Issue: #366
User-Visible: no
2026-08-29 08:15:47 +00:00
Codex 51657831e6 fix: quantise the light aperture amount of moving covers (#366)
A gate or door bound to a position-reporting cover fed current_position
into the light-barrier signature at toFixed(3) precision: every percent of
movement produced a new fingerprint, a full physicalBodyParts recompute
and a recut — up to ~100 heavy passes per gate cycle, plus LRU churn.

The light pipeline now consumes one quantised amount
(OPENING_LIGHT_AMOUNT_QUANTUM = 0.05, exact 0 and 1 nodes) at the single
point that feeds BOTH the signature and the cut geometry, so the cache key
and the drawn aperture agree by construction and a full sweep costs at
most 21 recomputes. The door LEAF animation stays smooth — _openingAmt is
quantised only for the light pipeline, nowhere else. Binary contact doors
are byte-identical to the previous behaviour (pinned by unit).

Assumption recorded in the spec: the 5% visual step of the light cut is
indistinguishable on real plans; if field impressions disagree, the
quantum is a one-constant change (0.02 => <=51 recomputes) or the decision
falls back to a debounce. LIGHT.md §Caching documents the grid.

Issue: #366
User-Visible: yes
2026-08-29 11:05:06 +03:00
35 changed files with 542 additions and 310 deletions
@@ -1,113 +1,113 @@
{
"schema": 1,
"fingerprint": "2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a",
"fingerprint": "92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-z66sNkRH.js",
"houseplan-assets/houseplan-card-7JITXtyy.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 273551,
"initialViewGzipBytes": 273581,
"lazyFiles": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/de-nY6CPgRE.js",
"houseplan-assets/editor-Btuj24RH.js",
"houseplan-assets/houseplan-editor-runtime-CztWrxSd.js",
"houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/de-DMfFz19N.js",
"houseplan-assets/editor-36HAh3Je.js",
"houseplan-assets/houseplan-editor-runtime-BuN48ldr.js",
"houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js"
],
"lazyGzipBytes": 168764,
"lazyGzipBytes": 168780,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/editor-Btuj24RH.js",
"houseplan-assets/houseplan-editor-runtime-CztWrxSd.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/editor-36HAh3Je.js",
"houseplan-assets/houseplan-editor-runtime-BuN48ldr.js"
],
"lazyEditorGzipBytes": 139471,
"lazyEditorGzipBytes": 139483,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js"
],
"lazyOnboardingGzipBytes": 9453,
"lazyOnboardingGzipBytes": 9456,
"lazyLocaleFiles": [
"houseplan-assets/de-nY6CPgRE.js"
"houseplan-assets/de-DMfFz19N.js"
],
"lazyLocaleGzipBytes": 22796,
"lazyLocaleGzipBytes": 22799,
"files": [
{
"path": "houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"sha256": "db0d4c348fee148aff02781bfb2c1d0fa4afb50b7f7c7670768b748675f5389a",
"path": "houseplan-assets/backdrop-pick-B3oldFOv.js",
"sha256": "bca0256fc222b1c3f6045a97d7a773f499701c1b9b3dcc8498c40cdd26de403b",
"rawBytes": 6967,
"gzipBytes": 2956,
"gzipBytes": 2958,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-nY6CPgRE.js",
"sha256": "1ea533abc21c2d2585941122c21212deeec234adf5099ae8afce611bc3a6cd49",
"path": "houseplan-assets/de-DMfFz19N.js",
"sha256": "307982665ac112914972383bbed35da47ce8e917009daa792e29119dfbe263ec",
"rawBytes": 74836,
"gzipBytes": 22796,
"gzipBytes": 22799,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-Btuj24RH.js",
"sha256": "e462de3e6a34cce0b93fabf0804a430ce81bcfd2fe6c26e65557ec65504cd964",
"path": "houseplan-assets/editor-36HAh3Je.js",
"sha256": "c0a8bb4c6cd7868ae5ad78b8d2ecca80e9a449f1d3c03b5c0eadb20a791a8285",
"rawBytes": 3831,
"gzipBytes": 1585,
"gzipBytes": 1586,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-z66sNkRH.js",
"sha256": "33ee209a395a96a13e293ca1d48c2a5af686141b094bb92966d369f9dbcc8813",
"rawBytes": 963751,
"gzipBytes": 272797,
"path": "houseplan-assets/houseplan-card-7JITXtyy.js",
"sha256": "663801a4536057db0e907a740e0cc96d01feabc475a3e7a9b60567259aef7fb2",
"rawBytes": 963878,
"gzipBytes": 272826,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-nY6CPgRE.js",
"houseplan-assets/editor-Btuj24RH.js",
"houseplan-assets/houseplan-editor-runtime-CztWrxSd.js",
"houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js"
"houseplan-assets/de-DMfFz19N.js",
"houseplan-assets/editor-36HAh3Je.js",
"houseplan-assets/houseplan-editor-runtime-BuN48ldr.js",
"houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-CztWrxSd.js",
"sha256": "ac4ed4d1f057b7bae3ca52141d71beb81fd0ff971fc81e28d71073ad9bd5a5b2",
"path": "houseplan-assets/houseplan-editor-runtime-BuN48ldr.js",
"sha256": "e05365fb1ce4c1d97cacba2b243d07b9ecb4203d79596ba022096de3356eda5d",
"rawBytes": 522033,
"gzipBytes": 134930,
"gzipBytes": 134939,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js",
"sha256": "9094f910509b9881e8d7882a9ad1855e39610da15a2a163088af4d1e96c8687f",
"path": "houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js",
"sha256": "cf09b5cf51ed2220e4afd6b9523a66d013764eb0a68d1be1bbce8083fd5516d9",
"rawBytes": 26195,
"gzipBytes": 6497,
"gzipBytes": 6498,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "cdd74054b0eda637b8e8184c96291b395fd558678f8e55af673931615e01b085",
"sha256": "2c76779b5485023d77e090cee3a9a59a5c558c60e30c7b25ed0b3bb3e34fa05e",
"rawBytes": 1089,
"gzipBytes": 754,
"gzipBytes": 755,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-z66sNkRH.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__="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-7JITXtyy.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}
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-z66sNkRH.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-Tyu0BfFF.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";class F{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)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,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 $(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 v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_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 _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-7JITXtyy.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-B3oldFOv.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";class F{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)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,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 $(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 v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_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 _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
${s.map(s=>{return _`
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
<img src=${this.host._display(s.url)} alt="" loading="lazy" decoding="async" />
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";try{await import("./houseplan-assets/houseplan-card-z66sNkRH.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).":"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__="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";try{await import("./houseplan-assets/houseplan-card-7JITXtyy.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).":"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)}
+50 -50
View File
@@ -1,113 +1,113 @@
{
"schema": 1,
"fingerprint": "2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a",
"fingerprint": "92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-z66sNkRH.js",
"houseplan-assets/houseplan-card-7JITXtyy.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 273551,
"initialViewGzipBytes": 273581,
"lazyFiles": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/de-nY6CPgRE.js",
"houseplan-assets/editor-Btuj24RH.js",
"houseplan-assets/houseplan-editor-runtime-CztWrxSd.js",
"houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/de-DMfFz19N.js",
"houseplan-assets/editor-36HAh3Je.js",
"houseplan-assets/houseplan-editor-runtime-BuN48ldr.js",
"houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js"
],
"lazyGzipBytes": 168764,
"lazyGzipBytes": 168780,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/editor-Btuj24RH.js",
"houseplan-assets/houseplan-editor-runtime-CztWrxSd.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/editor-36HAh3Je.js",
"houseplan-assets/houseplan-editor-runtime-BuN48ldr.js"
],
"lazyEditorGzipBytes": 139471,
"lazyEditorGzipBytes": 139483,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js"
],
"lazyOnboardingGzipBytes": 9453,
"lazyOnboardingGzipBytes": 9456,
"lazyLocaleFiles": [
"houseplan-assets/de-nY6CPgRE.js"
"houseplan-assets/de-DMfFz19N.js"
],
"lazyLocaleGzipBytes": 22796,
"lazyLocaleGzipBytes": 22799,
"files": [
{
"path": "houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"sha256": "db0d4c348fee148aff02781bfb2c1d0fa4afb50b7f7c7670768b748675f5389a",
"path": "houseplan-assets/backdrop-pick-B3oldFOv.js",
"sha256": "bca0256fc222b1c3f6045a97d7a773f499701c1b9b3dcc8498c40cdd26de403b",
"rawBytes": 6967,
"gzipBytes": 2956,
"gzipBytes": 2958,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-nY6CPgRE.js",
"sha256": "1ea533abc21c2d2585941122c21212deeec234adf5099ae8afce611bc3a6cd49",
"path": "houseplan-assets/de-DMfFz19N.js",
"sha256": "307982665ac112914972383bbed35da47ce8e917009daa792e29119dfbe263ec",
"rawBytes": 74836,
"gzipBytes": 22796,
"gzipBytes": 22799,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-Btuj24RH.js",
"sha256": "e462de3e6a34cce0b93fabf0804a430ce81bcfd2fe6c26e65557ec65504cd964",
"path": "houseplan-assets/editor-36HAh3Je.js",
"sha256": "c0a8bb4c6cd7868ae5ad78b8d2ecca80e9a449f1d3c03b5c0eadb20a791a8285",
"rawBytes": 3831,
"gzipBytes": 1585,
"gzipBytes": 1586,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-z66sNkRH.js",
"sha256": "33ee209a395a96a13e293ca1d48c2a5af686141b094bb92966d369f9dbcc8813",
"rawBytes": 963751,
"gzipBytes": 272797,
"path": "houseplan-assets/houseplan-card-7JITXtyy.js",
"sha256": "663801a4536057db0e907a740e0cc96d01feabc475a3e7a9b60567259aef7fb2",
"rawBytes": 963878,
"gzipBytes": 272826,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-nY6CPgRE.js",
"houseplan-assets/editor-Btuj24RH.js",
"houseplan-assets/houseplan-editor-runtime-CztWrxSd.js",
"houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js"
"houseplan-assets/de-DMfFz19N.js",
"houseplan-assets/editor-36HAh3Je.js",
"houseplan-assets/houseplan-editor-runtime-BuN48ldr.js",
"houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-CztWrxSd.js",
"sha256": "ac4ed4d1f057b7bae3ca52141d71beb81fd0ff971fc81e28d71073ad9bd5a5b2",
"path": "houseplan-assets/houseplan-editor-runtime-BuN48ldr.js",
"sha256": "e05365fb1ce4c1d97cacba2b243d07b9ecb4203d79596ba022096de3356eda5d",
"rawBytes": 522033,
"gzipBytes": 134930,
"gzipBytes": 134939,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-G4p88P9H.js",
"sha256": "9094f910509b9881e8d7882a9ad1855e39610da15a2a163088af4d1e96c8687f",
"path": "houseplan-assets/houseplan-onboarding-runtime-DB_uoFX7.js",
"sha256": "cf09b5cf51ed2220e4afd6b9523a66d013764eb0a68d1be1bbce8083fd5516d9",
"rawBytes": 26195,
"gzipBytes": 6497,
"gzipBytes": 6498,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-Tyu0BfFF.js",
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/backdrop-pick-B3oldFOv.js",
"houseplan-assets/houseplan-card-7JITXtyy.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "cdd74054b0eda637b8e8184c96291b395fd558678f8e55af673931615e01b085",
"sha256": "2c76779b5485023d77e090cee3a9a59a5c558c60e30c7b25ed0b3bb3e34fa05e",
"rawBytes": 1089,
"gzipBytes": 754,
"gzipBytes": 755,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-z66sNkRH.js"
"houseplan-assets/houseplan-card-7JITXtyy.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__="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-z66sNkRH.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__="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-7JITXtyy.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__="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-z66sNkRH.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-Tyu0BfFF.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";class F{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)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,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 $(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 v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_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 _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as c,b_ as n,b$ as r,A as p,b as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-7JITXtyy.js";import{i as b,c as $,e as v,r as f,a as y,b as w,s as D,t as S}from"./backdrop-pick-B3oldFOv.js";const k=1e3,C=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},M="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";class F{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)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),c=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,n="none"===h.fill?{...c||e,a:0}:c;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:n,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:n,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:c,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,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 $(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 v(e,o.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:a})}_renderBackdropGuard(){return f(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??p}_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 _`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return _`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return _`<div class="savedplans">
${s.map(s=>{return _`
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
<img src=${this.host._display(s.url)} alt="" loading="lazy" decoding="async" />
+1 -1
View File
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="2f53621bc0d0f8ce3e04153700c0c17286b37d4dae42519acf74d6023333442a";try{await import("./houseplan-assets/houseplan-card-z66sNkRH.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).":"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__="92b6c0d37be41565c819aabf24176ece7969b13a5fd56613d47138f26080f773";try{await import("./houseplan-assets/houseplan-card-7JITXtyy.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).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale entry: the main chunk is unavailable",e)}
+6
View File
@@ -2,6 +2,12 @@
## Unreleased
- A gate or door bound to a position-reporting cover no longer stutters the
whole card while it moves: the light cut through the opening now steps on a
5% grid, cutting the heavy geometry recomputes from about a hundred per
cycle to at most twenty
([#366](https://github.com/Matysh/houseplan-card/issues/366)).
- Furniture outline width now follows plan zoom like the same physical width
on lines and shapes, while independent width/depth resizing still keeps
horizontal, vertical and diagonal strokes equally thick
+5
View File
@@ -8,6 +8,11 @@
## Не выпущено
- Ворота или дверь на cover-сущности с позицией больше не дёргают карточку
во время движения: световой вырез проёма ступает по сетке 5%, и тяжёлых
пересчётов геометрии вместо ~сотни за цикл — не больше двадцати
([#366](https://github.com/Matysh/houseplan-card/issues/366)).
- Толщина контура мебели теперь меняется вместе с масштабом плана, как та же
физическая толщина у линий и фигур, а независимое изменение ширины и глубины
предмета по-прежнему не искажает горизонтальные, вертикальные и диагональные
+4 -1
View File
@@ -139,7 +139,10 @@ pass over the layer costs a fifth of one blurred mask per source.
## Caching
Barriers are keyed by their geometry fingerprint plus a compact, sorted
signature of bound interior door/gate opening amounts, never by `_cfgEpoch`:
signature of bound interior door/gate opening amounts (quantised to the
0.05 grid of `OPENING_LIGHT_AMOUNT_QUANTUM`, #366 — a moving cover steps the
light in 5% increments instead of forcing a recompute per percent), never by
`_cfgEpoch`:
the epoch lags behind geometry edited in place, and a stale barrier set is
invisible — the plan simply keeps lighting through a wall or closed door that
now exists. Unrelated HA updates preserve the signature and hit the bounded
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 141 KiB

+21 -21
View File
@@ -3,7 +3,7 @@
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"oxipng": null,
"sourceFingerprint": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"sourceFingerprint": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"captureScriptSha256": "fe76c8e2e27501e9a377d7aa15dd6cba6a018d55c07deb602dbee097f0a0a5ba",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -15,8 +15,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "0ca982495a7749907c48ebbddfd089c54385a4347f79f64e94af2c5a8d5c03eb"
},
"view-touch": {
"file": "02-view-touch.png",
@@ -26,8 +26,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "a4aa3af48578ee41594a9b1a6e98b4d39f3324a949a9fd9e63d5854576d5db85"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "3be239a5a494f6776540a9c0049694ee6f9d2ba927c8bcb4b26ee5a69a228a7d"
},
"space-create": {
"file": "03-space-create.png",
@@ -37,8 +37,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "0dc024726327f60f4a9ceaf3044381691f81f1090af81d1812e870f22d9343ba"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "a55b463055a565892ca139014e314a8f63fb5301c87fff2e48d7e69210fe9b6c"
},
"room-contour-close": {
"file": "04-room-contour-close.png",
@@ -48,8 +48,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "44cfaa95bd51e2cd628400e28db0ad8b2f0cd904385845bd402494f3f5c0d93c"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "dfad9a27371cfa397a3fd8dce7005f42d24649743f5322105ee894067d3a9f16"
},
"plan-context-tray": {
"file": "05-plan-context-tray.png",
@@ -59,8 +59,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "e0662429b423fb74151b583dcc2c8635b001b637d03bbf7a6b16aec46399c3f8"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "b75f348c632296da5c2fc142d53df613f757e1516acac315be50f5b4557729cc"
},
"device-editor": {
"file": "06-device-editor.png",
@@ -70,8 +70,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "2f861b3e0f3e09cbe462873887fc6a4412940393d37d782bcd0be58c9e82879e"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "8affadfce926fe2f6cf344c33f611c44fcbc79c1969f69c3a6935ff2e99c36c3"
},
"device-display-preview": {
"file": "06-device-display-preview.png",
@@ -81,8 +81,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "6d323b6f641b2cc7db64252f70567348bd3f2551f82e61fd3a2b4674d244918e"
},
"background-editor": {
"file": "07-background-editor.png",
@@ -92,8 +92,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "a1d1a905f1410f73231d2ea5d1dfbd8ae79c2fe8b0a79cff87ebb977ac7a3103"
},
"room-card": {
"file": "08-room-card.png",
@@ -103,8 +103,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "48a5685039a5e0de2a28190d857b193c86e2e63606e3c7a8c40fc9d2b190da4e"
},
"device-info": {
"file": "09-device-info.png",
@@ -114,8 +114,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "efd6edaeba9496509727bec13d19495b799339bdfe2176c791d3abcee83f45a5",
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
"sourceSha256": "d02374a4194e4601cb0a2047dc556f70b9fdf354894bdfeea9288d74c14742d8",
"imageSha256": "29a342811aaedaebc54ba337792d60cb454a7cf4c3063b210f6c4bafe0a4d87b"
}
}
}
+151
View File
@@ -0,0 +1,151 @@
# CODE-REVIEW-366-r1
Issue: #366 · «Glow #20: движущиеся ворота на cover-сущности вызывают до ~100
пересчётов геометрии света за одно открытие»
Этап: code · заход r1 · блокирующих циклов 0/2 (light track — бюджет 2 цикла)
SHA ревью: `51657831e6c0a00336a099795943b9f002f1455f` (HEAD ветки
`issue/366-cover-glow-quantum`, диапазон `origin/dev...HEAD`)
ТЗ: тело issue #366, ревизия 2 (принята SPEC-REVIEW-366-r2, зелёный вердикт,
0 Medium)
## Скоуп
Один коммит, класс A+B+C+D:
- `src/logic.ts` — новая константа `OPENING_LIGHT_AMOUNT_QUANTUM = 0.05` и
чистая функция `quantizeOpeningLightAmount`.
- `src/houseplan-card.ts` — импорт функции и её применение в **единственной**
точке сборки `passageStates` (:10381), которая питает и сигнатуру барьеров
(:10383-10387), и длину светового выреза (:10408, :10420-10423).
- `test/logic.test.mjs` — новый юнит на границы/зажимы/узлы + свип 0..1 через
`openingLightStateSignature`.
- `scripts/mutation-gate.mjs` — новый мутант `opening-light-quantum-identity`
(квант → identity), guard `--test-name-pattern="#366"`.
- `docs/LIGHT.md` §Caching — упоминание сетки 0.05.
- `docs/CHANGELOG.md` / `docs/CHANGELOG.ru.md` — запись в `## Unreleased`
в том же коммите, `User-Visible: yes` соблюдён.
- `docs/images/*` + `screenshots.json` — пересчитанный `sourceFingerprint`
из-за правки `src/**` (механический след check-docs, не отдельная работа).
- `dist/**`, `custom_components/houseplan/frontend/**` — синхронный бандл.
Никакой геометрии комнат/стен, `layout`, `marker.space`, `open_spans` diff не
трогает — инварианты модели (#254) на этот PR не применимы.
## Как проверялось
Прогнано лично на этом SHA (зелёного Validate на SHA `51657831` нет):
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | `npm run typecheck` | зелёный, без вывода |
| Юниты | `npm test` | `1534/0`, `skipped 1` — совпадает с заявленным в комментарии автора |
| Целевой юнит | `node --test --test-name-pattern="#366" test/logic.test.mjs` | 1/1 зелёный |
| Мутант (умеет падать) | `node scripts/mutation-gate.mjs --id=opening-light-quantum-identity` | `тест покраснел, как обязан` — AC5 подтверждён исполнением, не начитыванием |
| Сборка | `npm run build` | зелёная |
| Синхронность бандла | `npm run bundle:sync` затем `git status --porcelain` | пусто — `dist`, `custom_components/houseplan/frontend`, `demo/srv/assets` совпадают байт-в-байт с уже закоммиченным |
| Документация | `node scripts/check-docs.mjs` | `Documentation checks passed (7 files, 10 external links)` — обязателен, т.к. diff трогает `src/**` |
| Выбор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «Прямое совпадение (2)»: `smoke_opening_binding.mjs`, `smoke_registryless_opening.mjs` (символ `_openingAmt`) |
| Смок (AC4, названный автором) | `node demo/smoke_glow.mjs` | зелёный, все проверки `true`/`OK` |
| Смок (прямое совпадение) | `node demo/smoke_opening_binding.mjs` | зелёный |
| Смок (прямое совпадение) | `node demo/smoke_registryless_opening.mjs` | зелёный |
| Видимый рендер | `npm run golden:verify` (после свежего build+bundle:sync) | все сценарии `passed`, 0 упоминаний fail; включая `lighting-opaque-glow-two-doorways-dark`, где `cover.golden_door_half` стоит на `current_position: 50` — точный узел сетки 0.05, изображение не сдвинулось |
| Single-source-numbers | `node --test test/single-source-numbers.test.mjs` | зелёный (не относится напрямую к правке, но затронутое число — amount, видимое дважды — проверено ниже отдельно) |
Не прогонял и почему:
- **Полный набор `demo/smoke_*.mjs`** — smoke-select не назвал ничего сверх
двух прямых совпадений плюс явно названный автором `smoke_glow.mjs`; diff —
одна точка применения чистой функции, не новая подсистема. Все три
релевантных смока зелёные.
- **`npm run invariants`** — diff не трогает рёбра/толщину/layout/ссылки.
- **`python -m pytest tests_backend`** — diff не трогает `custom_components/**/*.py`
(единственный неродственный провал в моей среде — `backend-test-guard.mjs`
внутри полного прогона mutation-gate, `No module named pytest`; это
окружение без backend-venv, не относится к этой задаче, диагностировано и
отброшено).
- **`performance_smoke`** — не назван в AC, задача не меняет измеряемый в нём
путь напрямую (только уменьшает частоту его срабатывания); предрелизный
гейт прогонит его на полном наборе.
- Полный `node scripts/mutation-gate.mjs` (без `--id=`) не гонял целиком —
дорогой предрелизный гейт; целевой мутант прогнан отдельно и это то, что
доказывает AC5.
## Находки
Ни одной. High: 0, Medium: 0.
## Что проверено и корректно
**К1 (один квант, один источник).** `quantizeOpeningLightAmount` — чистая
функция: `NaN`→0 (через `Number.isFinite` guard), зажим в `[0,1]` до и после
округления, округление к ближайшему узлу сетки 0.05. 0 и 1 — точные узлы
арифметически (`0/0.05=0`, `1/0.05=20` — целые), не требуют отдельного кейса.
Применена **ровно в одной** точке — `houseplan-card.ts:10381`, при сборке
`passageStates`; это единственный производитель, который читает и сигнатура
(:10383-10387), и вырез (:10408 `openingLightApertureLength`, :10420-10423
`lightPartitionCuts`) — по построению не могут разойтись (single-source
принцип соблюдён: amount виден дважды — в сигнатуре кэша и в геометрии, — но
у обоих ровно один источник, сама переменная `amount` в замыкании
`flatMap`). Другие потребители `_openingAmt` — `:8835` (изометрия),
`:12494`, `:12615` (рендер створки/подписи) — не квантуются, что и требовал
контракт («анимация створки остаётся плавной»); проверено чтением всех
вызовов `_openingAmt` в файле (4 сайта, один изменён).
**К2 (≤21 сигнатура).** Математически: узлов сетки 0.05 на `[0,1]` ровно 21
(0, 0.05, …, 1.00). Юнит-свип шагом 0.001 подтверждает исполнением, не
только по построению.
**AC1** — юнит на границы/зажимы/узлы — присутствует и зелёный, воспроизвёл
локально.
**AC2** — свип-юнит на ≤21 сигнатуру — присутствует, зелёный, воспроизвёл;
дополнительно проверил, что мутант «квант→identity» красит именно этот
ассерт (`тест покраснел, как обязан`).
**AC3** (binary-двери байт-в-байт) — юнит фиксирует точные строки
`door:0.000` / `door:1.000` и то, что `quantizeOpeningLightAmount(1)` даёт ту
же сигнатуру, что и неквантованная `1`. Формат сигнатуры (`toFixed(3)`,
`logic.ts:371`) не менялся.
**AC4** (существующие смоки/golden #20 зелёные без правки ассертов) —
`smoke_glow.mjs`, `golden:verify` (полный набор, включая сценарий с cover на
50%) зелёные; ни один ассерт в этих файлах не тронут diff'ом (проверено —
diff не касается `demo/**` кроме `mutation-gate.mjs`).
**AC5** (мутант красит юнит) — прогнан лично, поймано 1/1.
**Трейлеры и changelog.** `Issue: #366`, `User-Visible: yes` на коммите;
`docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` правлены в этом же коммите —
требование §Commits соблюдено. Формулировки на пользовательском языке («не
дёргают карточку», «ступает по сетке 5%») без утечки реализации.
**docs/LIGHT.md** — Low из спек-ревью (r1-L2/r2-L2′: сетка квантования
должна попасть в LIGHT.md тем же коммитом) закрыт: §Caching описывает сетку
0.05 и ссылку на #366.
**Продуктовая рамка.** Правка обслуживает J1 (живой обзор состояния дома) —
устраняет визуальные подёргивания всей карточки на wall-планшете во время
движения ворот, что прямо противоречит "at a glance" ценности продукта.
Скоуп не расширен: чистая квантовая функция в одной точке конвейера, ничего
геометрического или конфигурационного не тронуто. Решение (квант 0.05, а не
debounce) — принятое предположение спек-ревью r1-M2, с ценой отката в одну
константу; реализация не отступает от контракта ТЗ.
## Чего не проверял
- Ручного тестирования на реальном wall-планшете нет (не входит в цикл ревью);
визуальная неразличимость шага 5% принята как предположение ещё на этапе
спеки (r1-M2), не переоценивается здесь заново — это продуктовый вопрос,
а не технический.
- Не мерил фактическое число пересчётов на живом cover со стрим-обновлениями
позиции (нет интеграционного стенда HA) — К2 доказан математически и
юнит-свипом, что признано в ТЗ достаточным доказательством (юнит вместо
browser-профиля).
- Полный `demo/smoke_*.mjs` (201 файл) и `performance_smoke` — не прогонял,
обоснование выше в разделе «Как проверялось».
- `tests_backend` — не прогонял, diff не трогает Python.
## Вердикт
Зелёный. Технический контракт реализован ровно как специфицирован в ревизии
2 ТЗ, все AC доказаны исполнением (не только чтением), затронутые смоки и
golden зелёные, трейлеры и changelog на месте, бандл синхронен.
+12
View File
@@ -680,6 +680,18 @@ const MUTANT_DEFINITIONS = [
replace: ' this._languageFailureUnsub = undefined;',
}],
},
{
id: 'opening-light-quantum-identity',
guard: 'node --test --test-name-pattern="#366" test/logic.test.mjs',
because: 'an identity quantum brings back ~100 barrier recomputes per moving-gate cycle — '
+ 'the sweep unit must bound distinct signatures to the 0.05 grid (#366)',
patches: [{
file: 'src/logic.ts',
find: ' return Math.min(1, Math.max(0,\n'
+ ' Math.round(safe / OPENING_LIGHT_AMOUNT_QUANTUM) * OPENING_LIGHT_AMOUNT_QUANTUM));',
replace: ' return safe;',
}],
},
{
id: 'backdrop-probe-always-safe',
guard: 'node demo/smoke_backdrop_guard.mjs',
+4 -1
View File
@@ -22,6 +22,7 @@ import {
pointOnBoundary, mergeRooms, splitRoomPath, polygonArea, closestPointOnBoundary, pointStrictlyInside as ptInside, islandsOf, sharedBoundary, distToSegment, outlineWithout, cutSegments, alignGuides, segmentAngle, is45, isExact45Vector, type AlignGuide, swipeTarget, clampScale, migratePdfUrls, roomFillModeOf, roomGlowOf, contentUrl,
snapToWall, snapPointAlongPoly, openingAmount, openingShoulders, interiorPoint,
isInteriorLightOpeningType, openingLightApertureLength, openingLightStateSignature,
quantizeOpeningLightAmount,
openingEntityReferences, filterOpeningEntityCandidates,
poleOfInaccessibility, subst,
averageLqi, fitView, declump, safeUrl, floorsOf, type FloorInfo,
@@ -10375,7 +10376,9 @@ export class HouseplanCard extends LitElement {
const ny = Math.cos(rad);
const interior = onFloor([o.rx + nx * probe, o.ry + ny * probe])
&& onFloor([o.rx - nx * probe, o.ry - ny * probe]);
return interior ? [{ opening: o, amount: this._openingAmt(o) }] : [];
// #366: the light pipeline consumes the quantised amount — the door
// LEAF animation stays smooth (_openingAmt elsewhere), only light steps.
return interior ? [{ opening: o, amount: quantizeOpeningLightAmount(this._openingAmt(o)) }] : [];
});
const openingStateSignature = openingLightStateSignature(
passageStates.map(({ opening, amount }) => ({
+16
View File
@@ -342,6 +342,22 @@ export interface OpeningLightStateEntry {
amount: number;
}
/**
* #366: one grid for the light pipeline's aperture amount. A moving cover
* publishes current_position by the percent; unquantised it forces a full
* barrier recompute per tick (~100 per gate cycle). Both the signature AND
* the cut geometry consume the same quantised value, so the cache key and
* the drawn aperture agree by construction. 0 and 1 are exact grid nodes —
* binary contact doors are byte-identical to the pre-#366 behaviour.
*/
export const OPENING_LIGHT_AMOUNT_QUANTUM = 0.05;
export function quantizeOpeningLightAmount(amount: number): number {
const safe = Number.isFinite(amount) ? Math.max(0, Math.min(1, amount)) : 0;
return Math.min(1, Math.max(0,
Math.round(safe / OPENING_LIGHT_AMOUNT_QUANTUM) * OPENING_LIGHT_AMOUNT_QUANTUM));
}
/** State-only identity for interior light apertures. Structural geometry is
* fingerprinted separately, so unrelated HA updates cannot evict barriers. */
export function openingLightStateSignature(
+37 -1
View File
@@ -24,7 +24,9 @@ import {
normalizeDeviceDisplay, isAlarmCapable,
liveText, liveTextValue, liveTextReference, liveTextToken,
hassValue, valueWithUnit, decorTextScale, decorTextLines,
LIVE_TEXT_DASH, LIVE_TEXT_VALUE_MAX, DECOR_TEXT_SCALE_MIN, DECOR_TEXT_SCALE_MAX } from '../test-build/logic.js';
LIVE_TEXT_DASH, LIVE_TEXT_VALUE_MAX, DECOR_TEXT_SCALE_MIN, DECOR_TEXT_SCALE_MAX,
quantizeOpeningLightAmount,
} from '../test-build/logic.js';
import {
iconFor, compileIconRules, isValidPattern, iconFromDeviceClasses,
} from '../test-build/rules.js';
@@ -1530,3 +1532,37 @@ test('liveTextValue: a formatter that omits the unit still gets one (the demo st
assert.equal(liveTextValue(h, { entity: 'sensor.tank' }), '68 %',
'formatEntityState returning a bare state must not cost the label its unit');
});
test('квант amount света: границы, узлы и ≤21 сигнатура за полный ход (#366)', () => {
// AC1: точные узлы и зажимы
assert.equal(quantizeOpeningLightAmount(0), 0);
assert.equal(quantizeOpeningLightAmount(1), 1);
assert.equal(quantizeOpeningLightAmount(NaN), 0);
assert.equal(quantizeOpeningLightAmount(-1), 0);
assert.equal(quantizeOpeningLightAmount(2), 1);
assert.equal(quantizeOpeningLightAmount(0.30), quantizeOpeningLightAmount(0.31), 'один узел');
assert.notEqual(quantizeOpeningLightAmount(0.30), quantizeOpeningLightAmount(0.33), 'разные узлы');
// AC2: свип 0..1 шагом 0.001 через сигнатуру — не больше 21 различной
const signatures = new Set();
for (let step = 0; step <= 1000; step++) {
const amount = quantizeOpeningLightAmount(step / 1000);
signatures.add(openingLightStateSignature([
{ id: 'gate', type: 'gate', contact: 'cover.gate', amount },
]));
}
assert.ok(signatures.size <= 21, `сигнатур ${signatures.size}, ожидалось ≤21`);
// конечные положения совпадают с неквантованными (binary-регресс, AC3)
const closed = openingLightStateSignature([
{ id: 'door', type: 'door', contact: 'binary_sensor.d', amount: 0 },
]);
const open = openingLightStateSignature([
{ id: 'door', type: 'door', contact: 'binary_sensor.d', amount: 1 },
]);
assert.equal(closed, 'door:0.000');
assert.equal(open, 'door:1.000');
assert.equal(openingLightStateSignature([
{ id: 'door', type: 'door', contact: 'binary_sensor.d', amount: quantizeOpeningLightAmount(1) },
]), open, 'квант единицы байт-в-байт');
});