mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 06:08:59 +00:00
A plan that still carried a `room_drafts` key while already on the current wall model could not be edited at all. The card mirrors the same migration, so a structural edit was refused before the request ever left the browser; the toast sent the user to "Optimize plans", which reports that everything is already optimal because it looks at something else entirely; and the export path calls the same migration, so the one way out — take a backup, fix the file by hand — was shut too. An empty `room_drafts: []`, carrying no data at all, was enough to do it. The carrier is now removed the way the first migration removes it: an empty key silently, drafts converted one for one into partitions. The #478 protection against a stale client re-adding the carrier moves to the layer that can actually tell the two apart — `validate_wall_model_transition` sees both the submission and the stored plan, and refuses when the drafts appear over a plan that does not have them. It no longer keys on the submitted model number: a stale card echoes back the number it was given, which is exactly how the outdated client slipped past this guard and met "conflicting wall identifiers" instead of "update the card and reload the page". The schema invariant keeps refusing a non-empty carrier as the last line. Both mirrors change together and stay identical; the parity fixture is untouched. Issue: #529 User-Visible: yes
2 lines
6.0 KiB
JavaScript
2 lines
6.0 KiB
JavaScript
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="6f12d9312dc39371e05ac6b5778215b61b18f6d249a4ae38b50c3945393f253b";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};
|