Files
houseplan-card/dist/houseplan-assets/editor-CSFqqHwT.js
T
Claudeandclaude[bot] 9edef041a9 fix: openings and markers keep their identity across a space switch (#525)
Lit reuses list nodes by position. The opening list and the device markers had
no keys, so on a space switch the leaf that held a slot kept its DOM node and
only changed values — and `.op-leaf` (transform) and `.op-arc`
(stroke-dashoffset) carry a 0.6 s transition, so the browser animated a door
that never moved: the new floor's leaf drove in from the previous floor's
opening angle. Measured on two spaces with a door in the same place and
opposite contact states: the node is reused, transform goes
`rotate(-90deg) → rotate(0deg)`, dash offset `0 → 125.66`, both transitions
`running`. The marker shell adds two more with its `box-shadow`.

Both lists are now rendered through `repeat(…, (item) => item.id, …)`, the
same lesson `glow-scene.ts` already learned for the Glow spots. The trap is
written where it starts — above the two transitions in `plan.styles.ts` —
because that is the file someone edits when adding the next animated property.
`houseplan-card.ts` is at its line ceiling, and the note would have cost the
budget a dozen lines for nothing: the swap itself is line-for-line.

The witness walks the shadow tree per element. `document.getAnimations()` is
empty here EVEN ON THE BROKEN CODE — the card lives in a shadow root and the
document-level call does not reach into it, and the issue proposed exactly
that call. The smoke also builds its own fixture: the demo home has no
openings at all, so two doors in two spaces are prepared in the smoke, and it
asserts the other half of the contract as well — a real contact change inside
one space still animates the leaf.

On `origin/dev` the smoke fails on five facts, naming the offenders:
`op-arc:stroke-dashoffset`, `op-leaf:transform`, `device-shell-frame:box-shadow`
twice. Mutants `openings-rendered-without-keys` and
`device-markers-rendered-without-keys` put each `map` back.

Perf, 7 samples against `19e421b3`: spaceSwitchMs 524.8 (limit 769.35, base
512.9), switchCycleMs 1293.9 (1696.28, 1256.5), firstStableRenderMs 2533.8
(3000, 2529.2), modelReadyMs 732.7 (944.97, 726.9), longTask.maxSingleMs 663
(910, 660) — `benchmark:compare` green in full.

The initial View graph grows 241 B gzip: `repeat` enters it for the first
time. The #438 ceiling is recentred 300 400 → 300 700 with the usual dated
note; measured 300 059 B keeps 641 B above and 1 359 B below the band. The
301 066 B budget is untouched, but only 366 B now separate the ceiling from
it — the #367 headroom debt has stopped being theoretical.

Issue: #525
User-Visible: yes
2026-09-11 01:42:46 +00:00

15 lines
3.7 KiB
JavaScript

globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="7d8c26b95986499fd35534164d8e2bc2e897f0fbe9d846bccffa49f45116a618";import{m as e,l as o,t,e3 as s,A as a,e4 as i,e5 as l,e6 as n,dU as r,b as c}from"./houseplan-card-DkiI2BOK.js";class f extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,f={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},h=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
.computeLabel=${e=>f[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(h.slice(0,3))}
${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a}
${d(h.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);