mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 21:28:59 +00:00
feat: refine hidden isometric Stage 4 (#570)
Issue: #570 User-Visible: no
This commit is contained in:
@@ -1,311 +1,295 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091",
|
||||
"fingerprint": "012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 290264,
|
||||
"initialViewGzipBytes": 290253,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 291787,
|
||||
"initialPanelGzipBytes": 291775,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2326,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/de-CqAe7_tE.js",
|
||||
"houseplan-assets/editor-Bj456-QF.js",
|
||||
"houseplan-assets/fr-6DtT8TWD.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-CX3rLqEO.js",
|
||||
"houseplan-assets/iso-scene-render-ByC7EDVa.js",
|
||||
"houseplan-assets/live-interaction-runtime-CO9GuTnQ.js",
|
||||
"houseplan-assets/pdf-export-DtC_EPb5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-sByomcRQ.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/de-CguB1iyB.js",
|
||||
"houseplan-assets/editor-CrFGSaM9.js",
|
||||
"houseplan-assets/fr-C84z_afh.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-D4ABn_qu.js",
|
||||
"houseplan-assets/iso-scene-render-Bgw9qRET.js",
|
||||
"houseplan-assets/live-interaction-runtime-rsvGGxyc.js",
|
||||
"houseplan-assets/pdf-export-BYCIg7JD.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js"
|
||||
],
|
||||
"lazyGzipBytes": 418580,
|
||||
"lazyGzipBytes": 418485,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/editor-Bj456-QF.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/editor-CrFGSaM9.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 205043,
|
||||
"lazyEditorGzipBytes": 204789,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 13916,
|
||||
"lazyOnboardingGzipBytes": 13918,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CqAe7_tE.js",
|
||||
"houseplan-assets/fr-6DtT8TWD.js"
|
||||
"houseplan-assets/de-CguB1iyB.js",
|
||||
"houseplan-assets/fr-C84z_afh.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 54845,
|
||||
"lazyLocaleGzipBytes": 54850,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/iso-scene-render-ByC7EDVa.js"
|
||||
"houseplan-assets/iso-scene-render-Bgw9qRET.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 13455,
|
||||
"lazyIsometricGzipBytes": 13232,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 10254,
|
||||
"lazyFurnitureArtGzipBytes": 10253,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/pdf-export-DtC_EPb5.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/pdf-export-BYCIg7JD.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 120126,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"sha256": "78f118c030ea12bf7100b0bfbdd6fed33c5dc05bf7995ad22a39ec8f7e8f270b",
|
||||
"path": "houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"sha256": "ffe795a2734eb89097af8091262b51382147585e78e15f12aab94ae2f499fa06",
|
||||
"rawBytes": 19956,
|
||||
"gzipBytes": 6879,
|
||||
"gzipBytes": 6882,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CqAe7_tE.js",
|
||||
"sha256": "75472ecaf81e38bda8863c4e33a1eb0df92dbfda73e404d9bc1f3425ba227185",
|
||||
"path": "houseplan-assets/de-CguB1iyB.js",
|
||||
"sha256": "5a0f753ecbc4e88ca984827b000347caa31c258afd78199e0d1763238ff1cfc4",
|
||||
"rawBytes": 91437,
|
||||
"gzipBytes": 27708,
|
||||
"gzipBytes": 27712,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Bj456-QF.js",
|
||||
"sha256": "2f72cb7b627b545f2b064b4d7681f8100fef5d20923114fb98a96601281c6cf2",
|
||||
"rawBytes": 3832,
|
||||
"gzipBytes": 1583,
|
||||
"path": "houseplan-assets/editor-CrFGSaM9.js",
|
||||
"sha256": "5bac1e3c7e6ad73e3118a35eb921af07dc8a80b2fa65ea6dd7225aa168d32992",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1584,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-6DtT8TWD.js",
|
||||
"sha256": "f54edff32a74fcd97767e5b3ae0bbfd04e13c38b08e1cb535067699172b6ca27",
|
||||
"path": "houseplan-assets/fr-C84z_afh.js",
|
||||
"sha256": "4afd0630204be56ece7a382bc60242443f225d32caddff64536d0815443d9da0",
|
||||
"rawBytes": 93701,
|
||||
"gzipBytes": 27137,
|
||||
"gzipBytes": 27138,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"sha256": "12f42b30b0d2c72681bad2928c9d847d31cec487bdb490544e2f1dcaeaad875a",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"sha256": "f50864c15117dcb83594bbe996bd9cddb1ab9c6c2f286ca4c33b2bfb8d03597f",
|
||||
"rawBytes": 33655,
|
||||
"gzipBytes": 10254,
|
||||
"gzipBytes": 10253,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/guard-DBKIUfbp.js",
|
||||
"sha256": "42e87d66b74ad024880fc0aff319092b8539a19b27f6163363514c05a55921ff",
|
||||
"rawBytes": 518,
|
||||
"gzipBytes": 369,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"sha256": "8c136c1fe9a35e06f0547fd196eac3d050eab295a8709e47ea1539c7da8cc774",
|
||||
"rawBytes": 1031164,
|
||||
"gzipBytes": 289461,
|
||||
"path": "houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"sha256": "d9b0cfd20b712bdb93d448b49d73a6cbd7293e694335663595a84987ffc30f48",
|
||||
"rawBytes": 1031295,
|
||||
"gzipBytes": 289449,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CqAe7_tE.js",
|
||||
"houseplan-assets/editor-Bj456-QF.js",
|
||||
"houseplan-assets/fr-6DtT8TWD.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-CX3rLqEO.js",
|
||||
"houseplan-assets/iso-scene-render-ByC7EDVa.js",
|
||||
"houseplan-assets/live-interaction-runtime-CO9GuTnQ.js",
|
||||
"houseplan-assets/pdf-export-DtC_EPb5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js"
|
||||
"houseplan-assets/de-CguB1iyB.js",
|
||||
"houseplan-assets/editor-CrFGSaM9.js",
|
||||
"houseplan-assets/fr-C84z_afh.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-D4ABn_qu.js",
|
||||
"houseplan-assets/iso-scene-render-Bgw9qRET.js",
|
||||
"houseplan-assets/live-interaction-runtime-rsvGGxyc.js",
|
||||
"houseplan-assets/pdf-export-BYCIg7JD.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"sha256": "1fe3d26ea0cca0ebc832ce613252e07dac376a695c0d301d1445443b3757ac53",
|
||||
"rawBytes": 668940,
|
||||
"gzipBytes": 178530,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"sha256": "c2a2ccf2fcb2d537d38392cd57518b7ea9a83b74b82fe032301af959d2b0b963",
|
||||
"rawBytes": 669271,
|
||||
"gzipBytes": 178646,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-sByomcRQ.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js",
|
||||
"sha256": "1645f0de954897116fe118bd5401620787de2a534cc1ee63031b527d8b53d189",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js",
|
||||
"sha256": "097b862d0e93a3acce33966099626b5f881c61a8f9319d53a5c6ab3781620383",
|
||||
"rawBytes": 28287,
|
||||
"gzipBytes": 7037,
|
||||
"gzipBytes": 7036,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-CX3rLqEO.js",
|
||||
"sha256": "436d728f938f6c10c6b5e1b1811630b206c4f288ae095030587c6f89c0e34906",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-D4ABn_qu.js",
|
||||
"sha256": "e57a90d6682635233d5ec5c4c09d4821b6d4608507d08cf33a174ad0fbc320a7",
|
||||
"rawBytes": 10756,
|
||||
"gzipBytes": 3679,
|
||||
"gzipBytes": 3686,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-sByomcRQ.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-ByC7EDVa.js",
|
||||
"sha256": "eef97bc8600452c8879051e86055efefae1a4577567db70f462776c100eea579",
|
||||
"rawBytes": 38562,
|
||||
"gzipBytes": 13086,
|
||||
"path": "houseplan-assets/iso-scene-render-Bgw9qRET.js",
|
||||
"sha256": "ad4c67e51d68d05452d872a67705ef12fc2f89f580c589ebb3923275b73ce278",
|
||||
"rawBytes": 39206,
|
||||
"gzipBytes": 13232,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-CO9GuTnQ.js",
|
||||
"sha256": "88f116dd07bce29f0803bfcaaf12db5d633c9eba4908bdcb889a5dabe1d4d838",
|
||||
"path": "houseplan-assets/live-interaction-runtime-rsvGGxyc.js",
|
||||
"sha256": "fa87403a675bc45b8ccc3b4ff1ba0ee190c2968cbf4285ddf3259c2a96e1962f",
|
||||
"rawBytes": 8018,
|
||||
"gzipBytes": 3168,
|
||||
"gzipBytes": 3169,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-DtC_EPb5.js",
|
||||
"sha256": "9709712290440090599d72ee1f9c6259adc3cba09d09f5d96e519020c3663102",
|
||||
"path": "houseplan-assets/pdf-export-BYCIg7JD.js",
|
||||
"sha256": "8ac5283781427724de92d360c57433c3e98a4ece3268c479c2107c8802905fc1",
|
||||
"rawBytes": 236244,
|
||||
"gzipBytes": 109872,
|
||||
"gzipBytes": 109873,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-CWqNlMOD.js",
|
||||
"sha256": "1364e8033b275cde43cc53a68b9aac6d4c2707d661b0b8dd1bb2a8be550dfab2",
|
||||
"path": "houseplan-assets/summary-panel-editor-B4-T9x1R.js",
|
||||
"sha256": "cfa0ccfa1189264d53ae836ef84bb83d7649648a3ae70bf2b5a3e214f964e85c",
|
||||
"rawBytes": 14824,
|
||||
"gzipBytes": 3398,
|
||||
"gzipBytes": 3402,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-BO0KhesH.js",
|
||||
"sha256": "97b014aab427c7472d8f465891e866f8b1488155c45d2435dff00f60cbb3255d",
|
||||
"path": "houseplan-assets/summary-panel-metrics-oqhGKlnp.js",
|
||||
"sha256": "5d9d0a660d1203d03f30360b3d04dc61403e5cd26dd3dd447b1b8866123bd91e",
|
||||
"rawBytes": 2610,
|
||||
"gzipBytes": 1306,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js",
|
||||
"sha256": "fff8b723ebc1912effbd81d167cd4f8b98517ca9295a8819663d54d5d69acb51",
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js",
|
||||
"sha256": "3a0c8369150e7e947166f7647407fb7dbb7541423bcdd474840357b293e4ef1b",
|
||||
"rawBytes": 73366,
|
||||
"gzipBytes": 20088,
|
||||
"gzipBytes": 20087,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-CWqNlMOD.js",
|
||||
"houseplan-assets/summary-panel-metrics-BO0KhesH.js"
|
||||
"houseplan-assets/summary-panel-editor-B4-T9x1R.js",
|
||||
"houseplan-assets/summary-panel-metrics-oqhGKlnp.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-sByomcRQ.js",
|
||||
"sha256": "13d44dea277a6bb984abbdfa1a9246369e4ef9886c0d28e38ef3ab98327e576d",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1762,
|
||||
"path": "houseplan-assets/zigbee-topology-CQT90rCw.js",
|
||||
"sha256": "c14a3d5b913760cb1bf8797bf8cfe5653e54afee030406bab0ebad771a34c06c",
|
||||
"rawBytes": 18961,
|
||||
"gzipBytes": 7424,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-torI07nL.js",
|
||||
"sha256": "7ac30adae966053bc84d05634b32069221b4f8cd58bfe5c6a3ab7da50335e1f6",
|
||||
"rawBytes": 18961,
|
||||
"gzipBytes": 7428,
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js",
|
||||
"sha256": "a1020eb9133af9fcb6eee0d7630f128b2ef5c3b92596b36072e0543f5fd869d3",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1763,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "bbcf3569a6edaf8475bebc56429c65d49802923dde9d1fba487068171f6eb9be",
|
||||
"sha256": "c534e03d98617267f28c02c9283c5826cdbc228a895a3be012ea7e58086d4441",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 803,
|
||||
"gzipBytes": 804,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "db358c8fd55babc928f9acb6abe341bc33cd7642e8e60b8ff7894f5c67255c2c",
|
||||
"sha256": "49d5d19ce2cda3a4255946bf4316c8344a1dff48aaaa42a8cd7698a675b6ba21",
|
||||
"rawBytes": 6165,
|
||||
"gzipBytes": 2326,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,14 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";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-DqlR1H6U.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=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${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(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);
|
||||
@@ -0,0 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{q as e,l as o,v as t,e3 as s,A as a,e4 as i,e5 as l,e6 as n,E as r,c}from"./houseplan-card-CrTmJZ8b.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=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${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(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);
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{dS as r,dT as t,dU as e}from"./houseplan-card-DqlR1H6U.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
|
||||
+7
-7
File diff suppressed because one or more lines are too long
+751
-751
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{l as t,b0 as s,A as e,t as a,b as o,cj as l,ck as i,cl as h,cm as c,aZ as n,cn as r,cs as p,co as d,cp as _,cq as u,cr as g,ct as m,cu as b,dp as $,dq as v,dr as f,aY as y,a_ as w,ds as D}from"./houseplan-card-DqlR1H6U.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-DCbvGxY2.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";class L{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)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?o`<hp-help data-help-key=${l}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{l as t,b3 as s,A as e,v as a,c as o,cm as l,cn as i,co as h,cp as c,b0 as n,cq as r,cv as p,cr as d,cs as _,ct as u,cu as g,cw as m,cx as b,ds as $,dt as v,du as f,a$ as y,b1 as w,dv as D}from"./houseplan-card-CrTmJZ8b.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-CGON6yBi.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";class L{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)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?o`<hp-help data-help-key=${l}
|
||||
.text=${a(h,l)} .ariaLabel=${a(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const a=l(e),o=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===a.fill?{...o||h,a:0}:o;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:a.showBorders,showNames:a.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:a.hideDecor,hideOpenings:a.hideOpenings,roomColor:a.color,roomOpacity:a.opacity,fillMode:"none"===a.fill?"custom":a.fill,customFill:p,glowEnabled:a.glow,bgColor:a.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:a.tempMin,tempMax:a.tempMax,showLqi:a.showLqi??this.host._config?.show_signal??!0,cardFontScale:a.cardFontScale,labelTemp:a.labelTemp,labelHum:a.labelHum,labelLqi:a.labelLqi,labelLight:a.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...m(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,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 a=await k(e);if("reject"===a.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===a.kind)return void(this.host._backdropGuard=a.state);const o=await S(e,a.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:o})}_renderBackdropGuard(){return C(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_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}),a=this.host._spaceDialog;return a&&a.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...a,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const a=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),o=this.host._spaceDialog,l=o?.saved?.find(s=>s.name===t);if(a&&o&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==o.planUrl)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 o`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return o`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return o`<div class="savedplans">
|
||||
${s.map(s=>{return o`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
+6
-6
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
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{b as a,A as e}from"./houseplan-card-DqlR1H6U.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-ospbRm0g.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{c as a,A as e}from"./houseplan-card-CrTmJZ8b.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-DQykYOsg.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
|
||||
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
|
||||
<input id="summary-local-show" data-summary-local-show type="checkbox"
|
||||
-1
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{e9 as e,L as t,a2 as n,J as i,an as o,N as r,bo as s,bn as a,a1 as c,a0 as l,ax as d,ea as u,eb as f,bq as m,ec as g}from"./houseplan-card-DqlR1H6U.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=d(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),s=i.binding.slice(o+1),a="device"===r?e.registry.devices?.[s]:null,c="entity"===r?e.registry.entities?.[s]:null,l="device"===r?s:c?.device_id,d="device"===r?a?.area_id:c?.area_id||l&&e.registry.devices?.[l]?.area_id,u="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||d||"",f=u&&e.areaToSpace[u]||i.space||e.firstSpaceId;l&&e.registry.devices?.[l]&&e.spaceIds.has(f)&&(!n.devices.has(l)||"entity"===r&&n.liveEntities.has(s))&&t.add(l)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=c(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:l(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*b(c,l,d=_){try{let u=0;for(const f of l){const l=c.spaces.find(e=>String(e?.id)===f.id);if(!l)continue;const m=e(l,f),g=d(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(c,m.physicalBodies)),yield}const _=m.cellCm/o;u+=a(v)*_*_/1e4}return u}catch{return null}}function h(e,t,n=_){const i=b(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function p(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=u(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{b as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,p as summarySystemValue,h as totalCleanFloorAreaM2};
|
||||
+1
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{e9 as e,P as t,a5 as n,M as i,aq as r,N as o,br as a,bq as s,a4 as l,a3 as c,aA as u,ea as d,eb as f,bt as m,ec as g}from"./houseplan-card-CrTmJZ8b.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const r=String(i.binding||"").indexOf(":");if(r<1)continue;const o=i.binding.slice(0,r),a=i.binding.slice(r+1),s="device"===o?e.registry.devices?.[a]:null,l="entity"===o?e.registry.entities?.[a]:null,c="device"===o?a:l?.device_id,u="device"===o?s?.area_id:l?.area_id||c&&e.registry.devices?.[c]?.area_id,d="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||u||"",f=d&&e.areaToSpace[d]||i.space||e.firstSpaceId;c&&e.registry.devices?.[c]&&e.spaceIds.has(f)&&(!n.devices.has(c)||"entity"===o&&n.liveEntities.has(a))&&t.add(c)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=l(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,r,o);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:c(e.rooms,t.walls,t.openCuts,i,t.cellCm,r,o)}}function*b(l,c,u=_){try{let d=0;for(const f of c){const c=l.spaces.find(e=>String(e?.id)===f.id);if(!c)continue;const m=e(c,f),g=u(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const s=t(e);if(!s)continue;const l=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,r,o,g.roomGeom,g.multiWallNodes)||s;v=y(v,a(l,m.physicalBodies)),yield}const _=m.cellCm/r;d+=s(v)*_*_/1e4}return d}catch{return null}}function h(e,t,n=_){const i=b(e,t,n);let r=i.next();for(;!r.done;)r=i.next();return r.value}function p(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=d(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{b as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,p as summarySystemValue,h as totalCleanFloorAreaM2};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{n as t,b as n}from"./zigbee-topology-torI07nL.js";import{B as e}from"./houseplan-card-DqlR1H6U.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{n as t,b as n}from"./zigbee-topology-CQT90rCw.js";import{F as e}from"./houseplan-card-CrTmJZ8b.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";try{await import("./houseplan-assets/houseplan-card-DqlR1H6U.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 houseplan-card.js: the implementation chunk is unavailable",e)}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";try{await import("./houseplan-assets/houseplan-card-CrTmJZ8b.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 houseplan-card.js: the implementation chunk is unavailable",e)}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -142,7 +142,7 @@ try {
|
||||
longTasks: await longTasks.stop(),
|
||||
};
|
||||
};
|
||||
// Current candidates split Stage 3 out of the initial graph. Comparison
|
||||
// Current candidates split the experimental Iso runtime out of the initial graph. Comparison
|
||||
// bundles remain valid: their older monolithic renderer has no preload
|
||||
// hook and is already available synchronously.
|
||||
const ensureIsoRuntime = async (card) => {
|
||||
@@ -249,11 +249,11 @@ try {
|
||||
const missingKinds = expectedKinds.filter((kind) => !snapshot.overlayKinds.includes(kind));
|
||||
const failures = [];
|
||||
if (snapshot.effectiveProjection !== 'iso') failures.push('effective projection is not iso');
|
||||
if (snapshot.isoStageRevision !== '3') failures.push('Stage 3 revision marker is absent');
|
||||
if (snapshot.isoStageRevision !== '4') failures.push('Stage 4 revision marker is absent');
|
||||
if (snapshot.structuralBuilds == null) failures.push('structural build counter is absent');
|
||||
if (snapshot.renderedOverlayCount < 1) failures.push('no raised overlay roots rendered');
|
||||
if (snapshot.raisedOverlayCount !== snapshot.renderedOverlayCount)
|
||||
failures.push('one or more Stage 3 overlay roots remained on the floor plane');
|
||||
failures.push('one or more Stage 4 overlay roots remained outside the low visual plane');
|
||||
if (snapshot.nudgedOverlayCount < 1) failures.push('dense fixture produced no bounded nudge');
|
||||
if (snapshot.raisedVacuumCount !== 0) failures.push('floor-bound vacuum was marked as raised');
|
||||
if (missingKinds.length) failures.push(`missing overlay kinds: ${missingKinds.join(', ')}`);
|
||||
@@ -262,11 +262,11 @@ try {
|
||||
const stage3 = snapshot.stage3RootsByKind[kind];
|
||||
if (total < 1) failures.push(`no rendered ${kind} roots`);
|
||||
else if (stage3 !== total)
|
||||
failures.push(`${kind} Stage 3 roots ${stage3}/${total}`);
|
||||
failures.push(`${kind} Stage 4 roots ${stage3}/${total}`);
|
||||
}
|
||||
for (const kind of fixture.stage3Dense.expectedOpeningKinds) {
|
||||
if (!(snapshot.openingSurfaceCounts[kind] > 0))
|
||||
failures.push(`no Stage 3 ${kind} opening surfaces`);
|
||||
failures.push(`no Stage 4 ${kind} opening surfaces`);
|
||||
}
|
||||
for (const [facet, count] of Object.entries(snapshot.facetCounts)) {
|
||||
if (!(count > 0)) failures.push(`no observable ${facet} facet${facet === 'pulse'
|
||||
@@ -280,7 +280,7 @@ try {
|
||||
if (!(snapshot.definitionCounts.patterns > 0)) failures.push('no shared texture pattern');
|
||||
if (!(snapshot.definitionCounts.filters > 0)) failures.push('no shared shadow filter');
|
||||
if (failures.length)
|
||||
throw new Error(`Stage 3 ${label} contract failed: ${failures.join('; ')}`);
|
||||
throw new Error(`Stage 4 ${label} contract failed: ${failures.join('; ')}`);
|
||||
};
|
||||
|
||||
window.__card?.remove?.();
|
||||
@@ -494,7 +494,7 @@ try {
|
||||
throw new Error(`${profile} performed a structural rebuild for an HA-only state update`);
|
||||
}
|
||||
if (requireStage3 && afterStateStage3.structuralBuilds !== steadyStage3.structuralBuilds)
|
||||
throw new Error(`${profile} rebuilt Stage 3 structure for an HA-only state update`);
|
||||
throw new Error(`${profile} rebuilt Stage 4 structure for an HA-only state update`);
|
||||
|
||||
let stage3States = nextStates;
|
||||
const openingUpdate = stage3Dense ? await duration(async () => {
|
||||
@@ -512,7 +512,7 @@ try {
|
||||
if (stage3Dense && requiresIsometric && afterOpeningStage3?.effectiveProjection !== 'iso')
|
||||
throw new Error(`${profile} entered Flat fallback during an opening update`);
|
||||
if (requireStage3 && afterOpeningStage3.structuralBuilds !== steadyStage3.structuralBuilds)
|
||||
throw new Error(`${profile} rebuilt Stage 3 structure for an opening update`);
|
||||
throw new Error(`${profile} rebuilt Stage 4 structure for an opening update`);
|
||||
|
||||
const overlayInteraction = stage3Dense ? await duration(async () => {
|
||||
const overlay = card.renderRoot.querySelector(requireStage3
|
||||
@@ -538,7 +538,7 @@ try {
|
||||
throw new Error(`${profile} entered Flat fallback during a hover/focus update`);
|
||||
if (requireStage3
|
||||
&& afterInteractionStage3.structuralBuilds !== steadyStage3.structuralBuilds) {
|
||||
throw new Error(`${profile} rebuilt Stage 3 structure for a hover/focus update`);
|
||||
throw new Error(`${profile} rebuilt Stage 4 structure for a hover/focus update`);
|
||||
}
|
||||
|
||||
let interactionDiagnostics = null;
|
||||
|
||||
+26
-26
@@ -612,7 +612,7 @@ export function prepareGoldenFixture(scenario) {
|
||||
const space = requireSpace();
|
||||
if (contract.roomMetrics) {
|
||||
if (space.id !== 'golden-lighting' || space.rooms.length !== 2)
|
||||
throw new Error(`golden Stage 3 metrics require lighting fixture: ${space.id}`);
|
||||
throw new Error(`golden Stage 4 metrics require lighting fixture: ${space.id}`);
|
||||
space.settings = {
|
||||
...(space.settings || {}), show_names: true,
|
||||
label_temp: true, label_hum: true, label_lqi: true, label_light: true,
|
||||
@@ -625,7 +625,7 @@ export function prepareGoldenFixture(scenario) {
|
||||
}
|
||||
if (contract.newDevice) {
|
||||
if (!fixture.devices?.[contract.newDevice])
|
||||
throw new Error(`golden Stage 3 new marker is missing: ${contract.newDevice}`);
|
||||
throw new Error(`golden Stage 4 new marker is missing: ${contract.newDevice}`);
|
||||
fixture.config.settings = {
|
||||
...(fixture.config.settings || {}), new_device_ids: [contract.newDevice],
|
||||
};
|
||||
@@ -633,14 +633,14 @@ export function prepareGoldenFixture(scenario) {
|
||||
if (contract.lockOpening) {
|
||||
const opening = (space.openings || []).find((item) => item.id === contract.lockOpening);
|
||||
if (!opening || !['door', 'gate'].includes(opening.type))
|
||||
throw new Error(`golden Stage 3 lock opening is missing: ${contract.lockOpening}`);
|
||||
throw new Error(`golden Stage 4 lock opening is missing: ${contract.lockOpening}`);
|
||||
if (!['locked', 'unlocked'].includes(contract.lockState))
|
||||
throw new Error(`golden Stage 3 lock state is invalid: ${contract.lockState}`);
|
||||
throw new Error(`golden Stage 4 lock state is invalid: ${contract.lockState}`);
|
||||
const entityId = `lock.golden_stage3_${contract.lockOpening.replaceAll('-', '_')}`;
|
||||
opening.lock = entityId;
|
||||
fixture.states[entityId] = {
|
||||
entity_id: entityId, state: contract.lockState,
|
||||
attributes: { friendly_name: `Golden Stage 3 ${contract.lockOpening} lock` },
|
||||
attributes: { friendly_name: `Golden Stage 4 ${contract.lockOpening} lock` },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1264,9 +1264,9 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
const stage = card.renderRoot.querySelector('.stage');
|
||||
const structuralBuilds = Number(stage?.getAttribute('data-hp-iso-structural-builds'));
|
||||
if (!scenario.stage3Golden?.noBorders
|
||||
&& (stage?.getAttribute('data-hp-iso-stage') !== '3'
|
||||
&& (stage?.getAttribute('data-hp-iso-stage') !== '4'
|
||||
|| !Number.isFinite(structuralBuilds) || structuralBuilds < 1)) {
|
||||
throw new Error(`isometric golden did not render the Stage 3 contract: ${scenario.id}`);
|
||||
throw new Error(`isometric golden did not render the Stage 4 contract: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
if (scenario.stage3Golden) {
|
||||
@@ -1275,7 +1275,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
const renderedStage = card.renderRoot.querySelector('.stage');
|
||||
if (card.hass?.themes?.darkMode !== expectedDark
|
||||
|| !renderedStage?.classList.contains(expectedDark ? 'theme-dark' : 'theme-light')) {
|
||||
throw new Error(`Stage 3 golden did not use HA darkMode: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 golden did not use HA darkMode: ${scenario.id}`);
|
||||
}
|
||||
const raisedRoots = [...card.renderRoot.querySelectorAll(
|
||||
'[data-hp-iso-overlay-kind][data-hp-iso-floor][data-hp-iso-visual]',
|
||||
@@ -1286,42 +1286,42 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
if (contract.noBorders) {
|
||||
if (raisedRoots.length || raisedSvg
|
||||
|| card.renderRoot.querySelector('[data-hp-iso-raised="true"]')) {
|
||||
throw new Error(`Stage 3 no-borders golden retained raised overlays: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 no-borders golden retained low overlays: ${scenario.id}`);
|
||||
}
|
||||
} else {
|
||||
const missing = (contract.requiredKinds || []).filter((kind) => !raisedKinds.has(kind));
|
||||
if (missing.length) {
|
||||
throw new Error(`Stage 3 golden is missing raised kinds ${missing.join(',')}: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 golden is missing low-overlay kinds ${missing.join(',')}: ${scenario.id}`);
|
||||
}
|
||||
if (!raisedRoots.length
|
||||
|| raisedRoots.some((root) => root.getAttribute('data-hp-iso-raised') !== 'true')) {
|
||||
throw new Error(`Stage 3 golden left an interactive overlay on the floor: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 golden lost the corrected low visual plane: ${scenario.id}`);
|
||||
}
|
||||
if (contract.requireNudged
|
||||
&& !raisedRoots.some((root) => root.getAttribute('data-hp-iso-nudged') === 'true')) {
|
||||
throw new Error(`Stage 3 golden fixture produced no bounded nudge: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 golden fixture produced no bounded nudge: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
const materialDefs = card.renderRoot.querySelectorAll('[data-hp-iso-material-def]').length;
|
||||
if (contract.requireMaterialDefs
|
||||
&& (!(materialDefs > 0) || materialDefs > contract.maxMaterialDefs)) {
|
||||
throw new Error(`Stage 3 golden material definitions ${materialDefs} are outside 1..${contract.maxMaterialDefs}: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 golden material definitions ${materialDefs} are outside 1..${contract.maxMaterialDefs}: ${scenario.id}`);
|
||||
}
|
||||
if (contract.requireNoMaterialDefs && materialDefs !== 0) {
|
||||
throw new Error(`Stage 3 solid fallback retained material definitions: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 solid fallback retained material definitions: ${scenario.id}`);
|
||||
}
|
||||
if (contract.requireTetherCues) {
|
||||
const tethers = [...card.renderRoot.querySelectorAll('.iso-overlay-tether')];
|
||||
if (contract.requireNoTetherCues) {
|
||||
const tether = card.renderRoot.querySelector('.iso-overlay-tether');
|
||||
const paintedFootprint = card.renderRoot.querySelector(
|
||||
'.iso-overlay-plate, .iso-overlay-plate-texture, #hp-iso-overlay-texture',
|
||||
);
|
||||
if (paintedFootprint || !tethers.length) {
|
||||
throw new Error(`Stage 3 golden lost invisible-footprint/tether contract: ${scenario.id}`);
|
||||
if (paintedFootprint || tether) {
|
||||
throw new Error(`Stage 4 golden retained a debug footprint/tether cue: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
if (contract.requireGrounding
|
||||
&& card.renderRoot.querySelectorAll('.iso-overlay-ground').length < raisedRoots.length) {
|
||||
throw new Error(`Stage 3 golden lost persistent grounding cues: ${scenario.id}`);
|
||||
if (contract.requireNoGrounding
|
||||
&& card.renderRoot.querySelector('.iso-overlay-ground, #hp-iso-overlay-ground')) {
|
||||
throw new Error(`Stage 4 golden retained a per-marker ground cue: ${scenario.id}`);
|
||||
}
|
||||
if (contract.requireDenseFacets) {
|
||||
const facets = {
|
||||
@@ -1333,7 +1333,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
};
|
||||
const missing = Object.entries(facets).filter(([, count]) => !(count > 0));
|
||||
if (missing.length) {
|
||||
throw new Error(`Stage 3 dense golden lacks ${missing.map(([name]) => name).join(',')}: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 dense golden lacks ${missing.map(([name]) => name).join(',')}: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
if (contract.openingKinds) {
|
||||
@@ -1342,7 +1342,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
)].map((node) => node.getAttribute('data-kind')).filter(Boolean));
|
||||
const missing = contract.openingKinds.filter((kind) => !renderedKinds.has(kind));
|
||||
if (missing.length) {
|
||||
throw new Error(`Stage 3 opening golden is missing ${missing.join(',')}: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 opening golden is missing ${missing.join(',')}: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
if (contract.requirePassage) {
|
||||
@@ -1350,7 +1350,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
|| card.renderRoot.querySelector(
|
||||
'[data-hp="iso-openings"] .iso-opening-panel[data-kind="passage"]',
|
||||
)) {
|
||||
throw new Error(`Stage 3 passage golden violated negative-volume contract: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 passage golden violated negative-volume contract: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
if (contract.requireVacuumFloor) {
|
||||
@@ -1359,7 +1359,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
if (!vacuumRoots.length || vacuumRoots.some((root) =>
|
||||
root.getAttribute('data-hp-iso-raised') === 'true'
|
||||
|| root.hasAttribute('data-hp-iso-overlay-kind'))) {
|
||||
throw new Error(`Stage 3 golden raised a floor-bound vacuum: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 golden raised a floor-bound vacuum: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2118,7 +2118,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
};
|
||||
const missing = Object.entries(live).filter(([, count]) => !(count > 0));
|
||||
if (missing.length) {
|
||||
throw new Error(`Stage 3 combined golden lacks ${missing.map(([name]) => name).join(',')}: ${scenario.id}`);
|
||||
throw new Error(`Stage 4 combined golden lacks ${missing.map(([name]) => name).join(',')}: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
let panelHost = null;
|
||||
|
||||
@@ -296,8 +296,8 @@ export const GOLDEN_SCENARIOS = Object.freeze([
|
||||
requireMaterialDefs: true, maxMaterialDefs: 12,
|
||||
requireVacuumFloor: theme === 'dark',
|
||||
requireDenseFacets: true,
|
||||
requireTetherCues: true,
|
||||
requireGrounding: true,
|
||||
requireNoTetherCues: true,
|
||||
requireNoGrounding: true,
|
||||
requireLiveLayers: theme === 'dark',
|
||||
},
|
||||
markerOverrides: stage3DenseMarkers,
|
||||
@@ -350,7 +350,7 @@ export const GOLDEN_SCENARIOS = Object.freeze([
|
||||
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
|
||||
stage3Golden: {
|
||||
requiredKinds: stage3RequiredOverlays, requireNudged: true,
|
||||
requireNoMaterialDefs: true, requireTetherCues: true,
|
||||
requireNoMaterialDefs: true, requireNoTetherCues: true, requireNoGrounding: true,
|
||||
},
|
||||
layoutOverrides: stage3DenseLayout,
|
||||
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
|
||||
@@ -360,7 +360,7 @@ export const GOLDEN_SCENARIOS = Object.freeze([
|
||||
stage3Fixture: { roomMetrics: true, lockOpening: 'light-door', lockState: 'locked' },
|
||||
stage3Golden: {
|
||||
requiredKinds: stage3RequiredOverlays, requireNudged: true,
|
||||
requireNoMaterialDefs: true, requireTetherCues: true,
|
||||
requireNoMaterialDefs: true, requireNoTetherCues: true, requireNoGrounding: true,
|
||||
},
|
||||
layoutOverrides: stage3DenseLayout,
|
||||
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
|
||||
|
||||
@@ -62,10 +62,12 @@ const requireReport = (report, budgets, label, expectedSha = null) => {
|
||||
throw new Error(`${label}: Iso structural build count is missing or changed on HA-only update`);
|
||||
}
|
||||
if (label === 'candidate' && report.profile === 'isometric-stage3-dense-v1') {
|
||||
if (report.stage3Required !== true) throw new Error(`${label}: Stage 3 was not required`);
|
||||
if (!sameJson(report.isoStageRevision, ['3'])
|
||||
|| report.rows.some((row) => row.isoStageRevision !== '3')) {
|
||||
throw new Error(`${label}: Stage 3 revision metadata is incomplete`);
|
||||
// The profile/schema name is intentionally stable for baseline continuity;
|
||||
// the candidate DOM revision tracks the current experimental Stage 4.
|
||||
if (report.stage3Required !== true) throw new Error(`${label}: Stage 4 was not required`);
|
||||
if (!sameJson(report.isoStageRevision, ['4'])
|
||||
|| report.rows.some((row) => row.isoStageRevision !== '4')) {
|
||||
throw new Error(`${label}: Stage 4 revision metadata is incomplete`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// #122/#160 Stage 3: alpha lifecycle, structural composition and flat editor boundary.
|
||||
// #122/#160/#570 Stage 4: alpha lifecycle, visual handoff and flat editor boundary.
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
|
||||
const { page, browser } = await launch({ width: 1000, height: 850 });
|
||||
@@ -125,7 +125,7 @@ const out = await page.evaluate(async () => {
|
||||
result.isoRendered = isoToggle?.getAttribute('aria-pressed') === 'true'
|
||||
&& !!root().querySelector('[data-hp="iso-underlay"] .iso-floor-side')
|
||||
&& !!root().querySelector('[data-hp="iso-walls"] .iso-wall-top');
|
||||
result.stage3RevisionAdvertised = stage?.getAttribute('data-hp-iso-stage') === '3';
|
||||
result.stage4RevisionAdvertised = stage?.getAttribute('data-hp-iso-stage') === '4';
|
||||
result.sharedProjectionSnapshot = [
|
||||
'.iso-underlay-svg', '.plan-svg', '.iso-shadows-svg', '.iso-walls-svg', '.iso-overlays-svg',
|
||||
]
|
||||
@@ -135,52 +135,49 @@ const out = await page.evaluate(async () => {
|
||||
const requiredDefs = [
|
||||
'hp-iso-wall-side', 'hp-iso-wall-top', 'hp-iso-wall-texture',
|
||||
'hp-iso-floor-texture', 'hp-iso-ambient-shadow', 'hp-iso-contact-shadow',
|
||||
'hp-iso-leaf-shadow', 'hp-iso-overlay-ground',
|
||||
'hp-iso-leaf-shadow',
|
||||
];
|
||||
result.stage3DefinitionsBounded = materialDefs.length >= requiredDefs.length
|
||||
result.stage4DefinitionsBounded = materialDefs.length >= requiredDefs.length
|
||||
&& materialDefs.length <= 16
|
||||
&& new Set(materialDefs.map((node) => node.id)).size === materialDefs.length
|
||||
&& requiredDefs.every((id) => materialDefs.some((node) => node.id === id));
|
||||
const shadowNodes = [...root().querySelectorAll(
|
||||
'.iso-ambient-shadow, .iso-contact-shadow, .iso-leaf-shadow, .iso-overlay-ground',
|
||||
'.iso-ambient-shadow, .iso-contact-shadow, .iso-leaf-shadow',
|
||||
)];
|
||||
const fixedLightTransform = card._isoSceneRuntime.isoFixedLightTransform(card._cellCm);
|
||||
result.fixedLightVectorShared = shadowNodes.some((node) => node.matches('.iso-overlay-ground'))
|
||||
&& ['iso-ambient-shadow', 'iso-contact-shadow', 'iso-leaf-shadow', 'iso-overlay-ground']
|
||||
result.fixedLightVectorShared = ['iso-ambient-shadow', 'iso-contact-shadow', 'iso-leaf-shadow']
|
||||
.every((name) => shadowNodes.some((node) => node.classList.contains(name)))
|
||||
&& shadowNodes.every((node) => node.getAttribute('transform') === fixedLightTransform);
|
||||
const hasOpeningSurface = (id, surface, material) => !!root().querySelector(
|
||||
`[data-hp="iso-openings"] [data-id="${id}"][data-surface="${surface}"].iso-material-${material}`,
|
||||
);
|
||||
result.stage3OpeningMaterials = [
|
||||
result.stage4OpeningMaterials = [
|
||||
['iso-centred-door', 'jamb-reveal', 'reveal'],
|
||||
['iso-centred-door', 'leaf-front', 'matte-leaf'],
|
||||
['iso-centred-door', 'leaf-back', 'matte-leaf'],
|
||||
['iso-centred-door', 'leaf-edge', 'matte-leaf'],
|
||||
['iso-centred-door', 'leaf-top', 'matte-leaf'],
|
||||
['iso-flipped-window', 'window-insert', 'light-window'],
|
||||
['iso-flipped-window', 'window-frame-side', 'light-frame'],
|
||||
['iso-flipped-window', 'window-frame-top', 'light-frame'],
|
||||
['iso-flipped-window', 'window-sill', 'light-sill'],
|
||||
['iso-flipped-window', 'window-sash-side', 'light-frame'],
|
||||
['iso-flipped-window', 'window-sash-bottom', 'light-frame'],
|
||||
['iso-flipped-window', 'window-sash-top', 'light-frame'],
|
||||
['iso-flipped-window', 'window-glass', 'glass-side'],
|
||||
['iso-flipped-window', 'window-glass-top', 'glass-top'],
|
||||
['iso-centred-gate', 'jamb-reveal', 'reveal'],
|
||||
['iso-centred-gate', 'leaf-front', 'matte-leaf'],
|
||||
].every(([id, surface, material]) => hasOpeningSurface(id, surface, material))
|
||||
&& !root().querySelector(
|
||||
'[data-hp="iso-openings"] [data-surface*="glass"],'
|
||||
+ '[data-hp="iso-openings"] .iso-material-dark-glass',
|
||||
);
|
||||
&& !root().querySelector('[data-hp="iso-openings"] .iso-material-dark-glass');
|
||||
const raisedRoot = (node, kind) => node?.getAttribute('data-hp-iso-overlay-kind') === kind
|
||||
&& node.getAttribute('data-hp-iso-raised') === 'true'
|
||||
&& node.getAttribute('data-hp-iso-floor')
|
||||
&& node.getAttribute('data-hp-iso-visual')
|
||||
&& node.getAttribute('data-hp-iso-floor') !== node.getAttribute('data-hp-iso-visual');
|
||||
result.stage3RaisesExactInteractiveRoots = raisedRoot(device, 'device')
|
||||
result.stage4UsesExactLowInteractiveRoots = raisedRoot(device, 'device')
|
||||
&& raisedRoot(roomLabel, 'room-label')
|
||||
&& raisedRoot(openingLock, 'opening-lock')
|
||||
&& ['device', 'room-label', 'opening-lock'].every((kind) => root().querySelector(
|
||||
`[data-hp="iso-raised-overlays"] .iso-overlay-tether`
|
||||
+ `[data-hp-iso-overlay-kind="${kind}"]`,
|
||||
));
|
||||
&& !root().querySelector('.iso-overlay-tether, .iso-overlay-ground, #hp-iso-overlay-ground');
|
||||
result.raisedFootprintsStayInvisible = !root().querySelector(
|
||||
'.iso-overlay-plate, .iso-overlay-plate-texture, #hp-iso-overlay-texture',
|
||||
);
|
||||
@@ -192,31 +189,12 @@ const out = await page.evaluate(async () => {
|
||||
const raisedNodes = [...root().querySelectorAll('[data-hp-iso-raised="true"]')]
|
||||
.filter((node) => node.closest('.devlayer'));
|
||||
const raisedGeometryMatches = (node) => {
|
||||
const kind = node.getAttribute('data-hp-iso-overlay-kind');
|
||||
const id = node.getAttribute('data-id');
|
||||
const suffix = id ? `[data-id="${CSS.escape(id)}"]` : '';
|
||||
const visual = (node.getAttribute('data-hp-iso-visual') || '').split(',').map(Number);
|
||||
const floorPoint = (node.getAttribute('data-hp-iso-floor') || '').split(',').map(Number);
|
||||
if (visual.length !== 2 || floorPoint.length !== 2) return false;
|
||||
const expected = sceneToClient(visual), actual = center(node);
|
||||
const ground = root().querySelector(
|
||||
`[data-hp="iso-overlay-grounds"] [data-hp-iso-overlay-kind="${kind}"]${suffix}`,
|
||||
) || root().querySelector(
|
||||
`[data-hp="iso-overlay-grounds"] [data-hp-iso-overlay-kind="${kind}"]`,
|
||||
);
|
||||
const tether = root().querySelector(
|
||||
`[data-hp="iso-raised-overlays"] .iso-overlay-tether[data-hp-iso-overlay-kind="${kind}"]${suffix}`,
|
||||
) || root().querySelector(
|
||||
`[data-hp="iso-raised-overlays"] .iso-overlay-tether[data-hp-iso-overlay-kind="${kind}"]`,
|
||||
);
|
||||
return Math.hypot(actual[0] - expected[0], actual[1] - expected[1]) <= 1
|
||||
&& !!ground && Math.hypot(Number(ground.getAttribute('cx')) - floorPoint[0],
|
||||
Number(ground.getAttribute('cy')) - floorPoint[1]) <= 1e-6
|
||||
&& !!tether
|
||||
&& Math.hypot(Number(tether.getAttribute('x1')) - floorPoint[0],
|
||||
Number(tether.getAttribute('y1')) - floorPoint[1]) <= 1e-6
|
||||
&& Math.hypot(Number(tether.getAttribute('x2')) - visual[0],
|
||||
Number(tether.getAttribute('y2')) - visual[1]) <= 1e-6;
|
||||
&& Math.hypot(floorPoint[0] - visual[0], floorPoint[1] - visual[1]) > 0;
|
||||
};
|
||||
result.globalFitContainsRaisedFootprints = raisedNodes.length >= 3
|
||||
&& raisedNodes.every((node) => inside(root().querySelector('.stage'), node));
|
||||
@@ -384,10 +362,10 @@ const out = await page.evaluate(async () => {
|
||||
&& !!root().querySelector('[data-hp="iso-walls"] .iso-wall-top')
|
||||
&& !!root().querySelector('[data-hp="iso-openings"] .iso-opening-panel')
|
||||
&& !root().querySelector('.iso-overlay-plate, .iso-overlay-plate-texture')
|
||||
&& !!root().querySelector('[data-hp="iso-raised-overlays"] .iso-overlay-tether')
|
||||
&& !root().querySelector('.iso-overlay-tether, .iso-overlay-ground, #hp-iso-overlay-ground')
|
||||
&& !root().querySelector('[data-hp-iso-material-def]')
|
||||
&& !root().querySelector('.iso-ambient-shadow, .iso-contact-shadow,'
|
||||
+ ' .iso-leaf-shadow, .iso-overlay-ground');
|
||||
+ ' .iso-leaf-shadow');
|
||||
CSS.supports = nativeCssSupports;
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// #122/#160 Stage 3: live-floor preservation, raised overlays, openings and touch paths.
|
||||
// #122/#160/#570 Stage 4: live-floor preservation, low overlays, openings and touch paths.
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
|
||||
const { page, browser } = await launch(
|
||||
@@ -56,7 +56,7 @@ const out = await page.evaluate(async () => {
|
||||
},
|
||||
});
|
||||
// Put one real interactive device almost on the shared wall so this smoke
|
||||
// always owns a deterministic non-zero nudge/tether witness.
|
||||
// always owns a deterministic non-zero nudge witness.
|
||||
original._layout.d_light1 = { ...original._layout.d_light1, s: 'f1', x: 0.548, y: 0.22 };
|
||||
original._layout['iso-vacuum'] = { s: 'f1', x: 0.18, y: 0.78 };
|
||||
original._hoverRoom = { space: 'f1', room: original._spaceModel('f1').rooms[0] };
|
||||
@@ -188,12 +188,10 @@ const out = await page.evaluate(async () => {
|
||||
];
|
||||
const overlaySvg = root(original).querySelector('.iso-overlays-svg');
|
||||
const wallSvg = root(original).querySelector('.iso-walls-svg');
|
||||
const overlayGround = root(original).querySelector('[data-hp="iso-overlay-grounds"]');
|
||||
const overlayGroundFilter = root(original).querySelector('#hp-iso-overlay-ground');
|
||||
const doorResolved = original._openingsR.find((opening) => opening.id === 'iso-door');
|
||||
const isoFloorMatrix = (root(original).querySelector('.iso-floor-scene')
|
||||
?.getAttribute('transform')?.match(/-?[\d.e+]+/gi) || []).map(Number);
|
||||
const wallHeight = 64 * (5 / original._cellCm);
|
||||
const wallHeight = 84 * (5 / original._cellCm);
|
||||
const openingTopPoint = doorResolved && isoFloorMatrix.length === 6
|
||||
? new DOMPoint(
|
||||
isoFloorMatrix[0] * doorResolved.rx + isoFloorMatrix[2] * doorResolved.ry + isoFloorMatrix[4],
|
||||
@@ -206,7 +204,7 @@ const out = await page.evaluate(async () => {
|
||||
&& ![...root(original).querySelectorAll('.iso-wall-top')]
|
||||
.some((path) => path.isPointInFill(openingTopPoint));
|
||||
const result = {
|
||||
isoOnTouch: root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '3'
|
||||
isoOnTouch: root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '4'
|
||||
&& !!root(original).querySelector('[data-hp="iso-walls"]'),
|
||||
flatIsoLayerParity: JSON.stringify({ ...flat, openings: 0, verticalOpenings: before.verticalOpenings })
|
||||
=== JSON.stringify(before)
|
||||
@@ -225,10 +223,11 @@ const out = await page.evaluate(async () => {
|
||||
&& ordered.slice(1).every((node, index) => Boolean(
|
||||
ordered[index].compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING
|
||||
)),
|
||||
overlayGroundLivesAboveWallsWithDefs: overlayGround?.closest('svg') === overlaySvg
|
||||
&& overlayGroundFilter?.closest('svg') === overlaySvg
|
||||
&& root(original).querySelectorAll('#hp-iso-overlay-ground').length === 1
|
||||
&& Boolean(wallSvg?.compareDocumentPosition(overlayGround) & Node.DOCUMENT_POSITION_FOLLOWING),
|
||||
overlaysStayAboveWallsWithoutDebugGrounds: !!overlaySvg && !!wallSvg
|
||||
&& Boolean(wallSvg.compareDocumentPosition(overlaySvg) & Node.DOCUMENT_POSITION_FOLLOWING)
|
||||
&& !root(original).querySelector(
|
||||
'.iso-overlay-ground, .iso-overlay-tether, #hp-iso-overlay-ground',
|
||||
),
|
||||
haColorUpdatePainted: [...root(original).querySelectorAll('radialGradient stop')]
|
||||
.some((stop) => stop.getAttribute('stop-color') === '#ff4020'),
|
||||
sameWallFingerprint: root(original).querySelector('[data-hp="iso-walls"]')?.dataset.fingerprint === wallFingerprint,
|
||||
@@ -295,10 +294,12 @@ const out = await page.evaluate(async () => {
|
||||
const noBordersRoom = root(original).querySelector('[data-hp="room-label"]');
|
||||
const noBordersLock = root(original).querySelector('.oplock');
|
||||
result.noBordersUsesTrueAffineFloor = original._effectiveProjection() === 'iso'
|
||||
&& root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '3'
|
||||
&& root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '4'
|
||||
&& root(original).querySelector('.plan-svg')?.getAttribute('data-hp-live-viewbox') === 'camera'
|
||||
&& floorMatrix.length === 6 && floorMatrix.every(Number.isFinite)
|
||||
&& (Math.abs(floorMatrix[1]) > 1e-6 || Math.abs(floorMatrix[2]) > 1e-6)
|
||||
&& Math.abs(floorMatrix[0] - 1) < 1e-6
|
||||
&& Math.abs(floorMatrix[1]) < 1e-6 && Math.abs(floorMatrix[2]) < 1e-6
|
||||
&& Math.abs(floorMatrix[3] - Math.cos(20 * Math.PI / 180)) < 1e-6
|
||||
&& root(original).querySelectorAll('[data-hp="opening"]').length === 3
|
||||
&& [noBordersDevice, noBordersRoom, noBordersLock].every((node) => node
|
||||
&& !node.hasAttribute('data-hp-iso-raised'));
|
||||
@@ -329,9 +330,9 @@ const out = await page.evaluate(async () => {
|
||||
original._cfgEpoch++;
|
||||
original.requestUpdate();
|
||||
await original.updateComplete;
|
||||
result.visibleBordersRestoreStage3 = !!root(original).querySelector('[data-hp="iso-openings"]')
|
||||
&& !!root(original).querySelector('[data-hp="iso-raised-overlays"]')
|
||||
&& root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '3'
|
||||
result.visibleBordersRestoreStage4 = !!root(original).querySelector('[data-hp="iso-openings"]')
|
||||
&& !!root(original).querySelector('[data-hp-iso-raised="true"]')
|
||||
&& root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '4'
|
||||
&& original._isoGeometryCache.size === 1;
|
||||
|
||||
original._hoverRoom = null;
|
||||
@@ -350,7 +351,7 @@ const out = await page.evaluate(async () => {
|
||||
result.runtimeNudgeNeverPersists = JSON.stringify(original._layout) === floorLayoutBeforeLive
|
||||
&& forbiddenWrites.length === 0 && storageWrites.length === 0
|
||||
&& !!root(original).querySelector('[data-hp-iso-nudged="true"]')
|
||||
&& !!root(original).querySelector('.iso-overlay-tether');
|
||||
&& !root(original).querySelector('.iso-overlay-tether, .iso-overlay-ground');
|
||||
Storage.prototype.setItem = nativeStorageSet;
|
||||
|
||||
const fallbackCalls = [];
|
||||
@@ -365,9 +366,6 @@ const out = await page.evaluate(async () => {
|
||||
await original.updateComplete;
|
||||
const fallbackNudged = root(original).querySelector('[data-hp-iso-nudged="true"][data-id]');
|
||||
const fallbackId = fallbackNudged?.getAttribute('data-id');
|
||||
const fallbackTether = fallbackId && root(original).querySelector(
|
||||
`.iso-overlay-tether[data-id="${CSS.escape(fallbackId)}"]`,
|
||||
);
|
||||
root(original).querySelector('[data-entity="light.ceiling"]')?.click();
|
||||
await frame();
|
||||
result.unsupportedDecorationKeepsIsoStructure = !!root(original).querySelector('[data-hp="iso-walls"]')
|
||||
@@ -377,7 +375,8 @@ const out = await page.evaluate(async () => {
|
||||
result.unsupportedDecorationKeepsFootprintInvisible = !root(original).querySelector(
|
||||
'.iso-overlay-plate, .iso-overlay-plate-texture, #hp-iso-overlay-texture',
|
||||
);
|
||||
result.unsupportedDecorationKeepsTether = !!fallbackTether;
|
||||
result.unsupportedDecorationAddsNoDebugCue = !!fallbackId
|
||||
&& !root(original).querySelector('.iso-overlay-tether, .iso-overlay-ground');
|
||||
result.unsupportedDecorationKeepsActions = fallbackCalls.length === 1;
|
||||
CSS.supports = supports;
|
||||
original.requestUpdate();
|
||||
|
||||
@@ -300,7 +300,7 @@ await page.evaluate(async () => {
|
||||
checks.alphaIsoEnabled = await page.evaluate(() => {
|
||||
const card = document.querySelector('houseplan-card');
|
||||
return card._effectiveProjection() === 'iso'
|
||||
&& card.renderRoot.querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '3';
|
||||
&& card.renderRoot.querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '4';
|
||||
});
|
||||
await runPan({ name: 'isoMouseRightEdge', kind: 'mouse', dx: -65, dy: 0, edge: 'right', parity: true });
|
||||
await runTouchZoomOut({ name: 'isoTouchZoomOut' });
|
||||
|
||||
Vendored
+135
-151
@@ -1,311 +1,295 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091",
|
||||
"fingerprint": "012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 290264,
|
||||
"initialViewGzipBytes": 290253,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 291787,
|
||||
"initialPanelGzipBytes": 291775,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2326,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/de-CqAe7_tE.js",
|
||||
"houseplan-assets/editor-Bj456-QF.js",
|
||||
"houseplan-assets/fr-6DtT8TWD.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-CX3rLqEO.js",
|
||||
"houseplan-assets/iso-scene-render-ByC7EDVa.js",
|
||||
"houseplan-assets/live-interaction-runtime-CO9GuTnQ.js",
|
||||
"houseplan-assets/pdf-export-DtC_EPb5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-sByomcRQ.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/de-CguB1iyB.js",
|
||||
"houseplan-assets/editor-CrFGSaM9.js",
|
||||
"houseplan-assets/fr-C84z_afh.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-D4ABn_qu.js",
|
||||
"houseplan-assets/iso-scene-render-Bgw9qRET.js",
|
||||
"houseplan-assets/live-interaction-runtime-rsvGGxyc.js",
|
||||
"houseplan-assets/pdf-export-BYCIg7JD.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js"
|
||||
],
|
||||
"lazyGzipBytes": 418580,
|
||||
"lazyGzipBytes": 418485,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/editor-Bj456-QF.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/editor-CrFGSaM9.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 205043,
|
||||
"lazyEditorGzipBytes": 204789,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 13916,
|
||||
"lazyOnboardingGzipBytes": 13918,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CqAe7_tE.js",
|
||||
"houseplan-assets/fr-6DtT8TWD.js"
|
||||
"houseplan-assets/de-CguB1iyB.js",
|
||||
"houseplan-assets/fr-C84z_afh.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 54845,
|
||||
"lazyLocaleGzipBytes": 54850,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/iso-scene-render-ByC7EDVa.js"
|
||||
"houseplan-assets/iso-scene-render-Bgw9qRET.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 13455,
|
||||
"lazyIsometricGzipBytes": 13232,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 10254,
|
||||
"lazyFurnitureArtGzipBytes": 10253,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/pdf-export-DtC_EPb5.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/pdf-export-BYCIg7JD.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 120126,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"sha256": "78f118c030ea12bf7100b0bfbdd6fed33c5dc05bf7995ad22a39ec8f7e8f270b",
|
||||
"path": "houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"sha256": "ffe795a2734eb89097af8091262b51382147585e78e15f12aab94ae2f499fa06",
|
||||
"rawBytes": 19956,
|
||||
"gzipBytes": 6879,
|
||||
"gzipBytes": 6882,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CqAe7_tE.js",
|
||||
"sha256": "75472ecaf81e38bda8863c4e33a1eb0df92dbfda73e404d9bc1f3425ba227185",
|
||||
"path": "houseplan-assets/de-CguB1iyB.js",
|
||||
"sha256": "5a0f753ecbc4e88ca984827b000347caa31c258afd78199e0d1763238ff1cfc4",
|
||||
"rawBytes": 91437,
|
||||
"gzipBytes": 27708,
|
||||
"gzipBytes": 27712,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Bj456-QF.js",
|
||||
"sha256": "2f72cb7b627b545f2b064b4d7681f8100fef5d20923114fb98a96601281c6cf2",
|
||||
"rawBytes": 3832,
|
||||
"gzipBytes": 1583,
|
||||
"path": "houseplan-assets/editor-CrFGSaM9.js",
|
||||
"sha256": "5bac1e3c7e6ad73e3118a35eb921af07dc8a80b2fa65ea6dd7225aa168d32992",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1584,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-6DtT8TWD.js",
|
||||
"sha256": "f54edff32a74fcd97767e5b3ae0bbfd04e13c38b08e1cb535067699172b6ca27",
|
||||
"path": "houseplan-assets/fr-C84z_afh.js",
|
||||
"sha256": "4afd0630204be56ece7a382bc60242443f225d32caddff64536d0815443d9da0",
|
||||
"rawBytes": 93701,
|
||||
"gzipBytes": 27137,
|
||||
"gzipBytes": 27138,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"sha256": "12f42b30b0d2c72681bad2928c9d847d31cec487bdb490544e2f1dcaeaad875a",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"sha256": "f50864c15117dcb83594bbe996bd9cddb1ab9c6c2f286ca4c33b2bfb8d03597f",
|
||||
"rawBytes": 33655,
|
||||
"gzipBytes": 10254,
|
||||
"gzipBytes": 10253,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/guard-DBKIUfbp.js",
|
||||
"sha256": "42e87d66b74ad024880fc0aff319092b8539a19b27f6163363514c05a55921ff",
|
||||
"rawBytes": 518,
|
||||
"gzipBytes": 369,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"sha256": "8c136c1fe9a35e06f0547fd196eac3d050eab295a8709e47ea1539c7da8cc774",
|
||||
"rawBytes": 1031164,
|
||||
"gzipBytes": 289461,
|
||||
"path": "houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"sha256": "d9b0cfd20b712bdb93d448b49d73a6cbd7293e694335663595a84987ffc30f48",
|
||||
"rawBytes": 1031295,
|
||||
"gzipBytes": 289449,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CqAe7_tE.js",
|
||||
"houseplan-assets/editor-Bj456-QF.js",
|
||||
"houseplan-assets/fr-6DtT8TWD.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-CX3rLqEO.js",
|
||||
"houseplan-assets/iso-scene-render-ByC7EDVa.js",
|
||||
"houseplan-assets/live-interaction-runtime-CO9GuTnQ.js",
|
||||
"houseplan-assets/pdf-export-DtC_EPb5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js"
|
||||
"houseplan-assets/de-CguB1iyB.js",
|
||||
"houseplan-assets/editor-CrFGSaM9.js",
|
||||
"houseplan-assets/fr-C84z_afh.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-D4ABn_qu.js",
|
||||
"houseplan-assets/iso-scene-render-Bgw9qRET.js",
|
||||
"houseplan-assets/live-interaction-runtime-rsvGGxyc.js",
|
||||
"houseplan-assets/pdf-export-BYCIg7JD.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BjAw4ds1.js",
|
||||
"sha256": "1fe3d26ea0cca0ebc832ce613252e07dac376a695c0d301d1445443b3757ac53",
|
||||
"rawBytes": 668940,
|
||||
"gzipBytes": 178530,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CJrrYe2p.js",
|
||||
"sha256": "c2a2ccf2fcb2d537d38392cd57518b7ea9a83b74b82fe032301af959d2b0b963",
|
||||
"rawBytes": 669271,
|
||||
"gzipBytes": 178646,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-sByomcRQ.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BtzaN0OP.js",
|
||||
"sha256": "1645f0de954897116fe118bd5401620787de2a534cc1ee63031b527d8b53d189",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B9DTOO67.js",
|
||||
"sha256": "097b862d0e93a3acce33966099626b5f881c61a8f9319d53a5c6ab3781620383",
|
||||
"rawBytes": 28287,
|
||||
"gzipBytes": 7037,
|
||||
"gzipBytes": 7036,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-DCbvGxY2.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/backdrop-pick-CGON6yBi.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-CX3rLqEO.js",
|
||||
"sha256": "436d728f938f6c10c6b5e1b1811630b206c4f288ae095030587c6f89c0e34906",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-D4ABn_qu.js",
|
||||
"sha256": "e57a90d6682635233d5ec5c4c09d4821b6d4608507d08cf33a174ad0fbc320a7",
|
||||
"rawBytes": 10756,
|
||||
"gzipBytes": 3679,
|
||||
"gzipBytes": 3686,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-sByomcRQ.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-ByC7EDVa.js",
|
||||
"sha256": "eef97bc8600452c8879051e86055efefae1a4577567db70f462776c100eea579",
|
||||
"rawBytes": 38562,
|
||||
"gzipBytes": 13086,
|
||||
"path": "houseplan-assets/iso-scene-render-Bgw9qRET.js",
|
||||
"sha256": "ad4c67e51d68d05452d872a67705ef12fc2f89f580c589ebb3923275b73ce278",
|
||||
"rawBytes": 39206,
|
||||
"gzipBytes": 13232,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/guard-DBKIUfbp.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-CO9GuTnQ.js",
|
||||
"sha256": "88f116dd07bce29f0803bfcaaf12db5d633c9eba4908bdcb889a5dabe1d4d838",
|
||||
"path": "houseplan-assets/live-interaction-runtime-rsvGGxyc.js",
|
||||
"sha256": "fa87403a675bc45b8ccc3b4ff1ba0ee190c2968cbf4285ddf3259c2a96e1962f",
|
||||
"rawBytes": 8018,
|
||||
"gzipBytes": 3168,
|
||||
"gzipBytes": 3169,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-DtC_EPb5.js",
|
||||
"sha256": "9709712290440090599d72ee1f9c6259adc3cba09d09f5d96e519020c3663102",
|
||||
"path": "houseplan-assets/pdf-export-BYCIg7JD.js",
|
||||
"sha256": "8ac5283781427724de92d360c57433c3e98a4ece3268c479c2107c8802905fc1",
|
||||
"rawBytes": 236244,
|
||||
"gzipBytes": 109872,
|
||||
"gzipBytes": 109873,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CrvWoAvu.js",
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-C_WDX48s.js",
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-CWqNlMOD.js",
|
||||
"sha256": "1364e8033b275cde43cc53a68b9aac6d4c2707d661b0b8dd1bb2a8be550dfab2",
|
||||
"path": "houseplan-assets/summary-panel-editor-B4-T9x1R.js",
|
||||
"sha256": "cfa0ccfa1189264d53ae836ef84bb83d7649648a3ae70bf2b5a3e214f964e85c",
|
||||
"rawBytes": 14824,
|
||||
"gzipBytes": 3398,
|
||||
"gzipBytes": 3402,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-BO0KhesH.js",
|
||||
"sha256": "97b014aab427c7472d8f465891e866f8b1488155c45d2435dff00f60cbb3255d",
|
||||
"path": "houseplan-assets/summary-panel-metrics-oqhGKlnp.js",
|
||||
"sha256": "5d9d0a660d1203d03f30360b3d04dc61403e5cd26dd3dd447b1b8866123bd91e",
|
||||
"rawBytes": 2610,
|
||||
"gzipBytes": 1306,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-ospbRm0g.js",
|
||||
"sha256": "fff8b723ebc1912effbd81d167cd4f8b98517ca9295a8819663d54d5d69acb51",
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-DQykYOsg.js",
|
||||
"sha256": "3a0c8369150e7e947166f7647407fb7dbb7541423bcdd474840357b293e4ef1b",
|
||||
"rawBytes": 73366,
|
||||
"gzipBytes": 20088,
|
||||
"gzipBytes": 20087,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-CWqNlMOD.js",
|
||||
"houseplan-assets/summary-panel-metrics-BO0KhesH.js"
|
||||
"houseplan-assets/summary-panel-editor-B4-T9x1R.js",
|
||||
"houseplan-assets/summary-panel-metrics-oqhGKlnp.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-sByomcRQ.js",
|
||||
"sha256": "13d44dea277a6bb984abbdfa1a9246369e4ef9886c0d28e38ef3ab98327e576d",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1762,
|
||||
"path": "houseplan-assets/zigbee-topology-CQT90rCw.js",
|
||||
"sha256": "c14a3d5b913760cb1bf8797bf8cfe5653e54afee030406bab0ebad771a34c06c",
|
||||
"rawBytes": 18961,
|
||||
"gzipBytes": 7424,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js",
|
||||
"houseplan-assets/zigbee-topology-torI07nL.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-torI07nL.js",
|
||||
"sha256": "7ac30adae966053bc84d05634b32069221b4f8cd58bfe5c6a3ab7da50335e1f6",
|
||||
"rawBytes": 18961,
|
||||
"gzipBytes": 7428,
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-Db3SaXOn.js",
|
||||
"sha256": "a1020eb9133af9fcb6eee0d7630f128b2ef5c3b92596b36072e0543f5fd869d3",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1763,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js",
|
||||
"houseplan-assets/zigbee-topology-CQT90rCw.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "bbcf3569a6edaf8475bebc56429c65d49802923dde9d1fba487068171f6eb9be",
|
||||
"sha256": "c534e03d98617267f28c02c9283c5826cdbc228a895a3be012ea7e58086d4441",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 803,
|
||||
"gzipBytes": 804,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "db358c8fd55babc928f9acb6abe341bc33cd7642e8e60b8ff7894f5c67255c2c",
|
||||
"sha256": "49d5d19ce2cda3a4255946bf4316c8344a1dff48aaaa42a8cd7698a675b6ba21",
|
||||
"rawBytes": 6165,
|
||||
"gzipBytes": 2326,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DqlR1H6U.js"
|
||||
"houseplan-assets/houseplan-card-CrTmJZ8b.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
-14
@@ -1,14 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";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-DqlR1H6U.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=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${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(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);
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{q as e,l as o,v as t,e3 as s,A as a,e4 as i,e5 as l,e6 as n,E as r,c}from"./houseplan-card-CrTmJZ8b.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=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${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(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);
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
-1
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{dS as r,dT as t,dU as e}from"./houseplan-card-DqlR1H6U.js";const s={},a=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return e}else if(this.ot===t)return e;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{a as i};
|
||||
Vendored
+7
-7
File diff suppressed because one or more lines are too long
+751
-751
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{l as t,b0 as s,A as e,t as a,b as o,cj as l,ck as i,cl as h,cm as c,aZ as n,cn as r,cs as p,co as d,cp as _,cq as u,cr as g,ct as m,cu as b,dp as $,dq as v,dr as f,aY as y,a_ as w,ds as D}from"./houseplan-card-DqlR1H6U.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-DCbvGxY2.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";class L{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)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?o`<hp-help data-help-key=${l}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{l as t,b3 as s,A as e,v as a,c as o,cm as l,cn as i,co as h,cp as c,b0 as n,cq as r,cv as p,cr as d,cs as _,ct as u,cu as g,cw as m,cx as b,ds as $,dt as v,du as f,a$ as y,b1 as w,dv as D}from"./houseplan-card-CrTmJZ8b.js";import{c as k,e as S,r as C,a as M}from"./backdrop-pick-CGON6yBi.js";const F=1e3,x=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},T="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";class L{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)}))}}}}_help(l){const i=`${l}.aria`,h=t(this.host.hass,this.host._config?.language);return s(h,l)&&s(h,i)?o`<hp-help data-help-key=${l}
|
||||
.text=${a(h,l)} .ariaLabel=${a(h,i)}></hp-help>`:e}_openSpaceDialog(t,s){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===t){const e=this.host._serverCfg.spaces.find(t=>t.id===s);if(!e)return;const a=l(e),o=e.settings?.custom_fill&&"object"==typeof e.settings.custom_fill?i(e.settings.custom_fill):null,p="none"===a.fill?{...o||h,a:0}:o;return void(this.host._spaceDialog={mode:t,spaceId:s,title:e.title,planUrl:e.plan_url||null,planFile:null,source:e.plan_url?"file":"draw",showBorders:a.showBorders,showNames:a.showNames,zeroWallStyle:r(e),displayTouched:!0,hideDecor:a.hideDecor,hideOpenings:a.hideOpenings,roomColor:a.color,roomOpacity:a.opacity,fillMode:"none"===a.fill?"custom":a.fill,customFill:p,glowEnabled:a.glow,bgColor:a.bgColor,bgMode:"static"===e.settings?.bg_mode||"daynight"===e.settings?.bg_mode?e.settings.bg_mode:null,northDeg:n({},e.settings),sunRays:"boolean"==typeof e.settings?.sun_rays?e.settings.sun_rays:null,tempMin:a.tempMin,tempMax:a.tempMax,showLqi:a.showLqi??this.host._config?.show_signal??!0,cardFontScale:a.cardFontScale,labelTemp:a.labelTemp,labelHum:a.labelHum,labelLqi:a.labelLqi,labelLight:a.labelLight,cellCm:Number(e.cell_cm)>0?Number(e.cell_cm):5,cellCmInput:c(Number(e.cell_cm)>0?Number(e.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const e=p(this.host._imperial);this.host._spaceDialog={mode:t,title:"",planUrl:null,planFile:null,...m(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:g,roomOpacity:u,fillMode:"custom",customFill:{...h,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:_,tempMax:d,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:e,cellCmInput:c(e,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 a=await k(e);if("reject"===a.kind)return void this.host._showToast(this.host._t("toast.plan_formats"));if("guard"===a.kind)return void(this.host._backdropGuard=a.state);const o=await S(e,a.ext,e.name);this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:o})}_renderBackdropGuard(){return C(this.host,t=>{this.host._spaceDialog&&(this.host._spaceDialog={...this.host._spaceDialog,planFile:t})},()=>{this.host._backdropGuard=null},this.host.hass)??e}_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}),a=this.host._spaceDialog;return a&&a.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...a,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){const s=this.host._spaceDialog,e=s?.saved?.find(s=>s.name===t);if(!s||!e||e.used_by.length||e.url===s.planUrl)return;const a=await this.host._confirmDanger({key:"delete-plan",kind:"destructive",title:this.host._t("confirm.delete_plan_title"),message:this.host._t("confirm.delete_plan_body"),objectName:t,confirmLabel:this.host._t("btn.delete"),cancelLabel:this.host._t("btn.cancel")}),o=this.host._spaceDialog,l=o?.saved?.find(s=>s.name===t);if(a&&o&&l&&l.url===e.url&&l.modified===e.modified&&!l.used_by.length&&l.url!==o.planUrl)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 o`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return o`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return o`<div class="savedplans">
|
||||
${s.map(s=>{return o`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
+6
-6
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
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{b as a,A as e}from"./houseplan-card-DqlR1H6U.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-ospbRm0g.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{c as a,A as e}from"./houseplan-card-CrTmJZ8b.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-DQykYOsg.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
|
||||
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
|
||||
<input id="summary-local-show" data-summary-local-show type="checkbox"
|
||||
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{e9 as e,L as t,a2 as n,J as i,an as o,N as r,bo as s,bn as a,a1 as c,a0 as l,ax as d,ea as u,eb as f,bq as m,ec as g}from"./houseplan-card-DqlR1H6U.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=d(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),s=i.binding.slice(o+1),a="device"===r?e.registry.devices?.[s]:null,c="entity"===r?e.registry.entities?.[s]:null,l="device"===r?s:c?.device_id,d="device"===r?a?.area_id:c?.area_id||l&&e.registry.devices?.[l]?.area_id,u="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||d||"",f=u&&e.areaToSpace[u]||i.space||e.firstSpaceId;l&&e.registry.devices?.[l]&&e.spaceIds.has(f)&&(!n.devices.has(l)||"entity"===r&&n.liveEntities.has(s))&&t.add(l)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=c(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:l(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*b(c,l,d=_){try{let u=0;for(const f of l){const l=c.spaces.find(e=>String(e?.id)===f.id);if(!l)continue;const m=e(l,f),g=d(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(c,m.physicalBodies)),yield}const _=m.cellCm/o;u+=a(v)*_*_/1e4}return u}catch{return null}}function h(e,t,n=_){const i=b(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function p(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=u(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{b as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,p as summarySystemValue,h as totalCleanFloorAreaM2};
|
||||
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{e9 as e,P as t,a5 as n,M as i,aq as r,N as o,br as a,bq as s,a4 as l,a3 as c,aA as u,ea as d,eb as f,bt as m,ec as g}from"./houseplan-card-CrTmJZ8b.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const r=String(i.binding||"").indexOf(":");if(r<1)continue;const o=i.binding.slice(0,r),a=i.binding.slice(r+1),s="device"===o?e.registry.devices?.[a]:null,l="entity"===o?e.registry.entities?.[a]:null,c="device"===o?a:l?.device_id,u="device"===o?s?.area_id:l?.area_id||c&&e.registry.devices?.[c]?.area_id,d="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||u||"",f=d&&e.areaToSpace[d]||i.space||e.firstSpaceId;c&&e.registry.devices?.[c]&&e.spaceIds.has(f)&&(!n.devices.has(c)||"entity"===o&&n.liveEntities.has(a))&&t.add(c)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=l(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,r,o);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:c(e.rooms,t.walls,t.openCuts,i,t.cellCm,r,o)}}function*b(l,c,u=_){try{let d=0;for(const f of c){const c=l.spaces.find(e=>String(e?.id)===f.id);if(!c)continue;const m=e(c,f),g=u(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const s=t(e);if(!s)continue;const l=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,r,o,g.roomGeom,g.multiWallNodes)||s;v=y(v,a(l,m.physicalBodies)),yield}const _=m.cellCm/r;d+=s(v)*_*_/1e4}return d}catch{return null}}function h(e,t,n=_){const i=b(e,t,n);let r=i.next();for(;!r.done;)r=i.next();return r.value}function p(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=d(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{b as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,p as summarySystemValue,h as totalCleanFloorAreaM2};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";import{n as t,b as n}from"./zigbee-topology-torI07nL.js";import{B as e}from"./houseplan-card-DqlR1H6U.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";import{n as t,b as n}from"./zigbee-topology-CQT90rCw.js";import{F as e}from"./houseplan-card-CrTmJZ8b.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="74cbeb9792716ab64cfbcf339d3044c6e8ae33533bdef7216a6d7d513ae04091";try{await import("./houseplan-assets/houseplan-card-DqlR1H6U.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 houseplan-card.js: the implementation chunk is unavailable",e)}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="012abf4b3924ad03971870eb8085ab1ac1a66b0d9b6676fe095285a8b0f201ab";try{await import("./houseplan-assets/houseplan-card-CrTmJZ8b.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 houseplan-card.js: the implementation chunk is unavailable",e)}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+29
-22
@@ -28,7 +28,7 @@ houseplan-card/
|
||||
│ ├─ houseplan-editor-runtime.ts # Plan/Devices/Background composition root
|
||||
│ ├─ decor-image-editor.ts # lazy Background/Furniture image palette, upload and properties controller
|
||||
│ ├─ houseplan-onboarding-runtime.ts # first-space/import dialogs, independent of editor
|
||||
│ ├─ iso-scene-render.ts # hidden alpha-only Stage 3 scene/runtime boundary
|
||||
│ ├─ iso-scene-render.ts # hidden alpha-only Stage 4 scene/runtime boundary
|
||||
│ ├─ furniture-art-runtime.ts # page-scoped lazy designer furniture artwork (ready/pending/fallback)
|
||||
│ ├─ pdf/ # lazy read-only A4 scene, writer, dialog and embedded font
|
||||
│ ├─ iso-overlays.ts # pure raised-overlay ownership, collision and nudge
|
||||
@@ -845,20 +845,21 @@ remove nuance/shadows without changing projection; only structural failure
|
||||
uses the Stage 1 latched Flat fallback. Details and fixed ratios are recorded in
|
||||
`docs/adr/122-isometric-stage2-composition.md`.
|
||||
|
||||
### Hidden Isometric Stage 3 spatial overlays (#160)
|
||||
### Hidden Isometric Stage 4 visual handoff (#570)
|
||||
|
||||
Stage 3 keeps the Stage 2 structural scene and moves the fixed camera authority
|
||||
to `rotDeg=4`, `tiltDeg=20`. `isoPlaneMatrix()` is shared by floor content,
|
||||
invisible raised-footprint corners, point projection and inverse floor hit mapping. The
|
||||
projected frame includes the floor edge, wall/opening tops and the raised
|
||||
height; blur/shadow extents never enter camera fit.
|
||||
Stage 4 keeps the Stage 2/3 structural scene and fixes the camera authority at
|
||||
`rotDeg=0`, `tiltDeg=20`, with scale-aware wall height 84. `isoPlaneMatrix()` is
|
||||
shared by floor content, invisible footprint corners, point projection and
|
||||
inverse floor hit mapping. The projected frame includes the floor edge,
|
||||
wall/opening tops and low overlay plane; blur/shadow extents never enter fit.
|
||||
|
||||
`src/iso-overlays.ts` is the pure boundary between floor-bound and raised
|
||||
`src/iso-overlays.ts` is the pure boundary between floor-bound and low-plane
|
||||
presentation. Device roots, room-label/card roots and opening-lock roots keep
|
||||
an immutable floor anchor and receive one computed raised visual anchor. Their
|
||||
conservative floor-parallel footprint stays calculation-only for collision and
|
||||
fit; only the grounding cue and tether are inert SVG. The existing screen-facing
|
||||
HTML root remains the sole hit, focus, tooltip and action target.
|
||||
an immutable floor anchor and receive one computed visual anchor four
|
||||
scale-aware units above the floor. Their conservative floor-parallel footprint
|
||||
stays calculation-only for collision and fit; Stage 4 paints no ground dot,
|
||||
tether, plate or per-marker shadow. The existing screen-facing HTML root remains
|
||||
the sole hit, focus, tooltip and action target.
|
||||
Vacuum, lighting, fills, backdrop and decor continue to consume `z=0`.
|
||||
|
||||
Wall collision consumes projected top and visible-side silhouettes produced
|
||||
@@ -867,28 +868,34 @@ LRU. The pure resolver uses a four CSS-pixel safety gap and a bounded 48
|
||||
CSS-pixel inward search toward a proven owning-room point. Every candidate path
|
||||
must remain strictly inside that room and outside its island holes. It changes
|
||||
only the visual anchor. Ownership failure, invalid wall geometry, an owner
|
||||
boundary or an exhausted cap yields a deterministic tethered placement and no
|
||||
boundary or an exhausted cap yields a deterministic unchanged placement and no
|
||||
write. Room labels use their room, device markers prefer a valid explicit room
|
||||
and otherwise the smallest strictly containing room, and lock badges inherit
|
||||
the physical room side selected by opening-host geometry rather than
|
||||
re-inferring ownership from their offset point.
|
||||
|
||||
Stage 3 opening bases add state-independent full-depth reveals, matte door/gate
|
||||
leaf thickness and light window frame/sill surfaces. Live
|
||||
Stage 4 opening bases retain state-independent full-depth reveals and matte
|
||||
door/gate leaf thickness. Windows use a 0.38H..1.00H fixed frame, a
|
||||
0.40H..0.98H sash and 0.45H..0.93H glass with neutral rails and separate blue
|
||||
side/top glass materials. Live
|
||||
`openingAmount()` still projects leaves after the LRU hit. Passage has no
|
||||
decorative volume. A bounded set of shared material definitions textures only
|
||||
generated 2.5D surfaces and uses one fixed visual-light vector; theme, HA Sun,
|
||||
hover/focus and filter capability remain presentation-only inputs.
|
||||
decorative volume. The shared wall/opening painter queue retains its global
|
||||
screen-depth slots and reorders only one window's existing slots by physical
|
||||
camera depth, preventing a rear sill from covering elevated glass. A bounded
|
||||
set of shared material definitions textures only generated 2.5D surfaces and
|
||||
uses one fixed visual-light vector; theme, HA Sun, hover/focus and filter
|
||||
capability remain presentation-only inputs.
|
||||
|
||||
The DOM exposes fail-closed evidence without becoming public API:
|
||||
`.stage[data-hp-iso-stage="3"]` carries the structural build counter, raised
|
||||
`.stage[data-hp-iso-stage="4"]` carries the structural build counter, low-plane
|
||||
interactive roots identify their overlay kind/raised/nudged state, and shared
|
||||
material definitions carry `data-hp-iso-material-def`. With borders disabled,
|
||||
raised roots and Stage 2/3 volume are absent while the true rotated floor
|
||||
raised roots and Stage 2/3/4 volume are absent while the true affine floor
|
||||
matrix remains active. Forced colours or missing filters strip texture and
|
||||
soft shadows only; topology/projection exceptions alone enter the established
|
||||
Flat fallback latch. Fixed values and the complete decision are recorded in
|
||||
`docs/adr/160-isometric-stage3-overlays.md`.
|
||||
Flat fallback latch. Historical Stage 3 decisions remain in
|
||||
`docs/adr/160-isometric-stage3-overlays.md`; the current reviewed contract and
|
||||
designer handoff are attached to issue #570.
|
||||
|
||||
## Markup editor (v1.4.0+)
|
||||
|
||||
|
||||
+51
-65
@@ -36,11 +36,11 @@ network requests.
|
||||
- `clientToScenePoint()` maps a client point to the current scene; floor hit
|
||||
testing then uses `unprojectFloorPoint()`.
|
||||
|
||||
Stage 1 uses a fixed orthographic affine camera:
|
||||
The current Stage 4 presentation uses a fixed orthographic affine camera:
|
||||
|
||||
```text
|
||||
rotDeg=0, tiltDeg=20, xyScale=1, zScale=1, origin=[500,500]
|
||||
wallHeight=64 plan units
|
||||
wallHeight=84 scale-aware visual units
|
||||
```
|
||||
|
||||
There is no perspective, free rotation or user tilt. Switching projections
|
||||
@@ -188,76 +188,62 @@ still use the Stage 1 latched Flat fallback. The known independent exact-SHA
|
||||
view-toggle performance debt remains tracked in #124; #122 neither weakens its
|
||||
budget nor treats fallback as benchmark success.
|
||||
|
||||
## Stage 3 spatial overlays and materials (#160)
|
||||
## Stage 4 visual handoff (#570)
|
||||
|
||||
Stage 3 evolves the same hidden `iso` presentation behind `hp_alpha`; it adds
|
||||
no public switch, configuration field or separate experiment id. The normative
|
||||
contract is `docs/specs/160-isometric-stage3.md` and the fixed implementation
|
||||
decisions are in `docs/adr/160-isometric-stage3-overlays.md`.
|
||||
Stage 4 refines the same hidden `iso` presentation behind `hp_alpha`; it adds no
|
||||
public switch, configuration field or experiment id. Stage 3 history remains in
|
||||
`docs/specs/160-isometric-stage3.md` and
|
||||
`docs/adr/160-isometric-stage3-overlays.md`; the current acceptance contract is
|
||||
the reviewed specification in issue #570 plus its revision-3 designer handoff.
|
||||
|
||||
### Camera, floor plane and raised plane
|
||||
### Camera and low screen-facing overlays
|
||||
|
||||
The current fixed camera is orthographic `rotDeg=4`, `tiltDeg=20`, with the
|
||||
same `[500,500]` pivot, scale and scale-aware 64-unit wall height. The four
|
||||
degree turn is part of `ISO_CAMERA`; floor SVG, point projection, inverse hit
|
||||
mapping, invisible raised footprints and fit bounds therefore use one affine
|
||||
authority.
|
||||
The camera is orthographic `rotDeg=0`, `tiltDeg=20`, with the same `[500,500]`
|
||||
pivot and scale-aware 84-unit wall height. Floor SVG, wall/opening projection,
|
||||
inverse hit mapping, invisible collision footprints and fit bounds share that
|
||||
one affine authority.
|
||||
|
||||
Device markers (including their badges), room labels/cards and opening lock
|
||||
badges use a raised plane at the scale-aware wall height plus a nominal four
|
||||
visual units. Vacuum puck/trail, Glow/spill, sunlight, room fills/hover,
|
||||
decor/furniture/backdrop and every persisted coordinate stay on the floor
|
||||
plane. A conservative floor-parallel footprint remains internal calculation
|
||||
geometry for collision, nudge and fit, but is never painted. Glyphs, values and
|
||||
text remain screen-facing on the existing HTML roots and retain their actions
|
||||
and minimum touch targets.
|
||||
Device markers, room labels/cards and opening-lock badges keep their canonical
|
||||
floor anchors but render on a low plane four visual units above the floor. Their
|
||||
conservative footprints still participate in deterministic, bounded in-room
|
||||
collision correction and fit. There is no painted plate, long tether, ground
|
||||
dot or per-marker shadow. The original screen-facing HTML root remains the only
|
||||
hit, focus, tooltip and action target, and selection/hover cannot invalidate the
|
||||
placement cache. Vacuum, Glow/spill, SUN, room fills/hover, arbitrary decor,
|
||||
furniture/backdrop and every persisted coordinate remain on `z=0`.
|
||||
|
||||
Each raised item keeps separate immutable floor and visual anchors. A grounding
|
||||
cue stays at the floor anchor. Collision is tested against cached projected wall
|
||||
tops and visible sides, expanded by a four CSS-pixel gap. A deterministic inward
|
||||
nudge may move only the visual anchor by at most 48 CSS pixels; its straight path
|
||||
must stay inside the owning room and outside island holes, and it never writes
|
||||
layout/config/storage. The owning-room resolver prefers an explicit valid owner,
|
||||
otherwise the smallest strictly containing room with a stable id tie-break;
|
||||
opening locks instead inherit the physical host side. Missing or ambiguous
|
||||
ownership degrades to a tethered placement rather than guessing or mutating
|
||||
data. A tether is always retained after nudge or near a wall and while the item
|
||||
is hovered, focused or selected; an idle item in free space may omit it.
|
||||
Room names remain screen-facing and lose stroke, text shadow, drop shadow and
|
||||
halo. Iso uses `#303936` on a light presentation and `#f2f0e8` on a dark one;
|
||||
contrast comes from colour and the bounded position correction, never an
|
||||
outline.
|
||||
|
||||
### Openings, materials and degradation
|
||||
### Openings, occlusion and materials
|
||||
|
||||
The structural cache now also carries jamb/reveal surfaces and deterministic
|
||||
window frame/sill geometry. Door and gate leaves are matte finite-thickness
|
||||
prisms; windows are light inserts without dark glass; passages remain an empty
|
||||
full-depth cut. `openingAmount()` is still applied after a structural-cache hit,
|
||||
so contact changes do not rebuild wall/floor booleans. `hide_openings` removes
|
||||
panels, reveal decoration and their shadows but preserves the cut and lock
|
||||
semantics.
|
||||
Door leaves turn by `50° × openingAmount`, paired window leaves by `65° ×
|
||||
openingAmount`, and gates retain their established 0–10° behaviour. The same
|
||||
canonical flips/host face that drive the floor symbol determine hinges and
|
||||
direction; missing, unknown or unavailable state keeps the existing static-plan
|
||||
fallback. Opening geometry stays inert and lock actions stay on the existing
|
||||
guarded lock badge/card.
|
||||
|
||||
One bounded set of shared gradients, patterns and filters provides low-amplitude
|
||||
theme-aware texture and a fixed visual-light direction. Generated walls,
|
||||
floor-edge surfaces and opening volume may use it. The invisible raised
|
||||
footprint and user floor content, room fills, Glow, sunlight, decor, glyph/text
|
||||
and vacuum never do.
|
||||
Neither theme nor HA Sun state enters the structural fingerprint.
|
||||
For wall height `H`, the fixed window frame spans `0.38H..1.00H`, its sash
|
||||
`0.40H..0.98H`, and clear glass `0.45H..0.93H`; frame/sash rails are `0.05H`.
|
||||
Frames and sill are neutral, glass side is `#c9e4f3` and its top face is
|
||||
`#e3f2fa`. Fixed and live faces remain in `buildIsoWallDepthQueue()`. Only the
|
||||
slots belonging to one window are resolved by physical camera depth, so raised
|
||||
glass covers the rear sill without reordering unrelated walls or openings.
|
||||
|
||||
Forced colours and missing filter support remove texture/soft shadow nuance,
|
||||
not geometry, raised ownership, tethers or actions. `show_borders: false` is
|
||||
the exact no-volume branch: Stage 2/3 structural and raised roots are absent,
|
||||
all overlays return to their floor anchors, and the floor still uses the real
|
||||
4° affine camera. Only topology/projection failure enters the established
|
||||
fingerprint-latched Flat fallback.
|
||||
The constant material/filter set remains theme-aware and bounded. Forced
|
||||
colours or missing filter support remove texture and soft shadows, not geometry,
|
||||
ownership or actions. `show_borders:false` is the exact no-volume branch: the
|
||||
floor keeps the real 0°/20° affine matrix and interactive overlays return to
|
||||
their floor anchors. `hide_openings` removes vertical decoration but preserves
|
||||
cuts, Glow/SUN and lock semantics.
|
||||
|
||||
The structural LRU remains capped at eight. Its key includes geometry, opening
|
||||
flips and fixed ratios, scale, camera, raised/opening heights and the Stage 3
|
||||
algorithm revision; it excludes HA state, opening amount, theme, Sun,
|
||||
interaction and capability state. Golden references and both exact-SHA
|
||||
performance profiles remain Linux-CI evidence and may not accept a Flat
|
||||
fallback as a successful Iso sample.
|
||||
|
||||
The Stage 3 renderer, opening volumes and overlay resolver form an independent
|
||||
`iso-scene-render` lazy graph. With `hp_alpha` off an ordinary View does not
|
||||
request that graph. Enabling the alpha loads it atomically through the same
|
||||
source-fingerprint handshake and one cache-busted retry used by other lazy
|
||||
runtimes; until a matching runtime is installed, the requested view remains
|
||||
Flat rather than mixing builds or rendering a partial Iso scene.
|
||||
The eight-entry structural LRU fingerprints the 0°/20°/84 profile, opening
|
||||
policy revision 2 and structural algorithm 5. It excludes HA state, live
|
||||
opening amount, hover/selection, theme, SUN and filter capability. The lazy
|
||||
`iso-scene-render` graph is still not requested with alpha off; topology,
|
||||
projection or module mismatch still enters the established fingerprint-latched
|
||||
Flat fallback. Linux exact-SHA goldens and performance profiles remain the
|
||||
canonical release evidence.
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
||||
|---|---|
|
||||
| Version | **v1.76.0-beta.2** everywhere (manifest, const.py, package.json, package-lock in both places, CARD_VERSION in the card and the editor runtime) |
|
||||
| Current local cycle | **Beta v1.76.0-beta.2 candidate** — prepared from the exact integrated `dev` tree; `main` remains on v1.75.0. User-visible fixes suppress delayed device activation after pinch (#563), arbitrate overlapping marker hit areas in favour of the painted marker under the pointer (#564), and expose current-space and device-tooltip state consistently to keyboard and assistive-technology users without changing room-label colours or touch/pen behaviour (#565). Internal work adds a field geometry/household regression corpus (#560), separates mutation registry, selection, evidence and execution (#558), hardens CI trust boundaries (#556), preserves semantically identical radar calibration sources (#567), aligns stale-layout invariant attribution with product ownership (#566), attributes mutation setup failures to the introducing tree (#568), and restores nine dead mutation witnesses (#569). |
|
||||
| Hidden Alpha Stage | #89 Stage 1 ships in v1.63.0-beta.1, #122 Stage 2 in v1.64.0 and #160 Stage 3 in v1.73.0-beta.1. The same hidden `iso` view uses the fixed 4° camera, raised/tethered device-room-lock overlays, deeper openings and bounded theme materials; #471 removes the overlay plates from paint while retaining their safety geometry. Since #448 the experiment is enabled only through the single indefinite browser-local `hp_alpha` gate; it is not expiring and has no per-stage key. Flat remains default; editors, `houseplan-space-card`, floor effects, stored coordinates and HA actions remain unchanged. Stage 3 stays internal and is absent from public changelog/user documentation. |
|
||||
| Hidden Alpha Stage | #89 Stage 1 ships in v1.63.0-beta.1, #122 Stage 2 in v1.64.0 and #160 Stage 3 in v1.73.0-beta.1; #570 adds the Stage 4 designer handoff. The same hidden `iso` view now uses a fixed 0°/20° camera and scale-aware wall height 84, low screen-facing device/room/lock overlays without tethers or ground dots, neutral full-height window frames with blue glass, and the existing bounded theme materials. Since #448 the experiment is enabled only through the single indefinite browser-local `hp_alpha` gate; it is not expiring and has no per-stage key. Flat remains default; editors, `houseplan-space-card`, floor effects, stored coordinates and HA actions remain unchanged. Stage 4 stays internal and is absent from public changelog/user documentation. |
|
||||
| Workflow | Superseded 2026-08-12: the pre-1.62 rule of "local edits without tests or commits" is **dead** — since release 1.62 every product change follows `PROCESS.md` (issue in `S5-ready`+, branch `issue/<NN>-slug`, trailers on every commit, review pipeline; `AGENTS.md` is the summary). Release mechanics below remain current. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the stable release is produced by `release.yml` (`workflow_dispatch` on `main` with the tag) — the only publisher of installable assets since #540: gates on the exact SHA (Validate, Full Performance, E2E on the candidate commit), one build, `houseplan.zip` archived from the committed tree, `SHA256SUMS`, draft → publish → read-back verification; a release published by hand in the GitHub form is turned back into a draft and walked through the same path, and a re-dispatch on a public tag is a repair that adds only missing assets. Release bodies are short and bilingual (Russian first); every bullet links its GitHub issue (#NN) so the #328 rules stay machine-checkable. A STABLE body aggregates the changelog since the PREVIOUS STABLE release (never since the last beta): features/fixes described across the line's beta changelogs must appear, while bugs that were introduced and fixed strictly inside the beta line (never shipped in any stable) are excluded — draft with `npm run release:notes -- <tag>`, curate by hand, then `npm run release:notes -- <tag> --verify` must pass. `Мелкие исправления и улучшения` / `Small fixes and improvements` is allowed only when the range really contains user-visible work not itemised in the body; a single-issue hotfix ships without it (the verifier enforces this). Every body ends with separate links to the Russian and English changelogs. Open or partially delivered issues are never presented as shipped. Telegram announcements are sent only for stable releases; beta and RC publication is silent. `docs/RELEASE-NOTES.md` is the current canonical body instance; `npm run release:prerelease -- <tag> --issues=… --yes` is the primary local publication path and the manual `Publish prerelease` workflow is its GitHub-only equivalent once present on `main`. Nothing is copied to the home instance by hand |
|
||||
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records; their labels carry priority and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
|
||||
| CI | #541 replaces three incompatible meanings of “green” with one machine-verifiable Validate proof: candidate SHA/tree, run ID/attempt, requested checks, actually executed jobs and independently checked content-addressed reuse. Review, merge and release share the same closed state machine; a light green dispatch cannot hide a full red run, and a dispatch without six executed mutant jobs cannot authorize review or merge. Prerelease publication requires a green full exact-SHA proof covering frontend/backend, smoke (including the #73 rAF frame sampler), golden, HACS/Hassfest and the short absolute-ceiling performance smoke. Obsolete same-ref Validate runs are cancelled. Full seven-sample base/candidate performance remains in `performance.yml` (`main` push, weekly, manual); stable release assets fail closed unless Validate and Full Performance are green for the exact tagged SHA and the stable-only CDP compositor screencast finds no empty/black presented frame. |
|
||||
|
||||
@@ -8432,15 +8432,18 @@ const MUTANT_DEFINITIONS = [
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'iso-placement-cache-ignores-selected',
|
||||
id: 'iso-window-occlusion-ignores-height',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="#473 W1" test/iso-scene-render.test.mjs',
|
||||
because: 'a selected plate must resolve its own placement; a signature without `selected` '
|
||||
+ 'hands back the unselected cached one — a stale picture with no failure (#473, perf delta of #160)',
|
||||
+ '&& node --test --test-name-pattern="shared painter queue puts elevated window glass" '
|
||||
+ 'test/iso-scene-render.test.mjs',
|
||||
because: 'Stage 4 must sort one windows existing shared-queue slots by physical camera depth; '
|
||||
+ 'falling back to floor/screen depth paints the rear sill over elevated glass without any '
|
||||
+ 'runtime failure (#570)',
|
||||
patches: [{
|
||||
file: 'src/iso-scene-render.ts',
|
||||
find: " input.layers.shadows ? 1 : 0, selected ? 1 : 0].join('|');",
|
||||
replace: " input.layers.shadows ? 1 : 0].join('|');",
|
||||
find: ' return (surfaceA?.cameraDepth ?? surfaceA?.depth ?? 0)\n'
|
||||
+ ' - (surfaceB?.cameraDepth ?? surfaceB?.depth ?? 0)',
|
||||
replace: ' return (surfaceA?.depth ?? 0) - (surfaceB?.depth ?? 0)',
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -8590,19 +8593,19 @@ const MUTANT_DEFINITIONS = [
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'stage3-w1-camera-rotation-reset',
|
||||
id: 'stage4-w1-camera-rotation-regresses',
|
||||
guard: 'node --test --test-name-pattern="exact fixed" test/iso-projection.test.mjs',
|
||||
because: 'W1: the reviewed Stage 3 camera is exactly +4 degrees; a front-on camera must '
|
||||
+ 'fail the exact projection contract (#160)',
|
||||
because: 'W1: the reviewed Stage 4 camera is exactly 0 degrees; restoring the old +4 degree '
|
||||
+ 'yaw must fail the exact designer-handoff projection contract (#570)',
|
||||
patches: [{
|
||||
file: 'src/iso-projection.ts',
|
||||
find: ' rotDeg: 4,\n',
|
||||
replace: ' rotDeg: 0,\n',
|
||||
find: ' rotDeg: 0,\n',
|
||||
replace: ' rotDeg: 4,\n',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'stage3-w2-room-label-left-on-floor',
|
||||
guard: 'node --test --test-name-pattern="exact D2 overlay matrix" test/iso-overlays.test.mjs',
|
||||
guard: 'node --test --test-name-pattern="exact Stage 4 overlay matrix" test/iso-overlays.test.mjs',
|
||||
because: 'W2: room labels/cards and value-bearing device roots belong to the raised plane; '
|
||||
+ 'the D2 matrix must reject a floor-plane room label (#160)',
|
||||
patches: [{
|
||||
@@ -8615,7 +8618,7 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'stage3-w3-vacuum-raised-with-devices',
|
||||
guard: 'node --test --test-name-pattern="raises only device" test/isometric-contract.test.mjs',
|
||||
guard: 'node --test --test-name-pattern="Stage 4 keeps device" test/isometric-contract.test.mjs',
|
||||
because: 'W3: the live vacuum puck/trail stays on the floor even while device, room and lock '
|
||||
+ 'roots are raised (#160)',
|
||||
patches: [{
|
||||
@@ -8677,8 +8680,8 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: ' onBuild(): void;\n liveFingerprint?: string;\n}',
|
||||
}, {
|
||||
file: 'src/iso-scene-render.ts',
|
||||
find: ' algorithm: 4,\n })}`;',
|
||||
replace: ' algorithm: 4,\n })}|${input.liveFingerprint ?? \'\'}`;',
|
||||
find: ' algorithm: 5,\n })}`;',
|
||||
replace: ' algorithm: 5,\n })}|${input.liveFingerprint ?? \'\'}`;',
|
||||
}, {
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: ' onBuild: () => { this._isoStructuralBuildCount += 1; },',
|
||||
@@ -8689,8 +8692,8 @@ const MUTANT_DEFINITIONS = [
|
||||
{
|
||||
id: 'stage3-w8-material-defs-created-per-face',
|
||||
guard: 'node demo/smoke_isometric_contract.mjs',
|
||||
because: 'W8: Stage 3 material patterns and filters are shared O(1) definitions, never '
|
||||
+ 'replicated for every face or marker (#160)',
|
||||
because: 'W8: Stage 4 material patterns and filters are shared O(1) definitions, never '
|
||||
+ 'replicated for every face or marker (#570)',
|
||||
patches: [{
|
||||
file: 'src/iso-scene-render.ts',
|
||||
find: ' return svg`<path class="iso-wall-top" d=${face.d} data-component=${face.component}\n'
|
||||
@@ -8723,24 +8726,22 @@ const MUTANT_DEFINITIONS = [
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'stage3-w11-nudged-overlay-loses-tether',
|
||||
id: 'stage4-w11-nudged-overlay-restores-tether',
|
||||
guard: 'node --test --test-name-pattern="wall-aware nudge" test/iso-overlays.test.mjs',
|
||||
because: 'W11: every non-zero nudge keeps a tether back to its immutable floor owner '
|
||||
+ 'anchor (#160)',
|
||||
because: 'W11: Stage 4 keeps collision nudge internal but removes the debug tether; restoring '
|
||||
+ 'it would recreate the tall visual clutter rejected by the designer handoff (#570)',
|
||||
patches: [{
|
||||
file: 'src/iso-overlays.ts',
|
||||
find: ' const tetherVisible = nudged || nearWallBefore || nearWallAfter\n'
|
||||
+ ' || !!input.hovered || !!input.focused || !!input.selected;',
|
||||
replace: ' const tetherVisible = !nudged && (nearWallBefore || nearWallAfter\n'
|
||||
+ ' || !!input.hovered || !!input.focused || !!input.selected);',
|
||||
find: ' const tetherVisible = false;',
|
||||
replace: ' const tetherVisible = nudged;',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'stage3-w12-separate-alpha-url-key-restored',
|
||||
guard: 'node --test --test-name-pattern="Labs iso is presentation-only" '
|
||||
+ 'test/isometric-contract.test.mjs',
|
||||
because: 'W12: Stage 3 shares the one permanent hp_alpha switch; a feature-specific URL '
|
||||
+ 'key or expiry must not return (#160)',
|
||||
because: 'W12: Stage 4 shares the one permanent hp_alpha switch; a feature-specific URL '
|
||||
+ 'key or expiry must not return (#570)',
|
||||
patches: [{
|
||||
file: 'src/labs.ts',
|
||||
find: "params.getAll('hp_alpha')",
|
||||
|
||||
+7
-10
@@ -775,7 +775,7 @@ export class HouseplanCard extends LitElement {
|
||||
});
|
||||
|
||||
/**
|
||||
* Stage 3 is an opt-in alpha surface. Keep its renderer and geometry outside
|
||||
* Stage 4 is an opt-in alpha surface. Keep its renderer and geometry outside
|
||||
* the ordinary View graph, but use the same exact-build, atomic installation
|
||||
* contract as the editor runtimes so a stale chunk can never half-install.
|
||||
*/
|
||||
@@ -1898,7 +1898,7 @@ export class HouseplanCard extends LitElement {
|
||||
private _isoGeometryCache = new Map<string, IsoSceneCacheEntry>();
|
||||
private _renderIsoScene: IsoRenderScene | null = null;
|
||||
private _isoProjectionSnapshot: 'flat' | 'iso' | null = null;
|
||||
/** Observable Stage 3 performance seam: increments only for a real
|
||||
/** Observable Stage 4 performance seam: increments only for a real
|
||||
* structural LRU miss, never for HA/theme/hover/opening-state paint. */
|
||||
private _isoStructuralBuildCount = 0;
|
||||
private _isoFallback = new Set<string>();
|
||||
@@ -6214,7 +6214,7 @@ export class HouseplanCard extends LitElement {
|
||||
const start = unprojectFloorPoint([view.x, view.y]), end = unprojectFloorPoint([view.x + view.w, view.y + view.h]);
|
||||
return { x: start[0], y: start[1], w: end[0] - start[0], h: end[1] - start[1] };
|
||||
}
|
||||
/** Stage 3 keeps one immutable floor point and one runtime visual point per
|
||||
/** Stage 4 keeps one immutable floor point and one runtime visual point per
|
||||
* raised item. This snapshot is presentation-only and never enters config. */
|
||||
private _isoOverlayScene(
|
||||
space: SpaceModel,
|
||||
@@ -6231,16 +6231,13 @@ export class HouseplanCard extends LitElement {
|
||||
const runtime = this._isoSceneRuntime;
|
||||
if (!runtime || !layers?.structural || !structural) return null;
|
||||
return runtime.buildIsoOverlayRenderScene({
|
||||
space, devices: devs, openings: this._openingsR, view, display: disp, layers,
|
||||
space, devices: devs, openings: this._openingsR, view, display: disp,
|
||||
wallSilhouettes: structural.wallSilhouettes,
|
||||
resolveCollisions,
|
||||
iconPct, deviceBasePct, showLqi, cellCm: this._cellCm,
|
||||
kioskIconScale: this._mode === 'view' ? this._kioskScale.icon : 1,
|
||||
kioskFontScale: this._mode === 'view' ? this._kioskScale.font : 1,
|
||||
stageSize: this._stageEl?.getBoundingClientRect?.(),
|
||||
selectedDeviceId: this._selId,
|
||||
focusedRoomId: this._roomFocus?.roomId,
|
||||
selectedOpeningId: this._openingInfo?.id,
|
||||
positionOf: (device) => this._pos(device),
|
||||
presentationOf: (device, withLqi) => this._devicePresentation(device, withLqi),
|
||||
labelPositionOf: (room, spaceId) => this._labelPos(room, spaceId),
|
||||
@@ -6255,7 +6252,7 @@ export class HouseplanCard extends LitElement {
|
||||
scene: IsoRenderScene | null = this._renderIsoScene,
|
||||
): number[] {
|
||||
if (projection === 'iso') {
|
||||
// No-borders is the accepted no-volume scene. Stage 2/3 structure and
|
||||
// No-borders is the accepted no-volume scene. Stage 2/3/4 structure and
|
||||
// raised overlays are absent there, so invisible wall/opening/overlay
|
||||
// height must not reframe the affine-projected floor.
|
||||
if (!this._spaceDisplayForRender().showBorders) {
|
||||
@@ -8318,7 +8315,7 @@ export class HouseplanCard extends LitElement {
|
||||
...o, rx: o.x * NORM_W, ry: o.y * H, rlen: o.length * NORM_W,
|
||||
};
|
||||
// Contour-wall hosts are identity metadata; their materialised x/y/angle
|
||||
// stay the legacy render input until the Stage 3 graph renderer.
|
||||
// stay the legacy render input until the Stage 4 graph renderer.
|
||||
if (!o.host || o.host.kind === 'wall') return [fallback];
|
||||
const resolution = resolvePartitionOpeningCompat(
|
||||
o, space.partitions, NORM_W, this._cellCm, this._gridPitch,
|
||||
@@ -11575,7 +11572,7 @@ export class HouseplanCard extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="stage ${iso ? `projection-iso ${deviceThemeClass(this._renderPlanHass)}` : ''} ${this._markup ? 'markup tool-' + this._tool + (this._tool === 'split' && !this._splitSel ? ' pickstage' : '') + (this._tool === 'wallthick' && this._wallThickHover ? ' wallhot' : '') : ''} ${this._mode === 'decor' ? 'dtool-' + this._decorTool : ''} ${space.bg ? '' : 'noplan'} mode-${this._mode}${this._bdMovable ? ' bdgrab' : ''}${this._bdDrag ? ' bdgrabbing' : ''}${dayCycle ? ` daycycle phase-${dayCycle.phase}` : ''}${this._booting ? ' hpboot' : ''}${this._bootSoft ? ' hpsettle' : ''}${this._modeTransitionBusy ? ' mode-transition' : ''}"
|
||||
data-hp-iso-stage=${iso ? '3' : nothing} data-hp-iso-structural-builds=${iso ? this._isoStructuralBuildCount : nothing}
|
||||
data-hp-iso-stage=${iso ? '4' : nothing} data-hp-iso-structural-builds=${iso ? this._isoStructuralBuildCount : nothing}
|
||||
?inert=${this._modeTransitionBusy}
|
||||
style="height:${modeVisual ? `${modeVisual.stageHeight}px` : this.panelHost ? 'auto' : this._kiosk ? '100dvh' : this._bootSoft && this._warmVp && this._warmSlot?.stageH ? `${this._warmSlot.stageH}px` : `calc(100dvh - ${this._hdrH}px)`}${transitionStageBg ? `;background:${transitionStageBg}` : ''};--hp-cell-visual-scale:${gridVisualScale(this._cellCm)};--wall-fill:${this._fillColors.wall_fill.c};--wall-fill-op:${this._fillColors.wall_fill.a};--hp-mode-architecture-opacity:${modeVisual ? modeVisual.architectureOpacity : this._mode === 'decor' ? 0.35 : 1};--hp-mode-view-weight:${modeVisual?.viewWeight ?? (this._mode === 'view' ? 1 : 0)};--hp-mode-editor-weight:${modeVisual?.editorWeight ?? (this._mode === 'view' ? 0 : 1)}${modeVisual ? `;--hp-mode-paper:${modeVisual.paperColor}` : ''}${dayCycle ? `;${dayCycleStageVars(dayCycle)}` : ''}"
|
||||
@click=${(e: MouseEvent) => this._markupClick(e)}
|
||||
|
||||
+120
-20
@@ -66,7 +66,7 @@ export interface IsoOpeningRevealBasis {
|
||||
}
|
||||
|
||||
/**
|
||||
* Versioned authority for every fixed Stage 3 opening dimension. It is part of
|
||||
* Versioned authority for every fixed Stage 4 opening dimension. It is part of
|
||||
* the structural fingerprint, so a policy change cannot reuse stale cached
|
||||
* opening volumes from an older algorithm.
|
||||
*/
|
||||
@@ -74,21 +74,33 @@ export interface IsoOpeningGeometryPolicy {
|
||||
revision: number;
|
||||
leafThicknessRatio: number;
|
||||
frameThicknessRatio: number;
|
||||
doorTurnDeg: number;
|
||||
windowTurnDeg: number;
|
||||
gateTurnDeg: number;
|
||||
gateTopRatio: number;
|
||||
windowBottomRatio: number;
|
||||
windowTopRatio: number;
|
||||
windowFrameBottomRatio: number;
|
||||
windowFrameTopRatio: number;
|
||||
windowSashBottomRatio: number;
|
||||
windowSashTopRatio: number;
|
||||
windowGlassBottomRatio: number;
|
||||
windowGlassTopRatio: number;
|
||||
doorTopRatio: number;
|
||||
}
|
||||
|
||||
export const ISO_OPENING_GEOMETRY_POLICY: Readonly<IsoOpeningGeometryPolicy> = Object.freeze({
|
||||
revision: 1,
|
||||
revision: 2,
|
||||
leafThicknessRatio: 0.04,
|
||||
frameThicknessRatio: 0.055,
|
||||
frameThicknessRatio: 0.05,
|
||||
doorTurnDeg: 50,
|
||||
windowTurnDeg: 65,
|
||||
gateTurnDeg: 10,
|
||||
gateTopRatio: 0.88,
|
||||
windowBottomRatio: 0.27,
|
||||
windowTopRatio: 0.78,
|
||||
windowFrameBottomRatio: 0.38,
|
||||
windowFrameTopRatio: 1,
|
||||
windowSashBottomRatio: 0.40,
|
||||
windowSashTopRatio: 0.98,
|
||||
windowGlassBottomRatio: 0.45,
|
||||
windowGlassTopRatio: 0.93,
|
||||
doorTopRatio: 0.92,
|
||||
});
|
||||
|
||||
@@ -97,6 +109,16 @@ export const ISO_OPENING_LEAF_THICKNESS_RATIO =
|
||||
export const ISO_OPENING_FRAME_THICKNESS_RATIO =
|
||||
ISO_OPENING_GEOMETRY_POLICY.frameThicknessRatio;
|
||||
|
||||
export interface IsoWindowProfile {
|
||||
frameBottom: number;
|
||||
frameTop: number;
|
||||
sashBottom: number;
|
||||
sashTop: number;
|
||||
glassBottom: number;
|
||||
glassTop: number;
|
||||
member: number;
|
||||
}
|
||||
|
||||
/** Immutable jamb/axis topology stored in the structural Iso LRU. */
|
||||
export interface IsoOpeningBasis {
|
||||
id: string;
|
||||
@@ -105,6 +127,7 @@ export interface IsoOpeningBasis {
|
||||
wallHeight: number;
|
||||
leafThickness: number;
|
||||
frameThickness: number;
|
||||
windowProfile: IsoWindowProfile | null;
|
||||
axis: IsoOpeningAxisBasis;
|
||||
face: IsoOpeningFaceBasis;
|
||||
reveals: readonly IsoOpeningRevealBasis[];
|
||||
@@ -118,6 +141,11 @@ export type IsoOpeningSurfaceKind =
|
||||
| 'leaf-edge'
|
||||
| 'leaf-top'
|
||||
| 'window-insert'
|
||||
| 'window-sash-side'
|
||||
| 'window-sash-bottom'
|
||||
| 'window-sash-top'
|
||||
| 'window-glass'
|
||||
| 'window-glass-top'
|
||||
| 'window-frame-side'
|
||||
| 'window-frame-top'
|
||||
| 'window-sill';
|
||||
@@ -126,6 +154,8 @@ export type IsoOpeningMaterial =
|
||||
| 'reveal'
|
||||
| 'matte-leaf'
|
||||
| 'light-window'
|
||||
| 'glass-side'
|
||||
| 'glass-top'
|
||||
| 'light-frame'
|
||||
| 'light-sill';
|
||||
|
||||
@@ -133,6 +163,8 @@ export interface IsoOpeningSurface {
|
||||
kind: IsoOpeningSurfaceKind;
|
||||
material: IsoOpeningMaterial;
|
||||
d: string;
|
||||
/** Orthographic camera depth of the face centre, used for local occlusion. */
|
||||
cameraDepth: number;
|
||||
depth: number;
|
||||
jamb?: 0 | 1;
|
||||
edge?: 'hinge' | 'tip';
|
||||
@@ -148,7 +180,7 @@ export interface IsoOpeningPanel {
|
||||
depth: number;
|
||||
material: 'matte-leaf' | 'light-window';
|
||||
thickness: number;
|
||||
/** Stage 3 prism/frame faces. The legacy d remains the centre face for compatibility. */
|
||||
/** Stage 4 prism/frame faces. The legacy d remains the centre face for compatibility. */
|
||||
surfaces: readonly IsoOpeningSurface[];
|
||||
}
|
||||
|
||||
@@ -276,15 +308,26 @@ export function buildIsoOpeningBasis(
|
||||
];
|
||||
} else if (input.type === 'window') {
|
||||
leaves = [
|
||||
leafBasis(input, 0, [-half, 0], [half, 0], -90,
|
||||
wallHeight * policy.windowBottomRatio, wallHeight * policy.windowTopRatio),
|
||||
leafBasis(input, 1, [half, 0], [-half, 0], 90,
|
||||
wallHeight * policy.windowBottomRatio, wallHeight * policy.windowTopRatio),
|
||||
leafBasis(input, 0, [-half, 0], [half, 0], -policy.windowTurnDeg,
|
||||
wallHeight * policy.windowSashBottomRatio,
|
||||
wallHeight * policy.windowSashTopRatio),
|
||||
leafBasis(input, 1, [half, 0], [-half, 0], policy.windowTurnDeg,
|
||||
wallHeight * policy.windowSashBottomRatio,
|
||||
wallHeight * policy.windowSashTopRatio),
|
||||
];
|
||||
} else {
|
||||
leaves = [leafBasis(input, 0, [-half, 0], [input.length, 0], -90, 0,
|
||||
leaves = [leafBasis(input, 0, [-half, 0], [input.length, 0], -policy.doorTurnDeg, 0,
|
||||
wallHeight * policy.doorTopRatio)];
|
||||
}
|
||||
const windowProfile = input.type === 'window' ? Object.freeze({
|
||||
frameBottom: wallHeight * policy.windowFrameBottomRatio,
|
||||
frameTop: wallHeight * policy.windowFrameTopRatio,
|
||||
sashBottom: wallHeight * policy.windowSashBottomRatio,
|
||||
sashTop: wallHeight * policy.windowSashTopRatio,
|
||||
glassBottom: wallHeight * policy.windowGlassBottomRatio,
|
||||
glassTop: wallHeight * policy.windowGlassTopRatio,
|
||||
member: wallHeight * policy.frameThicknessRatio,
|
||||
}) : null;
|
||||
return Object.freeze({
|
||||
id: input.id,
|
||||
sourceIndex: input.sourceIndex,
|
||||
@@ -292,6 +335,7 @@ export function buildIsoOpeningBasis(
|
||||
wallHeight,
|
||||
leafThickness: wallHeight * policy.leafThicknessRatio,
|
||||
frameThickness: wallHeight * policy.frameThicknessRatio,
|
||||
windowProfile,
|
||||
axis,
|
||||
face,
|
||||
reveals: Object.freeze(reveals),
|
||||
@@ -320,10 +364,17 @@ function projectedSurface(
|
||||
metadata: Pick<IsoOpeningSurface, 'jamb' | 'edge'> = {},
|
||||
): IsoOpeningSurface {
|
||||
const projected = points.map((value) => projectPlanPoint(value.point, value.z, camera));
|
||||
const rotation = camera.rotDeg * Math.PI / 180;
|
||||
const tilt = camera.tiltDeg * Math.PI / 180;
|
||||
const cameraDepth = points.reduce((sum, value) => sum
|
||||
+ (value.point[0] * Math.sin(rotation) + value.point[1] * Math.cos(rotation))
|
||||
* Math.sin(tilt)
|
||||
+ value.z * Math.cos(tilt), 0) / points.length;
|
||||
return Object.freeze({
|
||||
kind,
|
||||
material,
|
||||
d: `M ${projected.map(pointText).join(' L ')} Z`,
|
||||
cameraDepth,
|
||||
depth: Math.max(...projected.map((point) => point[1])),
|
||||
...metadata,
|
||||
});
|
||||
@@ -403,7 +454,7 @@ function leafPrismSurfaces(
|
||||
}
|
||||
|
||||
/**
|
||||
* Project state-independent Stage 3 jamb/reveal and window frame/sill surfaces.
|
||||
* Project state-independent Stage 4 jamb/reveal and window frame/sill surfaces.
|
||||
* Passage deliberately has no decorative volume. This function consumes only
|
||||
* the immutable structural basis and is safe to cache with that basis.
|
||||
*/
|
||||
@@ -426,8 +477,10 @@ export function projectIsoOpeningStructure(
|
||||
|| a.kind.localeCompare(b.kind) || (a.jamb ?? 0) - (b.jamb ?? 0)));
|
||||
}
|
||||
|
||||
const bottom = Math.min(...basis.leaves.map((leaf) => leaf.bottom));
|
||||
const top = Math.max(...basis.leaves.map((leaf) => leaf.top));
|
||||
const profile = basis.windowProfile;
|
||||
if (!profile) throw new Error('missing isometric window profile');
|
||||
const bottom = profile.frameBottom;
|
||||
const top = profile.frameTop;
|
||||
const member = Math.min(basis.frameThickness, Math.hypot(
|
||||
basis.axis.end[0] - basis.axis.start[0],
|
||||
basis.axis.end[1] - basis.axis.start[1],
|
||||
@@ -469,6 +522,56 @@ export function projectIsoOpeningStructure(
|
||||
|| a.kind.localeCompare(b.kind) || (a.jamb ?? 0) - (b.jamb ?? 0)));
|
||||
}
|
||||
|
||||
/** Live sash frame and glass, all derived from the same immutable window basis. */
|
||||
function windowSashSurfaces(
|
||||
basis: IsoOpeningBasis,
|
||||
leaf: IsoOpeningLeafBasis,
|
||||
tip: PlanPoint,
|
||||
camera: IsoCamera,
|
||||
): readonly IsoOpeningSurface[] {
|
||||
const profile = basis.windowProfile;
|
||||
if (!profile) throw new Error('missing isometric window profile');
|
||||
const vector = subtract(tip, leaf.hinge);
|
||||
const length = Math.hypot(vector[0], vector[1]);
|
||||
if (!(length > 1e-9)) return Object.freeze([]);
|
||||
const unit = frozenPoint(vector[0] / length, vector[1] / length);
|
||||
const member = Math.min(profile.member, length / 4,
|
||||
(profile.sashTop - profile.sashBottom) / 4);
|
||||
const innerHinge = add(leaf.hinge, scaled(unit, member));
|
||||
const innerTip = subtract(tip, scaled(unit, member));
|
||||
const paneThickness = Math.min(basis.frameThickness * 0.35, length * 0.02);
|
||||
const paneHalfNormal = frozenPoint(
|
||||
-unit[1] * paneThickness / 2,
|
||||
unit[0] * paneThickness / 2,
|
||||
);
|
||||
const glassFrontHinge = add(innerHinge, paneHalfNormal);
|
||||
const glassFrontTip = add(innerTip, paneHalfNormal);
|
||||
const glassBackHinge = subtract(innerHinge, paneHalfNormal);
|
||||
const glassBackTip = subtract(innerTip, paneHalfNormal);
|
||||
const surfaces: IsoOpeningSurface[] = [
|
||||
verticalSurface('window-sash-side', 'light-frame', leaf.hinge, innerHinge,
|
||||
profile.sashBottom, profile.sashTop, camera, { edge: 'hinge' }),
|
||||
verticalSurface('window-sash-side', 'light-frame', innerTip, tip,
|
||||
profile.sashBottom, profile.sashTop, camera, { edge: 'tip' }),
|
||||
verticalSurface('window-sash-bottom', 'light-frame', leaf.hinge, tip,
|
||||
profile.sashBottom, profile.sashBottom + member, camera),
|
||||
verticalSurface('window-sash-top', 'light-frame', leaf.hinge, tip,
|
||||
profile.sashTop - member, profile.sashTop, camera),
|
||||
verticalSurface('window-glass', 'glass-side', glassFrontHinge, glassFrontTip,
|
||||
profile.glassBottom, profile.glassTop, camera),
|
||||
verticalSurface('window-glass', 'glass-side', glassBackTip, glassBackHinge,
|
||||
profile.glassBottom, profile.glassTop, camera),
|
||||
projectedSurface('window-glass-top', 'glass-top', [
|
||||
{ point: glassFrontHinge, z: profile.glassTop },
|
||||
{ point: glassFrontTip, z: profile.glassTop },
|
||||
{ point: glassBackTip, z: profile.glassTop },
|
||||
{ point: glassBackHinge, z: profile.glassTop },
|
||||
], camera),
|
||||
];
|
||||
return Object.freeze(surfaces.sort((a, b) => a.depth - b.depth
|
||||
|| a.kind.localeCompare(b.kind) || String(a.edge || '').localeCompare(String(b.edge || ''))));
|
||||
}
|
||||
|
||||
/** Apply live state after the structural cache: O(leaves), no topology work. */
|
||||
export function projectIsoOpening(
|
||||
basis: IsoOpeningBasis,
|
||||
@@ -484,10 +587,7 @@ export function projectIsoOpening(
|
||||
const topHinge = projectPlanPoint(leaf.hinge, leaf.top, camera);
|
||||
const material = basis.type === 'window' ? 'light-window' : 'matte-leaf';
|
||||
const surfaces = basis.type === 'window'
|
||||
? Object.freeze([verticalSurface(
|
||||
'window-insert', 'light-window', leaf.hinge, tip,
|
||||
leaf.bottom, leaf.top, camera,
|
||||
)])
|
||||
? windowSashSurfaces(basis, leaf, tip, camera)
|
||||
: leafPrismSurfaces(leaf, tip, basis.leafThickness, camera);
|
||||
return Object.freeze({
|
||||
id: basis.id,
|
||||
|
||||
+10
-4
@@ -439,7 +439,11 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
|
||||
};
|
||||
}
|
||||
|
||||
const raisedHeight = wallHeight + visualOffset;
|
||||
// Stage 4 keeps the canonical floor anchor and collision footprint, but the
|
||||
// screen-facing content sits just above the floor instead of on a tall
|
||||
// wall-height mast. The wall height is still validated because the same
|
||||
// placement consumes wall silhouettes built from that physical height.
|
||||
const raisedHeight = visualOffset;
|
||||
const raisedScene = projectPlanPoint(floorAnchor, raisedHeight, camera);
|
||||
const owner = input.ownerAlreadyResolved
|
||||
? input.resolvedOwner ?? null
|
||||
@@ -582,8 +586,10 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
|
||||
raisedHeight, camera, nudgeScene);
|
||||
const nearWallAfter = geometryValid ? knownNearWallAfter ?? isNear(footprint) : true;
|
||||
const nudged = distanceCss > EPS;
|
||||
const tetherVisible = nudged || nearWallBefore || nearWallAfter
|
||||
|| !!input.hovered || !!input.focused || !!input.selected;
|
||||
// Ownership remains encoded by the immutable anchor and invisible bounded
|
||||
// footprint. Stage 4 deliberately removes the visible ground dot and long
|
||||
// tether which made the architectural view look like a debug overlay.
|
||||
const tetherVisible = false;
|
||||
return {
|
||||
plane, owner, floorAnchor, floorScene, raisedScene, visualScene, footprint,
|
||||
nudgeScene,
|
||||
@@ -591,7 +597,7 @@ export function resolveIsoOverlayPlacement(input: IsoOverlayPlacementInput): Iso
|
||||
nudgeDistanceCss: distanceCss,
|
||||
nudged, nearWallBefore, nearWallAfter,
|
||||
cleared: !nearWallAfter, capped,
|
||||
grounding: { center: floorScene, visible: input.filtersSupported !== false },
|
||||
grounding: { center: floorScene, visible: false },
|
||||
tether: tetherGeometry(floorScene, visualScene, tetherVisible),
|
||||
status, reason,
|
||||
};
|
||||
|
||||
+8
-11
@@ -21,18 +21,18 @@ export interface IsoFrameInput {
|
||||
}
|
||||
|
||||
export const ISO_CAMERA: Readonly<IsoCamera> = Object.freeze({
|
||||
rotDeg: 4,
|
||||
rotDeg: 0,
|
||||
tiltDeg: 20,
|
||||
xyScale: 1,
|
||||
zScale: 1,
|
||||
origin: Object.freeze([500, 500]) as PlanPoint,
|
||||
});
|
||||
|
||||
export const ISO_WALL_HEIGHT = 64;
|
||||
export const ISO_WALL_HEIGHT = 84;
|
||||
export const ISO_FLOOR_EDGE_HEIGHT = 10;
|
||||
/** Nominal Stage 3 offset; callers scale it with the same wall-height policy. */
|
||||
/** Low visual lift above the floor; callers scale it with the same grid policy. */
|
||||
export const ISO_OVERLAY_VISUAL_OFFSET = 4;
|
||||
export const ISO_RAISED_OVERLAY_HEIGHT = ISO_WALL_HEIGHT + ISO_OVERLAY_VISUAL_OFFSET;
|
||||
export const ISO_RAISED_OVERLAY_HEIGHT = ISO_OVERLAY_VISUAL_OFFSET;
|
||||
|
||||
function finiteCamera(camera: IsoCamera): boolean {
|
||||
return [camera.rotDeg, camera.tiltDeg, camera.xyScale, camera.zScale,
|
||||
@@ -104,13 +104,10 @@ export function isoFloorMatrixCss(camera: IsoCamera = ISO_CAMERA): string {
|
||||
return isoPlaneMatrixCss(0, camera);
|
||||
}
|
||||
|
||||
export function isoRaisedOverlayHeight(
|
||||
wallHeight: number, visualOffset: number,
|
||||
): number {
|
||||
if (!Number.isFinite(wallHeight) || !Number.isFinite(visualOffset)
|
||||
|| wallHeight < 0 || visualOffset < 0)
|
||||
throw new Error('invalid raised overlay height');
|
||||
return wallHeight + visualOffset;
|
||||
export function isoOverlayVisualHeight(visualOffset: number): number {
|
||||
if (!Number.isFinite(visualOffset) || visualOffset < 0)
|
||||
throw new Error('invalid isometric overlay height');
|
||||
return visualOffset;
|
||||
}
|
||||
|
||||
export function clientToScenePoint(
|
||||
|
||||
+43
-58
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Stage 3 isometric scene orchestration and inert SVG rendering.
|
||||
* Stage 4 isometric scene orchestration and inert SVG rendering.
|
||||
*
|
||||
* The card remains the owner of live HA state and editor interaction. This
|
||||
* module owns the presentation-only structural cache, overlay projection and
|
||||
@@ -74,7 +74,15 @@ const isoDepthStableKey = (entry: IsoWallDepthQueueEntry): string => {
|
||||
+ `:${surface.edge ?? ''}:${surface.id}:${surface.material}`;
|
||||
};
|
||||
|
||||
/** Far-to-near painter queue shared by every opaque Stage 3 structural surface. */
|
||||
/**
|
||||
* Far-to-near painter queue shared by every opaque Stage 4 structural surface.
|
||||
*
|
||||
* The established screen-depth order remains authoritative between unrelated
|
||||
* walls/openings. Inside one window, however, height is physical camera depth:
|
||||
* the glass above a sill must paint over the sill's rear projection. Reorder
|
||||
* only that window's already allocated queue slots, so the fix cannot move an
|
||||
* unrelated wall or opening into a different layer.
|
||||
*/
|
||||
export function buildIsoWallDepthQueue(
|
||||
geometry: IsoWallGeometry,
|
||||
openingSurfaces: readonly IsoOpeningRenderSurface[],
|
||||
@@ -93,7 +101,28 @@ export function buildIsoWallDepthQueue(
|
||||
entries.sort((a, b) => a.depth - b.depth
|
||||
|| isoDepthLayerRank(a) - isoDepthLayerRank(b)
|
||||
|| isoDepthStableKey(a).localeCompare(isoDepthStableKey(b)));
|
||||
return Object.freeze(entries);
|
||||
const windowSlots = new Map<string, IsoWallDepthQueueEntry[]>();
|
||||
for (const entry of entries) {
|
||||
if (entry.layer !== 'opening' || entry.surface.type !== 'window') continue;
|
||||
const slots = windowSlots.get(entry.surface.id) || [];
|
||||
slots.push(entry);
|
||||
windowSlots.set(entry.surface.id, slots);
|
||||
}
|
||||
for (const slots of windowSlots.values()) slots.sort((a, b) => {
|
||||
const surfaceA = a.layer === 'opening' ? a.surface : null;
|
||||
const surfaceB = b.layer === 'opening' ? b.surface : null;
|
||||
return (surfaceA?.cameraDepth ?? surfaceA?.depth ?? 0)
|
||||
- (surfaceB?.cameraDepth ?? surfaceB?.depth ?? 0)
|
||||
|| isoDepthStableKey(a).localeCompare(isoDepthStableKey(b));
|
||||
});
|
||||
const nextWindowSlot = new Map<string, number>();
|
||||
return Object.freeze(entries.map((entry) => {
|
||||
if (entry.layer !== 'opening' || entry.surface.type !== 'window') return entry;
|
||||
const id = entry.surface.id;
|
||||
const index = nextWindowSlot.get(id) || 0;
|
||||
nextWindowSlot.set(id, index + 1);
|
||||
return windowSlots.get(id)?.[index] || entry;
|
||||
}));
|
||||
}
|
||||
|
||||
export type IsoOverlayRenderEntry = {
|
||||
@@ -456,7 +485,7 @@ export function createIsoStructuralSource(
|
||||
wallHeight,
|
||||
raisedHeight,
|
||||
floorEdgeHeight,
|
||||
algorithm: 4,
|
||||
algorithm: 5,
|
||||
})}`;
|
||||
return {
|
||||
key,
|
||||
@@ -683,7 +712,6 @@ export interface IsoOverlaySceneInput {
|
||||
openings: readonly RenderOpening[];
|
||||
view: Rect;
|
||||
display: SpaceDisplay;
|
||||
layers: IsoDecorationLayers;
|
||||
wallSilhouettes: readonly IsoWallSilhouette[];
|
||||
/** Fit-envelope probes need unnudged bounds only, not wall collision search. */
|
||||
resolveCollisions?: boolean;
|
||||
@@ -694,9 +722,6 @@ export interface IsoOverlaySceneInput {
|
||||
kioskIconScale: number;
|
||||
kioskFontScale: number;
|
||||
stageSize?: { width: number; height: number } | null;
|
||||
selectedDeviceId?: string | null;
|
||||
focusedRoomId?: string | null;
|
||||
selectedOpeningId?: string | null;
|
||||
positionOf(device: DevItem): { x: number; y: number };
|
||||
presentationOf(device: DevItem, showLqi: boolean): ResolvedDevicePresentation;
|
||||
labelPositionOf(room: RoomCfg, spaceId: string): { x: number; y: number };
|
||||
@@ -797,14 +822,13 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
|
||||
floorAnchor: PlanPoint,
|
||||
footprintHalfSize: PlanPoint,
|
||||
preferredRoomId?: string | null,
|
||||
selected = false,
|
||||
): IsoOverlayPlacement => {
|
||||
const collisionMode = input.resolveCollisions === false ? 'fit' : 'live';
|
||||
const cacheKey = `${collisionMode}\u0000${kind}\u0000${id}`;
|
||||
const shapeSignature = [floorAnchor[0], floorAnchor[1],
|
||||
footprintHalfSize[0], footprintHalfSize[1],
|
||||
preferredRoomId || '', wallHeight, visualOffset,
|
||||
input.layers.shadows ? 1 : 0, selected ? 1 : 0].join('|');
|
||||
].join('|');
|
||||
const signature = `${shapeSignature}|${unitsPerPixel}`;
|
||||
const cached = lruRead(placements!, cacheKey);
|
||||
if (cached.hit && cached.value.signature === signature) return cached.value.placement;
|
||||
@@ -851,11 +875,6 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
|
||||
wallHeight,
|
||||
visualOffset,
|
||||
sceneUnitsPerCssPixel: unitsPerPixel,
|
||||
filtersSupported: input.layers.shadows,
|
||||
// A persistent tether keeps ownership explicit without a second hover-only
|
||||
// render pipeline for the inert raised geometry.
|
||||
hovered: true,
|
||||
selected,
|
||||
});
|
||||
lruWrite(placements!, cacheKey, { signature, shapeSignature, unitsPerPixel, placement },
|
||||
ISO_OVERLAY_PLACEMENT_CACHE_LIMIT);
|
||||
@@ -874,7 +893,6 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
|
||||
'device', device.id, [pos.x, pos.y],
|
||||
halfSize,
|
||||
preferredRoomId,
|
||||
input.selectedDeviceId === device.id,
|
||||
);
|
||||
devices.set(device.id, placement);
|
||||
entries.push({
|
||||
@@ -897,7 +915,6 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
|
||||
const placement = place(
|
||||
'room-label', overlayRoom.id, [pos.x, pos.y],
|
||||
halfSize, overlayRoom.id,
|
||||
input.focusedRoomId === room.id,
|
||||
);
|
||||
roomPlacements.set(room, placement);
|
||||
entries.push({
|
||||
@@ -923,7 +940,6 @@ export function buildIsoOverlayRenderScene(input: IsoOverlaySceneInput): IsoOver
|
||||
const placement = place(
|
||||
'opening-lock', String(opening.id), floorAnchor,
|
||||
halfSize, lockPlacement.preferredRoomId,
|
||||
input.selectedOpeningId === opening.id,
|
||||
);
|
||||
locks.set(String(opening.id), placement);
|
||||
entries.push({
|
||||
@@ -1045,7 +1061,7 @@ export interface IsoFramePresentation {
|
||||
raised: TemplateResult;
|
||||
}
|
||||
|
||||
/** Resolve every lazy Stage 3 artifact inside the card's single failure boundary. */
|
||||
/** Resolve every lazy Stage 4 artifact inside the card's single failure boundary. */
|
||||
export function resolveIsoFramePresentation(input: {
|
||||
projection: 'flat' | 'iso';
|
||||
display: SpaceDisplay;
|
||||
@@ -1079,14 +1095,14 @@ export function resolveIsoFramePresentation(input: {
|
||||
|
||||
const emptySvg = (): TemplateResult => svg`` as unknown as TemplateResult;
|
||||
|
||||
/** One scale-aware visual light vector shared by every Stage 3 shadow plane. */
|
||||
/** One scale-aware visual light vector shared by every Stage 4 shadow plane. */
|
||||
export function isoFixedLightTransform(cellCm: number): string {
|
||||
return `translate(${gridVisualUnits(4, cellCm)} ${gridVisualUnits(8, cellCm)})`;
|
||||
}
|
||||
|
||||
function renderIsoDefs(
|
||||
layers: IsoDecorationLayers,
|
||||
root: 'underlay' | 'shadows' | 'walls' | 'overlays',
|
||||
root: 'underlay' | 'shadows' | 'walls',
|
||||
cellCm: number,
|
||||
): TemplateResult {
|
||||
const visualScale = gridVisualScale(cellCm);
|
||||
@@ -1122,52 +1138,21 @@ function renderIsoDefs(
|
||||
<filter id="hp-iso-leaf-shadow" data-hp-iso-material-def x="-12%" y="-30%" width="124%" height="160%">
|
||||
<feGaussianBlur stdDeviation="${2 * visualScale}"></feGaussianBlur>
|
||||
</filter>` : nothing}
|
||||
${root === 'overlays' && layers.shadows ? svg`
|
||||
<filter id="hp-iso-overlay-ground" data-hp-iso-material-def x="-45%" y="-80%" width="190%" height="260%">
|
||||
<feGaussianBlur stdDeviation="${3 * visualScale}"></feGaussianBlur>
|
||||
</filter>` : nothing}
|
||||
</defs>` as unknown as TemplateResult;
|
||||
}
|
||||
|
||||
export function renderIsoOverlayGrounds(
|
||||
overlays: IsoOverlayRenderScene | null,
|
||||
layers: IsoDecorationLayers,
|
||||
cellCm: number,
|
||||
_overlays: IsoOverlayRenderScene | null,
|
||||
_layers: IsoDecorationLayers,
|
||||
_cellCm: number,
|
||||
): TemplateResult {
|
||||
if (!overlays) return emptySvg();
|
||||
const signature = overlays.entries.map((entry) => {
|
||||
const grounding = entry.placement.grounding;
|
||||
return `${entry.kind}\u0000${entry.id}\u0000${grounding.center[0]}\u0000${grounding.center[1]}`
|
||||
+ `\u0000${grounding.visible ? 1 : 0}\u0000${entry.groundRadius}`;
|
||||
}).join('\u0001');
|
||||
return guard([signature, layers.shadows, cellCm], () => svg`
|
||||
${renderIsoDefs(layers, 'overlays', cellCm)}
|
||||
<g class="iso-overlay-grounds" data-hp="iso-overlay-grounds"
|
||||
aria-hidden="true" pointer-events="none">${overlays.entries.map((entry) => {
|
||||
if (!layers.shadows || !entry.placement.grounding.visible) return nothing;
|
||||
const [cx, cy] = entry.placement.grounding.center;
|
||||
return svg`<ellipse class="iso-overlay-ground"
|
||||
data-hp-iso-overlay-kind=${entry.kind} data-id=${entry.id}
|
||||
cx=${cx} cy=${cy} rx=${entry.groundRadius} ry=${Math.max(1, entry.groundRadius * 0.32)}
|
||||
transform=${isoFixedLightTransform(cellCm)}></ellipse>`;
|
||||
})}</g>`) as unknown as TemplateResult;
|
||||
return emptySvg();
|
||||
}
|
||||
|
||||
export function renderIsoRaisedOverlays(
|
||||
overlays: IsoOverlayRenderScene | null,
|
||||
_overlays: IsoOverlayRenderScene | null,
|
||||
): TemplateResult {
|
||||
if (!overlays) return emptySvg();
|
||||
return guard([overlays], () => svg`
|
||||
<g class="iso-raised-overlays" data-hp="iso-raised-overlays"
|
||||
aria-hidden="true" pointer-events="none">
|
||||
<g class="iso-overlay-tethers">${overlays.entries.map((entry) => {
|
||||
const tether = entry.placement.tether;
|
||||
return tether.visible ? svg`<line class="iso-overlay-tether"
|
||||
data-hp-iso-overlay-kind=${entry.kind} data-id=${entry.id}
|
||||
x1=${tether.from[0]} y1=${tether.from[1]}
|
||||
x2=${tether.to[0]} y2=${tether.to[1]}></line>` : nothing;
|
||||
})}</g>
|
||||
</g>`) as unknown as TemplateResult;
|
||||
return emptySvg();
|
||||
}
|
||||
|
||||
export function renderIsoUnderlay(
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import {
|
||||
|
||||
export interface IsoWallFace {
|
||||
d: string;
|
||||
/** Exact visible quad, reused by Stage 3 overlay collision geometry. */
|
||||
/** Exact visible quad, reused by Stage 4 overlay collision geometry. */
|
||||
points: readonly ScenePoint[];
|
||||
depth: number;
|
||||
polygon: number;
|
||||
@@ -23,7 +23,7 @@ export interface IsoWallTopFace {
|
||||
export interface IsoWallGeometry {
|
||||
/** Compatibility aggregate used by collision/debug consumers. */
|
||||
topPath: string;
|
||||
/** Independently paintable top components for the shared Stage 3 depth queue. */
|
||||
/** Independently paintable top components for the shared Stage 4 depth queue. */
|
||||
topFaces: readonly IsoWallTopFace[];
|
||||
sides: readonly IsoWallFace[];
|
||||
contactPath: string;
|
||||
|
||||
+39
-25
@@ -326,24 +326,22 @@ export const planStyles = css`
|
||||
}
|
||||
.iso-opening-panel.iso-opening-leaf-edge,
|
||||
.iso-opening-panel.iso-opening-leaf-top { fill: #8d9497; }
|
||||
.iso-opening-panel.iso-material-light-window,
|
||||
.iso-opening-panel.iso-material-light-frame,
|
||||
.iso-opening-panel.iso-material-light-sill {
|
||||
fill: #edf7f9;
|
||||
fill: #fffef8;
|
||||
fill-opacity: 0.94;
|
||||
stroke: #94adb4;
|
||||
stroke: #b3b5ae;
|
||||
}
|
||||
.iso-overlay-ground {
|
||||
fill: rgba(20, 26, 29, 0.3);
|
||||
filter: url(#hp-iso-overlay-ground);
|
||||
pointer-events: none;
|
||||
.iso-opening-panel.iso-material-light-window,
|
||||
.iso-opening-panel.iso-material-glass-side {
|
||||
fill: #c9e4f3;
|
||||
fill-opacity: 0.88;
|
||||
stroke: #8fb4c7;
|
||||
}
|
||||
.iso-overlay-tether {
|
||||
stroke: rgba(54, 65, 71, 0.48);
|
||||
stroke-width: 1;
|
||||
stroke-dasharray: 3 2;
|
||||
vector-effect: non-scaling-stroke;
|
||||
pointer-events: none;
|
||||
.iso-opening-panel.iso-material-glass-top {
|
||||
fill: #e3f2fa;
|
||||
fill-opacity: 0.96;
|
||||
stroke: #9fc4d5;
|
||||
}
|
||||
.iso-ambient-shadow {
|
||||
fill: rgba(15, 21, 25, 0.22);
|
||||
@@ -376,19 +374,25 @@ export const planStyles = css`
|
||||
.stage.theme-dark .iso-opening-panel.iso-material-matte-leaf { fill: #626a6e; stroke: #899398; }
|
||||
.stage.theme-dark .iso-opening-panel.iso-opening-leaf-edge,
|
||||
.stage.theme-dark .iso-opening-panel.iso-opening-leaf-top { fill: #4b5458; }
|
||||
.stage.theme-dark .iso-opening-panel.iso-material-light-window,
|
||||
.stage.theme-dark .iso-opening-panel.iso-material-light-frame,
|
||||
.stage.theme-dark .iso-opening-panel.iso-material-light-sill {
|
||||
fill: #d8e8eb;
|
||||
stroke: #9eb8bf;
|
||||
fill: #e8e7df;
|
||||
stroke: #a9aca7;
|
||||
}
|
||||
.stage.theme-dark .iso-opening-panel.iso-material-light-window,
|
||||
.stage.theme-dark .iso-opening-panel.iso-material-glass-side {
|
||||
fill: #8fbfd5;
|
||||
stroke: #bad9e6;
|
||||
}
|
||||
.stage.theme-dark .iso-opening-panel.iso-material-glass-top {
|
||||
fill: #c9e6f2;
|
||||
stroke: #d9eef6;
|
||||
}
|
||||
.stage.theme-dark .iso-ambient-shadow { fill: rgba(0, 0, 0, 0.34); }
|
||||
.stage.theme-dark .iso-contact-shadow,
|
||||
.stage.theme-dark .iso-leaf-shadow { stroke: rgba(0, 0, 0, 0.38); }
|
||||
.stage.theme-dark .iso-texture-mark { fill: rgba(255, 255, 255, 0.24); }
|
||||
.stage.theme-dark .iso-texture-line { stroke: rgba(255, 255, 255, 0.18); }
|
||||
.stage.theme-dark .iso-overlay-ground { fill: rgba(0, 0, 0, 0.42); }
|
||||
.stage.theme-dark .iso-overlay-tether { stroke: rgba(224, 232, 235, 0.5); }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* HA's explicit theme wins over the operating-system preference. */
|
||||
.stage:not(.theme-light) .iso-side-hi { stop-color: #4c555a; }
|
||||
@@ -404,16 +408,16 @@ export const planStyles = css`
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-matte-leaf { fill: #626a6e; stroke: #899398; }
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-opening-leaf-edge,
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-opening-leaf-top { fill: #4b5458; }
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-light-window,
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-light-frame,
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-light-sill { fill: #d8e8eb; stroke: #9eb8bf; }
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-light-sill { fill: #e8e7df; stroke: #a9aca7; }
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-light-window,
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-glass-side { fill: #8fbfd5; stroke: #bad9e6; }
|
||||
.stage:not(.theme-light) .iso-opening-panel.iso-material-glass-top { fill: #c9e6f2; stroke: #d9eef6; }
|
||||
.stage:not(.theme-light) .iso-ambient-shadow { fill: rgba(0, 0, 0, 0.34); }
|
||||
.stage:not(.theme-light) .iso-contact-shadow,
|
||||
.stage:not(.theme-light) .iso-leaf-shadow { stroke: rgba(0, 0, 0, 0.38); }
|
||||
.stage:not(.theme-light) .iso-texture-mark { fill: rgba(255, 255, 255, 0.24); }
|
||||
.stage:not(.theme-light) .iso-texture-line { stroke: rgba(255, 255, 255, 0.18); }
|
||||
.stage:not(.theme-light) .iso-overlay-ground { fill: rgba(0, 0, 0, 0.42); }
|
||||
.stage:not(.theme-light) .iso-overlay-tether { stroke: rgba(224, 232, 235, 0.5); }
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
.iso-wall-side, .iso-wall-top, .iso-floor-side, .iso-opening-panel {
|
||||
@@ -422,12 +426,11 @@ export const planStyles = css`
|
||||
forced-color-adjust: auto;
|
||||
}
|
||||
.iso-ambient-shadow, .iso-contact-shadow, .iso-leaf-shadow,
|
||||
.iso-overlay-ground, .iso-material-texture { display: none; }
|
||||
.iso-overlay-tether { stroke: CanvasText; }
|
||||
.iso-material-texture { display: none; }
|
||||
}
|
||||
@supports not (filter: blur(1px)) {
|
||||
.iso-ambient-shadow, .iso-contact-shadow, .iso-leaf-shadow,
|
||||
.iso-overlay-ground, .iso-material-texture { display: none; }
|
||||
.iso-material-texture { display: none; }
|
||||
}
|
||||
/* Opaque plan paper (owner 2026-08-03): the scene bg_color / daynight sky
|
||||
shows ONLY around the plan, never through it. The colour is the
|
||||
@@ -761,6 +764,17 @@ export const planStyles = css`
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
justify-content: center;
|
||||
text-shadow: none;
|
||||
filter: none;
|
||||
-webkit-text-stroke: 0 transparent;
|
||||
/* Flat writes the configured room color inline. Iso View owns the
|
||||
approved contrast palette, so it must explicitly override that
|
||||
presentation-only inline value. */
|
||||
color: #303936 !important;
|
||||
}
|
||||
.stage.projection-iso.theme-dark.mode-view .roomlabel { color: #f2f0e8 !important; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.stage.projection-iso:not(.theme-light).mode-view .roomlabel { color: #f2f0e8 !important; }
|
||||
}
|
||||
.stage.mode-view .roomlabel:focus-visible {
|
||||
outline: 2px solid var(--hp-accent);
|
||||
|
||||
+14
-12
@@ -810,7 +810,7 @@ test('opening symbol goldens lock room, diagonal, flip-pair and hidden Iso contr
|
||||
}
|
||||
});
|
||||
|
||||
test('issue 160 Stage 3 goldens cover raised ownership, openings and solid fallbacks', () => {
|
||||
test('issue 570 Stage 4 reuses the historical iso goldens for visual handoff coverage', () => {
|
||||
const scenarios = GOLDEN_SCENARIOS.filter((item) => item.id.startsWith('isometric-stage3-'));
|
||||
assert.deepEqual(scenarios.map((item) => item.id), [
|
||||
'isometric-stage3-overlays-light',
|
||||
@@ -825,8 +825,8 @@ test('issue 160 Stage 3 goldens cover raised ownership, openings and solid fallb
|
||||
assert.deepEqual(new Set(overlays.map((item) => item.theme)), new Set(['light', 'dark']));
|
||||
assert.equal(overlays.every((item) => item.stage3Golden.requireNudged
|
||||
&& item.stage3Golden.requireDenseFacets
|
||||
&& item.stage3Golden.requireTetherCues
|
||||
&& item.stage3Golden.requireGrounding
|
||||
&& item.stage3Golden.requireNoTetherCues
|
||||
&& item.stage3Golden.requireNoGrounding
|
||||
&& item.stage3Golden.requiredKinds.includes('device')
|
||||
&& item.stage3Golden.requiredKinds.includes('room-label')
|
||||
&& item.stage3Golden.requiredKinds.includes('opening-lock')), true);
|
||||
@@ -866,8 +866,10 @@ test('issue 160 Stage 3 goldens cover raised ownership, openings and solid fallb
|
||||
const noFilter = scenarios.find((item) => item.disableIsoFilters);
|
||||
assert.equal(forced.stage3Golden.requireNoMaterialDefs, true);
|
||||
assert.equal(noFilter.stage3Golden.requireNoMaterialDefs, true);
|
||||
assert.equal(forced.stage3Golden.requireTetherCues, true);
|
||||
assert.equal(noFilter.stage3Golden.requireTetherCues, true);
|
||||
assert.equal(forced.stage3Golden.requireNoTetherCues, true);
|
||||
assert.equal(noFilter.stage3Golden.requireNoTetherCues, true);
|
||||
assert.equal(forced.stage3Golden.requireNoGrounding, true);
|
||||
assert.equal(noFilter.stage3Golden.requireNoGrounding, true);
|
||||
const noBorders = GOLDEN_SCENARIOS.find((item) => item.id === 'isometric-no-borders-dark');
|
||||
assert.equal(noBorders.stage3Golden.noBorders, true);
|
||||
|
||||
@@ -875,20 +877,20 @@ test('issue 160 Stage 3 goldens cover raised ownership, openings and solid fallb
|
||||
assert.match(harness,
|
||||
/\[data-hp-iso-overlay-kind\]\[data-hp-iso-floor\]\[data-hp-iso-visual\]/);
|
||||
assert.match(harness, /forcedColors: scenario\.forcedColors \? 'active' : 'none'/);
|
||||
assert.match(harness, /Stage 3 golden raised a floor-bound vacuum/);
|
||||
assert.match(harness, /Stage 3 no-borders golden retained raised overlays/);
|
||||
assert.match(harness, /Stage 4 golden raised a floor-bound vacuum/);
|
||||
assert.match(harness, /Stage 4 no-borders golden retained low overlays/);
|
||||
assert.match(harness, /!scenario\.stage3Golden\?\.noBorders[\s\S]*?data-hp-iso-stage/,
|
||||
'no-borders remains true Iso without requiring the intentionally absent volume stage');
|
||||
assert.match(harness,
|
||||
/\.\.\.\(scenario\.stage3Golden[\s\S]*?\? \{ themes: \{ darkMode: scenario\.theme === 'dark' \} \}[\s\S]*?: \{\}\)/,
|
||||
'explicit HA darkMode must be scoped to Stage 3 golden scenarios');
|
||||
'explicit HA darkMode must be scoped to isometric golden scenarios');
|
||||
assert.doesNotMatch(harness,
|
||||
/locale: \{ language: scenario\.language \|\| 'en' \},\s*themes:/,
|
||||
'legacy Flat and Iso golden inputs must not gain an unconditional HA themes field');
|
||||
assert.match(harness, /Stage 3 golden did not use HA darkMode/);
|
||||
assert.match(harness, /Stage 3 dense golden lacks/);
|
||||
assert.match(harness, /Stage 3 combined golden lacks/);
|
||||
assert.ok(harness.lastIndexOf('Stage 3 combined golden lacks')
|
||||
assert.match(harness, /Stage 4 golden did not use HA darkMode/);
|
||||
assert.match(harness, /Stage 4 dense golden lacks/);
|
||||
assert.match(harness, /Stage 4 combined golden lacks/);
|
||||
assert.ok(harness.lastIndexOf('Stage 4 combined golden lacks')
|
||||
> harness.lastIndexOf("await until(() => !!card.renderRoot.querySelector('.vactrail > path.case'))"),
|
||||
'combined live-layer preflight must run after the async vacuum fixture settles');
|
||||
});
|
||||
|
||||
+52
-16
@@ -1,12 +1,13 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
ISO_OPENING_FRAME_THICKNESS_RATIO, ISO_OPENING_LEAF_THICKNESS_RATIO,
|
||||
ISO_OPENING_FRAME_THICKNESS_RATIO, ISO_OPENING_GEOMETRY_POLICY,
|
||||
ISO_OPENING_LEAF_THICKNESS_RATIO,
|
||||
buildIsoOpeningBasis, isoOpeningBounds, projectIsoOpening,
|
||||
projectIsoOpeningStructure, resolveIsoDecoration,
|
||||
} from '../test-build/iso-openings.js';
|
||||
import { openingAmount } from '../test-build/logic.js';
|
||||
import { projectPlanPoint } from '../test-build/iso-projection.js';
|
||||
import { ISO_WALL_HEIGHT, projectPlanPoint } from '../test-build/iso-projection.js';
|
||||
|
||||
const opening = (patch = {}) => ({
|
||||
id: 'op-1', sourceIndex: 0, type: 'door', x: 100, y: 80,
|
||||
@@ -24,10 +25,11 @@ test('door basis is immutable and live amount preserves the exact jamb anchor',
|
||||
assert.match(closed.d, new RegExp(`^M ${hinge.replace('.', '\\.')}`));
|
||||
assert.match(open.d, new RegExp(`^M ${hinge.replace('.', '\\.')}`));
|
||||
assert.notEqual(open.d, closed.d);
|
||||
assert.equal(basis.leaves[0].top, 64 * 0.92);
|
||||
assert.equal(basis.leaves[0].top, ISO_WALL_HEIGHT * 0.92);
|
||||
assert.equal(basis.leaves[0].turnDeg, -50);
|
||||
});
|
||||
|
||||
test('Stage 3 basis freezes the real axis, physical face depth and jamb endpoints', () => {
|
||||
test('Stage 4 basis freezes the real axis, physical face depth and jamb endpoints', () => {
|
||||
const basis = buildIsoOpeningBasis(opening());
|
||||
assert.equal(Object.isFrozen(basis), true);
|
||||
assert.equal(Object.isFrozen(basis.axis), true);
|
||||
@@ -55,8 +57,8 @@ test('Stage 3 basis freezes the real axis, physical face depth and jamb endpoint
|
||||
{ jamb: 0, center: [70, 80], selected: [70, 85], opposite: [70, 75] },
|
||||
{ jamb: 1, center: [130, 80], selected: [130, 85], opposite: [130, 75] },
|
||||
]);
|
||||
assert.equal(basis.leafThickness, 64 * ISO_OPENING_LEAF_THICKNESS_RATIO);
|
||||
assert.equal(basis.frameThickness, 64 * ISO_OPENING_FRAME_THICKNESS_RATIO);
|
||||
assert.equal(basis.leafThickness, ISO_WALL_HEIGHT * ISO_OPENING_LEAF_THICKNESS_RATIO);
|
||||
assert.equal(basis.frameThickness, ISO_WALL_HEIGHT * ISO_OPENING_FRAME_THICKNESS_RATIO);
|
||||
|
||||
const rotated = buildIsoOpeningBasis(opening({
|
||||
angle: 90,
|
||||
@@ -102,14 +104,22 @@ test('door and gate expose matte fixed-thickness leaf prisms plus real jamb reve
|
||||
}
|
||||
});
|
||||
|
||||
test('window has only light inserts, frame and sill surfaces and no dark glass material', () => {
|
||||
test('window separates neutral fixed/sash frames from blue glass above the sill', () => {
|
||||
const basis = buildIsoOpeningBasis(opening({ type: 'window' }));
|
||||
assert.deepEqual(basis.windowProfile, {
|
||||
frameBottom: ISO_WALL_HEIGHT * 0.38,
|
||||
frameTop: ISO_WALL_HEIGHT,
|
||||
sashBottom: ISO_WALL_HEIGHT * 0.40,
|
||||
sashTop: ISO_WALL_HEIGHT * 0.98,
|
||||
glassBottom: ISO_WALL_HEIGHT * 0.45,
|
||||
glassTop: ISO_WALL_HEIGHT * 0.93,
|
||||
member: ISO_WALL_HEIGHT * 0.05,
|
||||
});
|
||||
const structure = projectIsoOpeningStructure(basis);
|
||||
assert.equal(structure.filter((surface) => surface.kind === 'jamb-reveal').length, 2);
|
||||
assert.equal(structure.filter((surface) => surface.kind === 'window-frame-side').length, 4);
|
||||
assert.equal(structure.filter((surface) => surface.kind === 'window-frame-top').length, 2);
|
||||
assert.equal(structure.filter((surface) => surface.kind === 'window-sill').length, 1);
|
||||
assert.equal(structure.some((surface) => /glass|dark/i.test(surface.material)), false);
|
||||
assert.equal(structure.filter((surface) => surface.kind.startsWith('window-'))
|
||||
.every((surface) => ['light-frame', 'light-sill'].includes(surface.material)), true);
|
||||
|
||||
@@ -117,12 +127,16 @@ test('window has only light inserts, frame and sill surfaces and no dark glass m
|
||||
assert.equal(panels.length, 2);
|
||||
assert.equal(panels.every((panel) => panel.material === 'light-window'
|
||||
&& panel.thickness === 0
|
||||
&& panel.surfaces.length === 1
|
||||
&& panel.surfaces[0].kind === 'window-insert'
|
||||
&& panel.surfaces[0].material === 'light-window'), true);
|
||||
&& panel.surfaces.length === 7
|
||||
&& panel.surfaces.filter((surface) => surface.material === 'light-frame').length === 4
|
||||
&& panel.surfaces.filter((surface) => surface.material === 'glass-side').length === 2
|
||||
&& panel.surfaces.filter((surface) => surface.material === 'glass-top').length === 1), true);
|
||||
assert.equal(panels.flatMap((panel) => panel.surfaces)
|
||||
.filter((surface) => surface.material.startsWith('glass'))
|
||||
.every((surface) => ['window-glass', 'window-glass-top'].includes(surface.kind)), true);
|
||||
});
|
||||
|
||||
test('live projection is O(leaves)-only and cannot mutate the structural Stage 3 basis', () => {
|
||||
test('live projection is O(leaves)-only and cannot mutate the structural Stage 4 basis', () => {
|
||||
const basis = buildIsoOpeningBasis(opening({ type: 'gate', flipV: true }));
|
||||
const snapshot = structuredClone(basis);
|
||||
const structure = projectIsoOpeningStructure(basis);
|
||||
@@ -133,17 +147,39 @@ test('live projection is O(leaves)-only and cannot mutate the structural Stage 3
|
||||
assert.deepEqual(basis, snapshot);
|
||||
});
|
||||
|
||||
test('window and gate retain two leaves, fixed height bounds and gate 10 degree turn', () => {
|
||||
test('window and gate retain two leaves with reviewed height and turn policies', () => {
|
||||
const windowBasis = buildIsoOpeningBasis(opening({ type: 'window' }));
|
||||
const gateBasis = buildIsoOpeningBasis(opening({ type: 'gate', flipV: true }));
|
||||
assert.equal(windowBasis.leaves.length, 2);
|
||||
assert.equal(windowBasis.leaves.every((leaf) => leaf.bottom === 64 * 0.27
|
||||
&& leaf.top === 64 * 0.78), true);
|
||||
assert.equal(ISO_OPENING_GEOMETRY_POLICY.revision, 2);
|
||||
assert.equal(windowBasis.leaves.every((leaf) => leaf.bottom === ISO_WALL_HEIGHT * 0.40
|
||||
&& leaf.top === ISO_WALL_HEIGHT * 0.98 && Math.abs(leaf.turnDeg) === 65), true);
|
||||
assert.deepEqual(gateBasis.leaves.map((leaf) => Math.abs(leaf.turnDeg)), [10, 10]);
|
||||
assert.equal(gateBasis.leaves.every((leaf) => leaf.top === 64 * 0.88), true);
|
||||
assert.equal(gateBasis.leaves.every((leaf) => leaf.top === ISO_WALL_HEIGHT * 0.88), true);
|
||||
assert.equal(projectIsoOpening(gateBasis, 1).length, 2);
|
||||
});
|
||||
|
||||
test('paired window leaves open toward their resolved exterior face in every orientation', () => {
|
||||
for (const angle of [0, 90, 180, 270, 37]) {
|
||||
const radians = angle * Math.PI / 180;
|
||||
const normal = [-Math.sin(radians), Math.cos(radians)];
|
||||
for (const flipV of [false, true]) for (const flipH of [false, true]) {
|
||||
const side = flipV ? 1 : -1;
|
||||
const basis = buildIsoOpeningBasis(opening({
|
||||
type: 'window', angle, flipV, flipH,
|
||||
face: { ox: normal[0] * side * 5, oy: normal[1] * side * 5, cm: 20, side },
|
||||
}));
|
||||
for (const leaf of basis.leaves) {
|
||||
const turn = leaf.turnDeg * Math.PI / 180;
|
||||
const swingNormal = (leaf.quarterVector[0] * Math.sin(turn)) * normal[0]
|
||||
+ (leaf.quarterVector[1] * Math.sin(turn)) * normal[1];
|
||||
assert.ok(swingNormal * side > 0,
|
||||
`angle=${angle} flipH=${flipH} flipV=${flipV} leaf=${leaf.leaf}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('isometric symbols keep one centre while flips change only direction', () => {
|
||||
const centred = buildIsoOpeningBasis(opening());
|
||||
const oppositeResolvedFace = buildIsoOpeningBasis(opening({
|
||||
|
||||
+20
-20
@@ -11,7 +11,6 @@ import {
|
||||
} from '../test-build/iso-overlays.js';
|
||||
import {
|
||||
ISO_RAISED_OVERLAY_HEIGHT,
|
||||
ISO_WALL_HEIGHT,
|
||||
projectPlanPoint,
|
||||
} from '../test-build/iso-projection.js';
|
||||
|
||||
@@ -43,7 +42,7 @@ const placement = (overrides = {}) => resolveIsoOverlayPlacement({
|
||||
...overrides,
|
||||
});
|
||||
|
||||
test('the exact D2 overlay matrix keeps only the three accepted roots raised', () => {
|
||||
test('the exact Stage 4 overlay matrix keeps only the three interactive roots on the low plane', () => {
|
||||
for (const kind of ['device', 'room-label', 'opening-lock'])
|
||||
assert.equal(isoOverlayPlane(kind, true), 'raised', kind);
|
||||
for (const kind of [
|
||||
@@ -124,7 +123,7 @@ test('a degenerate room has no safe point and placement degrades without throwin
|
||||
const result = placement({
|
||||
rooms: [degenerate],
|
||||
preferredRoomId: degenerate.id,
|
||||
wallSilhouettes: [{ outer: buildIsoFootprintPolygon([50, 50], [10, 10], ISO_WALL_HEIGHT) }],
|
||||
wallSilhouettes: [{ outer: buildIsoFootprintPolygon([50, 50], [10, 10], ISO_RAISED_OVERLAY_HEIGHT) }],
|
||||
});
|
||||
assert.equal(result.owner, null);
|
||||
assert.equal(result.status, 'degraded');
|
||||
@@ -140,11 +139,12 @@ test('footprint corners use the same affine camera on the raised plane', () => {
|
||||
close(footprint[index][0], projected[0]);
|
||||
close(footprint[index][1], projected[1]);
|
||||
});
|
||||
assert.notEqual(footprint[0][1], footprint[1][1],
|
||||
'the +4° footprint is a projected parallelogram');
|
||||
close(footprint[0][1], footprint[1][1]);
|
||||
assert.ok(footprint[2][1] > footprint[1][1],
|
||||
'zero yaw keeps the screen-facing footprint aligned with the plan axes');
|
||||
});
|
||||
|
||||
test('free raised overlay separates floor, raised, footprint and optional tether geometry', () => {
|
||||
test('free low overlay separates the immutable floor anchor from invisible collision geometry', () => {
|
||||
const normal = placement();
|
||||
assert.equal(normal.plane, 'raised');
|
||||
assert.deepEqual(normal.floorAnchor, [50, 50]);
|
||||
@@ -152,19 +152,19 @@ test('free raised overlay separates floor, raised, footprint and optional tether
|
||||
assert.deepEqual(normal.raisedScene, projectPlanPoint([50, 50], ISO_RAISED_OVERLAY_HEIGHT));
|
||||
assert.deepEqual(normal.visualScene, normal.raisedScene);
|
||||
assert.equal(normal.footprint.length, 4);
|
||||
assert.equal(normal.grounding.visible, true);
|
||||
assert.equal(normal.grounding.visible, false);
|
||||
assert.equal(normal.tether.visible, false);
|
||||
assert.equal(normal.status, 'ok');
|
||||
|
||||
for (const state of ['hovered', 'focused', 'selected'])
|
||||
assert.equal(placement({ [state]: true }).tether.visible, true, state);
|
||||
assert.equal(placement({ [state]: true }).tether.visible, false, state);
|
||||
assert.equal(placement({ filtersSupported: false }).grounding.visible, false,
|
||||
'unsupported filters remove the soft grounding shadow only');
|
||||
});
|
||||
|
||||
test('wall-aware nudge is deterministic, minimal, inward and never changes the floor anchor', () => {
|
||||
const wall = {
|
||||
outer: buildIsoFootprintPolygon([0, 50], [4, 60], ISO_WALL_HEIGHT),
|
||||
outer: buildIsoFootprintPolygon([0, 50], [4, 60], ISO_RAISED_OVERLAY_HEIGHT),
|
||||
};
|
||||
const input = {
|
||||
floorAnchor: [5, 50],
|
||||
@@ -188,12 +188,12 @@ test('wall-aware nudge is deterministic, minimal, inward and never changes the f
|
||||
const towardSafe = projectPlanPoint([50, 50], ISO_RAISED_OVERLAY_HEIGHT);
|
||||
const safeVector = [towardSafe[0] - first.raisedScene[0], towardSafe[1] - first.raisedScene[1]];
|
||||
assert.ok(first.nudgeScene[0] * safeVector[0] + first.nudgeScene[1] * safeVector[1] > 0);
|
||||
assert.equal(first.tether.visible, true);
|
||||
assert.equal(first.tether.visible, false);
|
||||
});
|
||||
|
||||
test('shared-wall and corner fixtures nudge only toward the selected room', () => {
|
||||
const verticalWall = {
|
||||
outer: buildIsoFootprintPolygon([50, 50], [3, 60], ISO_WALL_HEIGHT),
|
||||
outer: buildIsoFootprintPolygon([50, 50], [3, 60], ISO_RAISED_OVERLAY_HEIGHT),
|
||||
};
|
||||
const shared = placement({
|
||||
floorAnchor: [49, 50],
|
||||
@@ -212,14 +212,14 @@ test('shared-wall and corner fixtures nudge only toward the selected room', () =
|
||||
rooms: [square('corner', 0, 0, 100, 100, [50, 50])],
|
||||
preferredRoomId: 'corner',
|
||||
wallSilhouettes: [
|
||||
{ outer: buildIsoFootprintPolygon([0, 50], [4, 60], ISO_WALL_HEIGHT) },
|
||||
{ outer: buildIsoFootprintPolygon([50, 0], [60, 4], ISO_WALL_HEIGHT) },
|
||||
{ outer: buildIsoFootprintPolygon([0, 50], [4, 60], ISO_RAISED_OVERLAY_HEIGHT) },
|
||||
{ outer: buildIsoFootprintPolygon([50, 0], [60, 4], ISO_RAISED_OVERLAY_HEIGHT) },
|
||||
],
|
||||
});
|
||||
assert.equal(corner.nearWallBefore, true);
|
||||
assert.ok(corner.nudgeScene[0] > 0 && corner.nudgeScene[1] > 0,
|
||||
'corner marker follows the projected inward diagonal');
|
||||
assert.equal(corner.tether.visible, true);
|
||||
assert.equal(corner.tether.visible, false);
|
||||
});
|
||||
|
||||
test('nudge never crosses an island hole or a concave-room boundary', () => {
|
||||
@@ -275,9 +275,9 @@ test('nudge never crosses an island hole or a concave-room boundary', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('cap and ambiguous ownership fail safe with a visible tether', () => {
|
||||
test('cap and ambiguous ownership fail safe without bringing back debug cues', () => {
|
||||
const largeWall = {
|
||||
outer: buildIsoFootprintPolygon([25, 50], [30, 30], ISO_WALL_HEIGHT),
|
||||
outer: buildIsoFootprintPolygon([25, 50], [30, 30], ISO_RAISED_OVERLAY_HEIGHT),
|
||||
};
|
||||
const capped = placement({
|
||||
floorAnchor: [25, 50], wallSilhouettes: [largeWall], maxNudgeCssPx: 2,
|
||||
@@ -287,19 +287,19 @@ test('cap and ambiguous ownership fail safe with a visible tether', () => {
|
||||
assert.equal(capped.cleared, false);
|
||||
assert.equal(capped.status, 'degraded');
|
||||
assert.equal(capped.reason, 'nudge-cap');
|
||||
assert.equal(capped.tether.visible, true);
|
||||
assert.equal(capped.tether.visible, false);
|
||||
|
||||
const ownerless = placement({
|
||||
floorAnchor: [150, 50], rooms: [], preferredRoomId: null,
|
||||
wallSilhouettes: [{
|
||||
outer: buildIsoFootprintPolygon([150, 50], [10, 10], ISO_WALL_HEIGHT),
|
||||
outer: buildIsoFootprintPolygon([150, 50], [10, 10], ISO_RAISED_OVERLAY_HEIGHT),
|
||||
}],
|
||||
});
|
||||
assert.equal(ownerless.owner, null);
|
||||
assert.equal(ownerless.nudged, false);
|
||||
assert.equal(ownerless.status, 'degraded');
|
||||
assert.equal(ownerless.reason, 'missing-owner');
|
||||
assert.equal(ownerless.tether.visible, true);
|
||||
assert.equal(ownerless.tether.visible, false);
|
||||
});
|
||||
|
||||
test('malformed collision input degrades without a guessed move', () => {
|
||||
@@ -309,7 +309,7 @@ test('malformed collision input degrades without a guessed move', () => {
|
||||
assert.equal(result.status, 'degraded');
|
||||
assert.equal(result.reason, 'invalid-wall-geometry');
|
||||
assert.equal(result.nudged, false);
|
||||
assert.equal(result.tether.visible, true);
|
||||
assert.equal(result.tether.visible, false);
|
||||
});
|
||||
|
||||
test('show_borders:false is exact no-volume: floor anchor, no footprint/nudge/cues', () => {
|
||||
|
||||
@@ -3,24 +3,25 @@ import assert from 'node:assert/strict';
|
||||
import {
|
||||
ISO_CAMERA, ISO_OVERLAY_VISUAL_OFFSET, ISO_RAISED_OVERLAY_HEIGHT,
|
||||
ISO_WALL_HEIGHT, applyIsoMatrix, clientToScenePoint, isoFloorMatrix,
|
||||
isoPlaneMatrix, isoPlaneMatrixCss, isoRaisedOverlayHeight, projectPlanPoint,
|
||||
isoOverlayVisualHeight, isoPlaneMatrix, isoPlaneMatrixCss, projectPlanPoint,
|
||||
projectedFrame, unprojectFloorPoint,
|
||||
} from '../test-build/iso-projection.js';
|
||||
|
||||
const close = (actual, expected, epsilon = 1e-9) =>
|
||||
assert.ok(Math.abs(actual - expected) <= epsilon, `${actual} != ${expected}`);
|
||||
|
||||
test('Stage 3 camera is the exact fixed +4°/20° orthographic camera', () => {
|
||||
assert.equal(ISO_CAMERA.rotDeg, 4);
|
||||
test('Stage 4 camera is the exact fixed 0°/20° orthographic camera', () => {
|
||||
assert.equal(ISO_CAMERA.rotDeg, 0);
|
||||
assert.equal(ISO_CAMERA.tiltDeg, 20);
|
||||
const origin = projectPlanPoint([500, 500], 0);
|
||||
assert.deepEqual(origin, [500, 500]);
|
||||
const x = projectPlanPoint([600, 500], 0);
|
||||
const y = projectPlanPoint([500, 600], 0);
|
||||
assert.ok(x[0] > 500 && x[1] > 500, 'positive plan X follows the +4° yaw');
|
||||
assert.ok(y[0] < 500 && y[1] > 500, 'positive plan Y follows the +4° yaw');
|
||||
assert.ok(x[0] > 500 && x[1] === 500, 'positive plan X stays horizontal at zero yaw');
|
||||
assert.ok(y[0] === 500 && y[1] > 500, 'positive plan Y follows the tilted vertical axis');
|
||||
assert.equal(ISO_WALL_HEIGHT, 84);
|
||||
assert.equal(ISO_OVERLAY_VISUAL_OFFSET, 4);
|
||||
assert.equal(ISO_RAISED_OVERLAY_HEIGHT, 68);
|
||||
assert.equal(ISO_RAISED_OVERLAY_HEIGHT, 4);
|
||||
});
|
||||
|
||||
test('floor projection round-trips across the infinite canvas contract', () => {
|
||||
@@ -52,7 +53,7 @@ test('one canonical affine helper projects floor and raised planes', () => {
|
||||
assert.match(isoPlaneMatrixCss(z), /^matrix\([-0-9.e ]+\)$/);
|
||||
}
|
||||
assert.deepEqual(isoPlaneMatrix(0), isoFloorMatrix());
|
||||
assert.equal(isoRaisedOverlayHeight(64, 4), 68);
|
||||
assert.equal(isoOverlayVisualHeight(4), 4);
|
||||
});
|
||||
|
||||
test('projected frame includes raised wall tops and is view-state independent', () => {
|
||||
@@ -75,7 +76,7 @@ test('Stage 2 frame includes opening tops and the structural floor edge, not blu
|
||||
assert.deepEqual(Object.keys(stage2).sort(), ['h', 'w', 'x', 'y']);
|
||||
});
|
||||
|
||||
test('Stage 3 frame includes the final raised plane and diagonal camera corners', () => {
|
||||
test('Stage 4 frame already contains the low overlay plane at zero yaw', () => {
|
||||
const rect = { x: 100, y: 200, w: 400, h: 300 };
|
||||
const wallFrame = projectedFrame({ rect, wallHeight: ISO_WALL_HEIGHT });
|
||||
const raisedFrame = projectedFrame({
|
||||
@@ -83,15 +84,14 @@ test('Stage 3 frame includes the final raised plane and diagonal camera corners'
|
||||
wallHeight: ISO_WALL_HEIGHT,
|
||||
raisedHeight: ISO_RAISED_OVERLAY_HEIGHT,
|
||||
});
|
||||
assert.ok(raisedFrame.y < wallFrame.y, 'raised overlay top is not clipped');
|
||||
assert.ok(raisedFrame.h > wallFrame.h, 'raised overlay height participates in fit bounds');
|
||||
assert.deepEqual(raisedFrame, wallFrame, 'the wall top encloses the low overlay plane');
|
||||
const projectedFloor = [
|
||||
[rect.x, rect.y], [rect.x + rect.w, rect.y],
|
||||
[rect.x + rect.w, rect.y + rect.h], [rect.x, rect.y + rect.h],
|
||||
].map((point) => projectPlanPoint(point, 0));
|
||||
const projectedWidth = Math.max(...projectedFloor.map((point) => point[0]))
|
||||
- Math.min(...projectedFloor.map((point) => point[0]));
|
||||
assert.ok(projectedWidth > rect.w, 'the +4° camera expands the frame diagonally');
|
||||
close(projectedWidth, rect.w);
|
||||
});
|
||||
|
||||
test('client coordinates map through the current scene view', () => {
|
||||
@@ -105,7 +105,7 @@ test('degenerate cameras and frames throw instead of mixing projections', () =>
|
||||
assert.throws(() => unprojectFloorPoint([0, 0], { ...ISO_CAMERA, tiltDeg: 90 }));
|
||||
assert.throws(() => clientToScenePoint([0, 0], { left: 0, top: 0, width: 0, height: 1 },
|
||||
{ x: 0, y: 0, w: 1, h: 1 }));
|
||||
assert.throws(() => isoRaisedOverlayHeight(64, -1));
|
||||
assert.throws(() => isoOverlayVisualHeight(-1));
|
||||
assert.throws(() => projectedFrame({
|
||||
rect: { x: 0, y: 0, w: 1, h: 1 }, wallHeight: 64, raisedHeight: -1,
|
||||
}));
|
||||
|
||||
@@ -19,7 +19,12 @@ import {
|
||||
resolveIsoOverlayFitEnvelope,
|
||||
resolveIsoScene,
|
||||
} from '../test-build/iso-scene-render.js';
|
||||
import { ISO_OPENING_GEOMETRY_POLICY } from '../test-build/iso-openings.js';
|
||||
import {
|
||||
ISO_OPENING_GEOMETRY_POLICY,
|
||||
buildIsoOpeningBasis,
|
||||
projectIsoOpening,
|
||||
projectIsoOpeningStructure,
|
||||
} from '../test-build/iso-openings.js';
|
||||
import { buildIsoWallGeometry } from '../test-build/iso-walls.js';
|
||||
import { wallKey } from '../test-build/wall-thickness.js';
|
||||
import {
|
||||
@@ -40,7 +45,7 @@ const room = (id, x0, y0, x1, y1) => ({
|
||||
poly: [[x0, y0], [x1, y0], [x1, y1], [x0, y1]],
|
||||
});
|
||||
|
||||
test('every Stage 3 shadow plane uses the same scale-aware fixed-light vector', () => {
|
||||
test('every Stage 4 shadow plane uses the same scale-aware fixed-light vector', () => {
|
||||
assert.equal(isoFixedLightTransform(5), 'translate(4 8)');
|
||||
assert.equal(isoFixedLightTransform(1), 'translate(20 40)');
|
||||
});
|
||||
@@ -188,7 +193,7 @@ test('one painter queue paints a nearer wall after an unrelated rear opening', (
|
||||
const rearOpening = {
|
||||
id: 'rear-door', sourceIndex: 4, type: 'door', leaf: 0,
|
||||
kind: 'leaf-front', material: 'matte-leaf', d: 'M 0 0 L 1 0 L 1 1 Z',
|
||||
depth: nearWall.depth - 10,
|
||||
depth: nearWall.depth - 10, cameraDepth: nearWall.depth - 10,
|
||||
};
|
||||
const queue = buildIsoWallDepthQueue(geometry, [rearOpening]);
|
||||
const rearIndex = queue.findIndex((entry) => entry.layer === 'opening');
|
||||
@@ -200,6 +205,40 @@ test('one painter queue paints a nearer wall after an unrelated rear opening', (
|
||||
'the combined wall/opening order is deterministic');
|
||||
});
|
||||
|
||||
test('shared painter queue puts elevated window glass over its rear sill only in window slots', () => {
|
||||
const geometry = buildIsoWallGeometry([[[
|
||||
[0, 100], [100, 100], [100, 200], [0, 200], [0, 100],
|
||||
]]]);
|
||||
const basis = buildIsoOpeningBasis({
|
||||
id: 'window-overlap', sourceIndex: 2, type: 'window', x: 50, y: 100,
|
||||
angle: 0, length: 60, flipH: false, flipV: false,
|
||||
face: { ox: 0, oy: -5, cm: 20, side: -1 },
|
||||
});
|
||||
const surfaces = [
|
||||
...projectIsoOpeningStructure(basis),
|
||||
...projectIsoOpening(basis, 0).flatMap((panel) => panel.surfaces),
|
||||
].map((surface, index) => ({
|
||||
...surface, id: basis.id, sourceIndex: basis.sourceIndex,
|
||||
type: 'window', leaf: index,
|
||||
}));
|
||||
const queue = buildIsoWallDepthQueue(geometry, surfaces);
|
||||
const windowEntries = queue.filter((entry) => entry.layer === 'opening');
|
||||
assert.deepEqual(windowEntries.map((entry) => entry.surface.cameraDepth),
|
||||
windowEntries.map((entry) => entry.surface.cameraDepth).toSorted((a, b) => a - b),
|
||||
'one window reuses its shared queue slots in physical camera-depth order');
|
||||
const sillIndex = queue.findIndex((entry) => entry.layer === 'opening'
|
||||
&& entry.surface.kind === 'window-sill');
|
||||
const glassIndices = queue.flatMap((entry, index) => entry.layer === 'opening'
|
||||
&& entry.surface.material.startsWith('glass') ? [index] : []);
|
||||
assert.ok(sillIndex >= 0 && glassIndices.length === 6
|
||||
&& glassIndices.some((index) => index > sillIndex),
|
||||
'elevated glass must paint after the rear sill projection');
|
||||
const wallSlots = queue.flatMap((entry, index) => entry.layer === 'opening' ? [] : [index]);
|
||||
const reversed = buildIsoWallDepthQueue(geometry, [...surfaces].reverse());
|
||||
assert.deepEqual(reversed.flatMap((entry, index) => entry.layer === 'opening' ? [] : [index]),
|
||||
wallSlots, 'window-local occlusion cannot move unrelated wall slots');
|
||||
});
|
||||
|
||||
test('production overlay rooms preserve direct island holes and cache safe points', () => {
|
||||
const outer = room('outer', 0, 0, 100, 100);
|
||||
const island = room('island', 40, 40, 60, 60);
|
||||
@@ -335,10 +374,10 @@ test('opening lock without a canonical host owner never guesses from point conta
|
||||
assert.equal(placement?.owner, null);
|
||||
assert.equal(placement?.nudged, false);
|
||||
assert.equal(placement?.reason, 'missing-owner');
|
||||
assert.equal(placement?.tether.visible, true);
|
||||
assert.equal(placement?.tether.visible, false);
|
||||
});
|
||||
|
||||
test('Stage 3 reuses pure overlay placements and fit probes skip collision search', () => {
|
||||
test('Stage 4 reuses pure overlay placements and fit probes skip collision search', () => {
|
||||
const owner = room('owner', 0, 0, 100, 100);
|
||||
const space = {
|
||||
id: 'floor', title: 'Floor', cellCm: 5, vb: [0, 0, 100, 100], bg: null,
|
||||
@@ -458,7 +497,7 @@ test('visible wall side quads participate in overlay collision', () => {
|
||||
'the same raised footprint intersects a rendered vertical side');
|
||||
});
|
||||
|
||||
test('orphan hosted openings never become phantom Stage 3 volumes', () => {
|
||||
test('orphan hosted openings never become phantom Stage 4 volumes', () => {
|
||||
const base = {
|
||||
type: 'door', rx: 20, ry: 30, rlen: 40, angle: 0,
|
||||
flip_h: false, flip_v: false,
|
||||
@@ -746,19 +785,16 @@ const perfFixture = () => {
|
||||
return { input, wallSilhouettes };
|
||||
};
|
||||
|
||||
test('#473 W1: выделение входит в подпись кэша размещений', () => {
|
||||
test('#570 supersedes #473 W1: selection reuses the cue-free low placement', () => {
|
||||
const { input } = perfFixture();
|
||||
const plain = buildIsoOverlayRenderScene(input).devices.get('device');
|
||||
const selected = buildIsoOverlayRenderScene({ ...input, selectedDeviceId: 'device' })
|
||||
.devices.get('device');
|
||||
// Подпись без `selected` вернула бы тот же кэшированный объект — плита
|
||||
// выделенного устройства осталась бы без своего размещения.
|
||||
assert.notStrictEqual(selected, plain, 'выделение обязано дать своё размещение, а не кэш');
|
||||
// Кэш держит одну запись на (режим, вид, id): снятие выделения пересчитывает
|
||||
// заново, и это правильно — подпись изменилась. Тождества с `plain` здесь
|
||||
// быть не должно, проверяется только отсутствие подмены.
|
||||
// Stage 4 removed the selected/hover tether and ground cue. Selection no
|
||||
// longer changes collision geometry and therefore must not churn the cache.
|
||||
assert.strictEqual(selected, plain, 'selection is presentation-only after cue removal');
|
||||
const again = buildIsoOverlayRenderScene(input).devices.get('device');
|
||||
assert.notStrictEqual(again, selected, 'снятие выделения не отдаёт выделенное размещение');
|
||||
assert.strictEqual(again, selected, 'clearing selection keeps the same immutable placement');
|
||||
});
|
||||
|
||||
test('#473 W2: кэш размещений привязан к идентичности массива силуэтов', () => {
|
||||
@@ -808,7 +844,7 @@ test('#473 W4: AABB-отсечение учитывает зазор безоп
|
||||
// до точного теста, и плита легла бы вплотную к стене.
|
||||
// Стена — компактный силуэт: у длинной стены AABB в изометрии накрывает плиту
|
||||
// при любом сдвиге вдоль оси, и отсечение не участвует в решении.
|
||||
const wall = { outer: buildIsoFootprintPolygon([0, 50], [2, 2], ISO_WALL_HEIGHT) };
|
||||
const wall = { outer: buildIsoFootprintPolygon([0, 50], [2, 2], 0) };
|
||||
const owner = { id: 'owner', outer: [[0, 0], [100, 0], [100, 100], [0, 100]], holes: [], safePoint: [50, 50] };
|
||||
const placeAt = (x, safetyGapCssPx) => resolveIsoOverlayPlacement({
|
||||
kind: 'device', floorAnchor: [x, 50], footprintHalfSize: [2, 2],
|
||||
|
||||
@@ -42,7 +42,7 @@ test('Labs iso is presentation-only and absent from the secondary space card', (
|
||||
assert.doesNotMatch(labs, /CARD_VERSION|houseplan_card_labs_v1|params\.getAll\('hp-labs'\)/);
|
||||
});
|
||||
|
||||
test('Stage 3 code is loaded only through the hidden alpha runtime boundary', () => {
|
||||
test('Stage 4 code is loaded only through the hidden alpha runtime boundary', () => {
|
||||
assert.equal([...card.matchAll(/await import\('\.\/iso-scene-render'\)/g)].length, 1);
|
||||
for (const module of ['iso-scene-render', 'iso-walls', 'iso-openings', 'iso-overlays']) {
|
||||
const valueImport = new RegExp(`import\\s+(?!type\\b)[^;]+from './${module}';`, 's');
|
||||
@@ -56,14 +56,14 @@ test('Stage 3 code is loaded only through the hidden alpha runtime boundary', ()
|
||||
assert.match(card, /if \(this\._labsIso\) void this\._ensureIsoSceneRuntime\(\)/);
|
||||
});
|
||||
|
||||
test('Stage 3 browser harnesses await the lazy runtime and cold alpha-off skips its chunk', () => {
|
||||
test('Stage 4 browser harnesses await the lazy runtime and cold alpha-off skips its chunk', () => {
|
||||
for (const [label, source] of [
|
||||
['contract smoke', contractSmoke],
|
||||
['touch smoke', touchSmoke],
|
||||
['golden harness', goldenHarness],
|
||||
]) {
|
||||
assert.match(source, /await card\._ensureIsoSceneRuntime\(\)/,
|
||||
`${label} does not await the Stage 3 runtime`);
|
||||
`${label} does not await the Stage 4 runtime`);
|
||||
}
|
||||
assert.match(contractSmoke,
|
||||
/cleanAlphaOffSkipsIsoRuntimeRequest = isoRuntimeRequests\(\) === 0/);
|
||||
@@ -93,7 +93,7 @@ test('every browser smoke that explicitly selects Iso waits for its lazy runtime
|
||||
'the shared helper must await load and a painted settled frame');
|
||||
});
|
||||
|
||||
test('Stage 3 composes ordered inert SVG surfaces below screen-facing HTML', () => {
|
||||
test('Stage 4 composes ordered inert SVG surfaces below screen-facing HTML', () => {
|
||||
assert.match(card, /<svg class="iso-underlay-svg"/);
|
||||
assert.match(card, /<svg class="iso-shadows-svg"/);
|
||||
assert.match(card, /<svg class="iso-walls-svg"/);
|
||||
@@ -103,8 +103,8 @@ test('Stage 3 composes ordered inert SVG surfaces below screen-facing HTML', ()
|
||||
assert.match(sceneRender, /class="iso-opening-panel iso-\$\{surface\.type\} iso-opening-\$\{surface\.kind\} iso-material-\$\{surface\.material\}"/);
|
||||
assert.doesNotMatch(sceneRender, /iso-overlay-plate|hp-iso-overlay-texture/,
|
||||
'the invisible safety footprint must never become painted SVG geometry');
|
||||
assert.match(sceneRender, /class="iso-overlay-tether"/);
|
||||
assert.match(sceneRender, /class="iso-overlay-ground"/);
|
||||
assert.doesNotMatch(sceneRender, /class="iso-overlay-tether"|class="iso-overlay-ground"/,
|
||||
'the reviewed near-floor treatment has no debug tethers or ground dots');
|
||||
assert.match(sceneRender, /const depthQueue = buildIsoWallDepthQueue\(scene\.geometry, openingSurfaces\)/);
|
||||
assert.match(sceneRender, /depthQueue\.map\(\(entry\) =>/);
|
||||
assert.match(sceneRender, /entry\.layer === 'wall-top'[\s\S]*?fill-rule="evenodd"/,
|
||||
@@ -125,13 +125,12 @@ test('Stage 3 composes ordered inert SVG surfaces below screen-facing HTML', ()
|
||||
< frameResolver.indexOf('raised: renderIsoRaisedOverlays'));
|
||||
const grounds = section(sceneRender, 'export function renderIsoOverlayGrounds',
|
||||
'export function renderIsoRaisedOverlays');
|
||||
assert.match(grounds, /renderIsoDefs\(layers, 'overlays', cellCm\)/);
|
||||
assert.match(grounds, /class="iso-overlay-ground"[\s\S]*?transform=\$\{isoFixedLightTransform\(cellCm\)\}/);
|
||||
assert.match(grounds, /return emptySvg\(\)/);
|
||||
assert.doesNotMatch(styles, /perspective\s*:|preserve-3d|rotateX\(|rotateZ\(/);
|
||||
assert.doesNotMatch(sceneRender, /window-light|iso-window-light|iso-glow|iso-sun/);
|
||||
});
|
||||
|
||||
test('Stage 3 raises only device, room and lock roots while floor/live layers stay floor-bound', () => {
|
||||
test('Stage 4 keeps device, room and lock roots on one corrected low visual plane', () => {
|
||||
for (const renderer of [
|
||||
'_renderDecorLayer(undefined, view)', '_renderRoomHoverFill(roomHover)', '_renderGlowLayer(space, disp, view)',
|
||||
'_renderSunRays(space)', '_renderOpenings(disp)',
|
||||
@@ -147,12 +146,12 @@ test('Stage 3 raises only device, room and lock roots while floor/live layers st
|
||||
assert.match(card, new RegExp(`data-hp-iso-overlay-kind=\\$\\{isoPlacement\\?\\.plane === 'raised' \\? '${kind}'`));
|
||||
});
|
||||
|
||||
test('Stage 3 structural cache fingerprints geometry/camera/heights and excludes live paint', () => {
|
||||
test('Stage 4 structural cache fingerprints geometry/camera/heights and excludes live paint', () => {
|
||||
assert.match(sceneRender, /flipH: !!opening\.flip_h/);
|
||||
assert.match(sceneRender, /flipV: !!opening\.flip_v/);
|
||||
assert.match(sceneRender, /const floorEdgeHeight = gridVisualUnits\(ISO_FLOOR_EDGE_HEIGHT, input\.cellCm\)/);
|
||||
assert.match(sceneRender, /const raisedHeight = gridVisualUnits\(ISO_RAISED_OVERLAY_HEIGHT, input\.cellCm\)/);
|
||||
assert.match(sceneRender, /camera: ISO_CAMERA,[\s\S]*?wallHeight,[\s\S]*?raisedHeight,[\s\S]*?floorEdgeHeight,[\s\S]*?algorithm: 4/);
|
||||
assert.match(sceneRender, /camera: ISO_CAMERA,[\s\S]*?wallHeight,[\s\S]*?raisedHeight,[\s\S]*?floorEdgeHeight,[\s\S]*?algorithm: 5/);
|
||||
const source = section(sceneRender, 'export function createIsoStructuralSource', 'const unknownArray');
|
||||
const roomProjection = section(sceneRender,
|
||||
'export function isoStructuralRoomGeometry', 'export type IsoStructuralOpeningHost');
|
||||
@@ -167,11 +166,14 @@ test('Stage 3 structural cache fingerprints geometry/camera/heights and excludes
|
||||
'export function isoStructuralOpeningHost', 'export interface IsoStructuralSourceInput');
|
||||
for (const field of ['hostId: resolved.host.id', 't: resolved.t', 'depth: resolved.depth',
|
||||
'face: partitionOpeningFace']) assert.ok(hostProjection.includes(field), `missing ${field}`);
|
||||
assert.match(openings, /ISO_OPENING_GEOMETRY_POLICY[\s\S]*?revision: 1/);
|
||||
assert.match(openings, /ISO_OPENING_GEOMETRY_POLICY[\s\S]*?revision: 2/);
|
||||
assert.match(source,
|
||||
/buildIsoOpeningBasis\(\{ \.\.\.opening, face \}, wallHeight, openingGeometryPolicy\)/);
|
||||
assert.match(openings, /policy\.gateTurnDeg[\s\S]*?policy\.gateTopRatio/);
|
||||
assert.match(openings, /policy\.windowBottomRatio[\s\S]*?policy\.windowTopRatio/);
|
||||
assert.match(openings, /policy\.windowSashBottomRatio[\s\S]*?policy\.windowSashTopRatio/);
|
||||
assert.match(openings, /policy\.windowGlassBottomRatio[\s\S]*?policy\.windowGlassTopRatio/);
|
||||
assert.match(openings, /policy\.doorTurnDeg/);
|
||||
assert.match(openings, /policy\.windowTurnDeg/);
|
||||
assert.match(openings, /policy\.doorTopRatio/);
|
||||
assert.match(openings, /policy\.leafThicknessRatio[\s\S]*?policy\.frameThicknessRatio/);
|
||||
assert.match(contractSmoke,
|
||||
@@ -188,7 +190,7 @@ test('Stage 3 structural cache fingerprints geometry/camera/heights and excludes
|
||||
assert.match(sceneRender, /wallSilhouettes:\s*Object\.freeze\(\[[\s\S]*?\.\.\.wallTops,[\s\S]*?\.\.\.geometry\.sides\.map\(\(face\) => \(\{ outer: face\.points \}\)\),[\s\S]*?\]\)/);
|
||||
});
|
||||
|
||||
test('show_borders:false keeps the exact +4° floor matrix and removes every volume cue', () => {
|
||||
test('show_borders:false keeps the exact zero-yaw floor matrix and removes every volume cue', () => {
|
||||
assert.match(card, /isoLayers && !isoLayers\.floorSymbols/);
|
||||
assert.match(card, /<svg class=\$\{iso \? 'plan-svg' : nothing\}[\s\S]*?data-hp-live-viewbox=\$\{iso \? 'camera' : 'floor'\}/);
|
||||
assert.match(card, /transform=\$\{iso \? isoFloorMatrixCss\(\) : nothing\}/);
|
||||
@@ -235,7 +237,7 @@ test('one frame resolves one structural source and latches late topology/project
|
||||
const resolver = section(sceneRender, 'export function resolveIsoFramePresentation', 'const emptySvg');
|
||||
for (const step of ['resolveIsoDecorationLayers', 'resolveIsoOpeningPanels', 'renderIsoUnderlay',
|
||||
'renderIsoShadows', 'renderIsoWalls', 'renderIsoOverlayGrounds', 'renderIsoRaisedOverlays'])
|
||||
assert.ok(resolver.includes(step), `late Stage 3 step escaped the frame boundary: ${step}`);
|
||||
assert.ok(resolver.includes(step), `late Stage 4 step escaped the frame boundary: ${step}`);
|
||||
});
|
||||
|
||||
test('raised footprints participate in global and room fit without entering the structural LRU', () => {
|
||||
@@ -257,8 +259,8 @@ test('raised footprints participate in global and room fit without entering the
|
||||
assert.doesNotMatch(structural, /overlayFitEntries|resolveIsoOverlayFitEnvelope|buildIsoOverlayRenderScene/);
|
||||
});
|
||||
|
||||
test('raised DOM roots expose one floor/visual identity and preserve existing actions', () => {
|
||||
assert.match(card, /data-hp-iso-stage=\$\{iso \? '3' : nothing\}/);
|
||||
test('low-plane DOM roots expose one floor/visual identity and preserve existing actions', () => {
|
||||
assert.match(card, /data-hp-iso-stage=\$\{iso \? '4' : nothing\}/);
|
||||
for (const token of [
|
||||
'data-hp-iso-overlay-kind', 'data-hp-iso-raised', 'data-hp-iso-nudged',
|
||||
'data-hp-iso-floor', 'data-hp-iso-visual',
|
||||
@@ -277,7 +279,7 @@ test('raised DOM roots expose one floor/visual identity and preserve existing ac
|
||||
assert.match(styles, /\.iso-overlays-svg,[\s\S]*?pointer-events:\s*none/);
|
||||
});
|
||||
|
||||
test('Stage 3 materials and shadows are bounded, theme-aware and capability-safe', () => {
|
||||
test('Stage 4 materials and shadows are bounded, theme-aware and capability-safe', () => {
|
||||
const materialIds = [...sceneRender.matchAll(/id="(hp-iso-[^"]+)" data-hp-iso-material-def/g)]
|
||||
.map((match) => match[1]);
|
||||
assert.ok(materialIds.length >= 5 && materialIds.length <= 12,
|
||||
@@ -287,15 +289,29 @@ test('Stage 3 materials and shadows are bounded, theme-aware and capability-safe
|
||||
assert.match(styles, /\.stage:not\(\.theme-light\) \.iso-wall-side/);
|
||||
assert.doesNotMatch(styles, /iso-overlay-plate|hp-iso-overlay-texture/,
|
||||
'themes and forced colors must not recreate a painted footprint');
|
||||
assert.match(styles, /@supports not \(filter: blur\(1px\)\)[\s\S]*?\.iso-overlay-ground[\s\S]*?display:\s*none/);
|
||||
assert.doesNotMatch(styles, /iso-overlay-ground|iso-overlay-tether/,
|
||||
'themes and capability fallbacks must not recreate removed debug cues');
|
||||
assert.match(styles, /iso-material-glass-side[\s\S]*?fill:\s*#c9e4f3/);
|
||||
assert.match(styles, /iso-material-glass-top[\s\S]*?fill:\s*#e3f2fa/);
|
||||
const rendering = section(sceneRender, 'function renderIsoDefs(', 'export function resolveIsoDecorationLayers');
|
||||
assert.match(sceneRender, /return `translate\(\$\{gridVisualUnits\(4, cellCm\)\} \$\{gridVisualUnits\(8, cellCm\)\}\)`/);
|
||||
assert.equal([...sceneRender.matchAll(/transform=\$\{isoFixedLightTransform\(cellCm\)\}/g)].length, 4,
|
||||
'ambient, contact, opening and overlay grounding shadows share one fixed-light vector');
|
||||
assert.equal([...sceneRender.matchAll(/transform=\$\{isoFixedLightTransform\(cellCm\)\}/g)].length, 3,
|
||||
'ambient, contact and opening shadows share one fixed-light vector');
|
||||
assert.doesNotMatch(rendering, /sunState|_renderSun|Date\.now|Math\.random/);
|
||||
});
|
||||
|
||||
test('Stage 3 adds no schema, dependency, storage, network or HA action surface', () => {
|
||||
test('Stage 4 room labels override Flat inline colors with the approved clean palette', () => {
|
||||
const lightLabel = styleBodiesFor('projection-iso', 'mode-view', '.roomlabel');
|
||||
const darkLabel = styleBodiesFor('projection-iso', 'theme-dark', 'mode-view', '.roomlabel');
|
||||
assert.match(lightLabel, /text-shadow:\s*none/);
|
||||
assert.match(lightLabel, /filter:\s*none/);
|
||||
assert.match(lightLabel, /-webkit-text-stroke:\s*0 transparent/);
|
||||
assert.match(lightLabel, /color:\s*#303936\s*!important/,
|
||||
'Iso palette must win over the room color written inline for Flat');
|
||||
assert.match(darkLabel, /color:\s*#f2f0e8\s*!important/);
|
||||
});
|
||||
|
||||
test('Stage 4 adds no schema, dependency, storage, network or HA action surface', () => {
|
||||
for (const source of [openings, overlays, projection, sceneRender]) {
|
||||
assert.doesNotMatch(source, /localStorage\s*\.|fetch\(|XMLHttpRequest|WebSocket|callService\(|Math\.random/);
|
||||
}
|
||||
|
||||
@@ -98,17 +98,17 @@ test('performance budget refuses incomparable runtime profiles', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('isometric reports fail closed on exact SHA, effective projection and Stage 3 metadata', () => {
|
||||
test('isometric reports fail closed on exact SHA, effective projection and current Stage 4 metadata', () => {
|
||||
const isoBudgets = { ...budgets, profile: 'isometric-stage3-dense-v1' };
|
||||
const valid = report();
|
||||
Object.assign(valid, {
|
||||
profile: isoBudgets.profile,
|
||||
effectiveProjection: ['iso'],
|
||||
isoStageRevision: ['3'],
|
||||
isoStageRevision: ['4'],
|
||||
stage3Required: true,
|
||||
});
|
||||
valid.rows.forEach((row) => Object.assign(row, {
|
||||
effectiveProjection: 'iso', isoStageRevision: '3',
|
||||
effectiveProjection: 'iso', isoStageRevision: '4',
|
||||
isoStructuralBuilds: {
|
||||
supported: true, initial: 1, beforeHaUpdate: 2, afterHaUpdate: 2, haUpdateDelta: 0,
|
||||
},
|
||||
@@ -135,7 +135,7 @@ test('isometric reports fail closed on exact SHA, effective projection and Stage
|
||||
mutate(candidate);
|
||||
assert.throws(() => evaluatePerformanceBudget({
|
||||
baseline, candidate, budgets: isoBudgets,
|
||||
}), /sourceSha|effectiveProjection|Stage 3|structural build count/);
|
||||
}), /sourceSha|effectiveProjection|Stage 4|structural build count/);
|
||||
}
|
||||
assert.throws(() => evaluatePerformanceBudget({
|
||||
baseline, candidate: valid, budgets: isoBudgets, candidateSha: 'f'.repeat(40),
|
||||
|
||||
@@ -126,7 +126,7 @@ test('#160 Stage 3 budget preserves every historical common ceiling', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('#160 Stage 3 runner fails closed on the agreed observable DOM contract', () => {
|
||||
test('#570 current Stage 4 runner fails closed on the agreed observable DOM contract', () => {
|
||||
const runner = readFileSync(new URL('../demo/benchmark_large_house.mjs', import.meta.url), 'utf8');
|
||||
for (const contract of [
|
||||
'data-hp-iso-stage',
|
||||
@@ -143,7 +143,7 @@ test('#160 Stage 3 runner fails closed on the agreed observable DOM contract', (
|
||||
'no shared texture pattern',
|
||||
'no shared shadow filter',
|
||||
'no observable',
|
||||
'Stage 3 roots',
|
||||
'Stage 4 roots',
|
||||
'opening surfaces',
|
||||
'entered Flat fallback',
|
||||
'structural build counter is absent',
|
||||
@@ -151,21 +151,21 @@ test('#160 Stage 3 runner fails closed on the agreed observable DOM contract', (
|
||||
'haUpdateDelta',
|
||||
'performed a structural rebuild for an HA-only state update',
|
||||
'requires an exact git source SHA',
|
||||
]) assert.ok(runner.includes(contract), `missing Stage 3 runner contract: ${contract}`);
|
||||
]) assert.ok(runner.includes(contract), `missing Stage 4 runner contract: ${contract}`);
|
||||
assert.match(runner,
|
||||
/\[data-hp-iso-overlay-kind\]\[data-hp-iso-floor\]\[data-hp-iso-visual\]/,
|
||||
'Stage 3 counts must use interactive roots, not duplicated inert SVG diagnostics');
|
||||
'Stage 4 counts must use interactive roots, not duplicated inert SVG diagnostics');
|
||||
assert.match(runner, /raisedVacuumCount/,
|
||||
'floor-bound vacuum must have an independent negative raised-state check');
|
||||
assert.match(runner, /stage3 !== total/,
|
||||
'every rendered device, room label and lock root must participate in Stage 3');
|
||||
'every rendered device, room label and lock root must participate in Stage 4');
|
||||
assert.match(runner, /expectedOpeningKinds\.map/,
|
||||
'door, window and gate surfaces must contribute to the measured Stage 3 scene');
|
||||
'door, window and gate surfaces must contribute to the measured Stage 4 scene');
|
||||
assert.match(runner, /pulseDeviceIdsBySpace\?\.\[card\._space\]/,
|
||||
'every switched floor must retain its own observable pulse witness');
|
||||
assert.match(runner,
|
||||
/if \(snapshot\.effectiveProjection !== 'iso'\) failures\.push\('effective projection is not iso'\);/,
|
||||
'a dense Stage 3 report must reject Flat fallback instead of timing it as success');
|
||||
'a dense Stage 4 report must reject Flat fallback instead of timing it as success');
|
||||
assert.match(runner,
|
||||
/requiresIsoStructuralBuildCounter[\s\S]*?afterStateIsoStructuralBuilds !== steadyIsoStructuralBuilds/,
|
||||
'both isometric profiles must reject a structural rebuild on the HA-only window');
|
||||
|
||||
Reference in New Issue
Block a user