mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
refactor(editor): диалог «Оптимизировать планы» — свой модуль с узким портом (#642)
Первый вынос из монолита по образцу live-*/RadarSetupController. src/optimize-plans-dialog.ts: OptimizePlansDialogState, чистые preflightDiagnostics/preflightVersionsDiffer и класс OptimizePlansDialog (open/preview/toggleLivePositions/run/copyDiagnostics/ reportPreflightFailure/render, clipboardFallback) за портом из 18 членов. Модуль не знает HouseplanEditorHostPort; импортирует его только рантайм, карточка берёт тип. Инлайн-фолбэк буфера обмена — WeakMap по объекту диалога (CODE-REVIEW-295-r1 M2), дедуп dev-лога — поле класса: из карточки и порта ушли _preflightClipboardFallback и _reportedPreflightFingerprint, пять делегатов и две стрелки-заглушки. Харнесс (13 смоков, wall-draw-click, golden) зовёт card._editorRuntime.optimizePlans.* — меняется только адрес. 11 текстовых утверждений i18n.test.mjs о разметке и тосте диалога и гварды 8 мутантов переехали в юнит test/optimize-plans-dialog.test.mjs (test-build); новый мутант optimize-dialog-imports-host-port. monolith-baseline: delegates 159→154, portMembers 350→348, hostRefs 4948→4869, portPrivates 96→94, harnessPrivates 107→101, bundleBytes 2499647→2499182. Потолки ядер опущены на выигрыш; потолок lazy editor gzip пересчитан (+555 Б gzip при −465 Б сырых). Issue: #642 User-Visible: no
This commit is contained in:
@@ -1,127 +1,127 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6",
|
||||
"fingerprint": "d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 289449,
|
||||
"initialViewGzipBytes": 289273,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 290971,
|
||||
"initialPanelGzipBytes": 290796,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2324,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/de-CjsIUY-d.js",
|
||||
"houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/fr-D2W98VDc.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C5T09STo.js",
|
||||
"houseplan-assets/iso-scene-render-3HN3TF4R.js",
|
||||
"houseplan-assets/live-interaction-runtime-kRE37k2_.js",
|
||||
"houseplan-assets/pdf-export-DKVHRlMO.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-C4WynS64.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/de-C_-FOd3R.js",
|
||||
"houseplan-assets/editor-CuOqOWa_.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/fr-BubWZSkS.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DQp1tIHa.js",
|
||||
"houseplan-assets/iso-scene-render-DpO3ezuP.js",
|
||||
"houseplan-assets/live-interaction-runtime-CPQNHLqJ.js",
|
||||
"houseplan-assets/pdf-export-CFT_MzmM.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js"
|
||||
],
|
||||
"lazyGzipBytes": 460916,
|
||||
"lazyGzipBytes": 461471,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/editor-CuOqOWa_.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 244420,
|
||||
"lazyEditorGzipBytes": 244976,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 34521,
|
||||
"lazyOnboardingGzipBytes": 34523,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CjsIUY-d.js",
|
||||
"houseplan-assets/fr-D2W98VDc.js"
|
||||
"houseplan-assets/de-C_-FOd3R.js",
|
||||
"houseplan-assets/fr-BubWZSkS.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 55255,
|
||||
"lazyLocaleGzipBytes": 55257,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/iso-scene-render-3HN3TF4R.js"
|
||||
"houseplan-assets/iso-scene-render-DpO3ezuP.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 17640,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 16940,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/pdf-export-DKVHRlMO.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/pdf-export-CFT_MzmM.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 126827,
|
||||
"lazyPdfGzipBytes": 126826,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"sha256": "07acc6fc3e12ba2fe3e1b07e19a3f57308a0834a03db3c01bdba22ea43b9a7fd",
|
||||
"path": "houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"sha256": "37be8583306fa604aed0f7f79f7a6c3eb8e373de8e5d476fb4593881b7954f93",
|
||||
"rawBytes": 76524,
|
||||
"gzipBytes": 24571,
|
||||
"gzipBytes": 24572,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CjsIUY-d.js",
|
||||
"sha256": "25fa2d8f19df083532e8f022d86c21d290f3e79badaf7b4d058158c50a02286c",
|
||||
"path": "houseplan-assets/de-C_-FOd3R.js",
|
||||
"sha256": "033535ad61de5d4453c3b3729cf2afb69ed996267cbb83ebd2111b0ce6cad359",
|
||||
"rawBytes": 92247,
|
||||
"gzipBytes": 27864,
|
||||
"gzipBytes": 27865,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"sha256": "b1cdd26dec11ae7d904a675fbc27964757819886d1034ab63d2c9d91b42e3655",
|
||||
"path": "houseplan-assets/editor-CuOqOWa_.js",
|
||||
"sha256": "350885825a2046833dcef868485cae1efe99389d088fd502bd166d1ecc75b2a7",
|
||||
"rawBytes": 3833,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1581,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"sha256": "aee7494545b43e2c932163c572b19483d9ac35c022daf437bcfd3aa21589aa07",
|
||||
"path": "houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"sha256": "c6899e84bdfc9d83f635e63317260c6e3e0ae85104d966e0e297d56abcf45d91",
|
||||
"rawBytes": 25600,
|
||||
"gzipBytes": 5055,
|
||||
"gzipBytes": 5056,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-D2W98VDc.js",
|
||||
"sha256": "12e70d1138d31d87f8c7f25d750bef3ecb7c84e23925845c101f0af4298208c0",
|
||||
"path": "houseplan-assets/fr-BubWZSkS.js",
|
||||
"sha256": "6243e53b2d25b50f23e7b8544cf24ae2634f3d985d0ebb05ad881628121693f4",
|
||||
"rawBytes": 94579,
|
||||
"gzipBytes": 27391,
|
||||
"gzipBytes": 27392,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"sha256": "63e67cda0c59f37fee93410ae4193ae79d730b83293eea331edefaf63e280d11",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"sha256": "aac82444a5129c8991694a9a5421a63fc23479d41ed2cf44be2bba9cdb8bac38",
|
||||
"rawBytes": 52035,
|
||||
"gzipBytes": 16940,
|
||||
"isEntry": false,
|
||||
@@ -129,184 +129,184 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"sha256": "98c44bf6f915d6121132c133cc12b2e27ea344f1c8203e7af54741f317aa1a5e",
|
||||
"rawBytes": 1022206,
|
||||
"gzipBytes": 288647,
|
||||
"path": "houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"sha256": "6e2ecbe962122cfa7599c75e5ca8815d27c98b754cc437b7628b4ddafef10d9f",
|
||||
"rawBytes": 1021428,
|
||||
"gzipBytes": 288472,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CjsIUY-d.js",
|
||||
"houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"houseplan-assets/fr-D2W98VDc.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C5T09STo.js",
|
||||
"houseplan-assets/iso-scene-render-3HN3TF4R.js",
|
||||
"houseplan-assets/live-interaction-runtime-kRE37k2_.js",
|
||||
"houseplan-assets/pdf-export-DKVHRlMO.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js"
|
||||
"houseplan-assets/de-C_-FOd3R.js",
|
||||
"houseplan-assets/editor-CuOqOWa_.js",
|
||||
"houseplan-assets/fr-BubWZSkS.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DQp1tIHa.js",
|
||||
"houseplan-assets/iso-scene-render-DpO3ezuP.js",
|
||||
"houseplan-assets/live-interaction-runtime-CPQNHLqJ.js",
|
||||
"houseplan-assets/pdf-export-CFT_MzmM.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"sha256": "f476a861b5a2d77cd2ecea724c57d4e9e6dce6fa161eb18cc9479d2ec9752641",
|
||||
"rawBytes": 678218,
|
||||
"gzipBytes": 188846,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"sha256": "7bad833f2fd60af11c8411e56b97431c71ee3e4fff685de3019a9ac14e5bc1ba",
|
||||
"rawBytes": 678531,
|
||||
"gzipBytes": 189399,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-C4WynS64.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js",
|
||||
"sha256": "5613ece2554c3ee9afc3e71dce63370d3e0bbeab9489e297ffaee74f33670e6f",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js",
|
||||
"sha256": "1a16adf7c3ab18db3a9773930103843470acdeb2fde3870110c20a069cd28876",
|
||||
"rawBytes": 15986,
|
||||
"gzipBytes": 4895,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-C5T09STo.js",
|
||||
"sha256": "d836d918b89ee5685f7d2d10b194cf17feb7ca956c2112c78c4d62db7042ef24",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-DQp1tIHa.js",
|
||||
"sha256": "13cf97cdc63e8b66108521655d6e4585452fcf02ce9f4fc0b0bf19c13cc6f099",
|
||||
"rawBytes": 10759,
|
||||
"gzipBytes": 3686,
|
||||
"gzipBytes": 3685,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-C4WynS64.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-3HN3TF4R.js",
|
||||
"sha256": "0c1137699d170125b0b3a6a7dd5177a06e270d74e8b3020711c100b4c59bbe0d",
|
||||
"path": "houseplan-assets/iso-scene-render-DpO3ezuP.js",
|
||||
"sha256": "c979378b329bca0dbd23ddbae6ca2cad763bb5754959e9b98f395197d915a8b5",
|
||||
"rawBytes": 51983,
|
||||
"gzipBytes": 17640,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-kRE37k2_.js",
|
||||
"sha256": "e0537b8b7d78df3e70208e5601b95b538509265184027a62b559c43626372385",
|
||||
"path": "houseplan-assets/live-interaction-runtime-CPQNHLqJ.js",
|
||||
"sha256": "10e83f4eb0bdf1e4b0b0a4c9e377ade059bf5169488fa360821625a4df4bc91f",
|
||||
"rawBytes": 8216,
|
||||
"gzipBytes": 3203,
|
||||
"gzipBytes": 3206,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-DKVHRlMO.js",
|
||||
"sha256": "fef91b049f58622de45132f31b3ab7f606df5db8eee3fc68d3809b2c5ece7e5d",
|
||||
"path": "houseplan-assets/pdf-export-CFT_MzmM.js",
|
||||
"sha256": "03ee0fc3e88bc84e94c1a51fadc2e65a17635ba53ed875f1cdb794c3eb989106",
|
||||
"rawBytes": 236264,
|
||||
"gzipBytes": 109887,
|
||||
"gzipBytes": 109886,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-Bj45k8M3.js",
|
||||
"sha256": "1463acf70394eb7c8fc09b8182b227648b00919bb2fc5ad6a6ae189b1a051a2e",
|
||||
"path": "houseplan-assets/summary-panel-editor-11x1Zz63.js",
|
||||
"sha256": "2d4be3e90f3c0329c33b6465d57c82fc2ba93e30097838cd042c69f0cc0f08be",
|
||||
"rawBytes": 14862,
|
||||
"gzipBytes": 3423,
|
||||
"gzipBytes": 3426,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js"
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-3K7HlQXp.js",
|
||||
"sha256": "246a3ee30014c9096527044921c104b76cef9555ebd1b2fbfcaeea54daf010cc",
|
||||
"path": "houseplan-assets/summary-panel-metrics-BwUdppyx.js",
|
||||
"sha256": "5dbb467356ea155e5d4dee21c562cbfdf0a763b93d728f8227f7731b91837068",
|
||||
"rawBytes": 2611,
|
||||
"gzipBytes": 1306,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js",
|
||||
"sha256": "527c40315a7cbbfa84289d6c8360688df121f368bf2609488e0b3b527ed2988a",
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js",
|
||||
"sha256": "6e0e9808f1da372aef041d74e46c91e29d2d5fd98a9fbe24ee54debe58c06ee3",
|
||||
"rawBytes": 72637,
|
||||
"gzipBytes": 20166,
|
||||
"gzipBytes": 20163,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-Bj45k8M3.js",
|
||||
"houseplan-assets/summary-panel-metrics-3K7HlQXp.js"
|
||||
"houseplan-assets/summary-panel-editor-11x1Zz63.js",
|
||||
"houseplan-assets/summary-panel-metrics-BwUdppyx.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-CXRVgVl2.js",
|
||||
"sha256": "a68916a24db8c6acc389707ad40f95d8963d4521e40c09e6fa32b6430584569a",
|
||||
"path": "houseplan-assets/zigbee-topology-BIziY8_4.js",
|
||||
"sha256": "be1428736b1fcd653bec4a452d4895d8cfea015cc143b8560f5c6065c8b6ffea",
|
||||
"rawBytes": 18962,
|
||||
"gzipBytes": 7425,
|
||||
"gzipBytes": 7428,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-C4WynS64.js",
|
||||
"sha256": "598d78eb03a6b75e7bf9ce8ac7fdbc7ee514d6c7f056e8651309c541313ced0e",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js",
|
||||
"sha256": "af36ce4a7b2e9b6992e2c74a19e63f197a1960317fad3bb8f193d93ccccb84b8",
|
||||
"rawBytes": 3942,
|
||||
"gzipBytes": 1764,
|
||||
"gzipBytes": 1763,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "ff5753ceb4dce57c6f1701baad303c37770735cb53b2a61c14e1225e07ac898b",
|
||||
"sha256": "9a30c8176149f7b6b78431e5f876dd8ab3f86676bf31ecfbd8952821812a46ed",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 802,
|
||||
"gzipBytes": 801,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "90fb829ea2a8310205a7bc7c8d5d57bd38eedb0d62c961eed13d3bc3119b011a",
|
||||
"sha256": "e712ffa4a818f355510e4eee5ed9438c6f89ac60797f0838bba4b2298cb35d24",
|
||||
"rawBytes": 6165,
|
||||
"gzipBytes": 2324,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{A as e,c as a,s as t,l as i,d as n,ap as s,h as r,j as o,g as l,dH as c,dI as p,dJ as d,cL as h,dK as u,cK as m,dL as g,cQ as _,cP as b,dM as f,bs as v,br as w,bt as y,av as k,aC as $,aD as x,dN as S}from"./houseplan-card-e9vxRZcR.js";import{c as C,f as M,C as L}from"./form-kit.styles-Dlhk9WKB.js";const z=C+M(L,{withSwitch:!1}),P=new WeakSet;let D=null;function T(e){const a=e.renderRoot;if(!a||P.has(a))return;const t=a.ownerDocument??globalThis.document,i=t?.defaultView?.CSSStyleSheet;if(i&&"adoptedStyleSheets"in a)return D||(D=new i,D.replaceSync(z)),a.adoptedStyleSheets=[...a.adoptedStyleSheets,D],void P.add(a);if(a.querySelector("style[data-hp-form-kit]"))return void P.add(a);const n=t.createElement("style");n.dataset.hpFormKit="true",n.textContent=z,a.appendChild(n),P.add(a)}function H({title:t,help:i,body:n,id:s}){return a`<section class="hpf-card" data-card=${s??e}>
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{A as e,c as a,s as t,l as i,d as n,ap as s,h as r,j as o,g as l,dH as c,dI as p,dJ as d,cN as h,dK as u,cM as m,dL as g,cS as _,cR as b,dM as f,bt as v,bs as w,bu as y,av as k,aC as $,aD as x,dN as S}from"./houseplan-card-C2ifrS7A.js";import{c as C,f as M,C as L}from"./form-kit.styles-ORjqyA2O.js";const z=C+M(L,{withSwitch:!1}),P=new WeakSet;let D=null;function T(e){const a=e.renderRoot;if(!a||P.has(a))return;const t=a.ownerDocument??globalThis.document,i=t?.defaultView?.CSSStyleSheet;if(i&&"adoptedStyleSheets"in a)return D||(D=new i,D.replaceSync(z)),a.adoptedStyleSheets=[...a.adoptedStyleSheets,D],void P.add(a);if(a.querySelector("style[data-hp-form-kit]"))return void P.add(a);const n=t.createElement("style");n.dataset.hpFormKit="true",n.textContent=z,a.appendChild(n),P.add(a)}function H({title:t,help:i,body:n,id:s}){return a`<section class="hpf-card" data-card=${s??e}>
|
||||
${t?a`<div class="hpf-head"><h3>${t}</h3>${i??e}</div>`:e}
|
||||
<div class="hpf-body">${n}</div>
|
||||
</section>`}function E({title:t,help:i}){return a`<div class="hpf-sub"><h4>${t}</h4>${i??e}</div>`}function F({label:t,htmlFor:i,help:n,control:s,hint:r,error:o}){return a`<div class="hpf-field">
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,14 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-e9vxRZcR.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-C2ifrS7A.js";class f extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,f={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},h=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
.computeLabel=${e=>h[e.name]||e.name}
|
||||
.computeLabel=${e=>f[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${d(f.slice(0,3))}
|
||||
${d(h.slice(0,3))}
|
||||
${_?c`<div class="default-floor-error" role="alert"
|
||||
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
|
||||
${t(s,"editor.default_floor_missing",{id:_})}
|
||||
</div>`:a}
|
||||
${d(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);
|
||||
${d(h.slice(3))}
|
||||
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
|
||||
+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
File diff suppressed because one or more lines are too long
+9
-9
File diff suppressed because one or more lines are too long
+141
-141
File diff suppressed because one or more lines are too long
+10
-10
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
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__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{c as a,A as e}from"./houseplan-card-e9vxRZcR.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-aaGs9bci.js";import"./form-kit.styles-Dlhk9WKB.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__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{c as a,A as e}from"./houseplan-card-C2ifrS7A.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-BzQmPxkY.js";import"./form-kit.styles-ORjqyA2O.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__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{ek as e,N as t,a$ as n,L as i,ae as o,ac as r,bT as s,bS as a,a_ as c,aZ as l,b0 as d,el as u,em as f,bV as m,en as g}from"./houseplan-card-e9vxRZcR.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__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{ek as e,N as t,a$ as n,L as i,ae as o,ac as r,bV as s,bU as a,a_ as l,aZ as c,b0 as d,el as u,em as f,bX as m,en as g}from"./houseplan-card-C2ifrS7A.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,l="entity"===r?e.registry.entities?.[s]:null,c="device"===r?s:l?.device_id,d="device"===r?a?.area_id:l?.area_id||c&&e.registry.devices?.[c]?.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;c&&e.registry.devices?.[c]&&e.spaceIds.has(f)&&(!n.devices.has(c)||"entity"===r&&n.liveEntities.has(s))&&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,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:c(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*h(l,c,d=_){try{let u=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=d(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const l=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(l,m.physicalBodies)),yield}const _=m.cellCm/o;u+=a(v)*_*_/1e4}return u}catch{return null}}function p(e,t,n=_){const i=h(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function b(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{h as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,b as summarySystemValue,p as totalCleanFloorAreaM2};
|
||||
+3
-3
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__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{n as t,b as n}from"./zigbee-topology-CXRVgVl2.js";import{aM as e}from"./houseplan-card-e9vxRZcR.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};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{n as t,b as n}from"./zigbee-topology-BIziY8_4.js";import{aM as e}from"./houseplan-card-C2ifrS7A.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 f(t){if(!0!==t?.user?.is_admin)throw a("permission")}function p(n){const e=i(n);return e?c(e,"zha",async()=>{if(f(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(f(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,p=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{p=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 f=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&&p?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof f&&b.push(f),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{p as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";try{await import("./houseplan-assets/houseplan-card-e9vxRZcR.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__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";try{await import("./houseplan-assets/houseplan-card-C2ifrS7A.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
@@ -1666,7 +1666,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
await card.updateComplete;
|
||||
}
|
||||
if (scenario.dialog === 'optimize-orphan-references') {
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
// This golden owns orphan-reference copy/layout only. The shared visual
|
||||
// fixture is intentionally v7, but wall-model migration has its own
|
||||
@@ -1710,7 +1710,7 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
status: 'failed',
|
||||
reason: 'wall-null',
|
||||
}));
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
card._alignDialog = {
|
||||
...card._alignDialog,
|
||||
preflight: {
|
||||
|
||||
@@ -216,7 +216,7 @@ const out = await page.evaluate(async () => {
|
||||
c._modelCache = null; c._frame = null; c._space = 'g1';
|
||||
c.requestUpdate(); await c.updateComplete;
|
||||
|
||||
c._openAlignDialog();
|
||||
c._editorRuntime.optimizePlans.open();
|
||||
await c.updateComplete;
|
||||
const dlg = c._alignDialog;
|
||||
o.alignDialogOpens = !!dlg && !!sr().querySelector('hp-dialog .alignmsg');
|
||||
@@ -229,7 +229,7 @@ const out = await page.evaluate(async () => {
|
||||
const sent = [];
|
||||
const base = c.hass.callWS;
|
||||
c.hass = { ...c.hass, callWS: async (m) => { sent.push(m.type); return base(m); } };
|
||||
await c._runAlignToGrid();
|
||||
await c._editorRuntime.optimizePlans.run();
|
||||
await c.updateComplete;
|
||||
o.alignDialogClosed = c._alignDialog === null;
|
||||
o.alignUsedAtomicConfigLayoutWrite = sent.filter((t) => t === 'houseplan/plan/optimize').length === 1;
|
||||
@@ -249,7 +249,7 @@ const out = await page.evaluate(async () => {
|
||||
g.openings[0].y - (w0[1] + tt * (w1[1] - w0[1]))) < 1e-9;
|
||||
|
||||
// ---- 2b) …and the second run has nothing to do ------------------------
|
||||
c._openAlignDialog();
|
||||
c._editorRuntime.optimizePlans.open();
|
||||
await c.updateComplete;
|
||||
o.secondRunMovesNothing = c._alignDialog.report.moved === 0;
|
||||
o.secondRunOffersNoButton = !sr().querySelector('hp-dialog .btn.on');
|
||||
@@ -272,7 +272,7 @@ const out = await page.evaluate(async () => {
|
||||
c._layout = {};
|
||||
c._modelCache = null; c._frame = null; c._space = 'm1';
|
||||
c.requestUpdate(); await c.updateComplete;
|
||||
c._openAlignDialog(); await c.updateComplete;
|
||||
c._editorRuntime.optimizePlans.open(); await c.updateComplete;
|
||||
const md = c._alignDialog;
|
||||
o.alignPromiseUsesTheOwnScaleOfEachSpace = !!md && md.cm >= 50 && md.cm < 51;
|
||||
o.alignPromiseNamesTheSpaceItBelongsTo = !!md && md.where === 'Attic'
|
||||
@@ -292,11 +292,11 @@ const out = await page.evaluate(async () => {
|
||||
c._layout = {};
|
||||
c._modelCache = null; c._frame = null; c._space = 'a1';
|
||||
c.requestUpdate(); await c.updateComplete;
|
||||
c._openAlignDialog(); await c.updateComplete;
|
||||
c._editorRuntime.optimizePlans.open(); await c.updateComplete;
|
||||
const ad = c._alignDialog;
|
||||
o.angleOnlyOpeningCounts = !!ad && ad.report.moved === 1 && ad.report.rotated === 1;
|
||||
o.angleOnlyOpeningOffersTheButton = !!sr().querySelector('hp-dialog .btn.on');
|
||||
await c._runAlignToGrid();
|
||||
await c._editorRuntime.optimizePlans.run();
|
||||
await c.updateComplete;
|
||||
o.angleOnlyOpeningIsActuallyFixed = c._serverCfg.spaces[0].openings[0].angle === 0;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const out = await page.evaluate(async (sourceSpace) => {
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
const preview = card._alignDialog;
|
||||
const dialogText = card.renderRoot.querySelector('hp-dialog')?.textContent || '';
|
||||
@@ -68,9 +68,9 @@ const out = await page.evaluate(async (sourceSpace) => {
|
||||
await card.updateComplete;
|
||||
result.cancelDoesNotWrite = sent.length === 0;
|
||||
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
await card._runAlignToGrid();
|
||||
await card._editorRuntime.optimizePlans.run();
|
||||
await card.updateComplete;
|
||||
const north = card._serverCfg.spaces[0].rooms.find((room) => room.id === 'north-west');
|
||||
const south = card._serverCfg.spaces[0].rooms.find((room) => room.id === 'south-west');
|
||||
@@ -85,7 +85,7 @@ const out = await page.evaluate(async (sourceSpace) => {
|
||||
|
||||
await card._loadFromServer();
|
||||
await card.updateComplete;
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
result.reloadIsIdempotent = card._alignDialog.report.wallsStraightened === 0;
|
||||
card._alignDialog = null;
|
||||
|
||||
@@ -64,7 +64,7 @@ const out = await page.evaluate(async () => {
|
||||
card._modelCache = null; card._frame = null; card._space = 'coincident';
|
||||
card.requestUpdate(); await card.updateComplete;
|
||||
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const preview = card._alignDialog;
|
||||
const previewSpace = preview?.config?.spaces?.[0];
|
||||
result.previewIsExact = !!preview?.changed && preview.preflight?.ok === true
|
||||
@@ -77,7 +77,7 @@ const out = await page.evaluate(async () => {
|
||||
&& JSON.stringify(card._serverCfg) === JSON.stringify(original);
|
||||
result.reportRendersBothCounters = card.renderRoot.querySelectorAll('hp-dialog .alignmsg').length >= 2;
|
||||
|
||||
await card._runAlignToGrid(); await card.updateComplete;
|
||||
await card._editorRuntime.optimizePlans.run(); await card.updateComplete;
|
||||
const applied = card._serverCfg.spaces[0];
|
||||
result.applyUsesOneAtomicWrite = sent.filter((type) => type === 'houseplan/plan/optimize').length === 1;
|
||||
result.applyKeepsOpeningFields = applied.partitions == null
|
||||
@@ -103,8 +103,8 @@ const out = await page.evaluate(async () => {
|
||||
|
||||
// Apply once more and prove the ordinary shared wall is no longer hidden
|
||||
// underneath an independent-wall blocker in the remaining Thickness tool.
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
await card._runAlignToGrid(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
await card._editorRuntime.optimizePlans.run(); await card.updateComplete;
|
||||
card._setMode('plan'); card._markup = true;
|
||||
card._modelCache = null; card.requestUpdate(); await card.updateComplete;
|
||||
card._tool = 'wallthick';
|
||||
|
||||
@@ -95,7 +95,7 @@ const out = await page.evaluate(async () => {
|
||||
card._modelCache = null; card._frame = null; card._space = 'noisy';
|
||||
card.requestUpdate(); await card.updateComplete;
|
||||
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const preview = card._alignDialog;
|
||||
const previewText = card.renderRoot.querySelector('hp-dialog .body')?.textContent || '';
|
||||
result.previewOffersInvisibleCleanup = !!preview?.changed
|
||||
@@ -121,8 +121,8 @@ const out = await page.evaluate(async () => {
|
||||
result.cancelDoesNotWrite = sent.length === 0
|
||||
&& JSON.stringify(card._serverCfg) === JSON.stringify(original);
|
||||
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
await card._runAlignToGrid(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
await card._editorRuntime.optimizePlans.run(); await card.updateComplete;
|
||||
result.applyUsesOneAtomicWrite = sent.filter((type) => type === 'houseplan/plan/optimize').length === 1;
|
||||
result.applyStoresOnlyCanonicalRoomCoordinates = card._serverCfg.spaces[0].rooms
|
||||
.every((room) => room.poly.every(([x, y]) => isCanonical(x) && isCanonical(y)));
|
||||
@@ -135,7 +135,7 @@ const out = await page.evaluate(async () => {
|
||||
&& !lastToast.includes('0 records maintained');
|
||||
|
||||
await Promise.all([card._reloadConfigOnly(true), card._reloadLayoutOnly()]);
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
result.serverEventReloadIsExactNoOp = card._alignDialog?.changed === false
|
||||
&& card._alignDialog.report.latticeCoordinatesCanonicalized === 0
|
||||
&& !card.renderRoot.querySelector('hp-dialog .btn.on');
|
||||
@@ -145,7 +145,7 @@ const out = await page.evaluate(async () => {
|
||||
card._cfgContentFingerprint = ''; card._layoutContentFingerprint = '';
|
||||
card._loadOk = false;
|
||||
await card._loadFromServer();
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
result.coldReloadIsExactNoOp = card._alignDialog?.changed === false
|
||||
&& card._alignDialog.report.latticeCoordinatesCanonicalized === 0
|
||||
&& !card.renderRoot.querySelector('hp-dialog .btn.on');
|
||||
|
||||
@@ -69,7 +69,7 @@ const out = await page.evaluate(async () => {
|
||||
configRev: card._cfgRev, layoutRev: card._layoutRev,
|
||||
canUndo: card._canOptimizeUndo, undoKind: card._undoKind,
|
||||
};
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const englishText = card.renderRoot.querySelector('hp-dialog .body')?.textContent || '';
|
||||
result.englishFailureNamesThreeAndCountsRest = englishText.includes(
|
||||
'Could not safely verify the geometry of the following spaces: Alpha, beta, Space 3, and 1 more.',
|
||||
@@ -84,7 +84,7 @@ const out = await page.evaluate(async () => {
|
||||
&& englishText.includes('Alpha: The wall body did not build (the union came back empty)')
|
||||
&& englishText.includes('<img id="preflight-injection" src=x>');
|
||||
result.failureRendersNoApply = !card.renderRoot.querySelector('hp-dialog .btn.on');
|
||||
await card._runAlignToGrid(); await card.updateComplete;
|
||||
await card._editorRuntime.optimizePlans.run(); await card.updateComplete;
|
||||
result.redPreflightMakesZeroWrites = sent.length === 0;
|
||||
result.redPreflightPreservesAllState =
|
||||
JSON.stringify(card._serverCfg) === JSON.stringify(beforeRed.config)
|
||||
@@ -96,12 +96,12 @@ const out = await page.evaluate(async () => {
|
||||
// newly red result replaces the dialog and still cannot write.
|
||||
forceRed = false;
|
||||
setCandidate('en');
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const checksAfterGreenPreview = checks;
|
||||
result.greenPreviewOffersApply = !!card.renderRoot.querySelector('hp-dialog .btn.on');
|
||||
card._alignDialog.config.spaces[0].title = 'Changed after preview';
|
||||
forceRed = true;
|
||||
await card._runAlignToGrid(); await card.updateComplete;
|
||||
await card._editorRuntime.optimizePlans.run(); await card.updateComplete;
|
||||
result.changedFingerprintRechecks = checks === checksAfterGreenPreview + 1;
|
||||
result.changedFingerprintFailsClosed = sent.length === 0
|
||||
&& !card._alignDialog.preflight.ok
|
||||
@@ -112,10 +112,10 @@ const out = await page.evaluate(async () => {
|
||||
forceRed = false;
|
||||
setCandidate('en');
|
||||
const callsBeforeGreen = sent.length;
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const checksBeforeApply = checks;
|
||||
const previewCandidate = clone(card._alignDialog.config);
|
||||
await card._runAlignToGrid(); await card.updateComplete;
|
||||
await card._editorRuntime.optimizePlans.run(); await card.updateComplete;
|
||||
result.unchangedApplyDoesNotRecheck = checks === checksBeforeApply;
|
||||
result.greenApplyMakesOneAtomicWrite = sent.length === callsBeforeGreen + 1
|
||||
&& JSON.stringify(sent.at(-1).config) === JSON.stringify(previewCandidate);
|
||||
@@ -125,7 +125,7 @@ const out = await page.evaluate(async () => {
|
||||
// The just-written candidate is now a no-op: it skips the geometry pass and
|
||||
// retains the established message/absence of Apply.
|
||||
const checksBeforeNoOp = checks;
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const noOpText = card.renderRoot.querySelector('hp-dialog .body')?.textContent || '';
|
||||
result.noOpSkipsPreflight = checks === checksBeforeNoOp
|
||||
&& card._alignDialog.preflight === null;
|
||||
@@ -137,7 +137,7 @@ const out = await page.evaluate(async () => {
|
||||
// The same bounded copy and fallback/count policy is localized in Russian.
|
||||
forceRed = true;
|
||||
setCandidate('ru');
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const russianText = card.renderRoot.querySelector('hp-dialog .body')?.textContent || '';
|
||||
result.russianFailureHasExactCopy = russianText.includes(
|
||||
'Не удалось безопасно проверить геометрию следующих пространств: Alpha, beta, Пространство 3 и ещё 1.',
|
||||
|
||||
@@ -83,7 +83,7 @@ const out = await page.evaluate(async () => {
|
||||
card._modelCache = null; card._frame = null; card._space = 'micro';
|
||||
card.requestUpdate(); await card.updateComplete;
|
||||
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
const preview = card._alignDialog;
|
||||
result.previewOffersRoleAwareChange = !!preview?.changed
|
||||
&& preview.report.canonicalized === 1 && preview.report.wallsMerged === 0
|
||||
@@ -96,8 +96,8 @@ const out = await page.evaluate(async () => {
|
||||
result.cancelDoesNotWrite = sent.length === 0
|
||||
&& JSON.stringify(card._serverCfg) === JSON.stringify(original);
|
||||
|
||||
card._openAlignDialog(); await card.updateComplete;
|
||||
await card._runAlignToGrid(); await card.updateComplete;
|
||||
card._editorRuntime.optimizePlans.open(); await card.updateComplete;
|
||||
await card._editorRuntime.optimizePlans.run(); await card.updateComplete;
|
||||
const appliedWalls = card._serverCfg.spaces[0].walls;
|
||||
result.applyUsesOneAtomicWrite = sent.filter((type) => type === 'houseplan/plan/optimize').length === 1;
|
||||
result.applyStoresRoleAwareRuns = hasRoleAwareCanonicalRuns(appliedWalls);
|
||||
|
||||
@@ -92,7 +92,7 @@ const out = await page.evaluate(async () => {
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
const preview = card._alignDialog;
|
||||
const previewBody = card.renderRoot.querySelector('hp-dialog .body');
|
||||
@@ -134,9 +134,9 @@ const out = await page.evaluate(async () => {
|
||||
await card.updateComplete;
|
||||
result.cancelWritesNothing = calls.length === 0;
|
||||
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
card._toggleOptimizeLivePositions();
|
||||
card._editorRuntime.optimizePlans.toggleLivePositions();
|
||||
await card.updateComplete;
|
||||
const selectedText = card.renderRoot.querySelector('hp-dialog .body')?.textContent || '';
|
||||
result.explicitCleanupRebuildsPreviewWithoutWriting = calls.length === 0
|
||||
@@ -146,7 +146,7 @@ const out = await page.evaluate(async () => {
|
||||
&& card.renderRoot.querySelector('.optimize-cleanup')?.getAttribute('aria-pressed') === 'true'
|
||||
&& selectedText.includes('They are selected for removal.')
|
||||
&& !selectedText.includes('They will be kept.');
|
||||
await card._runAlignToGrid();
|
||||
await card._editorRuntime.optimizePlans.run();
|
||||
await card.updateComplete;
|
||||
result.applyUsesExactAtomicEndpoint = calls.filter((type) => type === 'houseplan/plan/optimize').length === 1;
|
||||
const restored = card._devices.find((device) => device.id === 'orphan');
|
||||
@@ -154,7 +154,7 @@ const out = await page.evaluate(async () => {
|
||||
&& restored.icon === 'mdi:washing-machine'
|
||||
&& !!card.renderRoot.querySelector('.dev[data-id="orphan"]');
|
||||
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
const noOpText = card.renderRoot.querySelector('hp-dialog .body')?.textContent || '';
|
||||
result.remainingOnlyWarningHasNoApply = card._alignDialog?.changed === false
|
||||
|
||||
@@ -32,8 +32,8 @@ const out = await page.evaluate(async () => {
|
||||
{ spaceId: 'bad2', displayName: 'Second', status: 'failed', reason: 'floor-null' },
|
||||
],
|
||||
};
|
||||
card._reportPreflightFailure(preflight);
|
||||
card._reportPreflightFailure(preflight); // dedup: same fingerprint logs once
|
||||
card._editorRuntime.optimizePlans.reportPreflightFailure(preflight);
|
||||
card._editorRuntime.optimizePlans.reportPreflightFailure(preflight); // dedup: same fingerprint logs once
|
||||
result.devLogOnce = warns.filter((w) => String(w[0]).includes('optimize preflight failed')).length === 1;
|
||||
const logged = warns.find((w) => String(w[0]).includes('optimize preflight failed'))?.[1];
|
||||
result.devLogShape = !!logged && logged.kind === 'houseplan-optimize-preflight'
|
||||
@@ -77,7 +77,7 @@ const out = await page.evaluate(async () => {
|
||||
let copied = null;
|
||||
const clipboard = { writeText: async (text) => { copied = text; } };
|
||||
Object.defineProperty(navigator, 'clipboard', { value: clipboard, configurable: true });
|
||||
await card._copyPreflightDiagnostics(); await update();
|
||||
await card._editorRuntime.optimizePlans.copyDiagnostics(); await update();
|
||||
let block = null;
|
||||
try { block = JSON.parse(copied); } catch { /* keep null */ }
|
||||
result.copiedBlock = !!block && block.kind === 'houseplan-optimize-preflight'
|
||||
@@ -91,7 +91,7 @@ const out = await page.evaluate(async () => {
|
||||
const saved = card._alignDialog;
|
||||
card._alignDialog = { ...saved, config: card._serverCfg };
|
||||
copied = null;
|
||||
await card._copyPreflightDiagnostics(); await update();
|
||||
await card._editorRuntime.optimizePlans.copyDiagnostics(); await update();
|
||||
const savedHash = JSON.parse(copied).failures[0].spaceGeometryFingerprint;
|
||||
card._alignDialog = saved; await update();
|
||||
result.fingerprintTracksCandidate = typeof candidateHash === 'string'
|
||||
@@ -103,7 +103,7 @@ const out = await page.evaluate(async () => {
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: { writeText: async () => { throw new Error('denied'); } }, configurable: true,
|
||||
});
|
||||
await card._copyPreflightDiagnostics(); await update();
|
||||
await card._editorRuntime.optimizePlans.copyDiagnostics(); await update();
|
||||
const pre = root().querySelector('hp-dialog details pre');
|
||||
result.inlineFallback = !!pre && pre.textContent.includes('houseplan-optimize-preflight');
|
||||
|
||||
@@ -115,7 +115,7 @@ const out = await page.evaluate(async () => {
|
||||
));
|
||||
await update();
|
||||
result.fallbackClearedOnClose = card._alignDialog === null
|
||||
&& card._preflightClipboardFallback === null;
|
||||
&& card._editorRuntime.optimizePlans.clipboardFallback === null;
|
||||
|
||||
console.warn = origWarn;
|
||||
return result;
|
||||
|
||||
@@ -79,7 +79,7 @@ const out = await page.evaluate(async (fixture) => {
|
||||
result.disabledCapturesNothing = !card._resize.dragging
|
||||
&& card._geometryHistory.size === 0;
|
||||
|
||||
card._openAlignDialog();
|
||||
card._editorRuntime.optimizePlans.open();
|
||||
await card.updateComplete;
|
||||
const previewSpace = card._alignDialog?.config?.spaces?.[0];
|
||||
result.previewProvesOuterRewrite = card._alignDialog?.changed === true
|
||||
@@ -89,7 +89,7 @@ const out = await page.evaluate(async (fixture) => {
|
||||
&& card._alignDialog?.config?.model_version === 10
|
||||
&& previewSpace?.partitions == null
|
||||
&& previewSpace?.openings?.every((opening) => wallHostResolves(previewSpace, opening));
|
||||
await card._runAlignToGrid();
|
||||
await card._editorRuntime.optimizePlans.run();
|
||||
await card.updateComplete;
|
||||
result.optimizeUsesOneWrite = sent.filter((type) => type === 'houseplan/plan/optimize').length === 1;
|
||||
const optimized = card._serverCfg.spaces[0];
|
||||
|
||||
@@ -156,7 +156,7 @@ const result = await page.evaluate(async () => {
|
||||
&& JSON.stringify(unrelated) === JSON.stringify({
|
||||
id: 'foreign-unrelated', a: [0.6, 0.5], b: [0.7, 0.5], cm: 15,
|
||||
});
|
||||
card._openAlignDialog(); await update();
|
||||
card._editorRuntime.optimizePlans.open(); await update();
|
||||
out.acceptedRoomIsOptimizeFixedPoint = card._alignDialog?.report?.partitionsReconciled === 0
|
||||
&& card._alignDialog?.changed === false;
|
||||
return out;
|
||||
|
||||
@@ -171,7 +171,7 @@ const res = await page.evaluate(async () => {
|
||||
c._setMode('view'); await c.updateComplete; await sleep(60);
|
||||
// Use the production preview instead of a partial synthetic report: report
|
||||
// fields grow with the optimizer contract and the dialog renders all of them.
|
||||
c._openAlignDialog();
|
||||
c._editorRuntime.optimizePlans.open();
|
||||
await c.updateComplete;
|
||||
out.dAlignOpenBefore = !!c._alignDialog;
|
||||
c.remove(); await sleep(20);
|
||||
|
||||
@@ -12,7 +12,7 @@ const chain = await runWallDrawFinishProfile(page);
|
||||
const chainHistory = await page.evaluate(async (chainIds) => {
|
||||
const card = window.__card;
|
||||
const optimizeNoop = () => {
|
||||
card._previewAlignDialog(false);
|
||||
card._editorRuntime.optimizePlans.preview(false);
|
||||
const changed = card._alignDialog?.changed;
|
||||
card._alignDialog = null;
|
||||
return changed === false;
|
||||
|
||||
@@ -199,7 +199,7 @@ export async function runWallDrawFinishProfile(page) {
|
||||
card._activateMarkupTool('select');
|
||||
const finishMs = performance.now() - started;
|
||||
const terminal = delta(snapshots(), before);
|
||||
card._previewAlignDialog(false);
|
||||
card._editorRuntime.optimizePlans.preview(false);
|
||||
const optimizeChanged = card._alignDialog?.changed;
|
||||
card._alignDialog = null;
|
||||
await card.updateComplete;
|
||||
|
||||
Vendored
+138
-138
@@ -1,127 +1,127 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6",
|
||||
"fingerprint": "d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 289449,
|
||||
"initialViewGzipBytes": 289273,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 290971,
|
||||
"initialPanelGzipBytes": 290796,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2324,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/de-CjsIUY-d.js",
|
||||
"houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/fr-D2W98VDc.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C5T09STo.js",
|
||||
"houseplan-assets/iso-scene-render-3HN3TF4R.js",
|
||||
"houseplan-assets/live-interaction-runtime-kRE37k2_.js",
|
||||
"houseplan-assets/pdf-export-DKVHRlMO.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-C4WynS64.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/de-C_-FOd3R.js",
|
||||
"houseplan-assets/editor-CuOqOWa_.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/fr-BubWZSkS.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DQp1tIHa.js",
|
||||
"houseplan-assets/iso-scene-render-DpO3ezuP.js",
|
||||
"houseplan-assets/live-interaction-runtime-CPQNHLqJ.js",
|
||||
"houseplan-assets/pdf-export-CFT_MzmM.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js"
|
||||
],
|
||||
"lazyGzipBytes": 460916,
|
||||
"lazyGzipBytes": 461471,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/editor-CuOqOWa_.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 244420,
|
||||
"lazyEditorGzipBytes": 244976,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 34521,
|
||||
"lazyOnboardingGzipBytes": 34523,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CjsIUY-d.js",
|
||||
"houseplan-assets/fr-D2W98VDc.js"
|
||||
"houseplan-assets/de-C_-FOd3R.js",
|
||||
"houseplan-assets/fr-BubWZSkS.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 55255,
|
||||
"lazyLocaleGzipBytes": 55257,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/iso-scene-render-3HN3TF4R.js"
|
||||
"houseplan-assets/iso-scene-render-DpO3ezuP.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 17640,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 16940,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/pdf-export-DKVHRlMO.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/pdf-export-CFT_MzmM.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 126827,
|
||||
"lazyPdfGzipBytes": 126826,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"sha256": "07acc6fc3e12ba2fe3e1b07e19a3f57308a0834a03db3c01bdba22ea43b9a7fd",
|
||||
"path": "houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"sha256": "37be8583306fa604aed0f7f79f7a6c3eb8e373de8e5d476fb4593881b7954f93",
|
||||
"rawBytes": 76524,
|
||||
"gzipBytes": 24571,
|
||||
"gzipBytes": 24572,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CjsIUY-d.js",
|
||||
"sha256": "25fa2d8f19df083532e8f022d86c21d290f3e79badaf7b4d058158c50a02286c",
|
||||
"path": "houseplan-assets/de-C_-FOd3R.js",
|
||||
"sha256": "033535ad61de5d4453c3b3729cf2afb69ed996267cbb83ebd2111b0ce6cad359",
|
||||
"rawBytes": 92247,
|
||||
"gzipBytes": 27864,
|
||||
"gzipBytes": 27865,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"sha256": "b1cdd26dec11ae7d904a675fbc27964757819886d1034ab63d2c9d91b42e3655",
|
||||
"path": "houseplan-assets/editor-CuOqOWa_.js",
|
||||
"sha256": "350885825a2046833dcef868485cae1efe99389d088fd502bd166d1ecc75b2a7",
|
||||
"rawBytes": 3833,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1581,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"sha256": "aee7494545b43e2c932163c572b19483d9ac35c022daf437bcfd3aa21589aa07",
|
||||
"path": "houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"sha256": "c6899e84bdfc9d83f635e63317260c6e3e0ae85104d966e0e297d56abcf45d91",
|
||||
"rawBytes": 25600,
|
||||
"gzipBytes": 5055,
|
||||
"gzipBytes": 5056,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-D2W98VDc.js",
|
||||
"sha256": "12e70d1138d31d87f8c7f25d750bef3ecb7c84e23925845c101f0af4298208c0",
|
||||
"path": "houseplan-assets/fr-BubWZSkS.js",
|
||||
"sha256": "6243e53b2d25b50f23e7b8544cf24ae2634f3d985d0ebb05ad881628121693f4",
|
||||
"rawBytes": 94579,
|
||||
"gzipBytes": 27391,
|
||||
"gzipBytes": 27392,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"sha256": "63e67cda0c59f37fee93410ae4193ae79d730b83293eea331edefaf63e280d11",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"sha256": "aac82444a5129c8991694a9a5421a63fc23479d41ed2cf44be2bba9cdb8bac38",
|
||||
"rawBytes": 52035,
|
||||
"gzipBytes": 16940,
|
||||
"isEntry": false,
|
||||
@@ -129,184 +129,184 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"sha256": "98c44bf6f915d6121132c133cc12b2e27ea344f1c8203e7af54741f317aa1a5e",
|
||||
"rawBytes": 1022206,
|
||||
"gzipBytes": 288647,
|
||||
"path": "houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"sha256": "6e2ecbe962122cfa7599c75e5ca8815d27c98b754cc437b7628b4ddafef10d9f",
|
||||
"rawBytes": 1021428,
|
||||
"gzipBytes": 288472,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CjsIUY-d.js",
|
||||
"houseplan-assets/editor-Dv3uqeSS.js",
|
||||
"houseplan-assets/fr-D2W98VDc.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C5T09STo.js",
|
||||
"houseplan-assets/iso-scene-render-3HN3TF4R.js",
|
||||
"houseplan-assets/live-interaction-runtime-kRE37k2_.js",
|
||||
"houseplan-assets/pdf-export-DKVHRlMO.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js"
|
||||
"houseplan-assets/de-C_-FOd3R.js",
|
||||
"houseplan-assets/editor-CuOqOWa_.js",
|
||||
"houseplan-assets/fr-BubWZSkS.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DQp1tIHa.js",
|
||||
"houseplan-assets/iso-scene-render-DpO3ezuP.js",
|
||||
"houseplan-assets/live-interaction-runtime-CPQNHLqJ.js",
|
||||
"houseplan-assets/pdf-export-CFT_MzmM.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime--nYeDmTG.js",
|
||||
"sha256": "f476a861b5a2d77cd2ecea724c57d4e9e6dce6fa161eb18cc9479d2ec9752641",
|
||||
"rawBytes": 678218,
|
||||
"gzipBytes": 188846,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-B5TSesBb.js",
|
||||
"sha256": "7bad833f2fd60af11c8411e56b97431c71ee3e4fff685de3019a9ac14e5bc1ba",
|
||||
"rawBytes": 678531,
|
||||
"gzipBytes": 189399,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-C4WynS64.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-zgKhvtTD.js",
|
||||
"sha256": "5613ece2554c3ee9afc3e71dce63370d3e0bbeab9489e297ffaee74f33670e6f",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BgE55y_R.js",
|
||||
"sha256": "1a16adf7c3ab18db3a9773930103843470acdeb2fde3870110c20a069cd28876",
|
||||
"rawBytes": 15986,
|
||||
"gzipBytes": 4895,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-YbdnEEmM.js",
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/backdrop-pick-CAEOSiNJ.js",
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-C5T09STo.js",
|
||||
"sha256": "d836d918b89ee5685f7d2d10b194cf17feb7ca956c2112c78c4d62db7042ef24",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-DQp1tIHa.js",
|
||||
"sha256": "13cf97cdc63e8b66108521655d6e4585452fcf02ce9f4fc0b0bf19c13cc6f099",
|
||||
"rawBytes": 10759,
|
||||
"gzipBytes": 3686,
|
||||
"gzipBytes": 3685,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-C4WynS64.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-3HN3TF4R.js",
|
||||
"sha256": "0c1137699d170125b0b3a6a7dd5177a06e270d74e8b3020711c100b4c59bbe0d",
|
||||
"path": "houseplan-assets/iso-scene-render-DpO3ezuP.js",
|
||||
"sha256": "c979378b329bca0dbd23ddbae6ca2cad763bb5754959e9b98f395197d915a8b5",
|
||||
"rawBytes": 51983,
|
||||
"gzipBytes": 17640,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-kRE37k2_.js",
|
||||
"sha256": "e0537b8b7d78df3e70208e5601b95b538509265184027a62b559c43626372385",
|
||||
"path": "houseplan-assets/live-interaction-runtime-CPQNHLqJ.js",
|
||||
"sha256": "10e83f4eb0bdf1e4b0b0a4c9e377ade059bf5169488fa360821625a4df4bc91f",
|
||||
"rawBytes": 8216,
|
||||
"gzipBytes": 3203,
|
||||
"gzipBytes": 3206,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-DKVHRlMO.js",
|
||||
"sha256": "fef91b049f58622de45132f31b3ab7f606df5db8eee3fc68d3809b2c5ece7e5d",
|
||||
"path": "houseplan-assets/pdf-export-CFT_MzmM.js",
|
||||
"sha256": "03ee0fc3e88bc84e94c1a51fadc2e65a17635ba53ed875f1cdb794c3eb989106",
|
||||
"rawBytes": 236264,
|
||||
"gzipBytes": 109887,
|
||||
"gzipBytes": 109886,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-YwpfkuFp.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-Ci984yNn.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-Bj45k8M3.js",
|
||||
"sha256": "1463acf70394eb7c8fc09b8182b227648b00919bb2fc5ad6a6ae189b1a051a2e",
|
||||
"path": "houseplan-assets/summary-panel-editor-11x1Zz63.js",
|
||||
"sha256": "2d4be3e90f3c0329c33b6465d57c82fc2ba93e30097838cd042c69f0cc0f08be",
|
||||
"rawBytes": 14862,
|
||||
"gzipBytes": 3423,
|
||||
"gzipBytes": 3426,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js"
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-3K7HlQXp.js",
|
||||
"sha256": "246a3ee30014c9096527044921c104b76cef9555ebd1b2fbfcaeea54daf010cc",
|
||||
"path": "houseplan-assets/summary-panel-metrics-BwUdppyx.js",
|
||||
"sha256": "5dbb467356ea155e5d4dee21c562cbfdf0a763b93d728f8227f7731b91837068",
|
||||
"rawBytes": 2611,
|
||||
"gzipBytes": 1306,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-aaGs9bci.js",
|
||||
"sha256": "527c40315a7cbbfa84289d6c8360688df121f368bf2609488e0b3b527ed2988a",
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-BzQmPxkY.js",
|
||||
"sha256": "6e0e9808f1da372aef041d74e46c91e29d2d5fd98a9fbe24ee54debe58c06ee3",
|
||||
"rawBytes": 72637,
|
||||
"gzipBytes": 20166,
|
||||
"gzipBytes": 20163,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-Dlhk9WKB.js",
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/form-kit.styles-ORjqyA2O.js",
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-Bj45k8M3.js",
|
||||
"houseplan-assets/summary-panel-metrics-3K7HlQXp.js"
|
||||
"houseplan-assets/summary-panel-editor-11x1Zz63.js",
|
||||
"houseplan-assets/summary-panel-metrics-BwUdppyx.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-CXRVgVl2.js",
|
||||
"sha256": "a68916a24db8c6acc389707ad40f95d8963d4521e40c09e6fa32b6430584569a",
|
||||
"path": "houseplan-assets/zigbee-topology-BIziY8_4.js",
|
||||
"sha256": "be1428736b1fcd653bec4a452d4895d8cfea015cc143b8560f5c6065c8b6ffea",
|
||||
"rawBytes": 18962,
|
||||
"gzipBytes": 7425,
|
||||
"gzipBytes": 7428,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-C4WynS64.js",
|
||||
"sha256": "598d78eb03a6b75e7bf9ce8ac7fdbc7ee514d6c7f056e8651309c541313ced0e",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-Sxlr6W0z.js",
|
||||
"sha256": "af36ce4a7b2e9b6992e2c74a19e63f197a1960317fad3bb8f193d93ccccb84b8",
|
||||
"rawBytes": 3942,
|
||||
"gzipBytes": 1764,
|
||||
"gzipBytes": 1763,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js",
|
||||
"houseplan-assets/zigbee-topology-CXRVgVl2.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js",
|
||||
"houseplan-assets/zigbee-topology-BIziY8_4.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "ff5753ceb4dce57c6f1701baad303c37770735cb53b2a61c14e1225e07ac898b",
|
||||
"sha256": "9a30c8176149f7b6b78431e5f876dd8ab3f86676bf31ecfbd8952821812a46ed",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 802,
|
||||
"gzipBytes": 801,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "90fb829ea2a8310205a7bc7c8d5d57bd38eedb0d62c961eed13d3bc3119b011a",
|
||||
"sha256": "e712ffa4a818f355510e4eee5ed9438c6f89ac60797f0838bba4b2298cb35d24",
|
||||
"rawBytes": 6165,
|
||||
"gzipBytes": 2324,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-e9vxRZcR.js"
|
||||
"houseplan-assets/houseplan-card-C2ifrS7A.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{A as e,c as a,s as t,l as i,d as n,ap as s,h as r,j as o,g as l,dH as c,dI as p,dJ as d,cL as h,dK as u,cK as m,dL as g,cQ as _,cP as b,dM as f,bs as v,br as w,bt as y,av as k,aC as $,aD as x,dN as S}from"./houseplan-card-e9vxRZcR.js";import{c as C,f as M,C as L}from"./form-kit.styles-Dlhk9WKB.js";const z=C+M(L,{withSwitch:!1}),P=new WeakSet;let D=null;function T(e){const a=e.renderRoot;if(!a||P.has(a))return;const t=a.ownerDocument??globalThis.document,i=t?.defaultView?.CSSStyleSheet;if(i&&"adoptedStyleSheets"in a)return D||(D=new i,D.replaceSync(z)),a.adoptedStyleSheets=[...a.adoptedStyleSheets,D],void P.add(a);if(a.querySelector("style[data-hp-form-kit]"))return void P.add(a);const n=t.createElement("style");n.dataset.hpFormKit="true",n.textContent=z,a.appendChild(n),P.add(a)}function H({title:t,help:i,body:n,id:s}){return a`<section class="hpf-card" data-card=${s??e}>
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{A as e,c as a,s as t,l as i,d as n,ap as s,h as r,j as o,g as l,dH as c,dI as p,dJ as d,cN as h,dK as u,cM as m,dL as g,cS as _,cR as b,dM as f,bt as v,bs as w,bu as y,av as k,aC as $,aD as x,dN as S}from"./houseplan-card-C2ifrS7A.js";import{c as C,f as M,C as L}from"./form-kit.styles-ORjqyA2O.js";const z=C+M(L,{withSwitch:!1}),P=new WeakSet;let D=null;function T(e){const a=e.renderRoot;if(!a||P.has(a))return;const t=a.ownerDocument??globalThis.document,i=t?.defaultView?.CSSStyleSheet;if(i&&"adoptedStyleSheets"in a)return D||(D=new i,D.replaceSync(z)),a.adoptedStyleSheets=[...a.adoptedStyleSheets,D],void P.add(a);if(a.querySelector("style[data-hp-form-kit]"))return void P.add(a);const n=t.createElement("style");n.dataset.hpFormKit="true",n.textContent=z,a.appendChild(n),P.add(a)}function H({title:t,help:i,body:n,id:s}){return a`<section class="hpf-card" data-card=${s??e}>
|
||||
${t?a`<div class="hpf-head"><h3>${t}</h3>${i??e}</div>`:e}
|
||||
<div class="hpf-body">${n}</div>
|
||||
</section>`}function E({title:t,help:i}){return a`<div class="hpf-sub"><h4>${t}</h4>${i??e}</div>`}function F({label:t,htmlFor:i,help:n,control:s,hint:r,error:o}){return a`<div class="hpf-field">
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,14 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-e9vxRZcR.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-C2ifrS7A.js";class f extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,f={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},h=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
.computeLabel=${e=>h[e.name]||e.name}
|
||||
.computeLabel=${e=>f[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${d(f.slice(0,3))}
|
||||
${d(h.slice(0,3))}
|
||||
${_?c`<div class="default-floor-error" role="alert"
|
||||
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
|
||||
${t(s,"editor.default_floor_missing",{id:_})}
|
||||
</div>`:a}
|
||||
${d(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);
|
||||
${d(h.slice(3))}
|
||||
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
|
||||
+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
File diff suppressed because one or more lines are too long
Vendored
+9
-9
File diff suppressed because one or more lines are too long
+141
-141
File diff suppressed because one or more lines are too long
+10
-10
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
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__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{c as a,A as e}from"./houseplan-card-e9vxRZcR.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-aaGs9bci.js";import"./form-kit.styles-Dlhk9WKB.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__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{c as a,A as e}from"./houseplan-card-C2ifrS7A.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-BzQmPxkY.js";import"./form-kit.styles-ORjqyA2O.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__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{ek as e,N as t,a$ as n,L as i,ae as o,ac as r,bT as s,bS as a,a_ as c,aZ as l,b0 as d,el as u,em as f,bV as m,en as g}from"./houseplan-card-e9vxRZcR.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__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{ek as e,N as t,a$ as n,L as i,ae as o,ac as r,bV as s,bU as a,a_ as l,aZ as c,b0 as d,el as u,em as f,bX as m,en as g}from"./houseplan-card-C2ifrS7A.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,l="entity"===r?e.registry.entities?.[s]:null,c="device"===r?s:l?.device_id,d="device"===r?a?.area_id:l?.area_id||c&&e.registry.devices?.[c]?.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;c&&e.registry.devices?.[c]&&e.spaceIds.has(f)&&(!n.devices.has(c)||"entity"===r&&n.liveEntities.has(s))&&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,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:c(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*h(l,c,d=_){try{let u=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=d(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const l=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(l,m.physicalBodies)),yield}const _=m.cellCm/o;u+=a(v)*_*_/1e4}return u}catch{return null}}function p(e,t,n=_){const i=h(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function b(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{h as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,b as summarySystemValue,p as totalCleanFloorAreaM2};
|
||||
+3
-3
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__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";import{n as t,b as n}from"./zigbee-topology-CXRVgVl2.js";import{aM as e}from"./houseplan-card-e9vxRZcR.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};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{n as t,b as n}from"./zigbee-topology-BIziY8_4.js";import{aM as e}from"./houseplan-card-C2ifrS7A.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 f(t){if(!0!==t?.user?.is_admin)throw a("permission")}function p(n){const e=i(n);return e?c(e,"zha",async()=>{if(f(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(f(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,p=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{p=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 f=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&&p?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof f&&b.push(f),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{p as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="be980214e65258ad0c8ec0480b3b70c46a1e6f817bba2cda8d2f16c104021db6";try{await import("./houseplan-assets/houseplan-card-e9vxRZcR.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__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";try{await import("./houseplan-assets/houseplan-card-C2ifrS7A.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
@@ -466,8 +466,14 @@ export const LAZY_FURNITURE_ART_GZIP_CEILING = 17_900;
|
||||
* inline-ошибки маркера и единая повторная валидация живут в lazy
|
||||
* editor-графе. Новый центр оставляет 1 044 Б сверху и 956 Б до
|
||||
* нижней границы полосы.
|
||||
* - #642: 245 400 → 246 000 (замер 244 975, база 244 420). Диалог
|
||||
* «Оптимизировать планы» уехал из рантайма в свой модуль за узкий порт:
|
||||
* сырых байт стало меньше на 465, а gzip вырос на 555 — обёртка класса и
|
||||
* адаптер порта из 18 членов уникальны, а сжатие соседних `this.host._t(`
|
||||
* в рантайме их не покрывает. Старый центр оставлял 425 Б сверху, меньше
|
||||
* порога шума #593; новый — 1 025 Б сверху и 975 Б до нижней границы.
|
||||
*/
|
||||
export const LAZY_EDITOR_GZIP_CEILING = 245_400;
|
||||
export const LAZY_EDITOR_GZIP_CEILING = 246_000;
|
||||
export const LAZY_GRAPH_CEILING_BAND = 2_000;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"delegates": 159,
|
||||
"portMembers": 350,
|
||||
"hostRefs": 4948,
|
||||
"portPrivates": 96,
|
||||
"harnessPrivates": 107,
|
||||
"bundleBytes": 2500387
|
||||
"delegates": 154,
|
||||
"portMembers": 348,
|
||||
"hostRefs": 4869,
|
||||
"portPrivates": 94,
|
||||
"harnessPrivates": 101,
|
||||
"bundleBytes": 2499182
|
||||
}
|
||||
|
||||
@@ -5254,13 +5254,18 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'optimize-preflight-bypassed',
|
||||
guard: 'node demo/smoke_optimize_geometry_preflight.mjs',
|
||||
// #642: диалог вынесен в свой модуль, гвард — его юнит.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="red preflight is a hard write barrier" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'a red preview must remain a hard write barrier even if a caller invokes the '
|
||||
+ 'private Apply method directly instead of clicking the deliberately absent button (#199)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: ' if (!d || d.busy || !this._serverCfg || !d.changed || !d.preflight?.ok) return;',
|
||||
replace: ' if (!d || d.busy || !this._serverCfg || !d.changed) return;',
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: ' if (!d || d.busy || !this.port.config() || !d.changed || !d.preflight?.ok) return;',
|
||||
// `!d.preflight` остаётся ради сужения типа: иначе мутант не компилируется
|
||||
// и ловится компилятором, а не тестом.
|
||||
replace: ' if (!d || d.busy || !this.port.config() || !d.changed || !d.preflight) return;',
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -5291,11 +5296,14 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'optimize-preflight-renders-apply-on-failure',
|
||||
guard: 'node demo/smoke_optimize_geometry_preflight.mjs',
|
||||
// #642: разметка диалога — исполнением в юните модуля.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="failed preflight names each reason and offers no Apply" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'the failure state is not a dismissible warning: rendering Apply invites a person '
|
||||
+ 'to treat an unsafe whole-plan candidate as an accepted risk (#199)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: ' ${!d.changed || !d.preflight?.ok ? nothing : html`',
|
||||
replace: ' ${!d.changed ? nothing : html`',
|
||||
}],
|
||||
@@ -5402,15 +5410,18 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'near-axis-optimize-confirmation-bypassed',
|
||||
guard: 'node demo/smoke_near_axis_optimize.mjs',
|
||||
// #642: открытие — метод модуля диалога; гвард — его юнит.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="preview never writes" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'opening the Optimize preview or cancelling it must never persist a lossy repair (#290)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-editor-runtime.ts',
|
||||
find: 'public _openAlignDialog = (): void => this._previewAlignDialog(false);',
|
||||
replace: 'public _openAlignDialog = (): void => {\n'
|
||||
+ ' this._previewAlignDialog(false);\n'
|
||||
+ ' void this._runAlignToGrid();\n'
|
||||
+ ' };',
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: ' public open(): void { this.preview(false); }',
|
||||
replace: ' public open(): void {\n'
|
||||
+ ' this.preview(false);\n'
|
||||
+ ' void this.run();\n'
|
||||
+ ' }',
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -6622,12 +6633,14 @@ const MUTANT_DEFINITIONS = [
|
||||
{
|
||||
id: 'preflight-reason-lost-in-dialog',
|
||||
// #295: диалог обязан называть причину отказа по каждому пространству.
|
||||
// #642: гвард — юнит модуля диалога, разметка исполнением.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node demo/smoke_preflight_diagnostics.mjs',
|
||||
+ '&& node --test --test-name-pattern="failed preflight names each reason and offers no Apply" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'отказ preflight без причины недиагностируем — ровно исходный дефект #295',
|
||||
patches: [{
|
||||
file: 'src/houseplan-editor-runtime.ts',
|
||||
find: " ${failure.displayName}: ${this.host._t(`gs.preflight_reason_${failure.reason}` as I18nKey)}",
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: " ${failure.displayName}: ${this.port.t(`gs.preflight_reason_${failure.reason}` as I18nKey)}",
|
||||
replace: " ${failure.displayName}",
|
||||
}],
|
||||
},
|
||||
@@ -6636,15 +6649,16 @@ const MUTANT_DEFINITIONS = [
|
||||
// CODE-REVIEW-295-r1 M1: хэш геометрии обязан браться из кандидата,
|
||||
// который проверял preflight, а не из сохранённого конфига — иначе блок
|
||||
// повторяет то, что и так даст экспорт пространства.
|
||||
// #642: сборка блока — чистая функция, сохранённый конфиг ей не виден;
|
||||
// подмена кандидата сохранённым конфигом возможна только у вызова.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node demo/smoke_preflight_diagnostics.mjs',
|
||||
+ '&& node --test --test-name-pattern="hashes the candidate, not the saved config" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'диагностика с хэшем сохранённой геометрии не несёт ничего сверх экспорта (AC4)',
|
||||
patches: [{
|
||||
// This method lives on HouseplanEditorRuntime after the editor split;
|
||||
// the saved config belongs to the typed host, not to the runtime itself.
|
||||
file: 'src/houseplan-editor-runtime.ts',
|
||||
find: " const spacesById = new Map(((candidate as any)?.spaces || [])",
|
||||
replace: " const spacesById = new Map(((this.host._serverCfg as any)?.spaces || [])",
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: " this.diagnostics(preflight, dialog.config ?? null), null, 2,",
|
||||
replace: " this.diagnostics(preflight, this.port.config()), null, 2,",
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -6679,39 +6693,71 @@ const MUTANT_DEFINITIONS = [
|
||||
id: 'preflight-fallback-survives-dialog-close',
|
||||
// CODE-REVIEW-295-r1 M2: инлайн-фолбэк живёт одно показание диалога;
|
||||
// переживший закрытие блок подсунет в отчёт диагностику чужого отказа.
|
||||
// #642: защита — ключ WeakMap по объекту диалога. Мутант подменяет его
|
||||
// общим ключом (сам контроллер), и фолбэк переживает закрытие.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node demo/smoke_preflight_diagnostics.mjs',
|
||||
+ '&& node --test --test-name-pattern="belongs to one dialog showing" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'застрявший фолбэк отдаёт в баг-репорт JSON предыдущего отказа, не текущего (AC4)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: "dismiss-on-scrim @hp-close=${() => { this._alignDialog = null; this._preflightClipboardFallback = null; }}>",
|
||||
replace: "dismiss-on-scrim @hp-close=${() => { this._alignDialog = null; }}>",
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: " this.fallbacks.set(dialog, text);",
|
||||
replace: " this.fallbacks.set(this, text);",
|
||||
}, {
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: " return dialog ? this.fallbacks.get(dialog) ?? null : null;",
|
||||
replace: " return dialog ? this.fallbacks.get(this) ?? null : null;",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'preflight-diagnostics-without-reason',
|
||||
// #295: копируемый блок без reason бесполезен для отчёта об ошибке.
|
||||
// #642: блок собирает чистая функция модуля диалога.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node demo/smoke_preflight_diagnostics.mjs',
|
||||
+ '&& node --test --test-name-pattern="preflightDiagnostics: candidate hash, reason" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'диагностический блок обязан нести reason каждого отказа',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: " reason: failure.reason,",
|
||||
replace: " reason: undefined,",
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: " reason: failure.reason,",
|
||||
replace: " reason: undefined,",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'preflight-dev-log-disabled',
|
||||
// #295: dev-лог — второй канал диагностики, его потерю обязан ловить смок.
|
||||
// #642: dev-лог и его дедуп живут в модуле диалога.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node demo/smoke_preflight_diagnostics.mjs',
|
||||
+ '&& node --test --test-name-pattern="logs one structured record per distinct fingerprint" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'структурированная запись отказа в консоли — часть контракта диагностики #295',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: " console.warn('[houseplan] optimize preflight failed', this._preflightDiagnostics(preflight, candidate));",
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: " console.warn('[houseplan] optimize preflight failed', this.diagnostics(preflight, candidate));",
|
||||
replace: " void preflight; void candidate;",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'optimize-dialog-imports-host-port',
|
||||
// #642 AC2: смысл выноса — узкий порт. Модуль, который снова тянет
|
||||
// широкий порт редактора, возвращает связность, ради снижения которой
|
||||
// и выносился, а юнит должен это увидеть даже для `import type`.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="the dialog port is narrow" '
|
||||
+ 'test/optimize-plans-dialog.test.mjs',
|
||||
because: 'диалог, получивший HouseplanEditorHostPort, снова связан с 350 членами карточки (#642)',
|
||||
patches: [{
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: "import { html, nothing, type TemplateResult } from 'lit';\n",
|
||||
replace: "import { html, nothing, type TemplateResult } from 'lit';\n"
|
||||
+ "import type { HouseplanEditorHostPort } from './houseplan-editor-runtime';\n",
|
||||
}, {
|
||||
file: 'src/optimize-plans-dialog.ts',
|
||||
find: " private reportedFingerprint: string | null = null;\n",
|
||||
replace: " private reportedFingerprint: string | null = null;\n"
|
||||
+ " public host: HouseplanEditorHostPort | null = null;\n",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'visual-mitre-limit-back-to-4',
|
||||
// #309: порог визуального среза возвращается к классическим 4·h — шип на
|
||||
|
||||
@@ -245,7 +245,7 @@ export function renderGeneralSettingsDialog(this: HouseplanEditorRuntime): Templ
|
||||
</div>` : nothing}
|
||||
${subsection({ title: t('gs.grid_group'), help: this._help('gs.grid_group.help') })}
|
||||
<div class="hpf-actions">
|
||||
<button class="btn" @click=${() => this._openAlignDialog()}>
|
||||
<button class="btn" @click=${() => this.optimizePlans.open()}>
|
||||
<ha-icon icon="mdi:broom"></ha-icon>${t('gs.align_all')}
|
||||
</button>
|
||||
${host._canOptimizeUndo && host._undoKind !== 'import' ? html`
|
||||
|
||||
+4
-66
@@ -12,6 +12,7 @@ import { keyed } from 'lit/directives/keyed.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import './hp-dialog';
|
||||
import type { HpDialog } from './hp-dialog';
|
||||
import type { OptimizePlansDialogState } from './optimize-plans-dialog';
|
||||
import './hp-confirm';
|
||||
import {
|
||||
HpConfirmController,
|
||||
@@ -137,7 +138,6 @@ import {
|
||||
iconUnit, iconCqw, itemOf, snapPt, MIN_ZOOM, PAN_SLACK, CANVAS_LIMIT, GRID_PITCH, GRID_STEP_N,
|
||||
clampCanvasR, clampCanvasN, type ContentItem, type Rect,
|
||||
} from './space-geometry';
|
||||
import { type OptimizeReport } from './plan-optimizer';
|
||||
import { resolveZeroWalls, zeroWallStyleOf } from './zero-walls';
|
||||
import {
|
||||
geometryOpenings, geometryPartitionOpeningCuts, geometryRoomOpeningInputs,
|
||||
@@ -2003,9 +2003,6 @@ export class HouseplanCard extends LitElement {
|
||||
private _onboardingShown = false; // the auto space dialog is shown once per session
|
||||
|
||||
private _rulesDialog: { rules: IconRule[]; test: string; busy: boolean } | null = null;
|
||||
/** Optimization preview plus the exact pair, so commit cannot differ from it. */
|
||||
/** #295: diagnostics text shown inline when the clipboard is unavailable. */
|
||||
private _preflightClipboardFallback: string | null = null;
|
||||
/** #295: integration version from houseplan/config/get; null on old backends. */
|
||||
private _haIntegrationVersion: string | null = null;
|
||||
/** #462: eager full-card recovery; the static space card never owns one. */
|
||||
@@ -2026,18 +2023,7 @@ export class HouseplanCard extends LitElement {
|
||||
private _haRadarStage1Api: number | null = null;
|
||||
private readonly _radarLive = new RadarLiveController(this);
|
||||
private _decorAssetSyncToken = 0;
|
||||
private _alignDialog: {
|
||||
report: OptimizeReport; config: any; layout: Record<string, any>;
|
||||
preflight: OptimizeGeometryPreflightResult | null;
|
||||
/** the promised maximum, in centimetres, ALREADY rounded up (AUD-158B1-01) */
|
||||
cm: number;
|
||||
/** the space that maximum belongs to, named only when there are several */
|
||||
where: string;
|
||||
changed: boolean;
|
||||
busy: boolean;
|
||||
/** False by default; true only after the secondary preview action. */
|
||||
removeLiveMissingPositions: boolean;
|
||||
} | null = null;
|
||||
private _alignDialog: OptimizePlansDialogState | null = null;
|
||||
|
||||
private _settingsDialog: {
|
||||
colors: FillColors; glowRadius: number; glowRadiusInput: string; bgColor: string | null;
|
||||
@@ -2500,7 +2486,6 @@ export class HouseplanCard extends LitElement {
|
||||
_pdfDialog: { state: true },
|
||||
_supportDialog: { state: true },
|
||||
_alignDialog: { state: true },
|
||||
_preflightClipboardFallback: { state: true },
|
||||
_backupExportDialog: { state: true },
|
||||
_backupImportDialog: { state: true },
|
||||
_importDialog: { state: true },
|
||||
@@ -2789,7 +2774,7 @@ export class HouseplanCard extends LitElement {
|
||||
if (this._openingInfo) { this._openingInfo = null; return; }
|
||||
if (this._infoCard) { this._closeInfoCard(); return; }
|
||||
if (this._rulesDialog) { this._rulesDialog = null; return; }
|
||||
if (this._alignDialog) { this._alignDialog = null; this._preflightClipboardFallback = null; return; }
|
||||
if (this._alignDialog) { this._alignDialog = null; return; }
|
||||
if (this._backupImportDialog) { this._backupImportDialog = null; return; }
|
||||
if (this._backupExportDialog) { this._backupExportDialog = null; return; }
|
||||
if (this._pdfDialog) { this._pdfDialog = false; return; }
|
||||
@@ -7393,7 +7378,6 @@ export class HouseplanCard extends LitElement {
|
||||
this._closeInfoCard();
|
||||
this._rulesDialog = null;
|
||||
this._alignDialog = null;
|
||||
this._preflightClipboardFallback = null;
|
||||
this._backupImportDialog = null;
|
||||
this._backupExportDialog = null;
|
||||
this._pdfDialog = false;
|
||||
@@ -10197,24 +10181,6 @@ export class HouseplanCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview whole-plan maintenance. Nothing is written here: the pure run
|
||||
* produces both the report and the exact config/layout pair to commit.
|
||||
*/
|
||||
|
||||
/** #295: dev-log once per distinct failing preflight, not once per render. */
|
||||
private _reportedPreflightFingerprint: string | null = null;
|
||||
private _reportPreflightFailure(
|
||||
preflight: OptimizeGeometryPreflightResult,
|
||||
candidate: ServerConfig | null,
|
||||
): void {
|
||||
return this._editorRuntimeOrThrow()._reportPreflightFailure(preflight, candidate);
|
||||
}
|
||||
|
||||
private async _copyPreflightDiagnostics(): Promise<void> {
|
||||
return this._editorRuntimeOrThrow()._copyPreflightDiagnostics();
|
||||
}
|
||||
|
||||
private _checkOptimizeGeometry(config: ServerConfig): OptimizeGeometryPreflightResult {
|
||||
return this._editorRuntimeOrThrow()._checkOptimizeGeometryImpl(config);
|
||||
}
|
||||
@@ -10230,26 +10196,6 @@ export class HouseplanCard extends LitElement {
|
||||
return this._editorRuntimeOrThrow()._checkSpacePhysicalGeometryImpl(config, spaceId, captureWallGeometry);
|
||||
}
|
||||
|
||||
private _previewAlignDialog(removeLiveMissingPositions: boolean): void {
|
||||
return this._editorRuntimeOrThrow()._previewAlignDialog(removeLiveMissingPositions);
|
||||
}
|
||||
|
||||
private _openAlignDialog = (): void => {
|
||||
return this._editorRuntimeOrThrow()._openAlignDialog();
|
||||
}
|
||||
|
||||
private _toggleOptimizeLivePositions = (): void => {
|
||||
return this._editorRuntimeOrThrow()._toggleOptimizeLivePositions();
|
||||
}
|
||||
|
||||
/**
|
||||
* The backend persists an intent before either store changes, then keeps a
|
||||
* one-deep snapshot that remains undoable until the next plan edit.
|
||||
*/
|
||||
private async _runAlignToGrid(): Promise<void> {
|
||||
return this._editorRuntimeOrThrow()._runAlignToGrid();
|
||||
}
|
||||
|
||||
/** Prevent a double click from sending two restores of the same snapshot. */
|
||||
private _optimizeUndoBusy = false;
|
||||
|
||||
@@ -10533,14 +10479,6 @@ export class HouseplanCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The confirmation separates geometry movement from lossless maintenance,
|
||||
* and promises the one-deep undo before either store is changed.
|
||||
*/
|
||||
private _renderAlignDialog(): TemplateResult {
|
||||
return this._editorRuntimeOrThrow()._renderAlignDialog();
|
||||
}
|
||||
|
||||
private _renderSettingsDialog(): TemplateResult {
|
||||
return this._editorRuntimeOrThrow()._renderSettingsDialog();
|
||||
}
|
||||
@@ -11305,7 +11243,7 @@ export class HouseplanCard extends LitElement {
|
||||
${this._settingsDialog ? this._editorRuntime ? this._renderSettingsDialog() : nothing : nothing}
|
||||
${this._supportDialog ? this._editorRuntime ? this._renderSupportDialog() : nothing : nothing}
|
||||
${this._pdfDialog ? this._renderPdfDialog() : nothing}
|
||||
${this._alignDialog ? this._editorRuntime ? this._renderAlignDialog() : nothing : nothing}
|
||||
${this._alignDialog && this._editorRuntime ? this._editorRuntime.optimizePlans.render() : nothing}
|
||||
${this._backupExportDialog ? this._editorRuntime ? this._renderBackupExportDialog() : nothing : nothing}
|
||||
${this._backupImportDialog ? this._editorRuntime ? this._renderBackupImportDialog() : nothing : nothing}
|
||||
${this._importDialog ? this._onboardingRuntime ? this._renderImportDialog() : nothing : nothing}
|
||||
|
||||
+26
-388
@@ -148,6 +148,7 @@ import {
|
||||
import { finishMarkerDialogClose } from './marker-dialog-close';
|
||||
import { renderRadarSection } from './editors/radar-section';
|
||||
import { radarDiscardRequest, RadarSetupController } from './radar-setup';
|
||||
import { OptimizePlansDialog, type OptimizePlansDialogState } from './optimize-plans-dialog';
|
||||
import {
|
||||
COLUMN_MAX_CM, canonicalColumnAngle, clampColumnCm, columnBody, floorMinusBodies, geometryArea,
|
||||
partitionBody, pointInPhysicalBody, sameColumnPlacement, type PartitionOpeningCut,
|
||||
@@ -182,7 +183,6 @@ import {
|
||||
spaceModels, iconCqw, gridLevels, CANVAS_LIMIT, SANE_LIMIT, GRID_PITCH, GRID_STEP_N,
|
||||
PLAN_SCALE_MIN, PLAN_SCALE_MAX, clampCanvasR, clampCanvasN, type Rect,
|
||||
} from './space-geometry';
|
||||
import { optimizePlans, type OptimizeReport } from './plan-optimizer';
|
||||
import {
|
||||
WALL_SEGMENT_MODEL_VERSION,
|
||||
adoptWallSegmentModelCandidateInPlace, commitWallSegmentModel,
|
||||
@@ -199,7 +199,7 @@ import {
|
||||
type OptimizeGeometryPreflightResult,
|
||||
} from './plan-geometry-preflight';
|
||||
import {
|
||||
canonicalizeConfigGeometry, canonicalizePosition, formatLatticeShiftCm,
|
||||
canonicalizeConfigGeometry, canonicalizePosition,
|
||||
} from './coordinate-canonicalization';
|
||||
import { enqueueSerializedWrite, type OptimisticAttempt } from './serialized-write-queue';
|
||||
import { applyCalibrationProposal, saveAutomaticCalibration, saveManualCalibration, saveVacuumMatrix,
|
||||
@@ -440,7 +440,7 @@ export interface HouseplanEditorHostPort {
|
||||
_adoptAuthoritative: (input: GatedAdoptionInput) => Promise<GatedAdoptionResult>; // #500: the one adoption entry
|
||||
_rollbackOptimistic: (attempt: OptimisticAttempt<ServerConfig>) => boolean;
|
||||
readonly _adoption: ConfigAdoption; // #500: identity owner; _serverCfg/_cfgRev/_layout/_layoutRev delegate to it
|
||||
_alignDialog: { report: OptimizeReport; config: any; layout: Record<string, any>; preflight: OptimizeGeometryPreflightResult | null; cm: number; where: string; changed: boolean; busy: boolean; removeLiveMissingPositions: boolean; } | null;
|
||||
_alignDialog: OptimizePlansDialogState | null;
|
||||
_alignPoint: number[] | null;
|
||||
_allRoomsFlat: () => { value: string; label: string; }[];
|
||||
_angleField: (value: unknown) => string;
|
||||
@@ -668,7 +668,6 @@ export interface HouseplanEditorHostPort {
|
||||
_pointerModality: PointerModalityController;
|
||||
_pointers: Map<number, { x: number; y: number; }>;
|
||||
_pos: (d: DevItem) => { x: number; y: number; };
|
||||
_preflightClipboardFallback: string | null;
|
||||
_prepareModeTransition: (request: number, from: ModeVisualState, targetMode: HouseplanMode, targetZoom: number, targetCenterX?: number, targetCenterY?: number) => void;
|
||||
_reducedMotion: boolean;
|
||||
_rawPhysicalBodiesR: () => number[][][];
|
||||
@@ -678,7 +677,6 @@ export interface HouseplanEditorHostPort {
|
||||
_renderCardPreview: (spaceScale: number, nameScale: number, labelScale: number) => TemplateResult;
|
||||
_renderCompass: () => TemplateResult;
|
||||
_renderPlanHass: any;
|
||||
_reportedPreflightFingerprint: string | null;
|
||||
_resign: () => void;
|
||||
_resize: ResizeController<
|
||||
ResizePreview, ResizeLiveLabel[], SpaceGeometryState, ResizeWallUnion, ResizeWallArtifact
|
||||
@@ -810,6 +808,8 @@ export class HouseplanEditorRuntime {
|
||||
// к контроллеру оттуда. Остальной класс использует `public _x` — поле
|
||||
// следует той же договорённости, а не заводит обёртку ради одного слова.
|
||||
public readonly _radarSetup: RadarSetupController;
|
||||
/** #642: «Оптимизировать планы» — свой модуль с узким портом, не делегаты карточки. */
|
||||
public readonly optimizePlans: OptimizePlansDialog;
|
||||
public constructor(public readonly host: HouseplanEditorHostPort) {
|
||||
this._decorImages = new DecorImageEditor(host, {
|
||||
decorSnap: (raw, pointerType) => this._decorSnap(raw, pointerType),
|
||||
@@ -836,6 +836,26 @@ export class HouseplanEditorRuntime {
|
||||
},
|
||||
confirmDiscard: () => host._confirmDanger(radarDiscardRequest((key) => host._t(key))),
|
||||
});
|
||||
this.optimizePlans = new OptimizePlansDialog({
|
||||
dialog: () => host._alignDialog,
|
||||
setDialog: (next) => { host._alignDialog = next; },
|
||||
t: (key, vars) => host._t(key, vars),
|
||||
hass: () => host.hass,
|
||||
config: () => host._serverCfg,
|
||||
planReady: () => !!host._norm,
|
||||
layout: () => host._layout,
|
||||
integrationVersion: () => host._haIntegrationVersion,
|
||||
cardVersion: () => displayVersion(CARD_VERSION),
|
||||
requestUpdate: () => host.requestUpdate(),
|
||||
showToast: (message) => host._showToast(message),
|
||||
errorText: (error) => host._errText(error),
|
||||
checkGeometry: (config) => host._checkOptimizeGeometry(config),
|
||||
referenceContext: (removeLive) => this._optimizeReferenceContext(removeLive),
|
||||
showMigrationBlocked: (error) => this._showWallModelMigrationBlocked(error),
|
||||
clearGeometryGesture: () => this._clearGeometryGesture(),
|
||||
commit: (config, layout) => commitPlanOptimization(host, config, layout),
|
||||
reloadAfterConflict: () => Promise.all([host._reloadConfigOnly(true), host._reloadLayoutOnly()]),
|
||||
});
|
||||
host._editorSecondary = new EditorSecondaryController({
|
||||
root: () => host.renderRoot as ShadowRoot,
|
||||
requestUpdate: () => host.requestUpdate(),
|
||||
@@ -8334,7 +8354,7 @@ public _saveSpaceCopy(): Promise<void> {
|
||||
return saveSpaceCopy(this.host, {
|
||||
clearGeometryGesture: () => this._clearGeometryGesture(),
|
||||
optimizeReferenceContext: () => this._optimizeReferenceContext(false),
|
||||
reportPreflightFailure: (result, config) => this._reportPreflightFailure(result, config),
|
||||
reportPreflightFailure: (result, config) => this.optimizePlans.reportPreflightFailure(result, config),
|
||||
saveConfigNow: (attempt) => this._saveConfigNow(attempt),
|
||||
setMode: () => this._setMode('plan'),
|
||||
showWallModelMigrationBlocked: (error) => this._showWallModelMigrationBlocked(error),
|
||||
@@ -8931,67 +8951,6 @@ public _renderSupportDialog(): TemplateResult {
|
||||
</hp-dialog>`;
|
||||
}
|
||||
|
||||
public _preflightDiagnostics(
|
||||
preflight: OptimizeGeometryPreflightResult,
|
||||
candidate: ServerConfig | null,
|
||||
): object {
|
||||
// CODE-REVIEW-295-r1 M1: hash the CANDIDATE spaces the preflight judged,
|
||||
// not the already-saved config — a saved-config hash is exactly what a
|
||||
// space export would reproduce, and the block promises what the export
|
||||
// does not carry.
|
||||
const spacesById = new Map(((candidate as any)?.spaces || [])
|
||||
.map((space) => [String(space?.id || ''), space]));
|
||||
return {
|
||||
kind: 'houseplan-optimize-preflight',
|
||||
origin: 'runtime',
|
||||
cardVersion: displayVersion(CARD_VERSION),
|
||||
checkedAt: new Date().toISOString(),
|
||||
preflightFingerprint: preflight.fingerprint,
|
||||
failures: preflight.failures.map((failure) => ({
|
||||
spaceId: failure.spaceId,
|
||||
displayName: failure.displayName,
|
||||
reason: failure.reason,
|
||||
detail: failure.detail ?? null,
|
||||
spaceGeometryFingerprint: spacesById.has(failure.spaceId)
|
||||
? spacePhysicalGeometryFingerprint(spacesById.get(failure.spaceId))
|
||||
: null,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
public _reportPreflightFailure(
|
||||
preflight: OptimizeGeometryPreflightResult,
|
||||
candidate: ServerConfig | null,
|
||||
): void {
|
||||
if (preflight.ok || preflight.fingerprint === this.host._reportedPreflightFingerprint) return;
|
||||
this.host._reportedPreflightFingerprint = preflight.fingerprint;
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[houseplan] optimize preflight failed', this._preflightDiagnostics(preflight, candidate));
|
||||
}
|
||||
|
||||
public _preflightVersionsDiffer(): boolean {
|
||||
const integration = this.host._haIntegrationVersion;
|
||||
return typeof integration === 'string' && integration.length > 0
|
||||
&& integration !== displayVersion(CARD_VERSION);
|
||||
}
|
||||
|
||||
public async _copyPreflightDiagnostics(): Promise<void> {
|
||||
const preflight = this.host._alignDialog?.preflight;
|
||||
if (!preflight || preflight.ok) return;
|
||||
const text = JSON.stringify(
|
||||
this._preflightDiagnostics(preflight, this.host._alignDialog?.config ?? null), null, 2,
|
||||
);
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
this.host._preflightClipboardFallback = null;
|
||||
this.host._showToast(this.host._t('gs.preflight_copied'));
|
||||
} catch {
|
||||
// Insecure context / embedded webview: surface the block inline so the
|
||||
// owner can select and copy it by hand.
|
||||
this.host._preflightClipboardFallback = text;
|
||||
}
|
||||
}
|
||||
|
||||
public _checkOptimizeGeometryImpl(config: ServerConfig): OptimizeGeometryPreflightResult {
|
||||
return checkOptimizeGeometry(config, {
|
||||
fallbackSpaceName: (index) => this.host._t('gs.align_preflight_space', {
|
||||
@@ -9075,90 +9034,6 @@ public _optimizeReferenceContext(
|
||||
};
|
||||
}
|
||||
|
||||
public _previewAlignDialog(removeLiveMissingPositions: boolean): void {
|
||||
if (!this.host._norm || !this.host._serverCfg) return;
|
||||
const spaces = this.host._serverCfg.spaces || [];
|
||||
let r;
|
||||
try {
|
||||
r = optimizePlans(
|
||||
this.host._serverCfg,
|
||||
this.host._layout || {},
|
||||
this._optimizeReferenceContext(removeLiveMissingPositions),
|
||||
);
|
||||
} catch (error) {
|
||||
this._showWallModelMigrationBlocked(error);
|
||||
return;
|
||||
}
|
||||
const preflight = r.changed ? this.host._checkOptimizeGeometry(r.config) : null;
|
||||
if (preflight) this._reportPreflightFailure(preflight, r.config);
|
||||
// The maximum geometry shift is an UPPER BOUND, not a sample. The run
|
||||
// measured every element in the centimetres of ITS OWN space — converting
|
||||
// one normalised maximum through the first space's `cell_cm` understated
|
||||
// a two-scale plan twentyfold (AUD-158B1-01) — and the last tenth is
|
||||
// rounded UP, so the promise can never be smaller than the deed.
|
||||
const cm = Math.ceil(r.report.maxShiftCm * 10) / 10;
|
||||
const sp = spaces.find((x: any) => x?.id != null && String(x.id) === r.report.maxSpace);
|
||||
const where = spaces.length > 1 && sp ? String(sp.title || sp.id) : '';
|
||||
// CODE-REVIEW-295-r1 M2: the inline clipboard fallback belongs to one
|
||||
// dialog showing — a reopened dialog must not display the previous
|
||||
// refusal's JSON while the visible reasons already describe a new one.
|
||||
this.host._preflightClipboardFallback = null;
|
||||
this.host._alignDialog = {
|
||||
report: r.report, config: r.config, layout: r.layout, cm, where,
|
||||
preflight, changed: r.changed, busy: false, removeLiveMissingPositions,
|
||||
};
|
||||
}
|
||||
|
||||
public _openAlignDialog = (): void => this._previewAlignDialog(false);
|
||||
|
||||
public _toggleOptimizeLivePositions = (): void => {
|
||||
const dialog = this.host._alignDialog;
|
||||
if (!dialog || dialog.busy || !dialog.report.liveMissingPositions.length) return;
|
||||
this._previewAlignDialog(!dialog.removeLiveMissingPositions);
|
||||
};
|
||||
|
||||
public async _runAlignToGrid(): Promise<void> {
|
||||
let d = this.host._alignDialog;
|
||||
if (!d || d.busy || !this.host._serverCfg || !d.changed || !d.preflight?.ok) return;
|
||||
const fingerprint = contentFingerprint(d.config);
|
||||
if (d.preflight.fingerprint !== fingerprint) {
|
||||
const preflight = this.host._checkOptimizeGeometry(d.config);
|
||||
this._reportPreflightFailure(preflight, d.config);
|
||||
d = { ...d, preflight };
|
||||
this.host._alignDialog = d;
|
||||
if (!preflight.ok) return;
|
||||
}
|
||||
this._clearGeometryGesture();
|
||||
this.host._alignDialog = { ...d, busy: true };
|
||||
try {
|
||||
await commitPlanOptimization(this.host, d.config, d.layout);
|
||||
this.host._alignDialog = null;
|
||||
this.host._preflightClipboardFallback = null;
|
||||
this.host._showToast(this.host._t('gs.align_done', {
|
||||
n: String(d.report.moved),
|
||||
m: String(d.report.migrated + d.report.canonicalized
|
||||
+ d.report.coordsCanonicalized + d.report.latticeCoordinatesCanonicalized
|
||||
+ d.report.wallsMerged + d.report.spansMerged
|
||||
+ d.report.partitionsMerged + d.report.partitionsReconciled
|
||||
+ d.report.openingsRehosted + d.report.wallsStraightened),
|
||||
r: String(d.report.spaceRefsRemapped + d.report.roomRefsRemapped
|
||||
+ d.report.positionsRemapped + d.report.markersDetached
|
||||
+ d.report.orphanRoomLabelsRemoved + d.report.orphanDevicePositionsRemoved
|
||||
+ d.report.orphanGroupPositionsRemoved),
|
||||
}));
|
||||
} catch (e: any) {
|
||||
if (this.host._alignDialog) this.host._alignDialog = { ...this.host._alignDialog, busy: false };
|
||||
if (e?.code === 'wall_model_client_outdated') {
|
||||
this.host._showToast(this.host._t('toast.wall_model_client_outdated'));
|
||||
return;
|
||||
}
|
||||
if (e?.code === 'conflict') {
|
||||
await Promise.all([this.host._reloadConfigOnly(true), this.host._reloadLayoutOnly()]);
|
||||
}
|
||||
this.host._showToast(this.host._t('toast.error', { err: this.host._errText(e) }));
|
||||
}
|
||||
}
|
||||
|
||||
public async _undoPlanOptimization(): Promise<void> {
|
||||
if (!this.host._canOptimizeUndo || this.host._optimizeUndoBusy) return;
|
||||
const undoKind = this.host._undoKind;
|
||||
@@ -9689,243 +9564,6 @@ public _rangeInput(
|
||||
?disabled=${disabled} aria-label=${ariaLabel || nothing} @input=${h} />`;
|
||||
}
|
||||
|
||||
public _renderAlignDialog(): TemplateResult {
|
||||
const d = this.host._alignDialog!;
|
||||
const r = d.report;
|
||||
const failed = d.changed && !d.preflight?.ok;
|
||||
const failures = d.preflight?.failures || [];
|
||||
const visibleNames = failures.slice(0, 3).map((failure) => failure.displayName);
|
||||
const spaces = visibleNames.length
|
||||
? visibleNames.join(', ')
|
||||
: this.host._t('gs.align_preflight_space', { n: '1' });
|
||||
const remaining = Math.max(0, failures.length - visibleNames.length);
|
||||
const more = remaining
|
||||
? this.host._t('gs.align_preflight_more', { n: String(remaining) })
|
||||
: '';
|
||||
const repaired = r.spaceRefsRemapped + r.roomRefsRemapped
|
||||
+ r.positionsRemapped + r.markersDetached;
|
||||
const modelMaintenance = r.migrated + r.canonicalized + r.coordsCanonicalized
|
||||
+ r.wallSegmentsMigrated
|
||||
+ r.roomDraftsMigrated + r.roomDraftSegmentsMigrated
|
||||
+ r.wallsMerged + r.spansMerged + r.partitionsMerged
|
||||
+ r.partitionsReconciled + r.openingsRehosted;
|
||||
const gridWarning = r.moved + r.rotated + r.coordsCanonicalized + r.wallsStraightened;
|
||||
const straightenCm = Math.ceil(r.maxStraightenShiftCm * 10) / 10;
|
||||
const straightenSpace = (this.host._serverCfg?.spaces || []).find(
|
||||
(space) => String(space?.id || '') === r.maxStraightenSpace,
|
||||
);
|
||||
const straightenWhere = (this.host._serverCfg?.spaces || []).length > 1 && straightenSpace
|
||||
? String(straightenSpace.title || straightenSpace.id) : '';
|
||||
const removed = r.orphanRoomLabelsRemoved + r.orphanDevicePositionsRemoved
|
||||
+ r.orphanGroupPositionsRemoved;
|
||||
const liveNames = r.liveMissingPositions.map((item) => item.name).filter(Boolean);
|
||||
const visibleLiveNames = liveNames.slice(0, 3).join(', ');
|
||||
const remainingLiveNames = Math.max(0, liveNames.length - 3);
|
||||
const liveNamesText = visibleLiveNames
|
||||
? this.host._t('gs.optimize_live_names', {
|
||||
names: visibleLiveNames,
|
||||
more: remainingLiveNames
|
||||
? this.host._t('gs.optimize_reference_more', { n: String(remainingLiveNames) }) : '',
|
||||
})
|
||||
: '';
|
||||
const registryLimited = r.unverifiedPositions.some(
|
||||
(item) => item.reason === 'registry_unavailable',
|
||||
);
|
||||
const detailStatus = (item: typeof r.removedPositions[number]): string => {
|
||||
if (r.removedPositions.some((removedItem) => removedItem.id === item.id)) {
|
||||
return this.host._t('gs.optimize_detail_removed');
|
||||
}
|
||||
if (r.liveMissingPositions.some((liveItem) => liveItem.id === item.id)) {
|
||||
return this.host._t('gs.optimize_detail_live');
|
||||
}
|
||||
return this.host._t('gs.optimize_detail_unverified');
|
||||
};
|
||||
const detailKind = (kind: typeof r.removedPositions[number]['kind']): string => this.host._t(
|
||||
kind === 'room_label' ? 'gs.optimize_detail_room_label'
|
||||
: kind === 'group' ? 'gs.optimize_detail_group'
|
||||
: kind === 'device' ? 'gs.optimize_detail_device'
|
||||
: 'gs.optimize_detail_unknown',
|
||||
);
|
||||
const referenceDetails = [
|
||||
...r.removedPositions,
|
||||
...r.liveMissingPositions.filter((item) => (
|
||||
!r.removedPositions.some((removedItem) => removedItem.id === item.id)
|
||||
)),
|
||||
...r.unverifiedPositions,
|
||||
];
|
||||
const visibleDetails = referenceDetails.slice(0, 10);
|
||||
const remainingDetails = Math.max(0, referenceDetails.length - visibleDetails.length);
|
||||
return html`<hp-dialog .hass=${this.host.hass} data-kind="settings" .title=${this.host._t('gs.align_title')} icon="mdi:broom"
|
||||
dismiss-on-scrim @hp-close=${() => { this.host._alignDialog = null; this.host._preflightClipboardFallback = null; }}>
|
||||
<div class="body">
|
||||
${failed
|
||||
? html`
|
||||
<p class="alignmsg">${this.host._t('gs.align_preflight_failed', { spaces, more })}</p>
|
||||
${failures.slice(0, 10).map((failure) => html`<p class="alignmsg">
|
||||
${failure.displayName}: ${this.host._t(`gs.preflight_reason_${failure.reason}` as I18nKey)}
|
||||
</p>`)}
|
||||
${failures.length > 10 ? html`<p class="alignmsg">
|
||||
${this.host._t('gs.align_preflight_more', { n: String(failures.length - 10) })}
|
||||
</p>` : nothing}
|
||||
<div class="rhint">${this.host._t('gs.align_preflight_hint')}</div>
|
||||
${this._preflightVersionsDiffer() ? html`
|
||||
<div class="rhint">${this.host._t('gs.preflight_update_hint')}</div>` : nothing}
|
||||
<div class="row">
|
||||
<button class="btn ghost" @click=${() => this._copyPreflightDiagnostics()}>
|
||||
<ha-icon icon="mdi:content-copy"></ha-icon>
|
||||
${this.host._t('gs.preflight_copy')}
|
||||
</button>
|
||||
</div>
|
||||
${this.host._preflightClipboardFallback ? html`<details open>
|
||||
<summary>${this.host._t('gs.preflight_copy')}</summary>
|
||||
<pre style="user-select:text;white-space:pre-wrap">${this.host._preflightClipboardFallback}</pre>
|
||||
</details>` : nothing}`
|
||||
: !d.changed
|
||||
? html`<p class="alignmsg">${this.host._t(
|
||||
r.liveMissingPositions.length || r.unverifiedPositions.length
|
||||
|| r.nestedRefsUnresolved
|
||||
? 'gs.optimize_no_automatic_changes' : 'gs.align_none',
|
||||
)}</p>`
|
||||
: html`
|
||||
${r.moved ? html`<p class="alignmsg">${this.host._t('gs.align_count', {
|
||||
n: String(r.moved), total: String(r.total), cm: String(d.cm),
|
||||
})}</p>` : nothing}
|
||||
${r.latticeCoordinatesCanonicalized ? html`
|
||||
<p class="alignmsg">${this.host._t('gs.optimize_lattice_summary', {
|
||||
n: String(r.latticeCoordinatesCanonicalized),
|
||||
cm: formatLatticeShiftCm(r.latticeMaxShiftCm),
|
||||
})}</p>
|
||||
${r.latticeSpaces.map((space) => html`<p class="alignmsg">${this.host._t(
|
||||
'gs.optimize_lattice_space', {
|
||||
space: space.space,
|
||||
n: String(space.canonicalized),
|
||||
far: String(space.far),
|
||||
},
|
||||
)}</p>`)}
|
||||
` : nothing}
|
||||
${d.where
|
||||
? html`<p class="alignmsg">${this.host._t('gs.align_where', { s: d.where })}</p>`
|
||||
: nothing}
|
||||
${r.rotated
|
||||
? html`<p class="alignmsg">${this.host._t('gs.align_turned', { n: String(r.rotated) })}</p>`
|
||||
: nothing}
|
||||
${r.wallSegmentsMigrated ? html`<p class="alignmsg">${this.host._t(
|
||||
'gs.wall_segments_migrated', { n: String(r.wallSegmentsMigrated) },
|
||||
)}</p>` : nothing}
|
||||
${r.roomDraftsMigrated || r.roomDraftSegmentsMigrated ? html`
|
||||
<p class="alignmsg">${this.host._t('gs.room_drafts_migrated', {
|
||||
drafts: String(r.roomDraftsMigrated),
|
||||
segments: String(r.roomDraftSegmentsMigrated),
|
||||
})}</p>` : nothing}
|
||||
${r.legacyZeroWallsMigrated ? html`<p class="alignmsg">${this.host._t(
|
||||
'gs.zero_walls_migrated', { n: String(r.legacyZeroWallsMigrated) },
|
||||
)}</p>` : nothing}
|
||||
${modelMaintenance ? html`<p class="alignmsg">${this.host._t('gs.optimize_changes', {
|
||||
m: String(r.migrated), c: String(r.canonicalized),
|
||||
p: String(r.coordsCanonicalized), w: String(r.wallsMerged),
|
||||
s: String(r.spansMerged), i: String(r.partitionsMerged),
|
||||
})}</p>` : nothing}
|
||||
${r.partitionsReconciled ? html`<p class="alignmsg">${this.host._t(
|
||||
'gs.optimize_coincident_partitions', { n: String(r.partitionsReconciled) },
|
||||
)}</p>` : nothing}
|
||||
${r.openingsRehosted ? html`<p class="alignmsg">${this.host._t(
|
||||
'gs.optimize_openings_rehosted', { n: String(r.openingsRehosted) },
|
||||
)}</p>` : nothing}
|
||||
${r.wallsStraightened ? html`<p class="alignmsg">${this.host._t(
|
||||
'gs.optimize_walls_straightened', {
|
||||
n: String(r.wallsStraightened), cm: String(straightenCm),
|
||||
},
|
||||
)}</p>` : nothing}
|
||||
${straightenWhere ? html`<p class="alignmsg">${this.host._t(
|
||||
'gs.optimize_walls_straightened_where', { s: straightenWhere },
|
||||
)}</p>` : nothing}
|
||||
${r.glowSpacesMigrated || r.glowRoomsMigrated
|
||||
? html`<p class="alignmsg">${this.host._t('gs.optimize_glow_migration', {
|
||||
spaces: String(r.glowSpacesMigrated),
|
||||
rooms: String(r.glowRoomsMigrated),
|
||||
})}</p>`
|
||||
: nothing}
|
||||
${gridWarning ? html`<div class="rhint">${this.host._t('gs.align_warn')}</div>` : nothing}`}
|
||||
${!failed && r.wallsStraightenSkipped ? html`<p class="rhint">${this.host._t(
|
||||
'gs.optimize_walls_straighten_skipped', {
|
||||
n: String(r.wallsStraightenSkipped),
|
||||
},
|
||||
)}</p>` : nothing}
|
||||
${repaired
|
||||
? html`<p class="alignmsg">${this.host._t('gs.optimize_references', {
|
||||
spaces: String(r.spaceRefsRemapped), rooms: String(r.roomRefsRemapped),
|
||||
positions: String(r.positionsRemapped), detached: String(r.markersDetached),
|
||||
})}</p>`
|
||||
: nothing}
|
||||
${removed
|
||||
? html`<p class="alignmsg">${this.host._t('gs.optimize_orphans_removed', {
|
||||
total: String(removed),
|
||||
rooms: String(r.orphanRoomLabelsRemoved),
|
||||
devices: String(r.orphanDevicePositionsRemoved),
|
||||
groups: String(r.orphanGroupPositionsRemoved),
|
||||
})}</p>`
|
||||
: nothing}
|
||||
${r.liveMissingPositions.length
|
||||
? html`<div class="optimize-live">
|
||||
<p class="alignmsg">${this.host._t(d.removeLiveMissingPositions
|
||||
? 'gs.optimize_live_positions_remove' : 'gs.optimize_live_positions', {
|
||||
n: String(r.liveMissingPositions.length), names: liveNamesText,
|
||||
})}</p>
|
||||
<button class="btn ghost optimize-cleanup" type="button"
|
||||
aria-pressed=${d.removeLiveMissingPositions ? 'true' : 'false'}
|
||||
@click=${() => this._toggleOptimizeLivePositions()} ?disabled=${d.busy}>
|
||||
<ha-icon icon=${d.removeLiveMissingPositions ? 'mdi:undo' : 'mdi:map-marker-remove-outline'}></ha-icon>
|
||||
${this.host._t(d.removeLiveMissingPositions
|
||||
? 'gs.optimize_live_keep' : 'gs.optimize_live_remove')}
|
||||
</button>
|
||||
${d.removeLiveMissingPositions
|
||||
? html`<div class="rhint optimize-selected" role="status">
|
||||
${this.host._t('gs.optimize_live_selected')}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
${r.unverifiedPositions.length
|
||||
? html`<div class="rhint" role="alert">
|
||||
${this.host._t('gs.optimize_unverified', {
|
||||
n: String(r.unverifiedPositions.length),
|
||||
})}
|
||||
${registryLimited ? ` ${this.host._t('gs.optimize_registry_limited')}` : ''}
|
||||
</div>`
|
||||
: nothing}
|
||||
${r.nestedRefsUnresolved
|
||||
? html`<div class="rhint" role="alert">${this.host._t('gs.optimize_vacuum_warning', {
|
||||
n: String(r.nestedRefsUnresolved),
|
||||
})}</div>`
|
||||
: nothing}
|
||||
${referenceDetails.length
|
||||
? html`<details class="optimize-details">
|
||||
<summary>${this.host._t('gs.optimize_details')}</summary>
|
||||
<ul>
|
||||
${visibleDetails.map((item) => html`<li>${this.host._t('gs.optimize_detail_item', {
|
||||
status: detailStatus(item), kind: detailKind(item.kind),
|
||||
id: item.id, space: item.spaceId,
|
||||
})}</li>`)}
|
||||
</ul>
|
||||
${remainingDetails
|
||||
? html`<div class="rhint">${this.host._t('gs.optimize_details_more', {
|
||||
n: String(remainingDetails),
|
||||
})}</div>`
|
||||
: nothing}
|
||||
</details>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="row" slot="footer">
|
||||
<span class="spacer"></span>
|
||||
<button class="btn ghost" data-hp="dialog-cancel" @click=${() => { this.host._alignDialog = null; this.host._preflightClipboardFallback = null; }}>${this.host._t('btn.cancel')}</button>
|
||||
${!d.changed || !d.preflight?.ok ? nothing : html`
|
||||
<button class="btn on" data-hp="dialog-confirm" @click=${() => this._runAlignToGrid()} ?disabled=${d.busy}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>${d.busy ? '…' : this.host._t('gs.align_run')}
|
||||
</button>`}
|
||||
</div>
|
||||
</hp-dialog>`;
|
||||
}
|
||||
|
||||
public _renderSettingsDialog(): TemplateResult { return renderGeneralSettingsDialog.call(this); }
|
||||
|
||||
public _openRulesDialog = (): void => {
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
/**
|
||||
* Диалог «Оптимизировать планы» (Общие настройки → Сетка), #642.
|
||||
*
|
||||
* Первый вынос из монолита по образцу `live-*` и `RadarSetupController`: узкий
|
||||
* порт `OptimizePlansDialogPort` из 18 членов вместо `HouseplanEditorHostPort`
|
||||
* на 350, без делегатов-заглушек в карточке. Модуль импортирует только
|
||||
* редакторский рантайм, поэтому остаётся в его ленивом чанке; карточка берёт
|
||||
* отсюда только тип состояния.
|
||||
*
|
||||
* Сам диалог (`_alignDialog`) остаётся `@state` карточки: харнесс и golden
|
||||
* пишут его напрямую. Сюда переехали два куска состояния, которые карточке
|
||||
* не принадлежали: инлайн-фолбэк буфера обмена и дедуп dev-лога.
|
||||
*/
|
||||
import { html, nothing, type TemplateResult } from 'lit';
|
||||
|
||||
import { formatLatticeShiftCm } from './coordinate-canonicalization';
|
||||
import type { DeviceLayout } from './device-position-history';
|
||||
import type { I18nKey } from './i18n';
|
||||
import {
|
||||
spacePhysicalGeometryFingerprint, type OptimizeGeometryPreflightResult,
|
||||
} from './plan-geometry-preflight';
|
||||
import { optimizePlans, type OptimizeReport } from './plan-optimizer';
|
||||
import type { SpaceReferenceRepairContext } from './space-reference-repair';
|
||||
import type { ServerConfig } from './types';
|
||||
import { contentFingerprint } from './visual-continuity';
|
||||
|
||||
/** Optimization preview plus the exact pair, so commit cannot differ from it. */
|
||||
export type OptimizePlansDialogState = {
|
||||
report: OptimizeReport; config: any; layout: Record<string, any>;
|
||||
preflight: OptimizeGeometryPreflightResult | null;
|
||||
/** the promised maximum, in centimetres, ALREADY rounded up (AUD-158B1-01) */
|
||||
cm: number;
|
||||
/** the space that maximum belongs to, named only when there are several */
|
||||
where: string;
|
||||
changed: boolean;
|
||||
busy: boolean;
|
||||
/** False by default; true only after the secondary preview action. */
|
||||
removeLiveMissingPositions: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Всё, что диалогу нужно от редактора. Порт собирает рантайм в конструкторе;
|
||||
* других зависимостей у модуля нет. Рост порта выше 20 членов — сигнал, что
|
||||
* сюда протекает чужая подсистема (юнит `optimize-plans-dialog.test.mjs`).
|
||||
*/
|
||||
export interface OptimizePlansDialogPort {
|
||||
dialog(): OptimizePlansDialogState | null;
|
||||
setDialog(next: OptimizePlansDialogState | null): void;
|
||||
t(key: I18nKey, vars?: Record<string, string | number>): string;
|
||||
hass(): unknown;
|
||||
config(): ServerConfig | null;
|
||||
/** the normalised plan exists — the preview has something to optimise */
|
||||
planReady(): boolean;
|
||||
layout(): DeviceLayout | null | undefined;
|
||||
integrationVersion(): string | null;
|
||||
/** `displayVersion(CARD_VERSION)` of the runtime; no second copy here */
|
||||
cardVersion(): string;
|
||||
requestUpdate(): void;
|
||||
showToast(message: string): void;
|
||||
errorText(error: unknown): string;
|
||||
checkGeometry(config: ServerConfig): OptimizeGeometryPreflightResult;
|
||||
referenceContext(removeLiveMissingPositions: boolean): SpaceReferenceRepairContext;
|
||||
showMigrationBlocked(error: unknown): void;
|
||||
clearGeometryGesture(): void;
|
||||
commit(config: ServerConfig, layout: DeviceLayout): Promise<void>;
|
||||
/** a write conflict reloads both stores before the error toast */
|
||||
reloadAfterConflict(): Promise<unknown>;
|
||||
}
|
||||
|
||||
export function preflightDiagnostics(
|
||||
preflight: OptimizeGeometryPreflightResult,
|
||||
candidate: ServerConfig | null,
|
||||
{ cardVersion, now }: { cardVersion: string; now: Date },
|
||||
): object {
|
||||
// CODE-REVIEW-295-r1 M1: hash the CANDIDATE spaces the preflight judged,
|
||||
// not the already-saved config — a saved-config hash is exactly what a
|
||||
// space export would reproduce, and the block promises what the export
|
||||
// does not carry.
|
||||
const spacesById = new Map(((candidate as any)?.spaces || []) // any-ok: кандидат приходит из untyped optimizePlans, форма spaces не гарантирована
|
||||
.map((space) => [String(space?.id || ''), space]));
|
||||
return {
|
||||
kind: 'houseplan-optimize-preflight',
|
||||
origin: 'runtime',
|
||||
cardVersion,
|
||||
checkedAt: now.toISOString(),
|
||||
preflightFingerprint: preflight.fingerprint,
|
||||
failures: preflight.failures.map((failure) => ({
|
||||
spaceId: failure.spaceId,
|
||||
displayName: failure.displayName,
|
||||
reason: failure.reason,
|
||||
detail: failure.detail ?? null,
|
||||
spaceGeometryFingerprint: spacesById.has(failure.spaceId)
|
||||
? spacePhysicalGeometryFingerprint(spacesById.get(failure.spaceId))
|
||||
: null,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function preflightVersionsDiffer(integration: string | null, cardVersion: string): boolean {
|
||||
return typeof integration === 'string' && integration.length > 0
|
||||
&& integration !== cardVersion;
|
||||
}
|
||||
|
||||
export class OptimizePlansDialog {
|
||||
/**
|
||||
* CODE-REVIEW-295-r1 M2: the inline clipboard fallback belongs to one
|
||||
* dialog showing. Keyed by the dialog object, a reopened dialog — always a
|
||||
* new object — can never display the previous refusal's JSON, and closing
|
||||
* needs no manual reset.
|
||||
*/
|
||||
private readonly fallbacks = new WeakMap<object, string>();
|
||||
/** #295: dev-log once per distinct failing preflight, not once per render. */
|
||||
private reportedFingerprint: string | null = null;
|
||||
|
||||
public constructor(private readonly port: OptimizePlansDialogPort) {}
|
||||
|
||||
/** #295: diagnostics text shown inline when the clipboard is unavailable. */
|
||||
public get clipboardFallback(): string | null {
|
||||
const dialog = this.port.dialog();
|
||||
return dialog ? this.fallbacks.get(dialog) ?? null : null;
|
||||
}
|
||||
|
||||
private diagnostics(
|
||||
preflight: OptimizeGeometryPreflightResult,
|
||||
candidate: ServerConfig | null,
|
||||
): object {
|
||||
return preflightDiagnostics(preflight, candidate, {
|
||||
cardVersion: this.port.cardVersion(), now: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
public reportPreflightFailure(
|
||||
preflight: OptimizeGeometryPreflightResult,
|
||||
candidate: ServerConfig | null,
|
||||
): void {
|
||||
if (preflight.ok || preflight.fingerprint === this.reportedFingerprint) return;
|
||||
this.reportedFingerprint = preflight.fingerprint;
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[houseplan] optimize preflight failed', this.diagnostics(preflight, candidate));
|
||||
}
|
||||
|
||||
public async copyDiagnostics(): Promise<void> {
|
||||
const dialog = this.port.dialog();
|
||||
const preflight = dialog?.preflight;
|
||||
if (!dialog || !preflight || preflight.ok) return;
|
||||
const text = JSON.stringify(
|
||||
this.diagnostics(preflight, dialog.config ?? null), null, 2,
|
||||
);
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
if (this.fallbacks.delete(dialog)) this.port.requestUpdate();
|
||||
this.port.showToast(this.port.t('gs.preflight_copied'));
|
||||
} catch {
|
||||
// Insecure context / embedded webview: surface the block inline so the
|
||||
// owner can select and copy it by hand.
|
||||
this.fallbacks.set(dialog, text);
|
||||
this.port.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview whole-plan maintenance. Nothing is written here: the pure run
|
||||
* produces both the report and the exact config/layout pair to commit.
|
||||
*/
|
||||
public preview(removeLiveMissingPositions: boolean): void {
|
||||
const config = this.port.config();
|
||||
if (!this.port.planReady() || !config) return;
|
||||
const spaces = config.spaces || [];
|
||||
let r;
|
||||
try {
|
||||
r = optimizePlans(
|
||||
config,
|
||||
this.port.layout() || {},
|
||||
this.port.referenceContext(removeLiveMissingPositions),
|
||||
);
|
||||
} catch (error) {
|
||||
this.port.showMigrationBlocked(error);
|
||||
return;
|
||||
}
|
||||
const preflight = r.changed ? this.port.checkGeometry(r.config) : null;
|
||||
if (preflight) this.reportPreflightFailure(preflight, r.config);
|
||||
// The maximum geometry shift is an UPPER BOUND, not a sample. The run
|
||||
// measured every element in the centimetres of ITS OWN space — converting
|
||||
// one normalised maximum through the first space's `cell_cm` understated
|
||||
// a two-scale plan twentyfold (AUD-158B1-01) — and the last tenth is
|
||||
// rounded UP, so the promise can never be smaller than the deed.
|
||||
const cm = Math.ceil(r.report.maxShiftCm * 10) / 10;
|
||||
const sp = spaces.find((x) => x?.id != null && String(x.id) === r.report.maxSpace);
|
||||
const where = spaces.length > 1 && sp ? String(sp.title || sp.id) : '';
|
||||
// A new dialog object also starts without an inline clipboard fallback.
|
||||
this.port.setDialog({
|
||||
report: r.report, config: r.config, layout: r.layout, cm, where,
|
||||
preflight, changed: r.changed, busy: false, removeLiveMissingPositions,
|
||||
});
|
||||
}
|
||||
|
||||
public open(): void { this.preview(false); }
|
||||
|
||||
public toggleLivePositions(): void {
|
||||
const dialog = this.port.dialog();
|
||||
if (!dialog || dialog.busy || !dialog.report.liveMissingPositions.length) return;
|
||||
this.preview(!dialog.removeLiveMissingPositions);
|
||||
}
|
||||
|
||||
/**
|
||||
* The backend persists an intent before either store changes, then keeps a
|
||||
* one-deep snapshot that remains undoable until the next plan edit.
|
||||
*/
|
||||
public async run(): Promise<void> {
|
||||
let d = this.port.dialog();
|
||||
if (!d || d.busy || !this.port.config() || !d.changed || !d.preflight?.ok) return;
|
||||
const fingerprint = contentFingerprint(d.config);
|
||||
if (d.preflight.fingerprint !== fingerprint) {
|
||||
const preflight = this.port.checkGeometry(d.config);
|
||||
this.reportPreflightFailure(preflight, d.config);
|
||||
d = { ...d, preflight };
|
||||
this.port.setDialog(d);
|
||||
if (!preflight.ok) return;
|
||||
}
|
||||
this.port.clearGeometryGesture();
|
||||
this.port.setDialog({ ...d, busy: true });
|
||||
try {
|
||||
await this.port.commit(d.config, d.layout);
|
||||
this.port.setDialog(null);
|
||||
this.port.showToast(this.port.t('gs.align_done', {
|
||||
n: String(d.report.moved),
|
||||
m: String(d.report.migrated + d.report.canonicalized
|
||||
+ d.report.coordsCanonicalized + d.report.latticeCoordinatesCanonicalized
|
||||
+ d.report.wallsMerged + d.report.spansMerged
|
||||
+ d.report.partitionsMerged + d.report.partitionsReconciled
|
||||
+ d.report.openingsRehosted + d.report.wallsStraightened),
|
||||
r: String(d.report.spaceRefsRemapped + d.report.roomRefsRemapped
|
||||
+ d.report.positionsRemapped + d.report.markersDetached
|
||||
+ d.report.orphanRoomLabelsRemoved + d.report.orphanDevicePositionsRemoved
|
||||
+ d.report.orphanGroupPositionsRemoved),
|
||||
}));
|
||||
} catch (e: any) { // any-ok: ответ WS HA не типизирован, code читается как у всех писателей конфига
|
||||
const current = this.port.dialog();
|
||||
if (current) this.port.setDialog({ ...current, busy: false });
|
||||
if (e?.code === 'wall_model_client_outdated') {
|
||||
this.port.showToast(this.port.t('toast.wall_model_client_outdated'));
|
||||
return;
|
||||
}
|
||||
if (e?.code === 'conflict') {
|
||||
await this.port.reloadAfterConflict();
|
||||
}
|
||||
this.port.showToast(this.port.t('toast.error', { err: this.port.errorText(e) }));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The confirmation separates geometry movement from lossless maintenance,
|
||||
* and promises the one-deep undo before either store is changed.
|
||||
*/
|
||||
render(): TemplateResult {
|
||||
const d = this.port.dialog()!;
|
||||
const r = d.report;
|
||||
const failed = d.changed && !d.preflight?.ok;
|
||||
const failures = d.preflight?.failures || [];
|
||||
const visibleNames = failures.slice(0, 3).map((failure) => failure.displayName);
|
||||
const spaces = visibleNames.length
|
||||
? visibleNames.join(', ')
|
||||
: this.port.t('gs.align_preflight_space', { n: '1' });
|
||||
const remaining = Math.max(0, failures.length - visibleNames.length);
|
||||
const more = remaining
|
||||
? this.port.t('gs.align_preflight_more', { n: String(remaining) })
|
||||
: '';
|
||||
const repaired = r.spaceRefsRemapped + r.roomRefsRemapped
|
||||
+ r.positionsRemapped + r.markersDetached;
|
||||
const modelMaintenance = r.migrated + r.canonicalized + r.coordsCanonicalized
|
||||
+ r.wallSegmentsMigrated
|
||||
+ r.roomDraftsMigrated + r.roomDraftSegmentsMigrated
|
||||
+ r.wallsMerged + r.spansMerged + r.partitionsMerged
|
||||
+ r.partitionsReconciled + r.openingsRehosted;
|
||||
const gridWarning = r.moved + r.rotated + r.coordsCanonicalized + r.wallsStraightened;
|
||||
const straightenCm = Math.ceil(r.maxStraightenShiftCm * 10) / 10;
|
||||
const straightenSpace = (this.port.config()?.spaces || []).find(
|
||||
(space) => String(space?.id || '') === r.maxStraightenSpace,
|
||||
);
|
||||
const straightenWhere = (this.port.config()?.spaces || []).length > 1 && straightenSpace
|
||||
? String(straightenSpace.title || straightenSpace.id) : '';
|
||||
const removed = r.orphanRoomLabelsRemoved + r.orphanDevicePositionsRemoved
|
||||
+ r.orphanGroupPositionsRemoved;
|
||||
const liveNames = r.liveMissingPositions.map((item) => item.name).filter(Boolean);
|
||||
const visibleLiveNames = liveNames.slice(0, 3).join(', ');
|
||||
const remainingLiveNames = Math.max(0, liveNames.length - 3);
|
||||
const liveNamesText = visibleLiveNames
|
||||
? this.port.t('gs.optimize_live_names', {
|
||||
names: visibleLiveNames,
|
||||
more: remainingLiveNames
|
||||
? this.port.t('gs.optimize_reference_more', { n: String(remainingLiveNames) }) : '',
|
||||
})
|
||||
: '';
|
||||
const registryLimited = r.unverifiedPositions.some(
|
||||
(item) => item.reason === 'registry_unavailable',
|
||||
);
|
||||
const detailStatus = (item: typeof r.removedPositions[number]): string => {
|
||||
if (r.removedPositions.some((removedItem) => removedItem.id === item.id)) {
|
||||
return this.port.t('gs.optimize_detail_removed');
|
||||
}
|
||||
if (r.liveMissingPositions.some((liveItem) => liveItem.id === item.id)) {
|
||||
return this.port.t('gs.optimize_detail_live');
|
||||
}
|
||||
return this.port.t('gs.optimize_detail_unverified');
|
||||
};
|
||||
const detailKind = (kind: typeof r.removedPositions[number]['kind']): string => this.port.t(
|
||||
kind === 'room_label' ? 'gs.optimize_detail_room_label'
|
||||
: kind === 'group' ? 'gs.optimize_detail_group'
|
||||
: kind === 'device' ? 'gs.optimize_detail_device'
|
||||
: 'gs.optimize_detail_unknown',
|
||||
);
|
||||
const referenceDetails = [
|
||||
...r.removedPositions,
|
||||
...r.liveMissingPositions.filter((item) => (
|
||||
!r.removedPositions.some((removedItem) => removedItem.id === item.id)
|
||||
)),
|
||||
...r.unverifiedPositions,
|
||||
];
|
||||
const visibleDetails = referenceDetails.slice(0, 10);
|
||||
const remainingDetails = Math.max(0, referenceDetails.length - visibleDetails.length);
|
||||
const fallback = this.clipboardFallback;
|
||||
return html`<hp-dialog .hass=${this.port.hass()} data-kind="settings" .title=${this.port.t('gs.align_title')} icon="mdi:broom"
|
||||
dismiss-on-scrim @hp-close=${() => this.port.setDialog(null)}>
|
||||
<div class="body">
|
||||
${failed
|
||||
? html`
|
||||
<p class="alignmsg">${this.port.t('gs.align_preflight_failed', { spaces, more })}</p>
|
||||
${failures.slice(0, 10).map((failure) => html`<p class="alignmsg">
|
||||
${failure.displayName}: ${this.port.t(`gs.preflight_reason_${failure.reason}` as I18nKey)}
|
||||
</p>`)}
|
||||
${failures.length > 10 ? html`<p class="alignmsg">
|
||||
${this.port.t('gs.align_preflight_more', { n: String(failures.length - 10) })}
|
||||
</p>` : nothing}
|
||||
<div class="rhint">${this.port.t('gs.align_preflight_hint')}</div>
|
||||
${preflightVersionsDiffer(this.port.integrationVersion(), this.port.cardVersion()) ? html`
|
||||
<div class="rhint">${this.port.t('gs.preflight_update_hint')}</div>` : nothing}
|
||||
<div class="row">
|
||||
<button class="btn ghost" @click=${() => this.copyDiagnostics()}>
|
||||
<ha-icon icon="mdi:content-copy"></ha-icon>
|
||||
${this.port.t('gs.preflight_copy')}
|
||||
</button>
|
||||
</div>
|
||||
${fallback ? html`<details open>
|
||||
<summary>${this.port.t('gs.preflight_copy')}</summary>
|
||||
<pre style="user-select:text;white-space:pre-wrap">${fallback}</pre>
|
||||
</details>` : nothing}`
|
||||
: !d.changed
|
||||
? html`<p class="alignmsg">${this.port.t(
|
||||
r.liveMissingPositions.length || r.unverifiedPositions.length
|
||||
|| r.nestedRefsUnresolved
|
||||
? 'gs.optimize_no_automatic_changes' : 'gs.align_none',
|
||||
)}</p>`
|
||||
: html`
|
||||
${r.moved ? html`<p class="alignmsg">${this.port.t('gs.align_count', {
|
||||
n: String(r.moved), total: String(r.total), cm: String(d.cm),
|
||||
})}</p>` : nothing}
|
||||
${r.latticeCoordinatesCanonicalized ? html`
|
||||
<p class="alignmsg">${this.port.t('gs.optimize_lattice_summary', {
|
||||
n: String(r.latticeCoordinatesCanonicalized),
|
||||
cm: formatLatticeShiftCm(r.latticeMaxShiftCm),
|
||||
})}</p>
|
||||
${r.latticeSpaces.map((space) => html`<p class="alignmsg">${this.port.t(
|
||||
'gs.optimize_lattice_space', {
|
||||
space: space.space,
|
||||
n: String(space.canonicalized),
|
||||
far: String(space.far),
|
||||
},
|
||||
)}</p>`)}
|
||||
` : nothing}
|
||||
${d.where
|
||||
? html`<p class="alignmsg">${this.port.t('gs.align_where', { s: d.where })}</p>`
|
||||
: nothing}
|
||||
${r.rotated
|
||||
? html`<p class="alignmsg">${this.port.t('gs.align_turned', { n: String(r.rotated) })}</p>`
|
||||
: nothing}
|
||||
${r.wallSegmentsMigrated ? html`<p class="alignmsg">${this.port.t(
|
||||
'gs.wall_segments_migrated', { n: String(r.wallSegmentsMigrated) },
|
||||
)}</p>` : nothing}
|
||||
${r.roomDraftsMigrated || r.roomDraftSegmentsMigrated ? html`
|
||||
<p class="alignmsg">${this.port.t('gs.room_drafts_migrated', {
|
||||
drafts: String(r.roomDraftsMigrated),
|
||||
segments: String(r.roomDraftSegmentsMigrated),
|
||||
})}</p>` : nothing}
|
||||
${r.legacyZeroWallsMigrated ? html`<p class="alignmsg">${this.port.t(
|
||||
'gs.zero_walls_migrated', { n: String(r.legacyZeroWallsMigrated) },
|
||||
)}</p>` : nothing}
|
||||
${modelMaintenance ? html`<p class="alignmsg">${this.port.t('gs.optimize_changes', {
|
||||
m: String(r.migrated), c: String(r.canonicalized),
|
||||
p: String(r.coordsCanonicalized), w: String(r.wallsMerged),
|
||||
s: String(r.spansMerged), i: String(r.partitionsMerged),
|
||||
})}</p>` : nothing}
|
||||
${r.partitionsReconciled ? html`<p class="alignmsg">${this.port.t(
|
||||
'gs.optimize_coincident_partitions', { n: String(r.partitionsReconciled) },
|
||||
)}</p>` : nothing}
|
||||
${r.openingsRehosted ? html`<p class="alignmsg">${this.port.t(
|
||||
'gs.optimize_openings_rehosted', { n: String(r.openingsRehosted) },
|
||||
)}</p>` : nothing}
|
||||
${r.wallsStraightened ? html`<p class="alignmsg">${this.port.t(
|
||||
'gs.optimize_walls_straightened', {
|
||||
n: String(r.wallsStraightened), cm: String(straightenCm),
|
||||
},
|
||||
)}</p>` : nothing}
|
||||
${straightenWhere ? html`<p class="alignmsg">${this.port.t(
|
||||
'gs.optimize_walls_straightened_where', { s: straightenWhere },
|
||||
)}</p>` : nothing}
|
||||
${r.glowSpacesMigrated || r.glowRoomsMigrated
|
||||
? html`<p class="alignmsg">${this.port.t('gs.optimize_glow_migration', {
|
||||
spaces: String(r.glowSpacesMigrated),
|
||||
rooms: String(r.glowRoomsMigrated),
|
||||
})}</p>`
|
||||
: nothing}
|
||||
${gridWarning ? html`<div class="rhint">${this.port.t('gs.align_warn')}</div>` : nothing}`}
|
||||
${!failed && r.wallsStraightenSkipped ? html`<p class="rhint">${this.port.t(
|
||||
'gs.optimize_walls_straighten_skipped', {
|
||||
n: String(r.wallsStraightenSkipped),
|
||||
},
|
||||
)}</p>` : nothing}
|
||||
${repaired
|
||||
? html`<p class="alignmsg">${this.port.t('gs.optimize_references', {
|
||||
spaces: String(r.spaceRefsRemapped), rooms: String(r.roomRefsRemapped),
|
||||
positions: String(r.positionsRemapped), detached: String(r.markersDetached),
|
||||
})}</p>`
|
||||
: nothing}
|
||||
${removed
|
||||
? html`<p class="alignmsg">${this.port.t('gs.optimize_orphans_removed', {
|
||||
total: String(removed),
|
||||
rooms: String(r.orphanRoomLabelsRemoved),
|
||||
devices: String(r.orphanDevicePositionsRemoved),
|
||||
groups: String(r.orphanGroupPositionsRemoved),
|
||||
})}</p>`
|
||||
: nothing}
|
||||
${r.liveMissingPositions.length
|
||||
? html`<div class="optimize-live">
|
||||
<p class="alignmsg">${this.port.t(d.removeLiveMissingPositions
|
||||
? 'gs.optimize_live_positions_remove' : 'gs.optimize_live_positions', {
|
||||
n: String(r.liveMissingPositions.length), names: liveNamesText,
|
||||
})}</p>
|
||||
<button class="btn ghost optimize-cleanup" type="button"
|
||||
aria-pressed=${d.removeLiveMissingPositions ? 'true' : 'false'}
|
||||
@click=${() => this.toggleLivePositions()} ?disabled=${d.busy}>
|
||||
<ha-icon icon=${d.removeLiveMissingPositions ? 'mdi:undo' : 'mdi:map-marker-remove-outline'}></ha-icon>
|
||||
${this.port.t(d.removeLiveMissingPositions
|
||||
? 'gs.optimize_live_keep' : 'gs.optimize_live_remove')}
|
||||
</button>
|
||||
${d.removeLiveMissingPositions
|
||||
? html`<div class="rhint optimize-selected" role="status">
|
||||
${this.port.t('gs.optimize_live_selected')}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
${r.unverifiedPositions.length
|
||||
? html`<div class="rhint" role="alert">
|
||||
${this.port.t('gs.optimize_unverified', {
|
||||
n: String(r.unverifiedPositions.length),
|
||||
})}
|
||||
${registryLimited ? ` ${this.port.t('gs.optimize_registry_limited')}` : ''}
|
||||
</div>`
|
||||
: nothing}
|
||||
${r.nestedRefsUnresolved
|
||||
? html`<div class="rhint" role="alert">${this.port.t('gs.optimize_vacuum_warning', {
|
||||
n: String(r.nestedRefsUnresolved),
|
||||
})}</div>`
|
||||
: nothing}
|
||||
${referenceDetails.length
|
||||
? html`<details class="optimize-details">
|
||||
<summary>${this.port.t('gs.optimize_details')}</summary>
|
||||
<ul>
|
||||
${visibleDetails.map((item) => html`<li>${this.port.t('gs.optimize_detail_item', {
|
||||
status: detailStatus(item), kind: detailKind(item.kind),
|
||||
id: item.id, space: item.spaceId,
|
||||
})}</li>`)}
|
||||
</ul>
|
||||
${remainingDetails
|
||||
? html`<div class="rhint">${this.port.t('gs.optimize_details_more', {
|
||||
n: String(remainingDetails),
|
||||
})}</div>`
|
||||
: nothing}
|
||||
</details>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="row" slot="footer">
|
||||
<span class="spacer"></span>
|
||||
<button class="btn ghost" data-hp="dialog-cancel" @click=${() => this.port.setDialog(null)}>${this.port.t('btn.cancel')}</button>
|
||||
${!d.changed || !d.preflight?.ok ? nothing : html`
|
||||
<button class="btn on" data-hp="dialog-confirm" @click=${() => this.run()} ?disabled=${d.busy}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>${d.busy ? '…' : this.port.t('gs.align_run')}
|
||||
</button>`}
|
||||
</div>
|
||||
</hp-dialog>`;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,11 @@ const CAPS = {
|
||||
// ни карточка, ни харнесс (реализация живёт в рантайме и зовётся оттуда),
|
||||
// 190 неиспользуемых импортов и 9 мёртвых констант — 784 строки. Потолок
|
||||
// опущен на выигрыш, запас над фактом три строки.
|
||||
'src/houseplan-card.ts': 12951,
|
||||
// 2026-09-24, #642: диалог «Оптимизировать планы» уехал в
|
||||
// src/optimize-plans-dialog.ts — ушли пять делегатов, две стрелки-заглушки,
|
||||
// поле фолбэка буфера обмена, дедуп dev-лога и литерал типа состояния,
|
||||
// 62 строки. Потолок опущен на выигрыш, запас прежний.
|
||||
'src/houseplan-card.ts': 12889,
|
||||
// #478 removed the persisted room-draft editor branch. Keep that reduction.
|
||||
// #485 keeps its large setup surface in editors/radar-section.ts; these are
|
||||
// only the dialog state/save seam and the thin lazy-render adapter.
|
||||
@@ -45,7 +49,10 @@ const CAPS = {
|
||||
// 2026-09-23, #624: снесены мёртвые копии объявлений карточки (warm-boot,
|
||||
// LS_*, GLOW_*, debounce, navigate, lruRead — 31 объявление и 6 типов) и
|
||||
// 196 неиспользуемых импортов — 400 строк. Потолок опущен на выигрыш.
|
||||
'src/houseplan-editor-runtime.ts': 12359,
|
||||
// 2026-09-24, #642: диалог «Оптимизировать планы» (превью, запуск,
|
||||
// диагностика preflight, разметка) уехал в src/optimize-plans-dialog.ts за
|
||||
// узкий порт из 18 членов — 362 строки. Потолок опущен на выигрыш.
|
||||
'src/houseplan-editor-runtime.ts': 11997,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+5
-13
@@ -244,14 +244,8 @@ test('Optimize distinguishes updated spaces from cleaned coordinate noise', () =
|
||||
);
|
||||
assert.equal(en['gs.zero_walls_migrated'], 'Virtual wall spans converted: {n}.');
|
||||
assert.equal(ru['gs.zero_walls_migrated'], 'Преобразовано виртуальных участков: {n}.');
|
||||
assert.match(cardSource, /gs\.zero_walls_migrated/);
|
||||
assert.match(cardSource, /p: String\(r\.coordsCanonicalized\)/);
|
||||
// #229: the independent-wall counter is reported, not silently accumulated
|
||||
assert.match(cardSource, /i: String\(r\.partitionsMerged\)/);
|
||||
assert.match(cardSource, /gs\.optimize_coincident_partitions/);
|
||||
assert.match(cardSource, /gs\.optimize_openings_rehosted/);
|
||||
assert.match(cardSource,
|
||||
/d\.report\.coordsCanonicalized \+ d\.report\.latticeCoordinatesCanonicalized/);
|
||||
// #642: разметка и тост диалога проверяются исполнением —
|
||||
// test/optimize-plans-dialog.test.mjs.
|
||||
});
|
||||
|
||||
test('issue 306 zero-wall failures have dedicated symmetric copy', () => {
|
||||
@@ -293,8 +287,8 @@ test('issue 291 Optimize reports lattice cleanup separately in both languages',
|
||||
ru['gs.optimize_lattice_space'],
|
||||
'{space}: канонизировано значений координат: {n}; оставлено значений вне сетки: {far}.',
|
||||
);
|
||||
assert.match(cardSource, /formatLatticeShiftCm\(r\.latticeMaxShiftCm\)/);
|
||||
assert.match(cardSource, /r\.latticeSpaces\.map/);
|
||||
// #642: вывод сдвига и строк по пространствам — исполнением в
|
||||
// test/optimize-plans-dialog.test.mjs.
|
||||
});
|
||||
|
||||
test('issue 252 Optimize keeps internal ids out of the main orphan report', () => {
|
||||
@@ -306,9 +300,7 @@ test('issue 252 Optimize keeps internal ids out of the main orphan report', () =
|
||||
ru['gs.optimize_orphans_removed'],
|
||||
'Убрано забытых записей: {total} — подписи комнат: {rooms}; устройства: {devices}; групповые метки: {groups}. Все они принадлежали пространствам, удалённым ранее.',
|
||||
);
|
||||
assert.match(cardSource, /gs\.optimize_orphans_removed/);
|
||||
assert.match(cardSource, /<details class="optimize-details">/);
|
||||
assert.doesNotMatch(cardSource, /this\._t\('gs\.optimize_reference_warning'/);
|
||||
// #642: сам отчёт диалога — исполнением в test/optimize-plans-dialog.test.mjs.
|
||||
for (const key of [
|
||||
'gs.optimize_orphans_removed', 'gs.optimize_live_positions',
|
||||
'gs.optimize_unverified', 'gs.optimize_vacuum_warning',
|
||||
|
||||
@@ -79,8 +79,10 @@ test('#550: assertion witnesses have no behavioural oracle hidden in setup', ()
|
||||
test('#550: preflight saved-config mutant targets the editor host and still compiles', () => {
|
||||
const mutant = MUTANTS.find(({ id }) => id === 'preflight-fingerprint-from-saved-config');
|
||||
assert.ok(mutant);
|
||||
assert.equal(mutant.patches[0].file, 'src/houseplan-editor-runtime.ts');
|
||||
assert.match(mutant.patches[0].replace, /this\.host\._serverCfg/);
|
||||
// #642: диалог уехал в свой модуль; сохранённый конфиг там — член узкого
|
||||
// порта, и подмена кандидата им по-прежнему типизирована.
|
||||
assert.equal(mutant.patches[0].file, 'src/optimize-plans-dialog.ts');
|
||||
assert.match(mutant.patches[0].replace, /this\.port\.config\(\)/);
|
||||
});
|
||||
|
||||
test('#486 panel registration, cleanup and read-only protections have mutation witnesses', () => {
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
// #642: диалог «Оптимизировать планы» — исполнением через test-build, а не
|
||||
// текстом монолита. Сюда же переехали 11 утверждений `i18n.test.mjs` о
|
||||
// разметке и тосте диалога (AC3) и гварды восьми мутантов реестра (AC4).
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {
|
||||
OptimizePlansDialog, preflightDiagnostics, preflightVersionsDiffer,
|
||||
} from '../test-build/optimize-plans-dialog.js';
|
||||
import { formatLatticeShiftCm } from '../test-build/coordinate-canonicalization.js';
|
||||
import { spacePhysicalGeometryFingerprint } from '../test-build/plan-geometry-preflight.js';
|
||||
import { contentFingerprint } from '../test-build/visual-continuity.js';
|
||||
|
||||
const MODULE_SOURCE = readFileSync(new URL('../src/optimize-plans-dialog.ts', import.meta.url), 'utf8');
|
||||
const BUILT_SOURCE = readFileSync(new URL('../test-build/optimize-plans-dialog.js', import.meta.url), 'utf8');
|
||||
|
||||
// ---------------------------------------------------------------- fixtures
|
||||
|
||||
const space = (id, title, x) => ({
|
||||
id, title, view_box: [0, 0, 1, 1], cell_cm: 5,
|
||||
rooms: [{ id: `${id}-room`, name: 'R', area: null, poly: [[0.1, 0.1], [x, 0.1], [x, 0.4], [0.1, 0.4]] }],
|
||||
});
|
||||
|
||||
const report = (patch = {}) => ({
|
||||
moved: 0, total: 0, rotated: 0, maxShift: 0, maxShiftCm: 0, maxSpace: '',
|
||||
modelFrom: 10, modelTo: 10, migrated: 0, glowSpacesMigrated: 0, glowRoomsMigrated: 0,
|
||||
canonicalized: 0, wallSegmentsMigrated: 0, roomDraftsMigrated: 0, roomDraftSegmentsMigrated: 0,
|
||||
legacyZeroWallsMigrated: 0, wallsMerged: 0, spansMerged: 0, partitionsMerged: 0,
|
||||
partitionsReconciled: 0, openingsRehosted: 0, wallsStraightened: 0, wallsStraightenSkipped: 0,
|
||||
maxStraightenShiftCm: 0, maxStraightenSpace: '', coordsCanonicalized: 0,
|
||||
latticeCoordinatesCanonicalized: 0, latticeCoordinatesFar: 0, latticeMaxShift: 0,
|
||||
latticeMaxShiftCm: 0, latticeSpaces: [],
|
||||
spaceRefsRemapped: 0, roomRefsRemapped: 0, positionsRemapped: 0, markersDetached: 0,
|
||||
orphanRoomLabelsRemoved: 0, orphanDevicePositionsRemoved: 0, orphanGroupPositionsRemoved: 0,
|
||||
liveMissingPositionsRemoved: 0, nestedRefsUnresolved: 0,
|
||||
removedPositions: [], liveMissingPositions: [], unverifiedPositions: [],
|
||||
...patch,
|
||||
});
|
||||
|
||||
const okPreflight = (config) => ({
|
||||
fingerprint: contentFingerprint(config), spaces: [], failures: [], ok: true,
|
||||
});
|
||||
|
||||
const failedPreflight = (fingerprint = 'fp-1') => ({
|
||||
fingerprint, spaces: [], ok: false,
|
||||
failures: [
|
||||
{ spaceId: 'a', displayName: 'Alpha', status: 'failed', reason: 'wall-exception', detail: 'Error' },
|
||||
{ spaceId: 'b2', displayName: 'Second', status: 'failed', reason: 'floor-null' },
|
||||
],
|
||||
});
|
||||
|
||||
const dialogOf = (patch = {}) => {
|
||||
const config = patch.config ?? { spaces: [space('a', 'Alpha', 0.5)], markers: [], settings: {} };
|
||||
return {
|
||||
report: report(), config, layout: {}, preflight: okPreflight(config),
|
||||
cm: 0, where: '', changed: true, busy: false, removeLiveMissingPositions: false,
|
||||
...patch,
|
||||
};
|
||||
};
|
||||
|
||||
/** Фейковый порт: ровно члены `OptimizePlansDialogPort`, каждый считает вызовы. */
|
||||
function fakePort(overrides = {}) {
|
||||
const calls = {
|
||||
commit: [], checkGeometry: [], toasts: [], requestUpdate: 0, migration: [],
|
||||
gesture: 0, reload: 0, referenceContext: [],
|
||||
};
|
||||
const state = {
|
||||
dialog: null,
|
||||
config: { spaces: [space('a', 'Alpha', 0.5)], markers: [], settings: {} },
|
||||
integration: null,
|
||||
};
|
||||
const port = {
|
||||
dialog: () => state.dialog,
|
||||
setDialog: (next) => { state.dialog = next; },
|
||||
t: (key, vars) => (vars ? `${key}${JSON.stringify(vars)}` : key),
|
||||
hass: () => ({}),
|
||||
config: () => state.config,
|
||||
planReady: () => true,
|
||||
layout: () => ({}),
|
||||
integrationVersion: () => state.integration,
|
||||
cardVersion: () => '9.9.9',
|
||||
requestUpdate: () => { calls.requestUpdate += 1; },
|
||||
showToast: (message) => { calls.toasts.push(message); },
|
||||
errorText: (error) => String(error?.message ?? error),
|
||||
checkGeometry: (config) => { calls.checkGeometry.push(config); return okPreflight(config); },
|
||||
referenceContext: (removeLive) => { calls.referenceContext.push(removeLive); return { removeLiveMissingPositions: removeLive }; },
|
||||
showMigrationBlocked: (error) => { calls.migration.push(error); },
|
||||
clearGeometryGesture: () => { calls.gesture += 1; },
|
||||
commit: async (config, layout) => { calls.commit.push([config, layout]); },
|
||||
reloadAfterConflict: async () => { calls.reload += 1; },
|
||||
...overrides,
|
||||
};
|
||||
return { port, calls, state, dialog: new OptimizePlansDialog(port) };
|
||||
}
|
||||
|
||||
// ------------------------------------------------------ TemplateResult helpers
|
||||
|
||||
const isTemplate = (value) => !!value && typeof value === 'object'
|
||||
&& Array.isArray(value.strings) && Array.isArray(value.values);
|
||||
|
||||
/** Сплющенная разметка: строки шаблона и значения, `nothing` — пусто. */
|
||||
function flatten(value) {
|
||||
if (value == null || value === false || typeof value === 'symbol' || typeof value === 'function') return '';
|
||||
if (Array.isArray(value)) return value.map(flatten).join('');
|
||||
if (isTemplate(value)) {
|
||||
return value.strings.reduce((out, part, index) => (
|
||||
out + part + (index < value.values.length ? flatten(value.values[index]) : '')
|
||||
), '');
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
/** Обработчики `@event=${fn}` вместе с текстом перед ними — чтобы найти кнопку. */
|
||||
function handlers(value, out = []) {
|
||||
if (Array.isArray(value)) { value.forEach((item) => handlers(item, out)); return out; }
|
||||
if (!isTemplate(value)) return out;
|
||||
value.values.forEach((item, index) => {
|
||||
const match = /@([\w-]+)=$/.exec(value.strings[index]);
|
||||
// Текст от начала тега до обработчика: по нему кнопка и находится.
|
||||
const before = value.strings.slice(0, index + 1).join('');
|
||||
const tag = before.slice(before.lastIndexOf('<'));
|
||||
if (match && typeof item === 'function') out.push({ event: match[1], before: tag, fn: item });
|
||||
else handlers(item, out);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
const handlerFor = (template, event, marker) => handlers(template)
|
||||
.find((item) => item.event === event && (!marker || item.before.includes(marker)))?.fn;
|
||||
|
||||
/**
|
||||
* `navigator` в Node — геттер без сеттера, `t.mock.property` его не
|
||||
* восстанавливает. Подменяем дескриптор и возвращаем исходный сами.
|
||||
*/
|
||||
function withClipboard(t, clipboard) {
|
||||
const original = Object.getOwnPropertyDescriptor(globalThis, 'navigator');
|
||||
Object.defineProperty(globalThis, 'navigator', { value: { clipboard }, configurable: true });
|
||||
t.after(() => {
|
||||
if (original) Object.defineProperty(globalThis, 'navigator', original);
|
||||
else delete globalThis.navigator;
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------- pure helpers
|
||||
|
||||
test('#642 preflightDiagnostics: candidate hash, reason, detail, version, fixed clock', () => {
|
||||
const candidate = { spaces: [space('a', 'Alpha', 0.6)] };
|
||||
const now = new Date('2026-09-24T10:00:00.000Z');
|
||||
const block = preflightDiagnostics(failedPreflight(), candidate, { cardVersion: '1.2.3', now });
|
||||
assert.equal(block.kind, 'houseplan-optimize-preflight');
|
||||
assert.equal(block.origin, 'runtime');
|
||||
assert.equal(block.cardVersion, '1.2.3');
|
||||
assert.equal(block.checkedAt, '2026-09-24T10:00:00.000Z');
|
||||
assert.equal(block.preflightFingerprint, 'fp-1');
|
||||
assert.deepEqual(block.failures.map((failure) => [failure.spaceId, failure.reason, failure.detail]), [
|
||||
['a', 'wall-exception', 'Error'], ['b2', 'floor-null', null],
|
||||
]);
|
||||
assert.equal(block.failures[0].spaceGeometryFingerprint, spacePhysicalGeometryFingerprint(candidate.spaces[0]));
|
||||
assert.equal(block.failures[1].spaceGeometryFingerprint, null, 'a space missing from the candidate has no hash');
|
||||
// Смок передаёт кандидата не всегда; блок всё равно собирается.
|
||||
assert.equal(preflightDiagnostics(failedPreflight(), undefined, { cardVersion: 'x', now }).failures[0]
|
||||
.spaceGeometryFingerprint, null);
|
||||
});
|
||||
|
||||
test('#642 preflightVersionsDiffer: only a known, different integration version advises an update', () => {
|
||||
assert.equal(preflightVersionsDiffer(null, '1.0.0'), false);
|
||||
assert.equal(preflightVersionsDiffer('', '1.0.0'), false);
|
||||
assert.equal(preflightVersionsDiffer('1.0.0', '1.0.0'), false);
|
||||
assert.equal(preflightVersionsDiffer('0.0.1-other', '1.0.0'), true);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ preview
|
||||
|
||||
test('#642 preview: a failed migration reports once and opens nothing', () => {
|
||||
const boom = new Error('wall model');
|
||||
const { dialog, calls, state } = fakePort({ referenceContext: () => { throw boom; } });
|
||||
dialog.preview(false);
|
||||
assert.deepEqual(calls.migration, [boom]);
|
||||
assert.equal(state.dialog, null);
|
||||
});
|
||||
|
||||
test('#642 preview: nothing without a plan or a saved config', () => {
|
||||
const noPlan = fakePort({ planReady: () => false });
|
||||
noPlan.dialog.open();
|
||||
assert.equal(noPlan.state.dialog, null);
|
||||
const noConfig = fakePort();
|
||||
noConfig.state.config = null;
|
||||
noConfig.dialog.open();
|
||||
assert.equal(noConfig.state.dialog, null);
|
||||
assert.deepEqual(noConfig.calls.referenceContext, []);
|
||||
});
|
||||
|
||||
test('#642 preview: cm is rounded UP, the space is named only when there are several', () => {
|
||||
const two = fakePort();
|
||||
two.state.config = { spaces: [space('a', 'Alpha', 0.5), space('b', 'Beta', 0.50337)], markers: [], settings: {} };
|
||||
two.dialog.preview(false);
|
||||
const d = two.state.dialog;
|
||||
assert.equal(d.changed, true);
|
||||
assert.equal(d.report.maxSpace, 'b');
|
||||
assert.equal(d.cm, Math.ceil(d.report.maxShiftCm * 10) / 10);
|
||||
assert.ok(d.cm >= d.report.maxShiftCm, 'the promise is never smaller than the deed');
|
||||
assert.equal(d.where, 'Beta');
|
||||
assert.equal(d.busy, false);
|
||||
assert.equal(d.removeLiveMissingPositions, false);
|
||||
assert.deepEqual(two.calls.checkGeometry, [d.config], 'a changed candidate is preflighted');
|
||||
assert.deepEqual(two.calls.referenceContext, [false]);
|
||||
|
||||
const one = fakePort();
|
||||
one.state.config = { spaces: [space('b', 'Beta', 0.50337)], markers: [], settings: {} };
|
||||
one.dialog.preview(false);
|
||||
assert.equal(one.state.dialog.where, '');
|
||||
});
|
||||
|
||||
test('#642 toggleLivePositions re-previews with the flag flipped, only when live positions exist', () => {
|
||||
const { dialog, state, calls } = fakePort();
|
||||
state.dialog = dialogOf();
|
||||
dialog.toggleLivePositions();
|
||||
assert.deepEqual(calls.referenceContext, [], 'no live-missing positions — nothing to toggle');
|
||||
state.dialog = dialogOf({ report: report({ liveMissingPositions: [{ id: 'm', name: 'Lamp', kind: 'device', spaceId: 'a' }] }) });
|
||||
dialog.toggleLivePositions();
|
||||
assert.deepEqual(calls.referenceContext, [true]);
|
||||
assert.equal(state.dialog.removeLiveMissingPositions, true);
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------------- run
|
||||
|
||||
test('#642 open: preview never writes, and cancel closes without a write', async () => {
|
||||
const { dialog, state, calls } = fakePort();
|
||||
state.config = { spaces: [space('a', 'Alpha', 0.5), space('b', 'Beta', 0.50337)], markers: [], settings: {} };
|
||||
dialog.open();
|
||||
await Promise.resolve();
|
||||
assert.ok(state.dialog?.changed && state.dialog.preflight?.ok, 'a writable preview is open');
|
||||
assert.deepEqual(calls.commit, [], 'opening the preview must not persist anything');
|
||||
handlerFor(dialog.render(), 'click', 'data-hp="dialog-cancel"')();
|
||||
assert.equal(state.dialog, null);
|
||||
assert.deepEqual(calls.commit, []);
|
||||
});
|
||||
|
||||
test('#642 run: red preflight is a hard write barrier', async () => {
|
||||
const { dialog, state, calls } = fakePort();
|
||||
state.dialog = dialogOf({ preflight: failedPreflight() });
|
||||
await dialog.run();
|
||||
assert.deepEqual(calls.commit, []);
|
||||
assert.equal(calls.gesture, 0);
|
||||
state.dialog = dialogOf({ changed: false });
|
||||
await dialog.run();
|
||||
state.dialog = dialogOf({ busy: true });
|
||||
await dialog.run();
|
||||
assert.deepEqual(calls.commit, [], 'unchanged or busy dialogs do not write either');
|
||||
});
|
||||
|
||||
test('#642 run: a stale fingerprint is rechecked, and a red recheck stops the write', async () => {
|
||||
const red = failedPreflight('fresh');
|
||||
const { dialog, state, calls } = fakePort({
|
||||
checkGeometry: (config) => { calls.checkGeometry.push(config); return red; },
|
||||
});
|
||||
const warns = [];
|
||||
const original = console.warn;
|
||||
console.warn = (...args) => { warns.push(args); };
|
||||
try {
|
||||
state.dialog = dialogOf({ preflight: { ...okPreflight({}), fingerprint: 'stale' } });
|
||||
const candidate = state.dialog.config;
|
||||
await dialog.run();
|
||||
assert.deepEqual(calls.checkGeometry, [candidate]);
|
||||
assert.equal(state.dialog.preflight, red, 'the dialog now shows the fresh refusal');
|
||||
assert.deepEqual(calls.commit, []);
|
||||
assert.equal(warns.length, 1, 'the fresh refusal is dev-logged');
|
||||
} finally {
|
||||
console.warn = original;
|
||||
}
|
||||
});
|
||||
|
||||
test('#642 run: success writes the exact pair, closes, and toasts counts including lattice cleanup', async () => {
|
||||
const seen = [];
|
||||
const { dialog, state, calls } = fakePort({
|
||||
commit: async (config, layout) => { seen.push(state.dialog?.busy); calls.commit.push([config, layout]); },
|
||||
});
|
||||
state.dialog = dialogOf({ report: report({ moved: 2, latticeCoordinatesCanonicalized: 7, orphanRoomLabelsRemoved: 1 }) });
|
||||
const { config, layout } = state.dialog;
|
||||
await dialog.run();
|
||||
assert.equal(calls.gesture, 1);
|
||||
assert.deepEqual(seen, [true], 'busy while the write is in flight');
|
||||
assert.equal(calls.commit.length, 1);
|
||||
assert.equal(calls.commit[0][0], config);
|
||||
assert.equal(calls.commit[0][1], layout);
|
||||
assert.deepEqual(calls.checkGeometry, [], 'an unchanged fingerprint is not rechecked');
|
||||
assert.equal(state.dialog, null);
|
||||
assert.deepEqual(calls.toasts, ['gs.align_done{"n":"2","m":"7","r":"1"}']);
|
||||
});
|
||||
|
||||
test('#642 run: conflict reloads both stores; outdated client gets its own toast', async () => {
|
||||
const conflict = fakePort({ commit: async () => { throw Object.assign(new Error('c'), { code: 'conflict' }); } });
|
||||
conflict.state.dialog = dialogOf();
|
||||
await conflict.dialog.run();
|
||||
assert.equal(conflict.calls.reload, 1);
|
||||
assert.equal(conflict.state.dialog.busy, false, 'the dialog stays open and usable');
|
||||
assert.deepEqual(conflict.calls.toasts, ['toast.error{"err":"c"}']);
|
||||
|
||||
const outdated = fakePort({ commit: async () => { throw { code: 'wall_model_client_outdated' }; } });
|
||||
outdated.state.dialog = dialogOf();
|
||||
await outdated.dialog.run();
|
||||
assert.equal(outdated.calls.reload, 0);
|
||||
assert.deepEqual(outdated.calls.toasts, ['toast.wall_model_client_outdated']);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------- diagnostics
|
||||
|
||||
test('#642 copyDiagnostics hashes the candidate, not the saved config', async (t) => {
|
||||
const { dialog, state, calls } = fakePort();
|
||||
const candidate = { spaces: [space('a', 'Alpha', 0.6)], markers: [], settings: {} };
|
||||
state.config = { spaces: [space('a', 'Alpha', 0.5)], markers: [], settings: {} };
|
||||
state.dialog = dialogOf({ config: candidate, preflight: failedPreflight() });
|
||||
let copied = null;
|
||||
withClipboard(t, { writeText: async (text) => { copied = text; } });
|
||||
await dialog.copyDiagnostics();
|
||||
const block = JSON.parse(copied);
|
||||
assert.equal(block.failures[0].spaceGeometryFingerprint, spacePhysicalGeometryFingerprint(candidate.spaces[0]));
|
||||
assert.notEqual(block.failures[0].spaceGeometryFingerprint, spacePhysicalGeometryFingerprint(state.config.spaces[0]));
|
||||
assert.equal(block.cardVersion, '9.9.9');
|
||||
assert.deepEqual(calls.toasts, ['gs.preflight_copied']);
|
||||
assert.equal(dialog.clipboardFallback, null);
|
||||
});
|
||||
|
||||
test('#642 copyDiagnostics: the inline fallback belongs to one dialog showing', async (t) => {
|
||||
const { dialog, state, calls } = fakePort();
|
||||
withClipboard(t, { writeText: async () => { throw new Error('denied'); } });
|
||||
state.dialog = dialogOf({ preflight: failedPreflight() });
|
||||
await dialog.copyDiagnostics();
|
||||
assert.match(dialog.clipboardFallback, /houseplan-optimize-preflight/);
|
||||
assert.equal(calls.requestUpdate, 1, 'the fallback is not @state — the module asks for the render');
|
||||
assert.match(flatten(dialog.render()), /<pre[^>]*>\{\n {2}"kind": "houseplan-optimize-preflight"/);
|
||||
// Закрытие настоящим обработчиком hp-close.
|
||||
handlerFor(dialog.render(), 'hp-close')();
|
||||
assert.equal(state.dialog, null);
|
||||
assert.equal(dialog.clipboardFallback, null);
|
||||
// Новый отказ — новый объект диалога — без JSON прошлого отказа.
|
||||
state.dialog = dialogOf({ preflight: failedPreflight('fp-2') });
|
||||
assert.equal(dialog.clipboardFallback, null);
|
||||
assert.doesNotMatch(flatten(dialog.render()), /<pre/);
|
||||
});
|
||||
|
||||
test('#642 copyDiagnostics does nothing for a green or missing preflight', async (t) => {
|
||||
const { dialog, state, calls } = fakePort();
|
||||
let writes = 0;
|
||||
withClipboard(t, { writeText: async () => { writes += 1; } });
|
||||
await dialog.copyDiagnostics();
|
||||
state.dialog = dialogOf();
|
||||
await dialog.copyDiagnostics();
|
||||
assert.equal(writes, 0);
|
||||
assert.deepEqual(calls.toasts, []);
|
||||
});
|
||||
|
||||
test('#642 reportPreflightFailure logs one structured record per distinct fingerprint', () => {
|
||||
const { dialog } = fakePort();
|
||||
const warns = [];
|
||||
const original = console.warn;
|
||||
console.warn = (...args) => { warns.push(args); };
|
||||
try {
|
||||
dialog.reportPreflightFailure(failedPreflight('fp-1'), null);
|
||||
dialog.reportPreflightFailure(failedPreflight('fp-1'), null);
|
||||
dialog.reportPreflightFailure({ ...okPreflight({}), fingerprint: 'fp-ok' }, null);
|
||||
assert.equal(warns.length, 1, 'dedup: the same fingerprint logs once, a green one never');
|
||||
assert.equal(warns[0][0], '[houseplan] optimize preflight failed');
|
||||
assert.equal(warns[0][1].kind, 'houseplan-optimize-preflight');
|
||||
assert.equal(warns[0][1].failures[0].reason, 'wall-exception');
|
||||
dialog.reportPreflightFailure(failedPreflight('fp-2'), null);
|
||||
assert.equal(warns.length, 2);
|
||||
} finally {
|
||||
console.warn = original;
|
||||
}
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ render
|
||||
|
||||
test('#642 render: changes — every counter, lattice lines and orphan report (i18n.test AC3)', () => {
|
||||
const { dialog, state } = fakePort();
|
||||
state.config = { spaces: [space('a', 'Alpha', 0.5), space('b', 'Beta', 0.5)], markers: [], settings: {} };
|
||||
const r = report({
|
||||
moved: 2, total: 9, coordsCanonicalized: 3, partitionsMerged: 5, migrated: 1, canonicalized: 4,
|
||||
wallsMerged: 6, spansMerged: 8, partitionsReconciled: 2, openingsRehosted: 1,
|
||||
legacyZeroWallsMigrated: 1, latticeCoordinatesCanonicalized: 4, latticeMaxShiftCm: 0.0123,
|
||||
latticeSpaces: [{ space: 'Alpha', canonicalized: 4, far: 1 }],
|
||||
orphanRoomLabelsRemoved: 1, orphanDevicePositionsRemoved: 2, orphanGroupPositionsRemoved: 0,
|
||||
removedPositions: [{ id: 'dev1', kind: 'device', spaceId: 'gone' }],
|
||||
wallsStraightened: 1, maxStraightenShiftCm: 1.23, maxStraightenSpace: 'b',
|
||||
});
|
||||
state.dialog = dialogOf({ report: r, cm: 1.5, where: 'Beta' });
|
||||
const text = flatten(dialog.render());
|
||||
assert.match(text, /gs\.align_count\{"n":"2","total":"9","cm":"1.5"\}/);
|
||||
assert.match(text, /gs\.align_where\{"s":"Beta"\}/);
|
||||
assert.match(text, /gs\.zero_walls_migrated\{"n":"1"\}/);
|
||||
// p — шум координат, i — независимые стены (#229): счётчики показываются, а не копятся молча.
|
||||
assert.match(text, /gs\.optimize_changes\{"m":"1","c":"4","p":"3","w":"6","s":"8","i":"5"\}/);
|
||||
assert.match(text, /gs\.optimize_coincident_partitions\{"n":"2"\}/);
|
||||
assert.match(text, /gs\.optimize_openings_rehosted\{"n":"1"\}/);
|
||||
assert.ok(text.includes(`gs.optimize_lattice_summary{"n":"4","cm":"${formatLatticeShiftCm(0.0123)}"}`));
|
||||
assert.match(text, /gs\.optimize_lattice_space\{"space":"Alpha","n":"4","far":"1"\}/);
|
||||
assert.match(text, /gs\.optimize_walls_straightened\{"n":"1","cm":"1.3"\}/);
|
||||
assert.match(text, /gs\.optimize_walls_straightened_where\{"s":"Beta"\}/);
|
||||
assert.match(text, /gs\.optimize_orphans_removed\{"total":"3","rooms":"1","devices":"2","groups":"0"\}/);
|
||||
assert.match(text, /<details class="optimize-details">/);
|
||||
assert.match(text, /gs\.align_warn/);
|
||||
assert.doesNotMatch(text, /gs\.optimize_reference_warning/);
|
||||
assert.match(text, /data-hp="dialog-confirm"/);
|
||||
});
|
||||
|
||||
test('#642 render: failed preflight names each reason and offers no Apply', () => {
|
||||
const { dialog, state } = fakePort();
|
||||
state.dialog = dialogOf({ preflight: failedPreflight() });
|
||||
const text = flatten(dialog.render());
|
||||
assert.match(text, /gs\.align_preflight_failed\{"spaces":"Alpha, Second","more":""\}/);
|
||||
assert.match(text, /Alpha: gs\.preflight_reason_wall-exception/);
|
||||
assert.match(text, /Second: gs\.preflight_reason_floor-null/);
|
||||
assert.match(text, /gs\.preflight_copy/);
|
||||
assert.doesNotMatch(text, /data-hp="dialog-confirm"/, 'the failure state is not a dismissible warning');
|
||||
assert.doesNotMatch(text, /gs\.preflight_update_hint/);
|
||||
state.integration = '0.0.1-other';
|
||||
assert.match(flatten(dialog.render()), /gs\.preflight_update_hint/);
|
||||
});
|
||||
|
||||
test('#642 render: nothing to do — plain «none» or «no automatic changes», never Apply', () => {
|
||||
const { dialog, state } = fakePort();
|
||||
state.dialog = dialogOf({ changed: false, preflight: null });
|
||||
let text = flatten(dialog.render());
|
||||
assert.match(text, /gs\.align_none/);
|
||||
assert.doesNotMatch(text, /data-hp="dialog-confirm"/);
|
||||
state.dialog = dialogOf({ changed: false, preflight: null, report: report({ nestedRefsUnresolved: 2 }) });
|
||||
text = flatten(dialog.render());
|
||||
assert.match(text, /gs\.optimize_no_automatic_changes/);
|
||||
assert.match(text, /gs\.optimize_vacuum_warning\{"n":"2"\}/);
|
||||
});
|
||||
|
||||
test('#642 render: live positions — names, toggle state and the selected hint', () => {
|
||||
const { dialog, state, calls } = fakePort();
|
||||
const live = ['A', 'B', 'C', 'D'].map((name, index) => ({ id: `m${index}`, name, kind: 'device', spaceId: 'a' }));
|
||||
state.dialog = dialogOf({ report: report({ liveMissingPositions: live }) });
|
||||
let text = flatten(dialog.render());
|
||||
assert.ok(text.includes('gs.optimize_live_positions{"n":"4","names":"gs.optimize_live_names{\\"names\\":\\"A, B, C\\",\\"more\\":\\"gs.optimize_reference_more{\\\\\\"n\\\\\\":\\\\\\"1\\\\\\"}\\"}"}'));
|
||||
assert.match(text, /aria-pressed=false/);
|
||||
assert.match(text, /gs\.optimize_live_remove/);
|
||||
assert.doesNotMatch(text, /gs\.optimize_live_selected/);
|
||||
state.dialog = { ...state.dialog, removeLiveMissingPositions: true };
|
||||
text = flatten(dialog.render());
|
||||
assert.match(text, /gs\.optimize_live_positions_remove/);
|
||||
assert.match(text, /aria-pressed=true/);
|
||||
assert.match(text, /gs\.optimize_live_keep/);
|
||||
assert.match(text, /gs\.optimize_live_selected/);
|
||||
handlerFor(dialog.render(), 'click', 'optimize-cleanup')();
|
||||
assert.deepEqual(calls.referenceContext, [false], 'the toggle re-previews with the flag flipped back');
|
||||
});
|
||||
|
||||
test('#642 render: details list at most ten items, statuses by kind, and the remainder', () => {
|
||||
const { dialog, state } = fakePort();
|
||||
const removed = Array.from({ length: 8 }, (_, index) => ({ id: `r${index}`, kind: 'room_label', spaceId: 'x' }));
|
||||
const unverified = Array.from({ length: 4 }, (_, index) => ({
|
||||
id: `u${index}`, kind: index ? 'group' : 'mystery', spaceId: 'y', reason: 'registry_unavailable',
|
||||
}));
|
||||
state.dialog = dialogOf({ report: report({ removedPositions: removed, unverifiedPositions: unverified }) });
|
||||
const text = flatten(dialog.render());
|
||||
assert.equal(text.split('<li>').length - 1, 10);
|
||||
assert.match(text, /gs\.optimize_detail_item\{"status":"gs\.optimize_detail_removed","kind":"gs\.optimize_detail_room_label","id":"r0","space":"x"\}/);
|
||||
assert.match(text, /gs\.optimize_detail_item\{"status":"gs\.optimize_detail_unverified","kind":"gs\.optimize_detail_unknown","id":"u0","space":"y"\}/);
|
||||
assert.match(text, /gs\.optimize_details_more\{"n":"2"\}/);
|
||||
assert.match(text, /gs\.optimize_unverified\{"n":"4"\}/);
|
||||
assert.match(text, /gs\.optimize_registry_limited/);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------ AC2: the port
|
||||
|
||||
const parsedModule = ts.createSourceFile('optimize-plans-dialog.ts', MODULE_SOURCE, ts.ScriptTarget.Latest, true);
|
||||
const portMembers = () => {
|
||||
const port = parsedModule.statements.find((statement) => ts.isInterfaceDeclaration(statement)
|
||||
&& statement.name.text === 'OptimizePlansDialogPort');
|
||||
assert.ok(port, 'the module declares its own port');
|
||||
return port.members.map((member) => member.name.getText(parsedModule)).sort();
|
||||
};
|
||||
|
||||
test('#642 AC2: the dialog port is narrow and the module never reaches the editor host', () => {
|
||||
// Импорты — и типовые тоже: `import type` стирается в сборке, поэтому
|
||||
// разбирается исходник модуля, а собранный файл проверяется отдельно.
|
||||
const specifiers = parsedModule.statements
|
||||
.filter((statement) => ts.isImportDeclaration(statement))
|
||||
.map((statement) => statement.moduleSpecifier.text);
|
||||
const host = /\/houseplan-(?:card|editor-runtime)$/;
|
||||
assert.deepEqual(specifiers.filter((specifier) => host.test(specifier)), []);
|
||||
const identifiers = new Set();
|
||||
const walk = (node) => {
|
||||
if (ts.isIdentifier(node)) identifiers.add(node.text);
|
||||
ts.forEachChild(node, walk);
|
||||
};
|
||||
walk(parsedModule);
|
||||
assert.equal(identifiers.has('HouseplanEditorHostPort'), false, 'the wide editor port is not referenced');
|
||||
assert.equal(identifiers.has('host'), false, 'no `host` field or parameter — only the narrow port');
|
||||
assert.doesNotMatch(BUILT_SOURCE, /houseplan-(?:card|editor-runtime)\.js/);
|
||||
|
||||
const members = portMembers();
|
||||
assert.ok(members.length <= 20, `port has ${members.length} members`);
|
||||
const { port, dialog } = fakePort();
|
||||
assert.ok(Object.keys(port).length <= 20);
|
||||
assert.deepEqual(Object.keys(port).sort(), members, 'the fake port is exactly the declared port');
|
||||
assert.equal('host' in dialog, false);
|
||||
});
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
"src/coordinate-canonicalization.ts",
|
||||
"src/serialized-write-queue.ts",
|
||||
"src/align-grid.ts",
|
||||
"src/plan-optimizer.ts", "src/plan-optimize-write.ts", "src/space-reference-repair.ts", "src/space-deletion.ts", "src/space-copy.ts", "src/space-copy-runtime.ts",
|
||||
"src/plan-optimizer.ts", "src/plan-optimize-write.ts", "src/optimize-plans-dialog.ts", "src/space-reference-repair.ts", "src/space-deletion.ts", "src/space-copy.ts", "src/space-copy-runtime.ts",
|
||||
"src/plan-geometry-preflight.ts", "src/draft-live-preflight.ts", "src/draft-live-commit.ts",
|
||||
"src/furniture.ts", "src/furniture-plan-art.generated.ts", "src/decor-assets.ts",
|
||||
"src/floating-surface.ts", "src/floating-surface-controller.ts", "src/help-behavior.ts", "src/support-feedback.ts",
|
||||
|
||||
Reference in New Issue
Block a user