feat: add room hover tooltip setting

Issue: #426
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-09-03 09:06:57 +03:00
parent ac99a2ff08
commit 8013f7d052
52 changed files with 815 additions and 620 deletions
@@ -1,125 +1,125 @@
{
"schema": 1,
"fingerprint": "816ff9c893e23b7441e73210f85024741ff6c5bf8dabd2fb0df9e8b8055bc693",
"fingerprint": "b69bdfe3df225cf1b96fea0159a03a620e38cb94ec6106f9ee3b0e57cdefd183",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-Dwkibvgo.js",
"houseplan-assets/houseplan-card-CxS6HLVV.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 291031,
"initialViewGzipBytes": 290942,
"lazyFiles": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/de-DJX-vLW1.js",
"houseplan-assets/editor-Dvui5H3b.js",
"houseplan-assets/fr-DVmfM6Kw.js",
"houseplan-assets/houseplan-editor-runtime-9AEP51ly.js",
"houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/de-CDRfP4mw.js",
"houseplan-assets/editor-Bze_fgW0.js",
"houseplan-assets/fr-B64rhazj.js",
"houseplan-assets/houseplan-editor-runtime-X23eakN9.js",
"houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js"
],
"lazyGzipBytes": 214972,
"lazyGzipBytes": 215441,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/editor-Dvui5H3b.js",
"houseplan-assets/houseplan-editor-runtime-9AEP51ly.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/editor-Bze_fgW0.js",
"houseplan-assets/houseplan-editor-runtime-X23eakN9.js"
],
"lazyEditorGzipBytes": 159157,
"lazyEditorGzipBytes": 159770,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js"
],
"lazyOnboardingGzipBytes": 14132,
"lazyOnboardingGzipBytes": 14129,
"lazyLocaleFiles": [
"houseplan-assets/de-DJX-vLW1.js",
"houseplan-assets/fr-DVmfM6Kw.js"
"houseplan-assets/de-CDRfP4mw.js",
"houseplan-assets/fr-B64rhazj.js"
],
"lazyLocaleGzipBytes": 48859,
"lazyLocaleGzipBytes": 48717,
"files": [
{
"path": "houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"sha256": "455450cbc7b74f052fb112680af9ba4be145af8c3a1061907ec541b63957f22f",
"path": "houseplan-assets/backdrop-pick-I85bKcdl.js",
"sha256": "7becca609cbb95b1ba278743fce5b3e93c7adf8a7bd9107783931f29279ac5df",
"rawBytes": 20974,
"gzipBytes": 7176,
"gzipBytes": 7175,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-DJX-vLW1.js",
"sha256": "73d198680c17602addf41855748533ed47233e81e9bdc02a786034aeac0e4e63",
"rawBytes": 81798,
"gzipBytes": 24701,
"path": "houseplan-assets/de-CDRfP4mw.js",
"sha256": "2f90acc29e659e771dea42290b74ceb54d8ab9a7fae9cfdbd7ce6bc4bda0b70b",
"rawBytes": 81632,
"gzipBytes": 24630,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-Dvui5H3b.js",
"sha256": "11578ded60410e4e879c9342ee74c8a0968477ea2bae98eb18480a0ad02d303c",
"path": "houseplan-assets/editor-Bze_fgW0.js",
"sha256": "2e2513efca4fa8d9019c3601693fb3ff3b9e050ba3fe054b190f81d286540dda",
"rawBytes": 3826,
"gzipBytes": 1580,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-DVmfM6Kw.js",
"sha256": "167bed2822ce7e2b38d47e8dc1d73648d10c04d469979bf05e414000b53487b4",
"rawBytes": 83973,
"gzipBytes": 24158,
"path": "houseplan-assets/fr-B64rhazj.js",
"sha256": "6a1a18220938a8d4631586a9dc7f8ef8243b77a23089777d0917975300960a82",
"rawBytes": 83754,
"gzipBytes": 24087,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-Dwkibvgo.js",
"sha256": "d16deb0fc6f591040a2f97d51102e3e355da35cb3d1b69aa79472406c28d3ed2",
"rawBytes": 1033612,
"gzipBytes": 290234,
"path": "houseplan-assets/houseplan-card-CxS6HLVV.js",
"sha256": "1607bc6eeb275b340391ff10bdb7c2c5f62e59d5d2a43adbdb412698a0a024d1",
"rawBytes": 1032923,
"gzipBytes": 290148,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-DJX-vLW1.js",
"houseplan-assets/editor-Dvui5H3b.js",
"houseplan-assets/fr-DVmfM6Kw.js",
"houseplan-assets/houseplan-editor-runtime-9AEP51ly.js",
"houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js"
"houseplan-assets/de-CDRfP4mw.js",
"houseplan-assets/editor-Bze_fgW0.js",
"houseplan-assets/fr-B64rhazj.js",
"houseplan-assets/houseplan-editor-runtime-X23eakN9.js",
"houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-9AEP51ly.js",
"sha256": "c1b8f90458afedddbbdde98570c517d6e66dfcee08ad4764b1ac2473bdf89fb4",
"rawBytes": 574225,
"gzipBytes": 150401,
"path": "houseplan-assets/houseplan-editor-runtime-X23eakN9.js",
"sha256": "53f256fffc8e6f02618e56dbf56f25258356c2ca221ee9918ccbc57d9e590f6b",
"rawBytes": 575918,
"gzipBytes": 151015,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js",
"sha256": "bd169896116229e76b37243106bde64755589017639890d36dd19282c872db7a",
"path": "houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js",
"sha256": "a5433ef2715f424016b31a2ae3c3c30f2cff05549230fd0ab16db3159834de3f",
"rawBytes": 28088,
"gzipBytes": 6956,
"gzipBytes": 6954,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "730163666440793e44be0ee5482f7080931370f94555a1d52072d3a54698447e",
"sha256": "f9d93e5aa355fc91dc8fad57e7669a0ac663e8b538f42ea671a1a319fe214c3e",
"rawBytes": 1183,
"gzipBytes": 797,
"gzipBytes": 794,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/houseplan-card-CxS6HLVV.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,14 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="816ff9c893e23b7441e73210f85024741ff6c5bf8dabd2fb0df9e8b8055bc693";import{b as e,l as o,t,df as s,A as a,dg as i,dh as l,di as n,E as r,c}from"./houseplan-card-Dwkibvgo.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
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b69bdfe3df225cf1b96fea0159a03a620e38cb94ec6106f9ee3b0e57cdefd183";import{b as e,l as o,t,dg as s,A as a,dh as i,di as l,dj as n,E as r,c}from"./houseplan-card-CxS6HLVV.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}
.computeLabel=${e=>f[e.name]||e.name}
.computeLabel=${e=>h[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(h.slice(0,3))}
${d(f.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);
${d(f.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)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="816ff9c893e23b7441e73210f85024741ff6c5bf8dabd2fb0df9e8b8055bc693";try{await import("./houseplan-assets/houseplan-card-Dwkibvgo.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__="b69bdfe3df225cf1b96fea0159a03a620e38cb94ec6106f9ee3b0e57cdefd183";try{await import("./houseplan-assets/houseplan-card-CxS6HLVV.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)}
@@ -140,6 +140,9 @@ def _global_settings(value: object) -> dict[str, Any]:
out = _copy_keys(value, ("glow_radius_cm", "bg_color", "north_deg", "bg_mode", "sun_rays"))
if not isinstance(value, dict):
return out
show_room_tooltip = value.get("show_room_tooltip")
if isinstance(show_room_tooltip, bool):
out["show_room_tooltip"] = show_room_tooltip
fill_colors = value.get("fill_colors")
if isinstance(fill_colors, dict):
out["fill_colors"] = {
@@ -1935,6 +1935,7 @@ CONFIG_SCHEMA = vol.All(
vol.Optional("north_deg"): _north_deg,
vol.Optional("bg_mode"): _BG_MODE,
vol.Optional("sun_rays"): bool,
vol.Optional("show_room_tooltip"): bool,
# Removed from the UI/runtime in 2026-08-08. Keep accepting the
# legacy field so an existing stored config can still load; the
# frontend ignores it and removes it on the next settings save.
+84
View File
@@ -0,0 +1,84 @@
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch();
const result = await page.evaluate(async () => {
const card = window.__card;
const root = () => card.shadowRoot || card.renderRoot;
const room = () => root().querySelector('.room');
const mouse = (type, x, y) => new PointerEvent(type, {
pointerType: 'mouse', bubbles: true, composed: true, clientX: x, clientY: y,
});
const out = {};
card._openSettingsDialog();
await card.updateComplete;
const settingRow = [...root().querySelectorAll('hp-dialog label.srcrow')]
.find((label) => label.textContent.trim() === 'Show the room information window on hover');
out.localizedControl = !!settingRow?.querySelector('ha-switch,input[type="checkbox"]');
out.defaultOn = card._settingsDialog?.showRoomTooltip === true;
card._settingsDialog = { ...card._settingsDialog, showRoomTooltip: false };
card._settingsDialog = null;
out.cancelKeepsAbsentDefault = !Object.hasOwn(card._serverCfg.settings, 'show_room_tooltip');
room().dispatchEvent(mouse('pointerenter', 180, 180));
room().dispatchEvent(mouse('pointermove', 180, 180));
await card.updateComplete;
out.defaultRoomTip = card._tip?.room === true && !!root().querySelector('.tip');
card._openSettingsDialog();
card._settingsDialog = { ...card._settingsDialog, showRoomTooltip: false };
await card._saveSettingsDialog();
await card.updateComplete;
out.falsePersists = card._serverCfg.settings.show_room_tooltip === false;
out.visibleRoomTipCleared = card._tip === null && !root().querySelector('.tip');
let roomAreaCalls = 0;
const originalRoomArea = card._roomArea.bind(card);
card._roomArea = (...args) => {
roomAreaCalls += 1;
return originalRoomArea(...args);
};
room().dispatchEvent(mouse('pointerenter', 190, 190));
room().dispatchEvent(mouse('pointermove', 190, 190));
await card.updateComplete;
out.disabledRoomTip = card._tip === null && !root().querySelector('.tip');
out.disabledSkipsArea = roomAreaCalls === 0;
out.roomHighlightSurvives = card._hoverRoom !== null
&& !!root().querySelector('.room-hover-fill-layer,.room-hover-outline-layer');
root().querySelector('.dev').dispatchEvent(mouse('pointermove', 200, 200));
await card.updateComplete;
out.deviceTipSurvives = card._tip?.room === false && !!root().querySelector('.tip');
card._openSettingsDialog();
out.reopenShowsOff = card._settingsDialog?.showRoomTooltip === false;
card._settingsDialog = { ...card._settingsDialog, showRoomTooltip: true };
await card._saveSettingsDialog();
await card.updateComplete;
out.trueStoredAsAbsent = !Object.hasOwn(card._serverCfg.settings, 'show_room_tooltip');
out.enableDoesNotRestoreStaleTip = card._tip?.room !== true;
room().dispatchEvent(mouse('pointermove', 210, 210));
await card.updateComplete;
out.roomTipRestoredOnMove = card._tip?.room === true && !!root().querySelector('.tip');
return out;
});
checkAll(result, {
localizedControl: true,
defaultOn: true,
cancelKeepsAbsentDefault: true,
defaultRoomTip: true,
falsePersists: true,
visibleRoomTipCleared: true,
disabledRoomTip: true,
disabledSkipsArea: true,
roomHighlightSurvives: true,
deviceTipSurvives: true,
reopenShowsOff: true,
trueStoredAsAbsent: true,
enableDoesNotRestoreStaleTip: true,
roomTipRestoredOnMove: true,
});
await finish(browser, result);
+58 -58
View File
@@ -1,125 +1,125 @@
{
"schema": 1,
"fingerprint": "816ff9c893e23b7441e73210f85024741ff6c5bf8dabd2fb0df9e8b8055bc693",
"fingerprint": "b69bdfe3df225cf1b96fea0159a03a620e38cb94ec6106f9ee3b0e57cdefd183",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-Dwkibvgo.js",
"houseplan-assets/houseplan-card-CxS6HLVV.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 291031,
"initialViewGzipBytes": 290942,
"lazyFiles": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/de-DJX-vLW1.js",
"houseplan-assets/editor-Dvui5H3b.js",
"houseplan-assets/fr-DVmfM6Kw.js",
"houseplan-assets/houseplan-editor-runtime-9AEP51ly.js",
"houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/de-CDRfP4mw.js",
"houseplan-assets/editor-Bze_fgW0.js",
"houseplan-assets/fr-B64rhazj.js",
"houseplan-assets/houseplan-editor-runtime-X23eakN9.js",
"houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js"
],
"lazyGzipBytes": 214972,
"lazyGzipBytes": 215441,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/editor-Dvui5H3b.js",
"houseplan-assets/houseplan-editor-runtime-9AEP51ly.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/editor-Bze_fgW0.js",
"houseplan-assets/houseplan-editor-runtime-X23eakN9.js"
],
"lazyEditorGzipBytes": 159157,
"lazyEditorGzipBytes": 159770,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js"
],
"lazyOnboardingGzipBytes": 14132,
"lazyOnboardingGzipBytes": 14129,
"lazyLocaleFiles": [
"houseplan-assets/de-DJX-vLW1.js",
"houseplan-assets/fr-DVmfM6Kw.js"
"houseplan-assets/de-CDRfP4mw.js",
"houseplan-assets/fr-B64rhazj.js"
],
"lazyLocaleGzipBytes": 48859,
"lazyLocaleGzipBytes": 48717,
"files": [
{
"path": "houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"sha256": "455450cbc7b74f052fb112680af9ba4be145af8c3a1061907ec541b63957f22f",
"path": "houseplan-assets/backdrop-pick-I85bKcdl.js",
"sha256": "7becca609cbb95b1ba278743fce5b3e93c7adf8a7bd9107783931f29279ac5df",
"rawBytes": 20974,
"gzipBytes": 7176,
"gzipBytes": 7175,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/de-DJX-vLW1.js",
"sha256": "73d198680c17602addf41855748533ed47233e81e9bdc02a786034aeac0e4e63",
"rawBytes": 81798,
"gzipBytes": 24701,
"path": "houseplan-assets/de-CDRfP4mw.js",
"sha256": "2f90acc29e659e771dea42290b74ceb54d8ab9a7fae9cfdbd7ce6bc4bda0b70b",
"rawBytes": 81632,
"gzipBytes": 24630,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-Dvui5H3b.js",
"sha256": "11578ded60410e4e879c9342ee74c8a0968477ea2bae98eb18480a0ad02d303c",
"path": "houseplan-assets/editor-Bze_fgW0.js",
"sha256": "2e2513efca4fa8d9019c3601693fb3ff3b9e050ba3fe054b190f81d286540dda",
"rawBytes": 3826,
"gzipBytes": 1580,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-DVmfM6Kw.js",
"sha256": "167bed2822ce7e2b38d47e8dc1d73648d10c04d469979bf05e414000b53487b4",
"rawBytes": 83973,
"gzipBytes": 24158,
"path": "houseplan-assets/fr-B64rhazj.js",
"sha256": "6a1a18220938a8d4631586a9dc7f8ef8243b77a23089777d0917975300960a82",
"rawBytes": 83754,
"gzipBytes": 24087,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-Dwkibvgo.js",
"sha256": "d16deb0fc6f591040a2f97d51102e3e355da35cb3d1b69aa79472406c28d3ed2",
"rawBytes": 1033612,
"gzipBytes": 290234,
"path": "houseplan-assets/houseplan-card-CxS6HLVV.js",
"sha256": "1607bc6eeb275b340391ff10bdb7c2c5f62e59d5d2a43adbdb412698a0a024d1",
"rawBytes": 1032923,
"gzipBytes": 290148,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-DJX-vLW1.js",
"houseplan-assets/editor-Dvui5H3b.js",
"houseplan-assets/fr-DVmfM6Kw.js",
"houseplan-assets/houseplan-editor-runtime-9AEP51ly.js",
"houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js"
"houseplan-assets/de-CDRfP4mw.js",
"houseplan-assets/editor-Bze_fgW0.js",
"houseplan-assets/fr-B64rhazj.js",
"houseplan-assets/houseplan-editor-runtime-X23eakN9.js",
"houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-9AEP51ly.js",
"sha256": "c1b8f90458afedddbbdde98570c517d6e66dfcee08ad4764b1ac2473bdf89fb4",
"rawBytes": 574225,
"gzipBytes": 150401,
"path": "houseplan-assets/houseplan-editor-runtime-X23eakN9.js",
"sha256": "53f256fffc8e6f02618e56dbf56f25258356c2ca221ee9918ccbc57d9e590f6b",
"rawBytes": 575918,
"gzipBytes": 151015,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-CnVK5oOC.js",
"sha256": "bd169896116229e76b37243106bde64755589017639890d36dd19282c872db7a",
"path": "houseplan-assets/houseplan-onboarding-runtime-C3A8efgN.js",
"sha256": "a5433ef2715f424016b31a2ae3c3c30f2cff05549230fd0ab16db3159834de3f",
"rawBytes": 28088,
"gzipBytes": 6956,
"gzipBytes": 6954,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-DWD0ZQTc.js",
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/backdrop-pick-I85bKcdl.js",
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "730163666440793e44be0ee5482f7080931370f94555a1d52072d3a54698447e",
"sha256": "f9d93e5aa355fc91dc8fad57e7669a0ac663e8b538f42ea671a1a319fe214c3e",
"rawBytes": 1183,
"gzipBytes": 797,
"gzipBytes": 794,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-Dwkibvgo.js"
"houseplan-assets/houseplan-card-CxS6HLVV.js"
],
"dynamicImports": []
}
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,14 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="816ff9c893e23b7441e73210f85024741ff6c5bf8dabd2fb0df9e8b8055bc693";import{b as e,l as o,t,df as s,A as a,dg as i,dh as l,di as n,E as r,c}from"./houseplan-card-Dwkibvgo.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
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b69bdfe3df225cf1b96fea0159a03a620e38cb94ec6106f9ee3b0e57cdefd183";import{b as e,l as o,t,dg as s,A as a,dh as i,di as l,dj as n,E as r,c}from"./houseplan-card-CxS6HLVV.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}
.computeLabel=${e=>f[e.name]||e.name}
.computeLabel=${e=>h[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(h.slice(0,3))}
${d(f.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);
${d(f.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)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
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
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="816ff9c893e23b7441e73210f85024741ff6c5bf8dabd2fb0df9e8b8055bc693";try{await import("./houseplan-assets/houseplan-card-Dwkibvgo.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__="b69bdfe3df225cf1b96fea0159a03a620e38cb94ec6106f9ee3b0e57cdefd183";try{await import("./houseplan-assets/houseplan-card-CxS6HLVV.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)}
+4
View File
@@ -2,6 +2,10 @@
## Unreleased
- General settings can now hide the room information window on mouse hover
without disabling the room highlight or device tooltips; existing plans keep
the window enabled by default ([#426](https://github.com/Matysh/houseplan-card/issues/426)).
## v1.71.0-beta.1 — 2026-09-03
- The Background editor can now upload and reuse private PNG, JPEG, WebP and
+5
View File
@@ -8,6 +8,11 @@
## Не выпущено
- В общих настройках теперь можно отключить информационное окно комнаты при
наведении мышью, не отключая подсветку комнаты и подсказки устройств;
существующие планы сохраняют включённое поведение по умолчанию
([#426](https://github.com/Matysh/houseplan-card/issues/426)).
## v1.71.0-beta.1 — 2026-09-03
- В редакторе подложки теперь можно загружать и повторно использовать закрытые
+14
View File
@@ -63,6 +63,20 @@ writer produce the same request; accepting either would reopen last-writer-wins
data loss. This changes only the WebSocket write contract. Stored config,
model/store versions, exports and read compatibility are unchanged.
## Room hover information preference (#426)
`settings.show_room_tooltip` is an optional global boolean. Absence and any
invalid legacy/future value read as the historical enabled default; only exact
`false` hides the floating room information window. Saving the enabled value
removes the key. The field does not change room highlighting or device
tooltips, and does not require a model/store version migration.
An older frontend ignores the field and temporarily shows the room window. An
older backend preserves it through the existing unknown-settings policy, so a
new frontend restores the disabled behavior after upgrade. Full backup/import
preserves the setting and the privacy-safe support projection includes only a
validated boolean.
## Stable wall identity — model v8 (#282)
Model v8 adds `space.wall_segments[]`, ordered `rooms[].wall_ids[]`, IDs on
+4
View File
@@ -1024,6 +1024,10 @@ separately promised workflows:
- [ ] Room hover adds a subtle accent wash and double contour without changing
the underlying room fill or Glow brightness
- [ ] Room tooltip shows average room temperature and humidity after the area line and before LQI; missing values are omitted [auto: smoke_ux_fixes]
- [ ] General settings can hide only the room tooltip: default/Cancel/save/reopen
semantics, skipped area work, persistent room highlight, unaffected
device tooltip and restoration on the next mouse move
[auto: smoke_room_tooltip_toggle]
- [ ] Average room temperature counts ONLY thermometer/air-monitor devices — fridges, TRV heads,
smart-plug chip temperatures (`*_device_temperature`) and diagnostic-category temps are excluded [manual]
- [ ] Space dialog is 500 px wide; the comfort-bounds inputs are compact (56 px)
+5
View File
@@ -51,6 +51,11 @@ desktop hover without a reload. Space/mode changes, page hiding and remounts
also discard transient hover. Keyboard focus and explicit click/tap surfaces
remain independent of this visual hover gate.
The default-on **Show the room information window on hover** preference applies
only after this pointer-modality gate has enabled real mouse hover. Turning it
off leaves the room highlight and device tooltips unchanged; it does not add a
touch or pen replacement for the room window.
## What “best-effort editors” means
On a coarse-pointer or no-hover device, an editor operation may:
+6 -1
View File
@@ -55,7 +55,7 @@ override them; a room may override its space; a marker may override its room.
| Level | Meaning | Stored data |
|---|---|---|
| Card | One dashboard instance | Initial space, language, icon size, value/LQI display, live state, kiosk and cycle |
| Global settings | Defaults for all spaces | Fill palette, background, Glow radius, north, sun, weather and icon rules |
| Global settings | Defaults for all spaces | Fill palette, background, Glow radius, north, sun, room-hover information and icon rules |
| Space | Floor, yard, garage or building | Plan image, scale, rooms, walls, openings, decor and display settings |
| Room | A closed outline | Name, optional HA area, temperature/humidity source and local fill |
| Wall | A room-contour or independent segment | Stable identity and thickness from 0 to 100 cm; zero-thickness appearance is selected per space |
@@ -196,6 +196,11 @@ View is the state with no editor open. Close the active editor to return to it.
| Kiosk | Actionable as in View | Read-only | Read-only; no editors |
| Static card | Not live or interactive | Render only | Render only |
The room highlight remains available in View and kiosk. To keep that highlight
but hide the floating room summary, turn off **General settings → Show the room
information window on hover**. The option is on by default and does not affect
device tooltips.
Each editor has a stable primary toolbar. Tool parameters and selected-object
actions appear in a context tray over the top of the canvas. On a narrow screen
the tray scrolls horizontally instead of shrinking the plan.
+6 -1
View File
@@ -55,7 +55,7 @@ House Plan — локальная интеграция и две Lovelace-кар
| Уровень | Что это | Что хранит |
|---|---|---|
| Карточка | Один экземпляр `houseplan-card` на дашборде | Стартовое пространство, язык, размер иконок, показ значений/LQI, живые состояния, киоск и автолистание |
| Общие настройки | Настройки всего House Plan | Палитры заливок, фон, радиус света, север, солнце, погода, правила иконок |
| Общие настройки | Настройки всего House Plan | Палитры заливок, фон, радиус света, север, солнце, окно комнаты при наведении, правила иконок |
| Пространство | Этаж, двор, гараж, отдельное строение | Подложку, масштаб сетки, комнаты, стены, проёмы, декор и настройки отображения |
| Комната | Только замкнутый контур | Название, необязательную HA-зону, источники температуры/влажности и локальную заливку |
| Стена | Сегмент контура комнаты или независимая стена | Стабильный ID и толщину 0–100 см; вид стен нулевой толщины задаётся для пространства |
@@ -207,6 +207,11 @@ title: План дома
| Киоск | Настенный экран | Интерактивны как в просмотре | Только отображение | Только отображение; редакторы отсутствуют |
| Статическая карточка | Компактная схема пространства | Только рисунок | Только рисунок | Без состояний и интерактивности |
Подсветка комнаты сохраняется в Просмотре и киоске. Чтобы оставить подсветку,
но убрать всплывающее описание комнаты, выключите **Общие настройки →
Показывать окно с информацией при наведении на комнату**. По умолчанию опция
включена и не влияет на подсказки устройств.
В редакторах сетка продолжается по всему рабочему холсту. В режиме просмотра сетка не показывается.
Основная панель каждого редактора содержит только постоянные инструменты, а
+4
View File
@@ -79,6 +79,10 @@ device state do not depend on that gate. A device action that actually dispatche
briefly scales its painted shell to 95% and back over 200 ms; informational,
editor and no-op paths do not imitate a successful action.
The global `settings.show_room_tooltip` preference controls only the floating
room information window. Missing or invalid values mean enabled; exact `false`
hides the window while room highlight and every device tooltip remain active.
Removed from this mode (they move, not die):
- icon dragging ("drag anywhere", v1.9 — consciously reversed),
- room-label dragging,
+7 -4
View File
@@ -6,7 +6,8 @@
backend validation/privacy projection, i18n, документация и QA
- **Связи:** #79 (room hover), #154 (pointer modality), #196 (содержимое
тултипа)
- **Ревизия:** 1 (2026-09-03)
- **Ревизия:** 2 (2026-09-03; размещение editor-only строк уточнено по
результату bundle-budget без изменения поведения)
## Сценарий
@@ -126,7 +127,10 @@ Hover-подсветка уже имеет независимое состоян
## UX и i18n
Добавить ключ `gs.show_room_tooltip` во все четыре синхронных словаря:
Добавить ключ `gs.show_room_tooltip` во все четыре синхронизированных lazy
editor-словаря `src/i18n/support/{en,ru,de,fr}.json`. Существующую editor-only
строку `gs.hint` перенести туда же, чтобы новый control не увеличивал initial
View graph:
- RU: `Показывать окно с информацией при наведении на комнату`;
- EN: `Show the room information window on hover`;
@@ -176,7 +180,7 @@ Hover-подсветка уже имеет независимое состоян
- `src/types.ts`, `src/logic.ts`, `src/houseplan-card.ts`,
`src/houseplan-editor-runtime.ts`.
- `src/i18n/{en,ru,de,fr}.json`.
- `src/i18n/support/{en,ru,de,fr}.json`, `src/i18n/support.ts`.
- `custom_components/houseplan/validation.py`,
`custom_components/houseplan/support_package.py`.
- `test/logic.test.mjs`, settings/source contract tests,
@@ -307,4 +311,3 @@ guard, но backend acceptance/type и support projection временно со
- Канонический ключ — `settings.show_room_tooltip`; default хранится отсутствием,
а явное значение требуется только для `false`.
- `houseplan-space-card` не меняется, потому что уже не имеет интерактивности.
+7
View File
@@ -27,6 +27,13 @@
/** @type {SmokeLink[]} */
export const SMOKE_LINKS = [
{
symbols: ['showRoomTooltipOf'],
smokes: ['smoke_room_tooltip_toggle.mjs'],
because: 'the production-bundle scenario proves the global-settings draft and persistence, '
+ 'room-only suppression before area work, immediate stale-room-tip cleanup, preserved room '
+ 'highlight/device tooltip and default-on restoration (#426)',
},
{
symbols: [
'applyDevicePlacement', 'devicePlacement', 'sameDevicePlacement',
+21 -32
View File
@@ -39,7 +39,7 @@ import {
spaceDisplayOf, resolveEffectiveRoomFill, fillColorsOf, DEFAULT_FILL_COLORS,
customFillOf, roomCustomFillOf, DEFAULT_CUSTOM_FILL,
type FillColors, type FillColorEntry, type ResolvedRoomFill, runServiceFor, RUN_TARGET_DOMAINS,
DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY, stageBgOf,
DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY, stageBgOf, showRoomTooltipOf,
DEFAULT_TEMP_MIN, DEFAULT_TEMP_MAX, type SpaceDisplay,
referencedContentUrls,
DISPLAY_MODES, TAP_ACTIONS, SPACE_FILL_UI_MODES, ROOM_FILL_MODES,
@@ -1026,6 +1026,7 @@ export class HouseplanCard extends LitElement {
lqi?: number | null;
temp?: number | null;
hum?: number | null;
room?: boolean;
} | null = null;
/** Room whose physical perimeter is highlighted in View. The explicit
* overlay is needed because thick wall bodies paint above room shapes. */
@@ -2219,6 +2220,7 @@ export class HouseplanCard extends LitElement {
colors: FillColors; glowRadius: number; bgColor: string | null;
/** sun on the plan (docs/SUN.md) */
northDeg: number | null; bgMode: 'static' | 'daynight'; sunRays: boolean;
showRoomTooltip: boolean;
busy: boolean;
} | null = null;
private _supportDialog: SupportDialogState | null = null;
@@ -7286,11 +7288,12 @@ export class HouseplanCard extends LitElement {
lqi?: number | null,
temp?: number | null,
hum?: number | null,
room = false,
): void {
this._notePointer(ev);
if (!this._pointerModality.hoverEnabled) return;
if (this._drag || this._deviceDrag) return;
this._tip = { x: ev.clientX, y: ev.clientY, title, meta, lqi, temp, hum };
this._tip = { x: ev.clientX, y: ev.clientY, title, meta, lqi, temp, hum, room };
}
// ================= ROOM MARKUP EDITOR =================
@@ -11577,6 +11580,10 @@ export class HouseplanCard extends LitElement {
let areaText: string | null | undefined;
const tip = (e: PointerEvent) => {
if (this._mode !== 'view') return;
if (!showRoomTooltipOf(this._settings)) {
if (this._tip?.room) this._tip = null;
return;
}
if (areaText === undefined) areaText = this._roomArea(r);
this._showTip(
e,
@@ -11585,8 +11592,15 @@ export class HouseplanCard extends LitElement {
showLqi ? this._roomLqi(r.area) : null,
this._roomTemp(r),
this._roomHum(r),
true,
);
};
const enterRoom = (event: PointerEvent) => {
this._notePointer(event);
if (this._pointerModality.hoverEnabled) {
this._hoverRoom = { space: space.id, room: r };
}
};
const myPoly = polyOf(r);
// A room's ordinary solid stroke must not run beneath its zero
// wall overlay — suppress it and draw a trimmed outline. This
@@ -11623,57 +11637,32 @@ export class HouseplanCard extends LitElement {
? svg`<path class="${cls}" style="${style}" fill-rule="evenodd"
data-hp="room" data-id=${hpId} data-area=${hpArea}
d="${[obstaclePath, ...holes.map(pathD)].join(' ')}"
@pointerenter=${(event: PointerEvent) => {
this._notePointer(event);
if (this._pointerModality.hoverEnabled) {
this._hoverRoom = { space: space.id, room: r };
}
}}
@pointerenter=${enterRoom}
@pointermove=${tip}
@pointerleave=${() => this._clearTransientHover()}></path>`
: holes.length && fillPoly
? svg`<path class="${cls}" style="${style}" fill-rule="evenodd"
data-hp="room" data-id=${hpId} data-area=${hpArea}
d="${[fillPoly, ...holes].map(pathD).join(' ')}"
@pointerenter=${(event: PointerEvent) => {
this._notePointer(event);
if (this._pointerModality.hoverEnabled) {
this._hoverRoom = { space: space.id, room: r };
}
}}
@pointerenter=${enterRoom}
@pointermove=${tip}
@pointerleave=${() => this._clearTransientHover()}></path>`
: fillPoly && fillPoly !== myPoly
? svg`<polygon class="${cls}" style="${style}" points="${fillPoly.map((p) => p.join(',')).join(' ')}"
data-hp="room" data-id=${hpId} data-area=${hpArea}
@pointerenter=${(event: PointerEvent) => {
this._notePointer(event);
if (this._pointerModality.hoverEnabled) {
this._hoverRoom = { space: space.id, room: r };
}
}}
@pointerenter=${enterRoom}
@pointermove=${tip}
@pointerleave=${() => this._clearTransientHover()}></polygon>`
: r.poly
? svg`<polygon class="${cls}" style="${style}" points="${r.poly.map((p) => p.join(',')).join(' ')}"
data-hp="room" data-id=${hpId} data-area=${hpArea}
@pointerenter=${(event: PointerEvent) => {
this._notePointer(event);
if (this._pointerModality.hoverEnabled) {
this._hoverRoom = { space: space.id, room: r };
}
}}
@pointerenter=${enterRoom}
@pointermove=${tip}
@pointerleave=${() => this._clearTransientHover()}></polygon>`
: svg`<rect class="${cls}" style="${style}"
data-hp="room" data-id=${hpId} data-area=${hpArea}
x="${r.x}" y="${r.y}" width="${r.w}" height="${r.h}" rx="${Math.min(r.w!, r.h!) * 0.03}"
@pointerenter=${(event: PointerEvent) => {
this._notePointer(event);
if (this._pointerModality.hoverEnabled) {
this._hoverRoom = { space: space.id, room: r };
}
}}
@pointerenter=${enterRoom}
@pointermove=${tip}
@pointerleave=${() => this._clearTransientHover()}></rect>`;
const trimmed = edgeCuts.length && myPoly
+19 -6
View File
@@ -33,7 +33,7 @@ import {
spaceDisplayOf, resolveEffectiveRoomFill, fillColorsOf, DEFAULT_FILL_COLORS,
customFillOf, roomCustomFillOf, DEFAULT_CUSTOM_FILL,
type FillColors, type FillColorEntry, type ResolvedRoomFill, runServiceFor, RUN_TARGET_DOMAINS,
DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY, stageBgOf,
DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY, stageBgOf, showRoomTooltipOf,
DEFAULT_TEMP_MIN, DEFAULT_TEMP_MAX, type SpaceDisplay,
referencedContentUrls,
DISPLAY_MODES, TAP_ACTIONS, SPACE_FILL_UI_MODES, ROOM_FILL_MODES,
@@ -1112,8 +1112,8 @@ export interface HouseplanEditorHostPort {
_sentPos: Map<string, DeviceLayout[string] | null>;
_serverCfg: ServerConfig | null;
_serverStorage: boolean;
_settings: { exclude_integrations?: string[]; group_lights?: boolean; show_all?: boolean; filter_seeded?: boolean; icon_rules?: { pattern: string; icon: string; }[]; };
_settingsDialog: { colors: FillColors; glowRadius: number; bgColor: string | null; northDeg: number | null; bgMode: "static" | "daynight"; sunRays: boolean; busy: boolean; } | null;
_settings: { exclude_integrations?: string[]; group_lights?: boolean; show_all?: boolean; filter_seeded?: boolean; icon_rules?: { pattern: string; icon: string; }[]; show_room_tooltip?: boolean; };
_settingsDialog: { colors: FillColors; glowRadius: number; bgColor: string | null; northDeg: number | null; bgMode: "static" | "daynight"; sunRays: boolean; showRoomTooltip: boolean; busy: boolean; } | null;
_supportDialog: SupportDialogState | null;
_showAll: boolean;
_showHidden: boolean;
@@ -1134,7 +1134,7 @@ export interface HouseplanEditorHostPort {
_t: (key: I18nKey, vars?: Record<string, string | number>) => string;
_undoDevicePosition: () => void;
_thickWallCuts: () => number[][];
_tip: { x: number; y: number; title: string; meta: string; lqi?: number | null; temp?: number | null; hum?: number | null; } | null;
_tip: { x: number; y: number; title: string; meta: string; lqi?: number | null; temp?: number | null; hum?: number | null; room?: boolean; } | null;
_toggleConfirmationLines: (intent: ResolvedToggleIntent) => string[];
_toggleConfirmationStateText: (target: ResolvedToggleTarget) => string;
_toggleIntent: (device: DevItem, devices?: readonly DevItem[]) => ResolvedToggleIntent | null;
@@ -9034,6 +9034,7 @@ public _openSettingsDialog = (): void => {
northDeg: northDegOf(this.host._settings, {}),
bgMode: bgModeOf(this.host._settings, {}),
sunRays: sunRaysOn(this.host._settings, {}),
showRoomTooltip: showRoomTooltipOf(this.host._settings),
busy: false,
};
};
@@ -10233,12 +10234,15 @@ public _updateDecorStyle(next: DecorStyle): void {
settings.bg_mode = d.bgMode;
if (d.sunRays) settings.sun_rays = true;
else delete settings.sun_rays;
if (d.showRoomTooltip) delete settings.show_room_tooltip;
else settings.show_room_tooltip = false;
// Legacy compatibility: old configs may still contain this accepted
// field, but weather no longer affects sunlight and the UI no longer
// exposes it. Saving general settings cleans the obsolete value up.
delete settings.weather_entity;
this.host._serverCfg = { ...cfg, settings };
await this._saveConfigNow();
if (!d.showRoomTooltip && this.host._tip?.room) this.host._tip = null;
this.host._settingsDialog = null;
this.host.requestUpdate();
this.host._showToast(this.host._t('gs.saved'));
@@ -10534,7 +10538,16 @@ public _renderSettingsDialog(): TemplateResult {
return html`<hp-dialog .hass=${this.host.hass} .title=${this.host._t('gs.title')} icon="mdi:cog-outline" wide
@hp-close=${() => (this.host._settingsDialog = null)}>
<div class="body">
<div class="rhint">${this.host._t('gs.hint')}</div>
<div class="rhint">${supportT(
langOf(this.host.hass, this.host._config?.language), 'gs.hint',
)}</div>
<label class="srcrow">
${this._boolInput(this.host._settingsDialog!.showRoomTooltip, (v) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, showRoomTooltip: v }))}
<span>${supportT(
langOf(this.host.hass, this.host._config?.language), 'gs.show_room_tooltip',
)}</span>
</label>
<label class="dispsection">${this.host._t('gs.light_group')}</label>
${this._renderColorRow('light_on', 'gs.light_on')}
${this._renderColorRow('light_off', 'gs.light_off')}
@@ -10660,7 +10673,7 @@ public _renderSettingsDialog(): TemplateResult {
</div>
<div class="row" slot="footer">
<button class="btn ghost" @click=${() =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, colors: JSON.parse(JSON.stringify(DEFAULT_FILL_COLORS)), glowRadius: this.host._imperial ? 9.8 : 3, bgColor: null, northDeg: null, bgMode: 'daynight', sunRays: false })}>
(this.host._settingsDialog = { ...this.host._settingsDialog!, colors: JSON.parse(JSON.stringify(DEFAULT_FILL_COLORS)), glowRadius: this.host._imperial ? 9.8 : 3, bgColor: null, northDeg: null, bgMode: 'daynight', sunRays: false, showRoomTooltip: true })}>
${this.host._t('gs.reset')}
</button>
<span class="spacer"></span>
-1
View File
@@ -422,7 +422,6 @@
"marker.sub_entity": "Entität",
"title.general_settings": "Allgemeine Einstellungen",
"gs.title": "Allgemeine Einstellungen",
"gs.hint": "Füllfarben gelten für jeden Bereich; jede Farbe hat ihre eigene Deckkraft. Der Füllmodus wird in den Einstellungen des jeweiligen Bereichs festgelegt.",
"gs.light_group": "Füllung: Licht",
"gs.light_on": "Lichter an",
"gs.light_off": "Alle Lichter aus",
-1
View File
@@ -422,7 +422,6 @@
"marker.sub_entity": "entity",
"title.general_settings": "General settings",
"gs.title": "General settings",
"gs.hint": "Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.",
"gs.light_group": "Fill: lights",
"gs.light_on": "Lights on",
"gs.light_off": "All lights off",
-1
View File
@@ -422,7 +422,6 @@
"marker.sub_entity": "entité",
"title.general_settings": "Paramètres généraux",
"gs.title": "Paramètres généraux",
"gs.hint": "Les couleurs de remplissage s’appliquent à tous les espaces ; chaque couleur possède sa propre opacité. Le mode de remplissage utilisé par un espace se règle dans la boîte de dialogue de cet espace.",
"gs.light_group": "Remplissage : lumières",
"gs.light_on": "Lumières allumées",
"gs.light_off": "Toutes les lumières éteintes",
-1
View File
@@ -422,7 +422,6 @@
"marker.sub_entity": "сущность",
"title.general_settings": "Общие настройки",
"gs.title": "Общие настройки",
"gs.hint": "Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.",
"gs.light_group": "Заливка: освещение",
"gs.light_on": "Свет включён",
"gs.light_off": "Весь свет выключен",
+2 -2
View File
@@ -1,4 +1,4 @@
/** Lazy Help & feedback dictionaries owned by the editor runtime (#423). */
/** Lazy editor-only dictionaries owned by the editor runtime (#423). */
import { subst } from '../logic';
import type { Lang } from './registry';
import de from './support/de.json' with { type: 'json' };
@@ -15,7 +15,7 @@ const SUPPORT_DICTIONARIES: Record<Lang, Record<SupportI18nKey, string>> = {
fr,
};
/** Translate form-only support copy with the same synchronous English fallback. */
/** Translate editor-only copy with the same synchronous English fallback. */
export function supportT(
lang: Lang,
key: SupportI18nKey,
+2
View File
@@ -1,4 +1,6 @@
{
"gs.hint": "Füllfarben gelten für jeden Bereich; jede Farbe hat ihre eigene Deckkraft. Der Füllmodus wird in den Einstellungen des jeweiligen Bereichs festgelegt.",
"gs.show_room_tooltip": "Rauminformationen beim Darüberfahren anzeigen",
"support.about_group": "Über die Karte",
"support.guide_group": "Dokumentation",
"support.guide": "Benutzerhandbuch",
+2
View File
@@ -1,4 +1,6 @@
{
"gs.hint": "Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.",
"gs.show_room_tooltip": "Show the room information window on hover",
"support.about_group": "About the card",
"support.guide_group": "Documentation",
"support.guide": "User guide",
+2
View File
@@ -1,4 +1,6 @@
{
"gs.hint": "Les couleurs de remplissage s’appliquent à tous les espaces ; chaque couleur possède sa propre opacité. Le mode de remplissage utilisé par un espace se règle dans la boîte de dialogue de cet espace.",
"gs.show_room_tooltip": "Afficher les informations de la pièce au survol",
"support.about_group": "À propos de la carte",
"support.guide_group": "Documentation d’aide",
"support.guide": "Guide utilisateur",
+2
View File
@@ -1,4 +1,6 @@
{
"gs.hint": "Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.",
"gs.show_room_tooltip": "Показывать окно с информацией при наведении на комнату",
"support.about_group": "О карточке",
"support.guide_group": "Документация",
"support.guide": "Руководство пользователя",
+7
View File
@@ -1351,6 +1351,13 @@ export function stageBgOf(settings: any, disp: { bgColor: string | null }): stri
return safeStoredColor(g, '');
}
/** Global room-hover information preference. Legacy and malformed values keep
* the historical default; only an explicit boolean false disables the window. */
export function showRoomTooltipOf(settings: unknown): boolean {
return (settings as { show_room_tooltip?: unknown } | null | undefined)
?.show_room_tooltip !== false;
}
// ---------------- global fill colors ----------------
export interface FillColorEntry {
+2
View File
@@ -247,6 +247,8 @@ export interface ServerConfig {
/** Internal registry-Area provenance; not a user-facing setting. */
marker_area_snapshot?: MarkerAreaSnapshot;
icon_rules?: { pattern: string; icon: string }[];
/** Only an explicit false hides the room hover information window. */
show_room_tooltip?: boolean;
};
}
+2 -2
View File
@@ -126,11 +126,11 @@ test('i18n: every registered dictionary carries the English key set', () => {
}
});
test('i18n: lazy support dictionaries carry matching keys and placeholders', () => {
test('i18n: lazy editor dictionaries carry matching keys and placeholders', () => {
const supportEn = supportDictionaries.get('en');
const keys = Object.keys(supportEn).sort();
const placeholders = (value) => (String(value).match(/\{\w+\}/gu) || []).sort();
assert.equal(keys.length, 42);
assert.equal(keys.length, 44);
assert.equal(Object.keys(en).filter((key) => key.startsWith('support.')).join(','), 'support.title');
for (const { code } of LANGUAGE_REGISTRY) {
const dictionary = supportDictionaries.get(code);
+12
View File
@@ -26,6 +26,7 @@ import {
hassValue, valueWithUnit, decorTextScale, decorTextLines,
LIVE_TEXT_DASH, LIVE_TEXT_VALUE_MAX, DECOR_TEXT_SCALE_MIN, DECOR_TEXT_SCALE_MAX,
quantizeOpeningLightAmount,
showRoomTooltipOf,
} from '../test-build/logic.js';
import {
iconFor, compileIconRules, isValidPattern, iconFromDeviceClasses,
@@ -35,6 +36,17 @@ test('tap action editor choices keep the canonical order', () => {
assert.deepEqual([...TAP_ACTIONS], ['info', 'more-info', 'toggle', 'run', 'none']);
});
test('room tooltip preference is default-on and only exact false disables it', () => {
for (const value of [undefined, null, true, 0, 1, 'false', [], {}]) {
assert.equal(showRoomTooltipOf(value), true);
}
assert.equal(showRoomTooltipOf({ show_room_tooltip: undefined }), true);
assert.equal(showRoomTooltipOf({ show_room_tooltip: null }), true);
assert.equal(showRoomTooltipOf({ show_room_tooltip: true }), true);
assert.equal(showRoomTooltipOf({ show_room_tooltip: 'false' }), true);
assert.equal(showRoomTooltipOf({ show_room_tooltip: false }), false);
});
test('display normalization and alarm-capable metadata share one contract', () => {
assert.equal(normalizeDeviceDisplay(undefined), 'badge');
assert.equal(normalizeDeviceDisplay('ripple'), 'icon_ripple');
+3
View File
@@ -200,6 +200,7 @@ def test_rich_plan_projection_preserves_safe_structure_and_drops_unknown_values(
"model_version": 9,
"settings": {
"north_deg": 30,
"show_room_tooltip": False,
"fill_colors": {"warm": {"c": "#ffaa00", "a": 0.5, "secret": "drop"}},
"decor_default_style": {
"color": "#123456", "width_cm": 2, "secret": "drop",
@@ -290,6 +291,7 @@ def test_rich_plan_projection_preserves_safe_structure_and_drops_unknown_values(
assert package["versions"]["card"] == "unknown"
assert plan["settings"]["fill_colors"] == {"warm": {"a": 0.5, "c": "#ffaa00"}}
assert plan["settings"]["show_room_tooltip"] is False
assert plan["settings"]["decor_default_style"] == {"color": "#123456", "width_cm": 2}
assert space["rooms"][0]["poly"] == [[0, 0], [2, 0]]
assert space["rooms"][0]["settings"]["temp_source_kind"] == "entity"
@@ -334,6 +336,7 @@ def test_projection_helpers_fail_closed_on_malformed_shapes():
assert support_package._points(None) == []
assert support_package._custom_fill(None) is None
assert support_package._global_settings(None) == {}
assert support_package._global_settings({"show_room_tooltip": "false"}) == {}
assert support_package._room_settings(ids, None) == {}
assert support_package._project_layout(ids, None) == {}
assert support_package._summary(None, None)["spaces"] == 0
+13
View File
@@ -479,6 +479,19 @@ def test_sun_settings_global():
v.CONFIG_SCHEMA({"spaces": [], "settings": {"weather_entity": {"e": 1}}})
def test_room_tooltip_global_setting_is_strict_boolean_and_round_trips():
for value in (True, False):
out = v.CONFIG_SCHEMA(
{"spaces": [], "settings": {"show_room_tooltip": value}}
)
assert out["settings"]["show_room_tooltip"] is value
for bad in (None, 0, 1, "false", [], {}):
with pytest.raises(vol.Invalid):
v.CONFIG_SCHEMA(
{"spaces": [], "settings": {"show_room_tooltip": bad}}
)
def test_marker_area_snapshot_is_strict_and_bounded():
"""#126: Area provenance accepts only exact direct bindings and non-empty Areas."""
snapshot = {