mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 06:38:57 +00:00
#451 moved every editor gesture onto the live painter, and the guides stayed behind in the settled scene. While a gesture runs, the settled scene is not re-rendered at all, so the guides did not follow the marker in the device editor, the shape in the backdrop editor, or the cursor while a contour is drawn in the plan editor. Measured with real pointer events on the demo stand against `origin/dev`, after waiting for the editor chrome to settle: three gestures, each exactly on another object's axis, 0 settled render cycles, `.alignline` 0 and no `.alignguides` group in all three. The report called it two breaks. It is one — the layer — plus one thing that would have broken the repair: `_alignPoint` read `_pos`, which during a live gesture answers from the snapshot of the last settled render. Over one drag: live 254.17 → 220.83 while `_pos` stayed at 254.17, eight grid steps behind, so a restored layer would have drawn the guide at the marker's old place. The live template now paints the guides in all three modes (the device editor had no template at all — `paintDevice` only moves the marker element), and `_alignPoint` takes the live position. The settled copy of `.hp-editor-only-layer` is made transparent for the duration of any editor gesture, not only in plan mode: two guides, one of them stale, is what the user would otherwise see when an unrelated settled render lands mid-gesture. `_renderAlignGuides` on the card becomes soft — a gesture that starts while the editor runtime is still loading must cost nothing, and an exception inside a `requestAnimationFrame` paint would take the whole gesture with it. The witness is rewritten around the defect that hid this for two stable releases: the old smoke assigned `_deviceDrag`/`_decorDraft` wholesale, and an assignment with `oldValue == null` does not route to the live path — it verified a state a real gesture never reaches. Every scenario now drives real `PointerEvent`s, waits for silence first (the `_hdrH` settling window right after entering a mode hands out settled frames that make even the broken code draw a guide), and asserts zero settled cycles during the movements plus exactly one `.alignguides` group. #400's exclusion is checked without touching the drag state: the dragged marker must simply be absent from the candidates. On `origin/dev` the smoke fails on nine of its facts; a witness that stays green before the fix was the actual bug here. Mutants: `live-editor-devices-drops-align-guides`, `live-editor-decor-drops-align-guides`, `live-editor-plan-drops-align-guides`, `align-point-reads-frozen-snapshot` — one per AC, all guarded by the smoke. `test/smoke-harness-contract.test.mjs` pins that the smoke cannot go back to fabricating gesture state. Issue: #521 User-Visible: yes
2 lines
6.0 KiB
JavaScript
2 lines
6.0 KiB
JavaScript
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="f05c68a63b313b206793c56f6e16b8ca3dc833f2a702373251b1119db27d7f4c";try{await import("./houseplan-card.js")}catch(e){if(!customElements.get("houseplan-panel")){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-panel",class extends HTMLElement{connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;min-height:100%;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 houseplan-panel.js: the card entry is unavailable",e)}const e=["panel","hass","narrow","route"];class n extends HTMLElement{constructor(){super(),this._narrow=!1,this.attachShadow({mode:"open"}),this._adoptPreUpgradeProperties()}connectedCallback(){this._adoptPreUpgradeProperties(),this._ensureShell()}_adoptPreUpgradeProperties(){for(const n of e){if(!Object.prototype.hasOwnProperty.call(this,n))continue;const e=this[n];delete this[n],this[n]=e}}get hass(){return this._hass}set hass(e){this._hass=e,this._ensureShell(),this._card&&(this._card.hass=e),this._syncMenuLabel()}get narrow(){return this._narrow}set narrow(e){this._narrow=Boolean(e),this.toggleAttribute("narrow",this._narrow),this._card&&(this._card.narrow=this._narrow)}get route(){return this._route}set route(e){this._route=e}get panel(){return this._panel}set panel(e){this._panel=e}_ensureShell(){if(this._card||!this.shadowRoot)return;const e=document.createElement("style");e.textContent="\n :host {\n display: block;\n width: 100%;\n /* HA mounts a custom panel inside <ha-panel-custom>, a block with\n * safe-area padding and NO height, so a percentage here resolves to\n * auto and the stage collapses to 0 px (#488). Take the viewport\n * like HA's own iframe panels do, minus the insets HA pads with. */\n height: 100vh;\n height: calc(100dvh - var(--safe-area-inset-top, 0px) - var(--safe-area-inset-bottom, 0px));\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n color: var(--primary-text-color);\n background: var(--primary-background-color);\n }\n .page {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n overflow: hidden;\n }\n .appbar {\n box-sizing: border-box;\n min-width: 0;\n min-height: var(--header-height, 56px);\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 0 16px;\n background: var(--app-header-background-color, var(--primary-background-color));\n color: var(--app-header-text-color, var(--primary-text-color));\n border-bottom: 1px solid var(--divider-color);\n z-index: 1;\n }\n .toolbar {\n min-width: 0;\n width: 100%;\n display: flex;\n align-items: center;\n gap: 12px;\n }\n .menu {\n box-sizing: border-box;\n width: 44px;\n height: 44px;\n min-width: 44px;\n min-height: 44px;\n margin: 0;\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 50%;\n color: inherit;\n background: transparent;\n cursor: pointer;\n }\n .menu:focus-visible {\n outline: 2px solid var(--primary-color);\n outline-offset: 2px;\n }\n @media (hover: hover) {\n .menu:hover { background: var(--secondary-background-color); }\n }\n .menu ha-icon { --mdc-icon-size: 24px; }\n .title {\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 20px;\n font-weight: 500;\n line-height: 1.2;\n }\n .title ha-icon {\n --mdc-icon-size: 24px;\n color: var(--primary-color);\n }\n .content {\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n }\n houseplan-card {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n }\n ";const n=document.createElement("div");n.className="page";const t=document.createElement("header");t.className="appbar";const a=document.createElement("div");a.className="toolbar",a.setAttribute("role","toolbar");const o=document.createElement("button");o.className="menu",o.dataset.hp="panel-menu",o.type="button",o.innerHTML='<ha-icon icon="mdi:menu"></ha-icon>',o.addEventListener("click",()=>{o.dispatchEvent(new CustomEvent("hass-toggle-menu",{bubbles:!0,composed:!0}))}),this._menuButton=o;const i=document.createElement("div");i.className="title",i.dataset.hp="panel-title",i.setAttribute("role","heading"),i.setAttribute("aria-level","1"),i.innerHTML='<ha-icon icon="mdi:floor-plan"></ha-icon><span>House Plan</span>',a.append(o,i),t.append(a);const r=document.createElement("main");r.className="content";const s=document.createElement("houseplan-card");s.panelHost=!0,s.narrow=this._narrow,s.setConfig({type:"custom:houseplan-card"}),void 0!==this._hass&&(s.hass=this._hass),r.append(s),n.append(t,r),this.shadowRoot.append(e,n),this._card=s,this._syncMenuLabel()}_syncMenuLabel(){if(!this._menuButton)return;const e=this._hass?.localize?.("ui.common.menu")||this._hass?.localize?.("ui.panel.lovelace.menu")||"Menu";this._menuButton.setAttribute("aria-label",e),this._menuButton.title=e}}customElements.get("houseplan-panel")||customElements.define("houseplan-panel",n);export{n as HouseplanPanel};
|