mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 14:39:22 +00:00
Монолит: мёртвый код снят по noUnusedLocals, связность измеряется шестью числами и гейтом (#624)
Карточка и редакторский рантайм держали ≈380 неиспользуемых импортов, 56 мёртвых объявлений и дублей типов (warm-boot, LS_*, GLOW_*, debounce, navigate, lruRead — копии карточки в рантайме) и 112 приватных членов карточки, которых не читал никто — делегаты `_editorRuntimeOrThrow()._x()`, оставшиеся от выноса #425, и аксессоры glow-состояния. Всё это снято; в 9 других файлах — по одиночной ошибке. Делегаты и поля, которых касаются браузерные смоки (`card._x(...)`), оставлены и посчитаны отдельно. Гейт `npm run lint:unused` (scripts/unused-locals-gate.mjs, в gate:small и Validate после сборки): `tsc --noUnusedLocals` чист, кроме приватных членов карточки из порта HouseplanEditorHostPort / `host.` (portPrivates) и членов, которых зовёт харнесс (harnessPrivates); храповик по шести числам scripts/monolith-metrics.mjs против scripts/monolith-baseline.json — delegates 260→159, portMembers 350, hostRefs 4948, portPrivates 96, harnessPrivates 107, bundleBytes 2 510 141→2 500 387. `npm run inventory` печатает те же числа. Заморозка 54 тестов, читающих монолит как текст (test/monolith-text-anchors.test.mjs); PROCESS.md §2.7 — правило. Логический исходник для контрактных тестов (test/houseplan-source.mjs) дописывает члены рантайма без делегата в карточке — контракт продукта не зависит от наличия заглушки. Потолки ядер и initial gzip опущены на выигрыш (292 000 → 290 400). Бандл пересобран, три копии синхронны. Issue: #624 User-Visible: no
This commit is contained in:
@@ -760,6 +760,11 @@ jobs:
|
||||
run: |
|
||||
node scripts/bundle-tree.mjs dist custom_components/houseplan/frontend
|
||||
npm run bundle:budget
|
||||
# #624: `tsc --noUnusedLocals` чист вне порта/харнесса, шесть чисел
|
||||
# связности монолита не выше scripts/monolith-baseline.json. После
|
||||
# сборки — одно из чисел считает байты dist/.
|
||||
- name: Мёртвый код и связность монолита
|
||||
run: npm run lint:unused
|
||||
# Бандл собирается один раз и уезжает артефактом (#336). Прежде каждая из
|
||||
# пяти браузерных job собирала его заново: `bundle:sync` — это `tsc
|
||||
# --noEmit` плюс rollup, около минуты на job.
|
||||
|
||||
+16
@@ -263,6 +263,22 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
fix-up. Серверный стопор — шаг слияния конвейера сверяет вершину ветки с
|
||||
SHA материала ревью (допустим только собственный doc-коммит публикации
|
||||
поверх) и при расхождении отменяет слияние с возвратом в `S6-in-progress`.
|
||||
- **Контракты по монолиту — исполнением, не regex по тексту (#624).**
|
||||
Новое утверждение о `src/houseplan-card.ts` или
|
||||
`src/houseplan-editor-runtime.ts` доказывается экспортом функции и её
|
||||
вызовом в `test-build`, а не поиском строки в исходнике: текстовый якорь
|
||||
краснеет на переносе метода без единой регрессии, и это делает вынос дороже,
|
||||
чем оставить монолит как есть. Список тестов, читающих монолит как текст,
|
||||
заморожен (`test/monolith-text-anchors.test.mjs`, 54 файла на 23.09.2026) и
|
||||
может только уменьшаться; новое имя в нём — находка ревью, а не запись в
|
||||
список. Связность монолита измеряется шестью числами
|
||||
(`scripts/monolith-metrics.mjs`: делегаты, члены порта, `host.`, приватные
|
||||
члены порта и харнесса, байты `dist/`), база — `scripts/monolith-baseline.json`;
|
||||
гейт `npm run lint:unused` (в `gate:small` и Validate после сборки) красит
|
||||
рост любого из них и любой мёртвый код по `noUnusedLocals` вне порта и
|
||||
харнесса. Снижение фиксируется тем же коммитом
|
||||
(`node scripts/unused-locals-gate.mjs --update`); рост — только с записью в
|
||||
issue задачи и правкой базы в том же коммите.
|
||||
- **Выход:** очередь на пре-релиз либо возврат в «В разработке», не более
|
||||
4 циклов (§4). Второй и последующие циклы разбираются по дельте (§2.10).
|
||||
|
||||
|
||||
@@ -1,109 +1,109 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce",
|
||||
"fingerprint": "69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 291016,
|
||||
"initialViewGzipBytes": 289452,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 292537,
|
||||
"initialPanelGzipBytes": 290976,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2325,
|
||||
"initialPanelOnlyGzipBytes": 2327,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/de-CZpcqQf7.js",
|
||||
"houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/fr-CaLl1Wrq.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C-Lu_oc7.js",
|
||||
"houseplan-assets/iso-scene-render-CReOrPSg.js",
|
||||
"houseplan-assets/live-interaction-runtime-CsFTHrW4.js",
|
||||
"houseplan-assets/pdf-export-CrSsg6Pr.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/de-BS9wHWi6.js",
|
||||
"houseplan-assets/editor-BMZm1YSF.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/fr-BXTCaZ6b.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DeghrMYb.js",
|
||||
"houseplan-assets/iso-scene-render-WErCGfb7.js",
|
||||
"houseplan-assets/live-interaction-runtime-DqZvf8N9.js",
|
||||
"houseplan-assets/pdf-export-CRHFi1dw.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js"
|
||||
],
|
||||
"lazyGzipBytes": 460952,
|
||||
"lazyGzipBytes": 460939,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/editor-BMZm1YSF.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 244437,
|
||||
"lazyEditorGzipBytes": 244421,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 34525,
|
||||
"lazyOnboardingGzipBytes": 34527,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CZpcqQf7.js",
|
||||
"houseplan-assets/fr-CaLl1Wrq.js"
|
||||
"houseplan-assets/de-BS9wHWi6.js",
|
||||
"houseplan-assets/fr-BXTCaZ6b.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 55258,
|
||||
"lazyLocaleGzipBytes": 55262,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/iso-scene-render-CReOrPSg.js"
|
||||
"houseplan-assets/iso-scene-render-WErCGfb7.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 17641,
|
||||
"lazyIsometricGzipBytes": 17645,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 16939,
|
||||
"lazyFurnitureArtGzipBytes": 16937,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/pdf-export-CrSsg6Pr.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/pdf-export-CRHFi1dw.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 126825,
|
||||
"lazyPdfGzipBytes": 126824,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"sha256": "4569fbec1b44637cc46fb92aa3bf985064608b4af5155a38f557d025c3aeecdc",
|
||||
"path": "houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"sha256": "fd9a1e2c1740a2b60c8104f28b4bb4c47b3c28142d665bfae84cb33e453db8a3",
|
||||
"rawBytes": 76524,
|
||||
"gzipBytes": 24572,
|
||||
"gzipBytes": 24574,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CZpcqQf7.js",
|
||||
"sha256": "d9abf54308cb9b642249e3fee8f71162719dc499ba9b4d1b5e16d24aacc0fb3a",
|
||||
"path": "houseplan-assets/de-BS9wHWi6.js",
|
||||
"sha256": "1f92be7e8df667e76658bb886bf4f202f7d9c3ae18d02efdec6d736ce7251ad9",
|
||||
"rawBytes": 92247,
|
||||
"gzipBytes": 27866,
|
||||
"gzipBytes": 27867,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"sha256": "b0880e3b3986e0424f8b0a8ebbf297a2b9ec45c8e1bcf7186b12b83087b1daa9",
|
||||
"path": "houseplan-assets/editor-BMZm1YSF.js",
|
||||
"sha256": "e1d7b66ad6a074ee18734017725b6a0e7073daa9679f73bf1092fec9c496742d",
|
||||
"rawBytes": 3833,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1584,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"sha256": "07463e624837fcaa7695d741feac5d05c1b6f71470b247ff804edce7ad52e79e",
|
||||
"path": "houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"sha256": "c42cd77459cea989cbfb7ff3d4d34ae41571cbc0d04fc79d31eb8f29cd01aa35",
|
||||
"rawBytes": 25600,
|
||||
"gzipBytes": 5056,
|
||||
"isEntry": false,
|
||||
@@ -111,202 +111,202 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-CaLl1Wrq.js",
|
||||
"sha256": "50850549b497303d1465ba42659c0328f3c06be1beca6ab76e977a0d019cf4de",
|
||||
"path": "houseplan-assets/fr-BXTCaZ6b.js",
|
||||
"sha256": "4be52d798269354b3dceb9f143b1589f1476a17a612a97a485d77c928a2a9ea5",
|
||||
"rawBytes": 94579,
|
||||
"gzipBytes": 27392,
|
||||
"gzipBytes": 27395,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"sha256": "281f8e8637ea0e6336b6103856581334da41fb130d48929dfccaa34246abe97a",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"sha256": "746f7d9b34f3ab2391a119d4ff7949ae4c4a029767933c067760946e4b4f2dd3",
|
||||
"rawBytes": 52035,
|
||||
"gzipBytes": 16939,
|
||||
"gzipBytes": 16937,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"sha256": "c70a8eff7f2975152f9236b8dd05800eae665ce17e8fc8529196ea677220bac1",
|
||||
"rawBytes": 1031784,
|
||||
"gzipBytes": 290212,
|
||||
"path": "houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"sha256": "a83510bc7fc6f01f53e237f75705fcb614e0eaa15755137b649990eb4526a93e",
|
||||
"rawBytes": 1022199,
|
||||
"gzipBytes": 288649,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CZpcqQf7.js",
|
||||
"houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"houseplan-assets/fr-CaLl1Wrq.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C-Lu_oc7.js",
|
||||
"houseplan-assets/iso-scene-render-CReOrPSg.js",
|
||||
"houseplan-assets/live-interaction-runtime-CsFTHrW4.js",
|
||||
"houseplan-assets/pdf-export-CrSsg6Pr.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js"
|
||||
"houseplan-assets/de-BS9wHWi6.js",
|
||||
"houseplan-assets/editor-BMZm1YSF.js",
|
||||
"houseplan-assets/fr-BXTCaZ6b.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DeghrMYb.js",
|
||||
"houseplan-assets/iso-scene-render-WErCGfb7.js",
|
||||
"houseplan-assets/live-interaction-runtime-DqZvf8N9.js",
|
||||
"houseplan-assets/pdf-export-CRHFi1dw.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"sha256": "38e4bfa090a4d2cdb1ccb5dae65c76768bd1286e946b3a56466446e1b42cefc9",
|
||||
"rawBytes": 678284,
|
||||
"gzipBytes": 188857,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"sha256": "6e23016bf9d6074118c32546177027da79e28ad8585fc908725e1af1798c3c74",
|
||||
"rawBytes": 678211,
|
||||
"gzipBytes": 188840,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js",
|
||||
"sha256": "ddf7494bfa398197847ba2b96f90bfb39bc325bacbf22835027b7a2379ab87e8",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js",
|
||||
"sha256": "3974377e68797bc186c4472f3c3f3157bedea7b51dcfd55a2728870ffb408730",
|
||||
"rawBytes": 15986,
|
||||
"gzipBytes": 4897,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-C-Lu_oc7.js",
|
||||
"sha256": "097ff6397602f18c1ed69ab50fc58830e43eca4a1e40c99d3c827981aedde33b",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-DeghrMYb.js",
|
||||
"sha256": "d54d3e5de2d468a686858650d0d53bd05244cd9a0b259dd887407fcc5acbdc60",
|
||||
"rawBytes": 10759,
|
||||
"gzipBytes": 3686,
|
||||
"gzipBytes": 3688,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-CReOrPSg.js",
|
||||
"sha256": "1e100c3c5bef97e44c16f201e41907eb028b00a66a3d27b65d1583884c813972",
|
||||
"path": "houseplan-assets/iso-scene-render-WErCGfb7.js",
|
||||
"sha256": "6772d50b89b5bd67a5474015f207ca2f38eca1a28e5ac60eef4fb69664fa5051",
|
||||
"rawBytes": 51983,
|
||||
"gzipBytes": 17641,
|
||||
"gzipBytes": 17645,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-CsFTHrW4.js",
|
||||
"sha256": "5d98b59a161dcf580ab5d85f8414b796e907bfb0ae659dac9bac91eb67101369",
|
||||
"path": "houseplan-assets/live-interaction-runtime-DqZvf8N9.js",
|
||||
"sha256": "73348aa79ee4844660967192325d8e92fc59bb22f58c16257ab6549cc27a866d",
|
||||
"rawBytes": 8216,
|
||||
"gzipBytes": 3207,
|
||||
"gzipBytes": 3209,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-CrSsg6Pr.js",
|
||||
"sha256": "a7f83db027e80ea688a89ad7a09d3be6b3bb2e368f5c18bcc28972ad55aa5551",
|
||||
"path": "houseplan-assets/pdf-export-CRHFi1dw.js",
|
||||
"sha256": "024231a2788e3ced48ee5ebaed5fde4acfef30c0ebcad5a36e8e6ba31b482a63",
|
||||
"rawBytes": 236264,
|
||||
"gzipBytes": 109886,
|
||||
"gzipBytes": 109887,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-BjEKe8V9.js",
|
||||
"sha256": "6d74cfa11a4dc9bc3eb6f247123be6752041af446b2b152b18f6a56690c26854",
|
||||
"path": "houseplan-assets/summary-panel-editor-D2YQTymt.js",
|
||||
"sha256": "4a908021bb1a50ec6260ac5a70fd498354c22c8abd3b354eba06133f74f50f86",
|
||||
"rawBytes": 14862,
|
||||
"gzipBytes": 3423,
|
||||
"gzipBytes": 3427,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js"
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-oJjTO9qs.js",
|
||||
"sha256": "dabe7653b8518e78fcf704d0a2bff1b45aedd02e0bd3d49d3e428752639df8f1",
|
||||
"path": "houseplan-assets/summary-panel-metrics-DSS3_nCF.js",
|
||||
"sha256": "0ab33c934349bc4295eea08a0cd47a241fd2f38fb4762a9617358cb88bacf6b1",
|
||||
"rawBytes": 2611,
|
||||
"gzipBytes": 1306,
|
||||
"gzipBytes": 1308,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js",
|
||||
"sha256": "6d0b9bd9892311c677e5ec8ff0b3c7cc7bb239d92f244d298a8a0fa516dc66e3",
|
||||
"rawBytes": 72733,
|
||||
"gzipBytes": 20175,
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js",
|
||||
"sha256": "e59722f015963f810ff64e8801d475eaec6ac5569198e5ddec46ffa80d34206a",
|
||||
"rawBytes": 72637,
|
||||
"gzipBytes": 20166,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-BjEKe8V9.js",
|
||||
"houseplan-assets/summary-panel-metrics-oJjTO9qs.js"
|
||||
"houseplan-assets/summary-panel-editor-D2YQTymt.js",
|
||||
"houseplan-assets/summary-panel-metrics-DSS3_nCF.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-BXiyBE_x.js",
|
||||
"sha256": "a19c9de73ca251bdd25306bb8f99e87a6032531817239ed7f691167d58158f4e",
|
||||
"path": "houseplan-assets/zigbee-topology-BgCwJt55.js",
|
||||
"sha256": "5e8532d48a653b627e51d6f862426a523759749ffa6d7c7421a6bb5953117dce",
|
||||
"rawBytes": 18962,
|
||||
"gzipBytes": 7430,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js",
|
||||
"sha256": "2e02c0c73b74f669c645555cb756b25bdd382a4d55d6176c4aa6c8923de55b64",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js",
|
||||
"sha256": "da347518ff7635fb30b3eb9928161a479116e7af6b454dfa9d4edae161091b78",
|
||||
"rawBytes": 3942,
|
||||
"gzipBytes": 1765,
|
||||
"gzipBytes": 1764,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "413b9549ebf0f0041ef99b54d18e4624fea808813eea8cf6b29405bdc9855aa9",
|
||||
"sha256": "a7480e327b096cdb1743a7186fa135e102c1144d2fd99ed1c47953529d15c207",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 804,
|
||||
"gzipBytes": 803,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "cc2b73659797ab651bef7cfeb54fcc99b2c6d9d29b904221fb6f40137653e438",
|
||||
"sha256": "6c31e3fc5215353af80614b1dd84a0a6d7c464c0cca59ece499a63c1e35f48aa",
|
||||
"rawBytes": 6165,
|
||||
"gzipBytes": 2325,
|
||||
"gzipBytes": 2327,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";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-BY9itxO5.js";import{c as C,f as M,C as L}from"./form-kit.styles-EA8ftmO7.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";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-74_n9MKq.js";import{c as C,f as M,C as L}from"./form-kit.styles-BgmGBrDY.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
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";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-BY9itxO5.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";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-74_n9MKq.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+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
+52
-52
File diff suppressed because one or more lines are too long
+5
-5
File diff suppressed because one or more lines are too long
+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
+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
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";import{c as a,A as e}from"./houseplan-card-BY9itxO5.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-DLY32mB5.js";import"./form-kit.styles-EA8ftmO7.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";import{c as a,A as e}from"./houseplan-card-74_n9MKq.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-BQYtTnkQ.js";import"./form-kit.styles-BgmGBrDY.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 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";import{ek as e,N as t,a$ as n,L as i,ae as o,ac as r,bT as a,bS as s,a_ as l,aZ as c,b0 as u,el as d,em as f,bV as m,en as g}from"./houseplan-card-74_n9MKq.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),a=i.binding.slice(o+1),s="device"===r?e.registry.devices?.[a]:null,l="entity"===r?e.registry.entities?.[a]:null,c="device"===r?a:l?.device_id,u="device"===r?s?.area_id:l?.area_id||c&&e.registry.devices?.[c]?.area_id,d="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||u||"",f=d&&e.areaToSpace[d]||i.space||e.firstSpaceId;c&&e.registry.devices?.[c]&&e.spaceIds.has(f)&&(!n.devices.has(c)||"entity"===r&&n.liveEntities.has(a))&&t.add(c)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=l(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,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,u=_){try{let d=0;for(const f of c){const c=l.spaces.find(e=>String(e?.id)===f.id);if(!c)continue;const m=e(c,f),g=u(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const s=t(e);if(!s)continue;const l=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||s;v=y(v,a(l,m.physicalBodies)),yield}const _=m.cellCm/o;d+=s(v)*_*_/1e4}return d}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=d(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};
|
||||
-1
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";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-BY9itxO5.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*h(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 p(e,t,n=_){const i=h(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function S(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 b(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,b as summaryEntityValue,S as summarySystemValue,p as totalCleanFloorAreaM2};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";import{n as t,b as n}from"./zigbee-topology-BXiyBE_x.js";import{aM as e}from"./houseplan-card-BY9itxO5.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};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";import{n as t,b as n}from"./zigbee-topology-BgCwJt55.js";import{aM as e}from"./houseplan-card-74_n9MKq.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";try{await import("./houseplan-assets/houseplan-card-BY9itxO5.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";try{await import("./houseplan-assets/houseplan-card-74_n9MKq.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
Vendored
+144
-144
@@ -1,109 +1,109 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce",
|
||||
"fingerprint": "69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 291016,
|
||||
"initialViewGzipBytes": 289452,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 292537,
|
||||
"initialPanelGzipBytes": 290976,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2325,
|
||||
"initialPanelOnlyGzipBytes": 2327,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/de-CZpcqQf7.js",
|
||||
"houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/fr-CaLl1Wrq.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C-Lu_oc7.js",
|
||||
"houseplan-assets/iso-scene-render-CReOrPSg.js",
|
||||
"houseplan-assets/live-interaction-runtime-CsFTHrW4.js",
|
||||
"houseplan-assets/pdf-export-CrSsg6Pr.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/de-BS9wHWi6.js",
|
||||
"houseplan-assets/editor-BMZm1YSF.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/fr-BXTCaZ6b.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DeghrMYb.js",
|
||||
"houseplan-assets/iso-scene-render-WErCGfb7.js",
|
||||
"houseplan-assets/live-interaction-runtime-DqZvf8N9.js",
|
||||
"houseplan-assets/pdf-export-CRHFi1dw.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js"
|
||||
],
|
||||
"lazyGzipBytes": 460952,
|
||||
"lazyGzipBytes": 460939,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/editor-BMZm1YSF.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 244437,
|
||||
"lazyEditorGzipBytes": 244421,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 34525,
|
||||
"lazyOnboardingGzipBytes": 34527,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CZpcqQf7.js",
|
||||
"houseplan-assets/fr-CaLl1Wrq.js"
|
||||
"houseplan-assets/de-BS9wHWi6.js",
|
||||
"houseplan-assets/fr-BXTCaZ6b.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 55258,
|
||||
"lazyLocaleGzipBytes": 55262,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/iso-scene-render-CReOrPSg.js"
|
||||
"houseplan-assets/iso-scene-render-WErCGfb7.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 17641,
|
||||
"lazyIsometricGzipBytes": 17645,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 16939,
|
||||
"lazyFurnitureArtGzipBytes": 16937,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/pdf-export-CrSsg6Pr.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/pdf-export-CRHFi1dw.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 126825,
|
||||
"lazyPdfGzipBytes": 126824,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"sha256": "4569fbec1b44637cc46fb92aa3bf985064608b4af5155a38f557d025c3aeecdc",
|
||||
"path": "houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"sha256": "fd9a1e2c1740a2b60c8104f28b4bb4c47b3c28142d665bfae84cb33e453db8a3",
|
||||
"rawBytes": 76524,
|
||||
"gzipBytes": 24572,
|
||||
"gzipBytes": 24574,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-CZpcqQf7.js",
|
||||
"sha256": "d9abf54308cb9b642249e3fee8f71162719dc499ba9b4d1b5e16d24aacc0fb3a",
|
||||
"path": "houseplan-assets/de-BS9wHWi6.js",
|
||||
"sha256": "1f92be7e8df667e76658bb886bf4f202f7d9c3ae18d02efdec6d736ce7251ad9",
|
||||
"rawBytes": 92247,
|
||||
"gzipBytes": 27866,
|
||||
"gzipBytes": 27867,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"sha256": "b0880e3b3986e0424f8b0a8ebbf297a2b9ec45c8e1bcf7186b12b83087b1daa9",
|
||||
"path": "houseplan-assets/editor-BMZm1YSF.js",
|
||||
"sha256": "e1d7b66ad6a074ee18734017725b6a0e7073daa9679f73bf1092fec9c496742d",
|
||||
"rawBytes": 3833,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1584,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"sha256": "07463e624837fcaa7695d741feac5d05c1b6f71470b247ff804edce7ad52e79e",
|
||||
"path": "houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"sha256": "c42cd77459cea989cbfb7ff3d4d34ae41571cbc0d04fc79d31eb8f29cd01aa35",
|
||||
"rawBytes": 25600,
|
||||
"gzipBytes": 5056,
|
||||
"isEntry": false,
|
||||
@@ -111,202 +111,202 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-CaLl1Wrq.js",
|
||||
"sha256": "50850549b497303d1465ba42659c0328f3c06be1beca6ab76e977a0d019cf4de",
|
||||
"path": "houseplan-assets/fr-BXTCaZ6b.js",
|
||||
"sha256": "4be52d798269354b3dceb9f143b1589f1476a17a612a97a485d77c928a2a9ea5",
|
||||
"rawBytes": 94579,
|
||||
"gzipBytes": 27392,
|
||||
"gzipBytes": 27395,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"sha256": "281f8e8637ea0e6336b6103856581334da41fb130d48929dfccaa34246abe97a",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"sha256": "746f7d9b34f3ab2391a119d4ff7949ae4c4a029767933c067760946e4b4f2dd3",
|
||||
"rawBytes": 52035,
|
||||
"gzipBytes": 16939,
|
||||
"gzipBytes": 16937,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"sha256": "c70a8eff7f2975152f9236b8dd05800eae665ce17e8fc8529196ea677220bac1",
|
||||
"rawBytes": 1031784,
|
||||
"gzipBytes": 290212,
|
||||
"path": "houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"sha256": "a83510bc7fc6f01f53e237f75705fcb614e0eaa15755137b649990eb4526a93e",
|
||||
"rawBytes": 1022199,
|
||||
"gzipBytes": 288649,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CZpcqQf7.js",
|
||||
"houseplan-assets/editor-C3IC2GQJ.js",
|
||||
"houseplan-assets/fr-CaLl1Wrq.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-C-Lu_oc7.js",
|
||||
"houseplan-assets/iso-scene-render-CReOrPSg.js",
|
||||
"houseplan-assets/live-interaction-runtime-CsFTHrW4.js",
|
||||
"houseplan-assets/pdf-export-CrSsg6Pr.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js"
|
||||
"houseplan-assets/de-BS9wHWi6.js",
|
||||
"houseplan-assets/editor-BMZm1YSF.js",
|
||||
"houseplan-assets/fr-BXTCaZ6b.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-DeghrMYb.js",
|
||||
"houseplan-assets/iso-scene-render-WErCGfb7.js",
|
||||
"houseplan-assets/live-interaction-runtime-DqZvf8N9.js",
|
||||
"houseplan-assets/pdf-export-CRHFi1dw.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-Bu7qx-5V.js",
|
||||
"sha256": "38e4bfa090a4d2cdb1ccb5dae65c76768bd1286e946b3a56466446e1b42cefc9",
|
||||
"rawBytes": 678284,
|
||||
"gzipBytes": 188857,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-CGmHUET4.js",
|
||||
"sha256": "6e23016bf9d6074118c32546177027da79e28ad8585fc908725e1af1798c3c74",
|
||||
"rawBytes": 678211,
|
||||
"gzipBytes": 188840,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DgQaWsRq.js",
|
||||
"sha256": "ddf7494bfa398197847ba2b96f90bfb39bc325bacbf22835027b7a2379ab87e8",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-CbQxbxm9.js",
|
||||
"sha256": "3974377e68797bc186c4472f3c3f3157bedea7b51dcfd55a2728870ffb408730",
|
||||
"rawBytes": 15986,
|
||||
"gzipBytes": 4897,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-D9K8tWdi.js",
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/backdrop-pick-DySspgf9.js",
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-C-Lu_oc7.js",
|
||||
"sha256": "097ff6397602f18c1ed69ab50fc58830e43eca4a1e40c99d3c827981aedde33b",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-DeghrMYb.js",
|
||||
"sha256": "d54d3e5de2d468a686858650d0d53bd05244cd9a0b259dd887407fcc5acbdc60",
|
||||
"rawBytes": 10759,
|
||||
"gzipBytes": 3686,
|
||||
"gzipBytes": 3688,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-CReOrPSg.js",
|
||||
"sha256": "1e100c3c5bef97e44c16f201e41907eb028b00a66a3d27b65d1583884c813972",
|
||||
"path": "houseplan-assets/iso-scene-render-WErCGfb7.js",
|
||||
"sha256": "6772d50b89b5bd67a5474015f207ca2f38eca1a28e5ac60eef4fb69664fa5051",
|
||||
"rawBytes": 51983,
|
||||
"gzipBytes": 17641,
|
||||
"gzipBytes": 17645,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-CsFTHrW4.js",
|
||||
"sha256": "5d98b59a161dcf580ab5d85f8414b796e907bfb0ae659dac9bac91eb67101369",
|
||||
"path": "houseplan-assets/live-interaction-runtime-DqZvf8N9.js",
|
||||
"sha256": "73348aa79ee4844660967192325d8e92fc59bb22f58c16257ab6549cc27a866d",
|
||||
"rawBytes": 8216,
|
||||
"gzipBytes": 3207,
|
||||
"gzipBytes": 3209,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-CrSsg6Pr.js",
|
||||
"sha256": "a7f83db027e80ea688a89ad7a09d3be6b3bb2e368f5c18bcc28972ad55aa5551",
|
||||
"path": "houseplan-assets/pdf-export-CRHFi1dw.js",
|
||||
"sha256": "024231a2788e3ced48ee5ebaed5fde4acfef30c0ebcad5a36e8e6ba31b482a63",
|
||||
"rawBytes": 236264,
|
||||
"gzipBytes": 109886,
|
||||
"gzipBytes": 109887,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CMri0Lbe.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-DobaZhEB.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-BjEKe8V9.js",
|
||||
"sha256": "6d74cfa11a4dc9bc3eb6f247123be6752041af446b2b152b18f6a56690c26854",
|
||||
"path": "houseplan-assets/summary-panel-editor-D2YQTymt.js",
|
||||
"sha256": "4a908021bb1a50ec6260ac5a70fd498354c22c8abd3b354eba06133f74f50f86",
|
||||
"rawBytes": 14862,
|
||||
"gzipBytes": 3423,
|
||||
"gzipBytes": 3427,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js"
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-oJjTO9qs.js",
|
||||
"sha256": "dabe7653b8518e78fcf704d0a2bff1b45aedd02e0bd3d49d3e428752639df8f1",
|
||||
"path": "houseplan-assets/summary-panel-metrics-DSS3_nCF.js",
|
||||
"sha256": "0ab33c934349bc4295eea08a0cd47a241fd2f38fb4762a9617358cb88bacf6b1",
|
||||
"rawBytes": 2611,
|
||||
"gzipBytes": 1306,
|
||||
"gzipBytes": 1308,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-DLY32mB5.js",
|
||||
"sha256": "6d0b9bd9892311c677e5ec8ff0b3c7cc7bb239d92f244d298a8a0fa516dc66e3",
|
||||
"rawBytes": 72733,
|
||||
"gzipBytes": 20175,
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-BQYtTnkQ.js",
|
||||
"sha256": "e59722f015963f810ff64e8801d475eaec6ac5569198e5ddec46ffa80d34206a",
|
||||
"rawBytes": 72637,
|
||||
"gzipBytes": 20166,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-EA8ftmO7.js",
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/form-kit.styles-BgmGBrDY.js",
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-BjEKe8V9.js",
|
||||
"houseplan-assets/summary-panel-metrics-oJjTO9qs.js"
|
||||
"houseplan-assets/summary-panel-editor-D2YQTymt.js",
|
||||
"houseplan-assets/summary-panel-metrics-DSS3_nCF.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-BXiyBE_x.js",
|
||||
"sha256": "a19c9de73ca251bdd25306bb8f99e87a6032531817239ed7f691167d58158f4e",
|
||||
"path": "houseplan-assets/zigbee-topology-BgCwJt55.js",
|
||||
"sha256": "5e8532d48a653b627e51d6f862426a523759749ffa6d7c7421a6bb5953117dce",
|
||||
"rawBytes": 18962,
|
||||
"gzipBytes": 7430,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-DNUVRNlJ.js",
|
||||
"sha256": "2e02c0c73b74f669c645555cb756b25bdd382a4d55d6176c4aa6c8923de55b64",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-BMoqqgmU.js",
|
||||
"sha256": "da347518ff7635fb30b3eb9928161a479116e7af6b454dfa9d4edae161091b78",
|
||||
"rawBytes": 3942,
|
||||
"gzipBytes": 1765,
|
||||
"gzipBytes": 1764,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js",
|
||||
"houseplan-assets/zigbee-topology-BXiyBE_x.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js",
|
||||
"houseplan-assets/zigbee-topology-BgCwJt55.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "413b9549ebf0f0041ef99b54d18e4624fea808813eea8cf6b29405bdc9855aa9",
|
||||
"sha256": "a7480e327b096cdb1743a7186fa135e102c1144d2fd99ed1c47953529d15c207",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 804,
|
||||
"gzipBytes": 803,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "cc2b73659797ab651bef7cfeb54fcc99b2c6d9d29b904221fb6f40137653e438",
|
||||
"sha256": "6c31e3fc5215353af80614b1dd84a0a6d7c464c0cca59ece499a63c1e35f48aa",
|
||||
"rawBytes": 6165,
|
||||
"gzipBytes": 2325,
|
||||
"gzipBytes": 2327,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BY9itxO5.js"
|
||||
"houseplan-assets/houseplan-card-74_n9MKq.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";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-BY9itxO5.js";import{c as C,f as M,C as L}from"./form-kit.styles-EA8ftmO7.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";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-74_n9MKq.js";import{c as C,f as M,C as L}from"./form-kit.styles-BgmGBrDY.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
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";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-BY9itxO5.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";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-74_n9MKq.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+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
+52
-52
File diff suppressed because one or more lines are too long
+5
-5
File diff suppressed because one or more lines are too long
+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
+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
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";import{c as a,A as e}from"./houseplan-card-BY9itxO5.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-DLY32mB5.js";import"./form-kit.styles-EA8ftmO7.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";import{c as a,A as e}from"./houseplan-card-74_n9MKq.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-BQYtTnkQ.js";import"./form-kit.styles-BgmGBrDY.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"
|
||||
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";import{ek as e,N as t,a$ as n,L as i,ae as o,ac as r,bT as a,bS as s,a_ as l,aZ as c,b0 as u,el as d,em as f,bV as m,en as g}from"./houseplan-card-74_n9MKq.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),a=i.binding.slice(o+1),s="device"===r?e.registry.devices?.[a]:null,l="entity"===r?e.registry.entities?.[a]:null,c="device"===r?a:l?.device_id,u="device"===r?s?.area_id:l?.area_id||c&&e.registry.devices?.[c]?.area_id,d="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||u||"",f=d&&e.areaToSpace[d]||i.space||e.firstSpaceId;c&&e.registry.devices?.[c]&&e.spaceIds.has(f)&&(!n.devices.has(c)||"entity"===r&&n.liveEntities.has(a))&&t.add(c)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=l(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,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,u=_){try{let d=0;for(const f of c){const c=l.spaces.find(e=>String(e?.id)===f.id);if(!c)continue;const m=e(c,f),g=u(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const s=t(e);if(!s)continue;const l=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||s;v=y(v,a(l,m.physicalBodies)),yield}const _=m.cellCm/o;d+=s(v)*_*_/1e4}return d}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=d(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};
|
||||
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";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-BY9itxO5.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*h(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 p(e,t,n=_){const i=h(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function S(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 b(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,b as summaryEntityValue,S as summarySystemValue,p as totalCleanFloorAreaM2};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";import{n as t,b as n}from"./zigbee-topology-BXiyBE_x.js";import{aM as e}from"./houseplan-card-BY9itxO5.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};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";import{n as t,b as n}from"./zigbee-topology-BgCwJt55.js";import{aM as e}from"./houseplan-card-74_n9MKq.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b9821d608f436104f6f390a7d9ca2821c0d3743a8ac9090e8606cdf9ed6a24ce";try{await import("./houseplan-assets/houseplan-card-BY9itxO5.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__="69203951e99e561d579810ce44a7a4baf1278315bb3708aedac1667a87eb8d97";try{await import("./houseplan-assets/houseplan-card-74_n9MKq.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
@@ -14,6 +14,7 @@
|
||||
"gate:small": "node scripts/gate-small.mjs",
|
||||
"toolchain:check": "node scripts/toolchain-pins.mjs --check",
|
||||
"bundle:budget": "node scripts/bundle-budget.mjs",
|
||||
"lint:unused": "node scripts/unused-locals-gate.mjs",
|
||||
"invariants": "tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node scripts/model-invariants.mjs",
|
||||
"docs:capture": "node scripts/assert-capture-env.mjs docs && npm run build && node demo/docs/capture.mjs",
|
||||
"docs:accept": "node scripts/docs-accept.mjs",
|
||||
|
||||
@@ -327,8 +327,14 @@ export const LOW_HEADROOM_WARNING_BYTES = 15_000;
|
||||
* повторном открытии диалог теряет единую dirty-семантику до загрузки
|
||||
* редактора. Факт оставляет 959 Б сверху и 1 041 Б до нижней границы;
|
||||
* общий бюджет не меняется.
|
||||
*
|
||||
* 2026-09-23, #624: 292 000 → 290 400 (замер 289 448). Из карточки сняты
|
||||
* мёртвые делегаты и поля, из обоих ядер — мёртвые копии объявлений и
|
||||
* неиспользуемые импорты; rollup выбрасывал их и раньше, но не всё: минус
|
||||
* 1 593 Б gzip в стартовом графе. Факт оставляет 952 Б сверху и 1 048 Б до
|
||||
* нижней границы; общий бюджет не меняется.
|
||||
*/
|
||||
export const INITIAL_VIEW_GZIP_CEILING = 292_000;
|
||||
export const INITIAL_VIEW_GZIP_CEILING = 290_400;
|
||||
export const INITIAL_VIEW_CEILING_BAND = 2_000;
|
||||
|
||||
/**
|
||||
|
||||
@@ -321,9 +321,10 @@ export const CHECKS = {
|
||||
roots: ['docs/**', 'README.md', 'README.ru.md', 'PROCESS.md', 'AGENTS.md', 'CONTRIBUTING.md', ...WORKFLOW],
|
||||
},
|
||||
frontend: {
|
||||
// npm run typecheck, npm test, npm run build, bundle:budget
|
||||
entries: ['test/*.test.mjs', 'scripts/no-new-any.mjs', 'scripts/bundle-budget.mjs', 'scripts/fix-test-build.mjs'],
|
||||
roots: [...BUILD_INPUTS, 'test/**', 'tsconfig*.json', ...WORKFLOW],
|
||||
// npm run typecheck, npm test, npm run build, bundle:budget, lint:unused (#624)
|
||||
entries: ['test/*.test.mjs', 'scripts/no-new-any.mjs', 'scripts/bundle-budget.mjs', 'scripts/fix-test-build.mjs',
|
||||
'scripts/unused-locals-gate.mjs'],
|
||||
roots: [...BUILD_INPUTS, 'test/**', 'tsconfig*.json', 'scripts/monolith-baseline.json', 'demo/smoke_*.mjs', 'demo/benchmark_*.mjs', 'demo/guard/*.mjs', ...WORKFLOW],
|
||||
},
|
||||
changed_mutants: {
|
||||
entries: ['scripts/mutation-*.mjs', 'scripts/*-guard.mjs', 'test/*.test.mjs', 'demo/smoke_*.mjs', 'tests_backend/**/*.py'],
|
||||
|
||||
@@ -73,6 +73,9 @@ export function postBuildSteps() {
|
||||
{ name: 'юниты (npm test)', cmd: npm, args: ['test'] },
|
||||
{ name: 'копии бандла совпадают (bundle-tree)', cmd: process.execPath, args: ['scripts/bundle-tree.mjs', 'dist', 'custom_components/houseplan/frontend'], hint: 'npm run bundle:sync' },
|
||||
{ name: 'бюджет бандла', cmd: npm, args: ['run', 'bundle:budget'] },
|
||||
// #624: мёртвый код по noUnusedLocals и храповик связности монолита; после
|
||||
// сборки, потому что одно из чисел — размер dist/.
|
||||
{ name: 'мёртвый код и связность монолита (lint:unused)', cmd: process.execPath, args: ['scripts/unused-locals-gate.mjs'], hint: 'node scripts/unused-locals-gate.mjs --update при осознанном снижении' },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// underestimates the coverage that exists (review R5-2). The counts live here
|
||||
// now, one command away, and STATUS.md describes the layers instead.
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { METRIC_NAMES, collectMetrics, readBaseline } from './monolith-metrics.mjs';
|
||||
|
||||
const count = (dir, match, re) =>
|
||||
readdirSync(dir)
|
||||
@@ -25,3 +26,17 @@ const rows = [
|
||||
];
|
||||
const w = Math.max(...rows.map(([n]) => n.length));
|
||||
for (const [name, n] of rows) console.log(`${name.padEnd(w)} ${n}`);
|
||||
|
||||
// #624: связность монолита — те же шесть чисел и тот же модуль, что у гейта
|
||||
// `npm run lint:unused` («одно число — один источник»); рядом — база, чтобы
|
||||
// движение было видно без git blame. bundleBytes есть только после сборки.
|
||||
const { metrics } = collectMetrics(process.cwd());
|
||||
const baseline = readBaseline(process.cwd()) || {};
|
||||
console.log('\nmonolith (scripts/monolith-metrics.mjs; база scripts/monolith-baseline.json)');
|
||||
const mw = Math.max(...METRIC_NAMES.map((n) => n.length));
|
||||
for (const name of METRIC_NAMES) {
|
||||
const now = metrics[name];
|
||||
const base = baseline[name];
|
||||
const delta = now != null && base != null && now !== base ? ` (${now > base ? '+' : ''}${now - base} к базе ${base})` : '';
|
||||
console.log(`${name.padEnd(mw)} ${now ?? 'n/a — npm run build'}${delta}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"delegates": 159,
|
||||
"portMembers": 350,
|
||||
"hostRefs": 4948,
|
||||
"portPrivates": 96,
|
||||
"harnessPrivates": 107,
|
||||
"bundleBytes": 2500387
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Метрики связности монолита и мёртвый код в нём (#624).
|
||||
*
|
||||
* После выноса редакторского рантайма (#425) карточка и рантайм вместе стали
|
||||
* длиннее на 22 %: карточка держит сотни однострочных делегатов
|
||||
* `return this._editorRuntimeOrThrow()._x(...)`, рантайм читает её приватные
|
||||
* поля через порт `HouseplanEditorHostPort` из 350+ членов, а `noUnusedLocals`
|
||||
* был выключен — компилятор молчал о ≈380 неиспользуемых импортах и ≈60
|
||||
* мёртвых объявлениях. Этот модуль — единственный источник шести чисел, по
|
||||
* которым связность измеряется («одно число — один источник», PROCESS.md):
|
||||
* их читают и гейт `scripts/unused-locals-gate.mjs`, и `npm run inventory`.
|
||||
*
|
||||
* delegates методов карточки, тело которых — один вызов рантайма
|
||||
* portMembers членов интерфейса HouseplanEditorHostPort
|
||||
* hostRefs обращений `host.` в src/** вне карточки
|
||||
* portPrivates приватных членов карточки, которых карточка не читает,
|
||||
* но читает рантайм или другой модуль через порт
|
||||
* harnessPrivates приватных членов карточки, которых не читает никто в
|
||||
* продукте, но зовут браузерные смоки (`card._x(...)`):
|
||||
* мёртвый продуктовый код на службе харнесса
|
||||
* bundleBytes байт в dist/ после сборки — сумма всех файлов, потому что
|
||||
* `dist/houseplan-card.js` — загрузчик на 1 КБ, а код лежит
|
||||
* в content-hashed чанках `dist/houseplan-assets/`; судится
|
||||
* с полосой BUNDLE_BYTES_BAND, остальные — точно
|
||||
*
|
||||
* Приватный член карточки, который TypeScript считает непрочитанным, — не
|
||||
* обязательно мёртвый: рантайм зовёт его как `host._x`. Поэтому гейт не
|
||||
* включает `noUnusedLocals` в tsconfig (флаг нельзя ограничить «кроме членов
|
||||
* порта», а объявить 300 членов публичными ради флага — спрятать метрику), а
|
||||
* разбирает диагностики сам: разрешены поимённо только члены порта и члены,
|
||||
* которых касается харнесс, и оба множества считаются.
|
||||
*/
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
||||
import { join, relative, resolve } from 'node:path';
|
||||
import ts from 'typescript';
|
||||
|
||||
export const CARD_FILE = 'src/houseplan-card.ts';
|
||||
export const RUNTIME_FILE = 'src/houseplan-editor-runtime.ts';
|
||||
export const PORT_INTERFACE = 'HouseplanEditorHostPort';
|
||||
export const RUNTIME_ACCESSOR = '_editorRuntimeOrThrow';
|
||||
export const BASELINE_FILE = 'scripts/monolith-baseline.json';
|
||||
export const METRIC_NAMES = ['delegates', 'portMembers', 'hostRefs', 'portPrivates', 'harnessPrivates', 'bundleBytes'];
|
||||
|
||||
/** Диагностики «объявлено, но не используется». */
|
||||
export const UNUSED_CODES = new Set([6133, 6138, 6192, 6196, 6198, 6199]);
|
||||
|
||||
const parse = (name, text) => ts.createSourceFile(name, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
||||
|
||||
const classNamed = (file, name) => file.statements.find(
|
||||
(statement) => ts.isClassDeclaration(statement) && statement.name?.text === name,
|
||||
);
|
||||
|
||||
/**
|
||||
* Делегат: метод/аксессор карточки, тело которого — ровно одно выражение
|
||||
* `this._editorRuntimeOrThrow().<член>…` (с `return` или без, `async` или нет).
|
||||
*/
|
||||
export function isDelegateBody(body, sourceFile) {
|
||||
if (!body || !ts.isBlock(body) || body.statements.length !== 1) return false;
|
||||
const [statement] = body.statements;
|
||||
let expression = null;
|
||||
if (ts.isReturnStatement(statement)) expression = statement.expression ?? null;
|
||||
else if (ts.isExpressionStatement(statement)) expression = statement.expression;
|
||||
if (!expression) return false;
|
||||
if (ts.isAwaitExpression(expression)) expression = expression.expression;
|
||||
// this._editorRuntimeOrThrow()._x(...) | this._editorRuntimeOrThrow()._x
|
||||
let node = expression;
|
||||
while (ts.isCallExpression(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)
|
||||
|| ts.isNonNullExpression(node) || ts.isAsExpression(node)) {
|
||||
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)
|
||||
&& node.expression.name.text === RUNTIME_ACCESSOR && node.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
||||
return node !== expression; // сам вызов аксессора без обращения к члену — не делегат
|
||||
}
|
||||
node = ts.isCallExpression(node) ? node.expression : node.expression;
|
||||
}
|
||||
void sourceFile;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Число делегатов в классе HouseplanCard. */
|
||||
export function countDelegates(cardText) {
|
||||
const file = parse(CARD_FILE, cardText);
|
||||
const card = classNamed(file, 'HouseplanCard');
|
||||
if (!card) throw new Error(`${CARD_FILE}: класс HouseplanCard не найден`);
|
||||
let count = 0;
|
||||
for (const member of card.members) {
|
||||
if (ts.isMethodDeclaration(member) || ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) {
|
||||
if (isDelegateBody(member.body, file)) count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Имена членов интерфейса HouseplanEditorHostPort. */
|
||||
export function portMemberNames(runtimeText) {
|
||||
const file = parse(RUNTIME_FILE, runtimeText);
|
||||
const port = file.statements.find(
|
||||
(statement) => ts.isInterfaceDeclaration(statement) && statement.name.text === PORT_INTERFACE,
|
||||
);
|
||||
if (!port) throw new Error(`${RUNTIME_FILE}: интерфейс ${PORT_INTERFACE} не найден`);
|
||||
const names = new Set();
|
||||
for (const member of port.members) {
|
||||
const name = member.name?.getText(file);
|
||||
if (name) names.add(name.replace(/^['"]|['"]$/g, ''));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/** Обращения `host.<имя>` в исходниках вне карточки: общее число и имена. */
|
||||
export function hostReferences(sourcesByPath) {
|
||||
let count = 0;
|
||||
const names = new Set();
|
||||
for (const [path, text] of Object.entries(sourcesByPath)) {
|
||||
if (path.replaceAll('\\', '/').endsWith(CARD_FILE)) continue;
|
||||
for (const match of text.matchAll(/(?<![\w$])host\.([A-Za-z_$][\w$]*)/g)) {
|
||||
count += 1;
|
||||
names.add(match[1]);
|
||||
}
|
||||
}
|
||||
return { count, names };
|
||||
}
|
||||
|
||||
/** Имена, которых браузерные смоки касаются как свойств: `card._x(`, `c._x =`, `['_x']`. */
|
||||
export function harnessMemberNames(demoSourcesByPath) {
|
||||
const names = new Set();
|
||||
for (const text of Object.values(demoSourcesByPath)) {
|
||||
for (const match of text.matchAll(/(?:\.|\[['"])(_[A-Za-z][\w$]*)(?![\w$])/g)) names.add(match[1]);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбор диагностик компилятора с `noUnusedLocals`. Каждая — либо
|
||||
* `allowed` (приватный член карточки из порта или харнесса), либо нарушение.
|
||||
*/
|
||||
export function classifyUnused(diagnostics, { cardText, portNames, hostNames, harnessNames }) {
|
||||
const cardLines = cardText.split('\n');
|
||||
const allowed = { port: [], harness: [] };
|
||||
const violations = [];
|
||||
for (const d of diagnostics) {
|
||||
const name = /'([^']+)'/.exec(d.message)?.[1] ?? '';
|
||||
const isCard = d.file.replaceAll('\\', '/').endsWith(CARD_FILE);
|
||||
const lineText = isCard ? (cardLines[d.line - 1] ?? '') : '';
|
||||
const isPrivateMember = /^\s+(?:@\w+(?:\([^)]*\))?\s+)?private\b/.test(lineText);
|
||||
if (isCard && isPrivateMember && name) {
|
||||
if (portNames.has(name) || hostNames.has(name)) { allowed.port.push(name); continue; }
|
||||
if (harnessNames.has(name)) { allowed.harness.push(name); continue; }
|
||||
}
|
||||
violations.push({ ...d, name });
|
||||
}
|
||||
return { allowed, violations };
|
||||
}
|
||||
|
||||
/** Суммарный размер файлов dist/ в байтах; null — если сборки нет. */
|
||||
export function bundleBytes(distDir) {
|
||||
if (!existsSync(distDir)) return null;
|
||||
let total = 0;
|
||||
const walk = (dir) => {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const path = join(dir, entry);
|
||||
const stat = statSync(path);
|
||||
if (stat.isDirectory()) walk(path); else total += stat.size;
|
||||
}
|
||||
};
|
||||
walk(distDir);
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Диагностики `noUnusedLocals` по tsconfig проекта, в плоском виде. */
|
||||
export function unusedDiagnostics(root, tsconfig = 'tsconfig.json') {
|
||||
const configPath = resolve(root, tsconfig);
|
||||
const config = ts.readConfigFile(configPath, ts.sys.readFile);
|
||||
if (config.error) throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'));
|
||||
const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, root);
|
||||
const program = ts.createProgram(parsed.fileNames, { ...parsed.options, noUnusedLocals: true, noEmit: true });
|
||||
const out = [];
|
||||
for (const d of ts.getPreEmitDiagnostics(program)) {
|
||||
if (!UNUSED_CODES.has(d.code) || !d.file) continue;
|
||||
const { line } = d.file.getLineAndCharacterOfPosition(d.start ?? 0);
|
||||
out.push({
|
||||
file: relative(root, d.file.fileName).replaceAll('\\', '/'),
|
||||
line: line + 1,
|
||||
code: d.code,
|
||||
message: ts.flattenDiagnosticMessageText(d.messageText, ' '),
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Все .ts под src/ — путь → текст. */
|
||||
export function readSources(root, dir = 'src') {
|
||||
const out = {};
|
||||
const walk = (d) => {
|
||||
for (const entry of readdirSync(d)) {
|
||||
const path = join(d, entry);
|
||||
if (statSync(path).isDirectory()) walk(path);
|
||||
else if (/\.ts$/.test(entry) && !/\.d\.ts$/.test(entry)) out[relative(root, path).replaceAll('\\', '/')] = readFileSync(path, 'utf8');
|
||||
}
|
||||
};
|
||||
walk(resolve(root, dir));
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Харнесс — то, что исполняет Validate: смоки `demo/smoke_*.mjs`, бенчмарки
|
||||
* и `demo/guard`. Ручные съёмки (`shot_*`, `verify_*`) входом не считаются
|
||||
* (см. NOT_AN_INPUT в check-inputs.mjs) и мёртвый член карточки не оправдывают.
|
||||
*/
|
||||
export const HARNESS_FILE = /^(?:smoke_|benchmark_).*\.mjs$/;
|
||||
export function readDemoSources(root) {
|
||||
const out = {};
|
||||
const demo = resolve(root, 'demo');
|
||||
if (!existsSync(demo)) return out;
|
||||
for (const entry of readdirSync(demo)) {
|
||||
const path = join(demo, entry);
|
||||
if (statSync(path).isFile() && HARNESS_FILE.test(entry)) out[`demo/${entry}`] = readFileSync(path, 'utf8');
|
||||
}
|
||||
const guard = join(demo, 'guard');
|
||||
if (existsSync(guard)) {
|
||||
for (const entry of readdirSync(guard)) {
|
||||
if (/\.mjs$/.test(entry)) out[`demo/guard/${entry}`] = readFileSync(join(guard, entry), 'utf8');
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Полный замер: шесть чисел плюс разбор диагностик. `diagnostics` можно
|
||||
* подать снаружи (тесты), иначе компилятор запускается по tsconfig.
|
||||
*/
|
||||
export function collectMetrics(root, { diagnostics = null, distDir = 'dist' } = {}) {
|
||||
const sources = readSources(root);
|
||||
const cardText = sources[CARD_FILE];
|
||||
const runtimeText = sources[RUNTIME_FILE];
|
||||
if (!cardText || !runtimeText) throw new Error('карточка или рантайм не найдены в src/');
|
||||
const portNames = portMemberNames(runtimeText);
|
||||
const host = hostReferences(sources);
|
||||
const harnessNames = harnessMemberNames(readDemoSources(root));
|
||||
const diags = diagnostics ?? unusedDiagnostics(root);
|
||||
const { allowed, violations } = classifyUnused(diags, { cardText, portNames, hostNames: host.names, harnessNames });
|
||||
return {
|
||||
metrics: {
|
||||
delegates: countDelegates(cardText),
|
||||
portMembers: portNames.size,
|
||||
hostRefs: host.count,
|
||||
portPrivates: allowed.port.length,
|
||||
harnessPrivates: allowed.harness.length,
|
||||
bundleBytes: bundleBytes(resolve(root, distDir)),
|
||||
},
|
||||
allowed,
|
||||
violations,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Полоса для `bundleBytes` — как у gzip-потолка стартового графа (#438,
|
||||
* `bundle-budget.mjs`): размер dist/ меняется от любой правки `src/**` в
|
||||
* любой задаче, а ребейз ветки конвейером пересобирает бандл поверх нового
|
||||
* `dev`. Точное число красило бы каждую ветку за чужой коммит. Пять чисел
|
||||
* исходника такой полосы не получают: они меняются только правкой монолита.
|
||||
*/
|
||||
export const BUNDLE_BYTES_BAND = 2_000;
|
||||
|
||||
/** Сравнение с базой: что выросло, что упало. Отсутствующее в базе — рост. */
|
||||
export function compareWithBaseline(current, baseline, { bundleBand = BUNDLE_BYTES_BAND } = {}) {
|
||||
const grown = [];
|
||||
const shrunk = [];
|
||||
for (const name of METRIC_NAMES) {
|
||||
const now = current[name];
|
||||
const base = baseline?.[name];
|
||||
if (now == null) continue;
|
||||
const band = name === 'bundleBytes' ? bundleBand : 0;
|
||||
if (base == null || now > base + band) grown.push({ name, base: base ?? null, now });
|
||||
else if (now < base - band) shrunk.push({ name, base, now });
|
||||
}
|
||||
return { grown, shrunk };
|
||||
}
|
||||
|
||||
export function readBaseline(root) {
|
||||
const path = resolve(root, BASELINE_FILE);
|
||||
return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : null;
|
||||
}
|
||||
|
||||
export function formatMetrics(metrics) {
|
||||
return METRIC_NAMES.map((name) => `${name}=${metrics[name] ?? 'n/a'}`).join(' ');
|
||||
}
|
||||
@@ -8605,6 +8605,50 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: " current = [line]; // mutant: last physical line instead of the paragraph\n }\n for (const paragraph of paragraphs) {",
|
||||
}],
|
||||
},
|
||||
// #624: гейт мёртвого кода и связности монолита — три защиты, три мутанта.
|
||||
{
|
||||
id: 'unused-gate-allows-everything',
|
||||
guard: 'node --test --test-name-pattern="#624 разбор диагностик" test/monolith-metrics.test.mjs',
|
||||
because: 'the allow-list must admit only private card members read through the port or the '
|
||||
+ 'smoke harness; admitting everything hides real dead code behind a green gate (#624 AC1)',
|
||||
patches: [{
|
||||
file: 'scripts/monolith-metrics.mjs',
|
||||
find: " if (isCard && isPrivateMember && name) {",
|
||||
replace: " if (name) { // mutant: any named diagnostic is allowed",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'monolith-metrics-baseline-strict',
|
||||
guard: 'node --test --test-name-pattern="#624 храповик" test/monolith-metrics.test.mjs',
|
||||
because: 'the ratchet reddens on a growth of one; a >= comparison lets bundle bytes and '
|
||||
+ 'coupling creep up one unit per commit (#624 AC1-b, AC2)',
|
||||
patches: [{
|
||||
file: 'scripts/monolith-metrics.mjs',
|
||||
find: " if (base == null || now > base + band) grown.push({ name, base: base ?? null, now });",
|
||||
replace: " if (base == null || now > base + band + 1) grown.push({ name, base: base ?? null, now }); // mutant: one unit of slack",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'monolith-delegates-return-only',
|
||||
guard: 'node --test --test-name-pattern="#624 делегаты" test/monolith-metrics.test.mjs',
|
||||
because: 'a delegate without `return` or with `await` is still a delegate; counting only the '
|
||||
+ 'return form undercounts and lets the metric fall without any extraction (#624 AC2)',
|
||||
patches: [{
|
||||
file: 'scripts/monolith-metrics.mjs',
|
||||
find: " else if (ts.isExpressionStatement(statement)) expression = statement.expression;",
|
||||
replace: " else if (ts.isExpressionStatement(statement)) return false; // mutant: return-form only",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'monolith-anchors-length-only',
|
||||
guard: 'node --test --test-name-pattern="#624 AC4: подмена" test/monolith-text-anchors.test.mjs',
|
||||
because: 'the freeze compares names, not counts: a renamed anchor test must read as a new one (#624 AC4)',
|
||||
patches: [{
|
||||
file: 'test/monolith-text-anchors.test.mjs',
|
||||
find: " return actual.filter((name) => !allowed.has(name)).sort();",
|
||||
replace: " return actual.length > frozen.length ? actual.slice(frozen.length).sort() : []; // mutant: length only",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'process-reconcile-restarts-healthy-run',
|
||||
guard: 'node --test test/process-reconcile.test.mjs',
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Гейт «мёртвый код и связность монолита» (#624): `npm run lint:unused`.
|
||||
*
|
||||
* node scripts/unused-locals-gate.mjs # проверить против базы
|
||||
* node scripts/unused-locals-gate.mjs --update # записать текущие числа в базу
|
||||
* node scripts/unused-locals-gate.mjs --json # числа и разбор в JSON
|
||||
*
|
||||
* Две проверки, оба ответа — в одном прогоне:
|
||||
*
|
||||
* 1. `tsc --noUnusedLocals` по всему проекту чист, кроме приватных членов
|
||||
* карточки, которых карточка не читает, но читает рантайм через порт
|
||||
* (`portPrivates`) или зовут браузерные смоки (`harnessPrivates`). Любая
|
||||
* другая диагностика — неиспользуемый импорт, мёртвая константа, локальная
|
||||
* переменная, приватный член вне порта и харнесса — красный: это код,
|
||||
* который компилятор доказал мёртвым.
|
||||
*
|
||||
* 2. Храповик: шесть чисел `scripts/monolith-metrics.mjs` не растут
|
||||
* относительно `scripts/monolith-baseline.json` (`bundleBytes` — с полосой
|
||||
* ±2 000 Б, как gzip-потолок #438; остальные — точно). Снижение — не ошибка, но
|
||||
* база обязана быть опущена тем же коммитом (`--update`): незафиксированный
|
||||
* выигрыш монолит отыграет обратно первой же правкой. Рост допускается
|
||||
* только с явной записью в issue задачи и правкой базы в том же коммите —
|
||||
* гейт печатает, какое число и на сколько.
|
||||
*
|
||||
* `bundleBytes` требует собранного `dist/`: гейт стоит после `npm run build`
|
||||
* (в `gate:small` и в job `frontend` Validate). Без сборки число не судится,
|
||||
* и гейт говорит об этом явно, а не молчит зелёным.
|
||||
*/
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { isMainModule } from './spawn-portable.mjs';
|
||||
import {
|
||||
BASELINE_FILE, METRIC_NAMES, collectMetrics, compareWithBaseline, formatMetrics, readBaseline,
|
||||
} from './monolith-metrics.mjs';
|
||||
|
||||
/**
|
||||
* Чистое решение гейта: код выхода и строки отчёта по замеру и базе.
|
||||
* `violations` — диагностики вне разрешённых; `grown` — выросшие числа.
|
||||
*/
|
||||
export function decide({ metrics, violations, baseline }) {
|
||||
const lines = [];
|
||||
let fail = false;
|
||||
lines.push(`monolith: ${formatMetrics(metrics)}`);
|
||||
if (violations.length) {
|
||||
fail = true;
|
||||
lines.push(`FAIL мёртвый код — ${violations.length} диагностик(и) noUnusedLocals вне порта и харнесса:`);
|
||||
for (const v of violations.slice(0, 40)) lines.push(` ${v.file}:${v.line} TS${v.code} ${v.message}`);
|
||||
if (violations.length > 40) lines.push(` … ещё ${violations.length - 40}`);
|
||||
} else {
|
||||
lines.push('ok мёртвого кода нет: единственные непрочитанные члены — порт и харнесс');
|
||||
}
|
||||
if (!baseline) {
|
||||
fail = true;
|
||||
lines.push(`FAIL базы ${BASELINE_FILE} нет — создать: node scripts/unused-locals-gate.mjs --update`);
|
||||
return { fail, lines, grown: [], shrunk: [] };
|
||||
}
|
||||
const { grown, shrunk } = compareWithBaseline(metrics, baseline);
|
||||
if (grown.length) {
|
||||
fail = true;
|
||||
for (const g of grown) {
|
||||
lines.push(`FAIL связность выросла: ${g.name} ${g.base ?? 'нет в базе'} → ${g.now}`
|
||||
+ ' — вернуть или обосновать в issue и поднять базу тем же коммитом');
|
||||
}
|
||||
}
|
||||
if (shrunk.length) {
|
||||
// Снижение без записи в базу — тоже отказ: храповик работает в обе стороны.
|
||||
fail = true;
|
||||
for (const s of shrunk) lines.push(`FAIL связность упала, база не опущена: ${s.name} ${s.base} → ${s.now} — node scripts/unused-locals-gate.mjs --update`);
|
||||
}
|
||||
if (!grown.length && !shrunk.length) lines.push('ok все числа равны базе');
|
||||
if (metrics.bundleBytes == null) {
|
||||
fail = true;
|
||||
lines.push('FAIL dist/ не собран — bundleBytes не судится; сначала npm run build');
|
||||
}
|
||||
return { fail, lines, grown, shrunk };
|
||||
}
|
||||
|
||||
export function baselineFrom(metrics) {
|
||||
const out = {};
|
||||
for (const name of METRIC_NAMES) out[name] = metrics[name];
|
||||
return out;
|
||||
}
|
||||
|
||||
if (isMainModule(import.meta.url)) {
|
||||
const root = process.cwd();
|
||||
const update = process.argv.includes('--update');
|
||||
const json = process.argv.includes('--json');
|
||||
const result = collectMetrics(root);
|
||||
if (update) {
|
||||
if (result.metrics.bundleBytes == null) {
|
||||
console.error('dist/ не собран — база без bundleBytes была бы ложью; сначала npm run build');
|
||||
process.exit(1);
|
||||
}
|
||||
writeFileSync(resolve(root, BASELINE_FILE), `${JSON.stringify(baselineFrom(result.metrics), null, 2)}\n`);
|
||||
console.log(`${BASELINE_FILE} записан: ${formatMetrics(result.metrics)}`);
|
||||
}
|
||||
const baseline = readBaseline(root);
|
||||
const decision = decide({ metrics: result.metrics, violations: result.violations, baseline });
|
||||
if (json) {
|
||||
console.log(JSON.stringify({ metrics: result.metrics, allowed: result.allowed, violations: result.violations, ...decision }, null, 2));
|
||||
} else {
|
||||
for (const line of decision.lines) console.log(line);
|
||||
}
|
||||
process.exit(decision.fail && !update ? 1 : 0);
|
||||
}
|
||||
@@ -14,8 +14,8 @@
|
||||
import { html, nothing, type TemplateResult } from 'lit';
|
||||
|
||||
import {
|
||||
callout, chip, colorField, ensureFormKitStyles, field, fieldGrid, footerStatus, formCard, rangeEnds, rangeLine,
|
||||
segmented, sourcePicker, subsection, textLink, tintBlock, toggleRow, unitInput,
|
||||
callout, colorField, ensureFormKitStyles, field, fieldGrid, footerStatus, formCard, rangeEnds,
|
||||
rangeLine, segmented, sourcePicker, subsection, textLink, tintBlock, toggleRow, unitInput,
|
||||
} from './form-kit';
|
||||
import { forgetMarkerBaseline, markerDirty, markerProblems } from './marker-form-state';
|
||||
import { settingsCopy } from './settings-copy';
|
||||
|
||||
+62
-846
File diff suppressed because it is too large
Load Diff
+61
-461
@@ -6,7 +6,7 @@
|
||||
* 2) LEGACY fallback — baked-in country-house data (src/data/*), coordinates in a 1489×1053 canvas.
|
||||
* The icon layout is stored on the server (houseplan/layout/*), fallback — localStorage.
|
||||
*/
|
||||
import { LitElement, html, svg, nothing, TemplateResult, PropertyValues } from 'lit';
|
||||
import { html, svg, nothing, TemplateResult } from 'lit';
|
||||
import { displayVersion } from './card-version';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
import { renderVacuumMapsSection } from './editors/vacuum-maps-section';
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { renderGeneralSettingsDialog } from './editors/general-settings-dialog';
|
||||
import { renderRoomSettingsDialog } from './editors/room-settings-dialog';
|
||||
import { calibrationTarget, planVacuumFit } from './vacuum-route-edit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import {
|
||||
cancelHouseplanPointerMove, flushHouseplanPointerMove, queueHouseplanPointerMove,
|
||||
} from './pointer-move-queue';
|
||||
@@ -39,25 +38,16 @@ import {
|
||||
type IconRule, type CompiledIconRule,
|
||||
} from './rules';
|
||||
import {
|
||||
lqiColor, snapToGrid, snapSegment45, samePoint, pointInPolygon, markerIdForBinding,
|
||||
segmentCm, formatLength, roomEdges, roomPoly, paperRoomShapes, pointStrictlyInside, roomsOverlap,
|
||||
pointOnBoundary, mergeRooms, splitRoomPath, polygonArea, closestPointOnBoundary, pointStrictlyInside as ptInside, islandsOf, sharedBoundary, distToSegment, outlineWithout, cutSegments, alignGuides, segmentAngle, is45, isExact45Vector, type AlignGuide, swipeTarget, clampScale, migratePdfUrls, roomFillModeOf, roomGlowOf, contentUrl,
|
||||
snapToWall, snapPointAlongPoly, openingAmount, openingShoulders, interiorPoint,
|
||||
isInteriorLightOpeningType, openingEntityReferences, filterOpeningEntityCandidates,
|
||||
poleOfInaccessibility, subst,
|
||||
averageLqi, fitView, declump, safeUrl, floorsOf, type FloorInfo,
|
||||
stateIcon, lightColorOf, parseRoomRef, diffNewDevices, resolveGlowValues, resolveGlowAppearance,
|
||||
glowAlpha, normalizeGlowColorOverride, isControllable,
|
||||
spaceDisplayOf, resolveEffectiveRoomFill, fillColorsOf, DEFAULT_FILL_COLORS,
|
||||
customFillOf, roomCustomFillOf, DEFAULT_CUSTOM_FILL,
|
||||
type FillColors, type FillColorEntry, type ResolvedRoomFill, runServiceFor, RUN_TARGET_DOMAINS,
|
||||
DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY, stageBgOf, showRoomTooltipOf,
|
||||
DEFAULT_TEMP_MIN, DEFAULT_TEMP_MAX, type SpaceDisplay,
|
||||
referencedContentUrls,
|
||||
DISPLAY_MODES, TAP_ACTIONS, SPACE_FILL_UI_MODES, ROOM_FILL_MODES,
|
||||
normalizeDeviceDisplay, isAlarmCapable, displayIsNeutral, displayWantsValue, type DeviceDisplayMode,
|
||||
liveText, liveTextReference, liveTextToken, hassValue, valueWithUnit, decorTextScale, decorTextLines,
|
||||
DECOR_TEXT_BASE,
|
||||
snapToGrid, snapSegment45, samePoint, pointInPolygon, markerIdForBinding, formatLength, roomPoly,
|
||||
pointStrictlyInside, roomsOverlap, pointOnBoundary, mergeRooms, splitRoomPath, polygonArea,
|
||||
closestPointOnBoundary, pointStrictlyInside as ptInside, distToSegment, cutSegments, alignGuides,
|
||||
segmentAngle, type AlignGuide, clampScale, migratePdfUrls, snapToWall, snapPointAlongPoly,
|
||||
openingAmount, openingShoulders, filterOpeningEntityCandidates, poleOfInaccessibility, subst,
|
||||
type FloorInfo, parseRoomRef, resolveGlowValues, normalizeGlowColorOverride, isControllable,
|
||||
spaceDisplayOf, DEFAULT_FILL_COLORS, customFillOf, DEFAULT_CUSTOM_FILL, type FillColors,
|
||||
type FillColorEntry, RUN_TARGET_DOMAINS, DEFAULT_ROOM_COLOR, DEFAULT_ROOM_OPACITY, stageBgOf,
|
||||
showRoomTooltipOf, DEFAULT_TEMP_MIN, DEFAULT_TEMP_MAX, normalizeDeviceDisplay,
|
||||
type DeviceDisplayMode, liveTextReference, liveTextToken, DECOR_TEXT_BASE,
|
||||
} from './logic';
|
||||
import {
|
||||
resolveSafeResize,
|
||||
@@ -79,13 +69,7 @@ import {
|
||||
placeResizeAreaLabel, resizeMeasuredEdges,
|
||||
type ResizeAreaPlacement,
|
||||
} from './resize-labels';
|
||||
import {
|
||||
computeSunRays, dayPhase, northDegOf, bgModeOf, sunRaysOn, sunRayOriginOf,
|
||||
sunStateOf, rayPeakAlpha, raysVisible, rayColor, RAY_FADE_MS, type SunRay,
|
||||
rayStops, resolveDayCycle, dayCycleFingerprint, type DayCycleState,
|
||||
rayRimEdges, rimStops, rimPeakAlpha, RIM_COLOR, type SunRayOrigin,
|
||||
} from './sun';
|
||||
import { dayCycleStageVars, renderDayCycleEnvironment } from './day-cycle-render';
|
||||
import { northDegOf, bgModeOf, sunRaysOn, sunRayOriginOf, type SunRayOrigin } from './sun';
|
||||
import {
|
||||
furnitureDefaultCm,
|
||||
furnitureGraphic, furnitureCorners,
|
||||
@@ -100,24 +84,14 @@ import { FURNITURE_ART_RUNTIME } from './furniture-art-runtime';
|
||||
import { FURNITURE_ART_FINGERPRINT, GENERATED_FURNITURE_ART } from './furniture-plan-art.generated';
|
||||
import { furnitureWallSurfacesFor, type FurnitureWallSurface } from './furniture-wall-surface';
|
||||
import {
|
||||
degradeWalls, rekeyWallsAfterMoveChecked, wallRecordCarrierViolations,
|
||||
setWallThickness, setWallThicknessForRoom, cmToField, wallCmToUnits,
|
||||
multiWallNodesForGeometry,
|
||||
wallEdgeBodies, wallBodiesGeometry, wallBodiesGeometryPath, wallBodiesUnionPath,
|
||||
recutWallBodiesGeometry,
|
||||
floorFootprintGeometry,
|
||||
innerContourForRoom, roomWallProfile, outsetContour,
|
||||
openingInnerFaceOffsetFromIndex, openingTunnelGeometriesFromIndex,
|
||||
openingWallIndex as buildOpeningWallIndex, resolveOpeningWallAssociation,
|
||||
applyWallThicknessToNewRoom,
|
||||
drawWallPreviewD, linearWallJoinPatches, DRAW_WALL_DEFAULT_CM,
|
||||
wallIntervals, materializeWallIntervals,
|
||||
normalizeWallIntervals,
|
||||
intervalCmAt, wallBodyNeedsSolid, wallHatchNeedsSolid, wallHatchStepUnits,
|
||||
HATCH_BASE_STEP_UNITS, type OpeningTunnelGeometry, type OpeningWallIndex,
|
||||
type LinearWallSegment, type WallEntry, type WallInterval,
|
||||
type MultiWallNodeMap,
|
||||
innerEdgeSpan, ownEdgeOffsets, thicknessCmAt,
|
||||
degradeWalls, rekeyWallsAfterMoveChecked, wallRecordCarrierViolations, setWallThickness,
|
||||
setWallThicknessForRoom, cmToField, wallCmToUnits, multiWallNodesForGeometry, wallBodiesGeometry,
|
||||
wallBodiesGeometryPath, wallBodiesUnionPath, innerContourForRoom,
|
||||
openingInnerFaceOffsetFromIndex, resolveOpeningWallAssociation, applyWallThicknessToNewRoom,
|
||||
drawWallPreviewD, linearWallJoinPatches, DRAW_WALL_DEFAULT_CM, wallIntervals,
|
||||
materializeWallIntervals, intervalCmAt, wallHatchStepUnits, HATCH_BASE_STEP_UNITS,
|
||||
type OpeningWallIndex, type LinearWallSegment, type WallEntry, type WallInterval,
|
||||
type MultiWallNodeMap, innerEdgeSpan, ownEdgeOffsets, thicknessCmAt,
|
||||
} from './wall-thickness';
|
||||
import {
|
||||
junctionLimitViolations, increasedViolations,
|
||||
@@ -128,96 +102,60 @@ import {
|
||||
type OpenSpanEntry,
|
||||
} from './open-spans';
|
||||
import { ContentSigner } from './signing';
|
||||
import {
|
||||
resolveFixedFloor, resolveInitialSpace, settleBestEffort,
|
||||
type FixedFloorSelection, type InitialSpaceSelection,
|
||||
} from './initial-load';
|
||||
import { selectActiveSpaceModel, selectSpaceModelById } from './space-model-selection';
|
||||
import { type InitialSpaceSelection } from './initial-load';
|
||||
import { selectSpaceModelById } from './space-model-selection';
|
||||
import {
|
||||
applyRoomTempThresholdDraft, createEmptySpaceConfig, initialSpaceDisplayDraft,
|
||||
roomTempThresholdDraft, roomTempThresholdInputValues, strictNumber, switchSpacePlanSource,
|
||||
touchSpaceDisplay, type SpaceDialogState,
|
||||
roomTempThresholdDraft, roomTempThresholdInputValues, strictNumber, type SpaceDialogState,
|
||||
} from './space-dialog';
|
||||
import { rememberSpaceDialogBaseline, spaceDialogProblems } from './editors/space-form-state';
|
||||
import { generalProblems, rememberGeneralBaseline } from './editors/general-form-state';
|
||||
import { rememberRoomBaseline } from './editors/room-form-state';
|
||||
import { forgetMarkerBaseline, rememberMarkerBaseline } from './editors/marker-form-state';
|
||||
import { commitPlanOptimization } from './plan-optimize-write';
|
||||
import { openSpaceCopyDialog, renderSpaceCopyDialog, saveSpaceCopy } from './space-copy-runtime';
|
||||
import { mdiHomeCityOutline } from '@mdi/js';
|
||||
import { saveSpaceCopy } from './space-copy-runtime';
|
||||
import {
|
||||
Affine, applyAffine, readVacTelemetry,
|
||||
autoCalibrate, pushTrailPoint, isVacMoving, vacTrailMode, vacMapIdWithFallback,
|
||||
parseVacSourceCandidate, resolveVacSource, resolveCurrentVacPath, trimVacPathTarget, areaCentroid,
|
||||
vacCalibrationResidualCm, vacRoomNameMatchCount, VAC_CALIBRATION_WARN_CM,
|
||||
VAC_TELEPORT_GAP_MS, VAC_STALE_MS,
|
||||
FitParams, fitMatrix, initialFit, reanchorFit, VacRoom,
|
||||
Pt as VacPt, type VacPath, type VacSourceCandidate,
|
||||
type VacSourceResolution, type VacSourceStatus,
|
||||
Affine, applyAffine, readVacTelemetry, autoCalibrate, vacTrailMode, areaCentroid,
|
||||
vacCalibrationResidualCm, vacRoomNameMatchCount, VAC_CALIBRATION_WARN_CM, FitParams, fitMatrix,
|
||||
reanchorFit, VacRoom, Pt as VacPt, type VacSourceCandidate, type VacSourceResolution,
|
||||
} from './vacuum';
|
||||
import {
|
||||
buildDevices, deviceFromMarkerDraft, effectiveExcludedIntegrations, seedHiddenBindings, lqiFor, tempFor, humFor, climateTempFor, isHumEntity,
|
||||
areaTemp, areaHum, sourceValue, areaClimateMap,
|
||||
resolvedLightSources, resolvedLightState, resolvedLightStats,
|
||||
hasOwnSpatialSource, hasOwnStatefulLightSource, ownControllableEntities,
|
||||
forcedLightEntityOf,
|
||||
resolveDeviceLightSettings, selectSpatialGlowSource,
|
||||
resolvedDeviceStateEntities, removedPlanBindings, isRemovedPlanEntity,
|
||||
deletePlanMarkerRecords, effectiveMarkerControls, persistedExternalControls,
|
||||
removeMarkerControlReferences, rewriteMarkerControlReferences, markerControlWouldCycle,
|
||||
resolveIcon,
|
||||
type AreaClimate,
|
||||
buildDevices, deviceFromMarkerDraft, effectiveExcludedIntegrations, seedHiddenBindings,
|
||||
resolvedLightSources, hasOwnSpatialSource, ownControllableEntities, selectSpatialGlowSource,
|
||||
removedPlanBindings, isRemovedPlanEntity, deletePlanMarkerRecords, effectiveMarkerControls,
|
||||
persistedExternalControls, removeMarkerControlReferences, rewriteMarkerControlReferences,
|
||||
markerControlWouldCycle,
|
||||
} from './devices';
|
||||
import {
|
||||
bindingCandidates, buildDeviceInbox, filterDeviceInbox,
|
||||
type DeviceInboxCategory, type DeviceInboxReason, type DeviceInboxRow,
|
||||
} from './device-inbox';
|
||||
import {
|
||||
formatToggleConfirmation, formatToggleIntent, projectedTapAction, resolveToggleIntent,
|
||||
sameToggleOperationTargets, toggleCoverEntity, toggleIntentName, toggleOperation,
|
||||
toggleEntityCandidates, unavailableToggleTargetNames,
|
||||
type ResolvedToggleIntent, type ResolvedToggleTarget,
|
||||
type ToggleNextEffect, type ToggleNoneReason,
|
||||
type ToggleSkipReason,
|
||||
formatToggleIntent, projectedTapAction, type ResolvedToggleIntent, type ResolvedToggleTarget,
|
||||
type ToggleNextEffect, type ToggleNoneReason, type ToggleSkipReason,
|
||||
} from './device-toggle';
|
||||
import { toggleEntityWriteFields } from './marker-toggle-entity';
|
||||
import { removeMarkerAreaSnapshots } from './device-area-relocation';
|
||||
import {
|
||||
adoptVirtualLightServerSnapshot,
|
||||
applyVirtualLightEvent,
|
||||
reconcileVirtualLightSnapshot,
|
||||
virtualLightFingerprint,
|
||||
virtualLightSnapshot,
|
||||
virtualLightWire,
|
||||
type VirtualLightSnapshot,
|
||||
} from './virtual-light-state';
|
||||
import { type VirtualLightSnapshot } from './virtual-light-state';
|
||||
import type {
|
||||
OpeningCfg, PartitionOpeningHost,
|
||||
RoomCfg, PartitionCfg, WallColumnCfg,
|
||||
SpaceModel, PdfRef, Marker, ServerConfig, DevItem, CardConfig,
|
||||
MarkerValueBadge, ValueBadgePosition, ValueBadgeSource, ZeroWallStyle,
|
||||
OpeningCfg, PartitionOpeningHost, RoomCfg, PartitionCfg, WallColumnCfg, SpaceModel, PdfRef,
|
||||
Marker, ServerConfig, DevItem, CardConfig, MarkerValueBadge, ValueBadgePosition,
|
||||
ValueBadgeSource,
|
||||
} from './types';
|
||||
import {
|
||||
radarAfterBindingChange, radarConfigFromDraft, radarDraft, recognizeRadar,
|
||||
type RadarEditorDraft,
|
||||
radarConfigFromDraft, radarDraft, recognizeRadar, type RadarEditorDraft,
|
||||
} from './radar-editor';
|
||||
import { finishMarkerDialogClose } from './marker-dialog-close';
|
||||
import { renderRadarSection } from './editors/radar-section';
|
||||
import { radarDiscardRequest, RadarSetupController } from './radar-setup';
|
||||
import {
|
||||
COLUMN_MAX_CM, canonicalColumnAngle, clampColumnCm, columnBody,
|
||||
directionalOccluders, floorMinusBodies, geometryArea, geometryOuterRings,
|
||||
geometryAllRings, intersectionPaths, partitionBody, polyclipPathD,
|
||||
pointInOpaquePlanBody, pointInPhysicalBody, sameColumnPlacement,
|
||||
physicalBodyParts,
|
||||
type PartitionOpeningCut,
|
||||
COLUMN_MAX_CM, canonicalColumnAngle, clampColumnCm, columnBody, floorMinusBodies, geometryArea,
|
||||
partitionBody, pointInPhysicalBody, sameColumnPlacement, type PartitionOpeningCut,
|
||||
} from './physical-geometry';
|
||||
import {
|
||||
hostedOpeningIntervalsOverlap, materializePartitionOpening,
|
||||
partitionOpeningJambMargin, partitionOpeningNeedsStrictValidation,
|
||||
partitionOpeningFace,
|
||||
partitionPlacementIntervals, resolvePartitionOpeningCompat, resolvePartitionOpeningStrict,
|
||||
type PartitionOpeningOrphanReason, type ResolvedPartitionOpening,
|
||||
hostedOpeningIntervalsOverlap, materializePartitionOpening, partitionOpeningJambMargin,
|
||||
partitionOpeningNeedsStrictValidation, partitionOpeningFace, partitionPlacementIntervals,
|
||||
resolvePartitionOpeningCompat, resolvePartitionOpeningStrict,
|
||||
} from './partition-openings';
|
||||
import {
|
||||
buildHiddenWallDiagnosticGeometry, buildPlanSnapGeometry,
|
||||
@@ -226,20 +164,14 @@ import {
|
||||
type PlanSnapCandidate, type PlanSnapEndpoint, type PlanSnapGeometry, type PlanSnapSegment,
|
||||
} from './plan-snap-overlay';
|
||||
import {
|
||||
atomizeWallSegments, buildWallFaceGraph, findNewWallFacesInGraphs, findWallFaceAtPoint,
|
||||
normalizeUnifiedWallTool, wallChainSegments, chainSegmentCms,
|
||||
buildWallFaceGraph, findNewWallFacesInGraphs, findWallFaceAtPoint, chainSegmentCms,
|
||||
type WallFaceGraph, type WallGraphFace, type WallGraphSourceSegment,
|
||||
} from './wall-face-graph';
|
||||
import { parameterOnPartition, planRoomDeletion } from './room-deletion';
|
||||
import {
|
||||
planWallFaceRepair, repairMovesHostedPartition, type WallFaceRepairProposal,
|
||||
} from './wall-face-repair';
|
||||
import {
|
||||
LightSegment, polygonSegments, splitAtIntersections, visibilityPolygon,
|
||||
} from './light-visibility';
|
||||
import './space-card';
|
||||
import { cardStyles } from './styles';
|
||||
import { editorSecondaryStyles } from './editor-secondary.styles';
|
||||
import {
|
||||
EditorSecondaryController,
|
||||
type EditorSecondaryCopy,
|
||||
@@ -247,11 +179,8 @@ import {
|
||||
type EditorToolbarGroup,
|
||||
} from './editor-secondary';
|
||||
import {
|
||||
fitInSquare, planRect, contentBounds, spaceModels, contentFrame, contentItems, spaceFrame,
|
||||
spaceCenter, iconUnit, iconCqw, gridLevels, itemOf, snapPt,
|
||||
MIN_ZOOM, PAN_SLACK, CANVAS_LIMIT, SANE_LIMIT, GRID_PITCH, GRID_STEP_N,
|
||||
PLAN_SCALE_MIN, PLAN_SCALE_MAX,
|
||||
clampCanvasR, clampCanvasN, type ContentItem, type Rect,
|
||||
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 {
|
||||
@@ -260,29 +189,17 @@ import {
|
||||
fixedTopologyWallLineageHints,
|
||||
resolveRoomOpeningHost, wallModelOffGridValueCount, WallSegmentModelError,
|
||||
} from './wall-segment-model';
|
||||
import {
|
||||
legacyZeroContourLines, resolveZeroWalls, zeroContourLines,
|
||||
zeroWallHasOpening, zeroWallStyleOf,
|
||||
} from './zero-walls';
|
||||
import { resolveZeroWalls, zeroWallHasOpening, zeroWallStyleOf } from './zero-walls';
|
||||
import { snapNearAxisEndpoint } from './near-axis';
|
||||
import type { SpaceReferenceRepairContext } from './space-reference-repair';
|
||||
import { collectSpaceMarkerDependencies, spaceDeletionMessage } from './space-deletion';
|
||||
import {
|
||||
checkSpacePhysicalGeometry,
|
||||
checkOptimizeGeometry,
|
||||
geometryOpenCuts,
|
||||
geometryOpenings,
|
||||
geometryPartitionOpeningCuts,
|
||||
geometryRoomOpeningInputs,
|
||||
spacePhysicalGeometryFingerprint,
|
||||
type GeometryOpeningProjection,
|
||||
checkSpacePhysicalGeometry, checkOptimizeGeometry, geometryOpenCuts, geometryOpenings,
|
||||
geometryPartitionOpeningCuts, spacePhysicalGeometryFingerprint,
|
||||
type OptimizeGeometryPreflightResult,
|
||||
} from './plan-geometry-preflight';
|
||||
import {
|
||||
canonicalizeConfigGeometry,
|
||||
canonicalizeLayoutGeometry,
|
||||
canonicalizePosition,
|
||||
formatLatticeShiftCm,
|
||||
canonicalizeConfigGeometry, canonicalizePosition, formatLatticeShiftCm,
|
||||
} from './coordinate-canonicalization';
|
||||
import { enqueueSerializedWrite, type OptimisticAttempt } from './serialized-write-queue';
|
||||
import { applyCalibrationProposal, saveAutomaticCalibration, saveManualCalibration, saveVacuumMatrix,
|
||||
@@ -304,68 +221,29 @@ import {
|
||||
import { classifyPlanFile, encodePlanFile, renderBackdropGuard } from './backdrop-pick';
|
||||
import { CommandStack } from './command-stack';
|
||||
import type { DeviceLayout, DevicePositionState } from './device-position-history';
|
||||
import { resolvedSvgScreenBlend, svgScreenBlendSupported } from './glow-blend';
|
||||
import {
|
||||
CONTINUITY_LONG_HIDDEN_MS,
|
||||
VisualContinuityController,
|
||||
contentFingerprint,
|
||||
subscribePageVisibility,
|
||||
visualFrameFingerprint,
|
||||
type PageVisibilitySignal,
|
||||
} from './visual-continuity';
|
||||
import { contentFingerprint } from './visual-continuity';
|
||||
import { PointerModalityController } from './pointer-modality';
|
||||
import { type ResolvedDevicePresentation } from './device-presentation';
|
||||
import { type FiniteActivityRuntime } from './activity-runtime';
|
||||
import {
|
||||
entityVisualSample, entityVisualSamplesForDevice,
|
||||
type DeviceActivity, type DeviceVisualState, type EntityVisualSample,
|
||||
} from './device-visual';
|
||||
import {
|
||||
activitySourceSignature, deviceA11yState, presentationClasses, resolveDevicePresentation,
|
||||
resolvePresentationSources, type ResolvedDevicePresentation,
|
||||
} from './device-presentation';
|
||||
import {
|
||||
ACTIVITY_WINDOW_MS, advanceFiniteActivity, createFiniteActivityRuntime,
|
||||
resetFiniteActivityRuntime, stampFiniteActivity, type FiniteActivityRuntime,
|
||||
} from './activity-runtime';
|
||||
import {
|
||||
recommendedValueBadgeSource, valueBadgeCandidates, valueBadgeSourceFromKey,
|
||||
valueBadgeSourceKey, valueBadgeTitle, valueBadgeWriteFields, valueSourceWriteFields,
|
||||
recommendedValueBadgeSource, valueBadgeCandidates, valueBadgeWriteFields, valueSourceWriteFields,
|
||||
type ValueBadgeCandidate,
|
||||
} from './device-value-badge';
|
||||
import {
|
||||
createRenderDeviceSnapshot, presentationSnapshotKey, renderDeviceSnapshotPositions,
|
||||
type RenderDeviceSnapshot,
|
||||
} from './render-device-snapshot';
|
||||
import { deviceFaceStyle, deviceThemeClass, renderDeviceFace } from './device-face';
|
||||
import { effectiveDeviceBaseSize } from './device-marker-geometry';
|
||||
import {
|
||||
ModeTransitionController, viewportFromViewBox,
|
||||
type HouseplanMode, type ModeTransitionState, type ModeVisualState, type ModeViewBox,
|
||||
} from './mode-transition';
|
||||
import {
|
||||
currentLabs, hashSpace, noteLabsRender, subscribeLabs, type LabsSnapshot,
|
||||
} from './labs';
|
||||
import { type RenderDeviceSnapshot } from './render-device-snapshot';
|
||||
import { type HouseplanMode, type ModeVisualState } from './mode-transition';
|
||||
import { projectPlanPoint } from './iso-projection';
|
||||
import {
|
||||
acquireHaRegistries, activeRegistryHass, cacheHaBindingStatuses,
|
||||
fullRegistryHass, haRegistryBuildSignature, haRegistryDiagnostics, haRegistrySnapshot,
|
||||
openingEntityAvailable, refreshHaRegistries, renderOpeningEntityAvailable,
|
||||
resolveHaBindingStatus,
|
||||
type HaBindingStatus, type HaRegistrySnapshot,
|
||||
} from './ha-binding-status';
|
||||
import { type HaBindingStatus, type HaRegistrySnapshot } from './ha-binding-status';
|
||||
import type { DecorShape, DecorStyle } from './editors/decor/types';
|
||||
import {
|
||||
DECOR_ASSETS_API_VERSION, type DecorAsset,
|
||||
} from './decor-assets';
|
||||
import { DecorImageEditor } from './decor-image-editor';
|
||||
import {
|
||||
DEFAULT_DECOR_STYLE, boxAnchors, boxCorners, clamp01, decorCmToUnits,
|
||||
decorStrokeUnits, decorStyleOf, decorStylePatch,
|
||||
decorUnitsToCm, mergeSnapGeometry, normalizeAngle, resizeDecorBox,
|
||||
resizedBoxTopLeft, snapDecorPoint, validDecorDraft,
|
||||
boxAnchors, clamp01, decorCmToUnits, decorStylePatch, decorUnitsToCm, mergeSnapGeometry,
|
||||
normalizeAngle, resizeDecorBox, resizedBoxTopLeft, snapDecorPoint, validDecorDraft,
|
||||
type DecorBox, type SnapGeometry,
|
||||
} from './editors/decor/geometry';
|
||||
import { decorStyleToSettings } from './editors/decor/geometry';
|
||||
import { renderOpeningTunnelFills } from './render/opening-tunnels';
|
||||
import {
|
||||
openingVisibleMetrics, renderOpeningVisibleGeometry,
|
||||
type OpeningFaceOffset, type OpeningVisibleSpec,
|
||||
@@ -381,14 +259,7 @@ import {
|
||||
} from './opening-dimensions';
|
||||
import type { OpeningPlacementCandidate, OpMeasure, RenderOpening } from './interaction-types';
|
||||
import { safeStoredColor } from './color';
|
||||
import {
|
||||
gridCellFieldToCm, gridCellFieldValue, gridVisualScale, gridVisualUnits,
|
||||
newSpaceCellCm, wallThickHoverHalfUnits,
|
||||
} from './grid-scale';
|
||||
import {
|
||||
applySpaceOrder, canStartTabDrag, markersNeedingPlacement, passedDragThreshold,
|
||||
reorderSpaceIds,
|
||||
} from './space-order';
|
||||
import { gridCellFieldValue, gridVisualUnits, newSpaceCellCm } from './grid-scale';
|
||||
import { applyOpeningMoves, mergeCollinearPartitions, spaceMergeGeometry } from './wall-merge';
|
||||
import { reconcileCoincidentPartitions } from './coincident-partitions';
|
||||
import { finalizeWallChainSpace } from './writer-fixed-point';
|
||||
@@ -423,27 +294,6 @@ type ResizeWallUnion = ReturnType<typeof wallBodiesUnionPath>;
|
||||
type ResizeWallArtifact = ReturnType<typeof wallBodiesGeometry>;
|
||||
/** Keeps every previously valid scale at the maximum 20 cm grid scale lossless. */
|
||||
const DECOR_TEXT_CM_MAX = 2000;
|
||||
/** HP-1552 boot-veil timing (AUD-1552-02). The veil holds for at least
|
||||
* BOOT_MIN_MS; every stage-height change restarts a BOOT_QUIET_MS
|
||||
* trailing-quiescence requirement (chrome still settling near the cap
|
||||
* extends the wait); BOOT_MAX_MS lifts the veil unconditionally.
|
||||
* BOOT_MIN_MS exceeds the old 600 ms window by a frame-latency margin: a
|
||||
* panel applied right at the window's edge (~590 ms) only materializes in
|
||||
* the stage height a couple of rAF/render frames later. */
|
||||
const BOOT_MIN_MS = 700;
|
||||
const BOOT_QUIET_MS = 250;
|
||||
const BOOT_MAX_MS = 1200;
|
||||
/** AUD-1552-02: post-reveal grace during which late chrome shifts glide
|
||||
* (CSS height transition on the stage) instead of snapping. */
|
||||
const BOOT_SOFT_MS = 1500;
|
||||
type LruRead<V> = { hit: true; value: V } | { hit: false };
|
||||
const lruRead = <K, V>(cache: Map<K, V>, key: K): LruRead<V> => {
|
||||
if (!cache.has(key)) return { hit: false };
|
||||
const value = cache.get(key)!;
|
||||
cache.delete(key);
|
||||
cache.set(key, value);
|
||||
return { hit: true, value };
|
||||
};
|
||||
const lruWrite = <K, V>(cache: Map<K, V>, key: K, value: V, limit: number): void => {
|
||||
cache.delete(key);
|
||||
cache.set(key, value);
|
||||
@@ -467,193 +317,14 @@ const lruWrite = <K, V>(cache: Map<K, V>, key: K, value: V, limit: number): void
|
||||
* the key keeps two DIFFERENT cards on one page from adopting each other's
|
||||
* header height (same config twice on one view is indistinguishable — and
|
||||
* then the heights match anyway). */
|
||||
/** DEV-B703-03 — what the dead instance was LOOKING at. The header height
|
||||
* alone was not enough: `_view` (the pan) never left the instance, and the
|
||||
* EDITOR zoom is deliberately not persisted (`_saveZoom` is view-only) while
|
||||
* the editor MODE is (LS_NAV). So a re-mount inside an editor came back at
|
||||
* the view-mode zoom, and a panned view came back re-centred on the plan —
|
||||
* the owner's «чуть-чуть дёргается масштаб». The memo now carries the whole
|
||||
* viewport, so the restore is the same rect, not the same zoom number. */
|
||||
type WarmViewport = {
|
||||
space: string;
|
||||
mode: 'view' | 'plan' | 'devices' | 'decor';
|
||||
projection: 'flat' | 'iso';
|
||||
activeLabsIso: boolean;
|
||||
logicalCenter: { x: number; y: number } | null;
|
||||
zoom: number;
|
||||
view: { x: number; y: number; w: number; h: number } | null;
|
||||
/** the view-mode viewport an editor was entered from (_viewModeSnap) */
|
||||
snap: { space: string; zoom: number; cx?: number; cy?: number } | null;
|
||||
tool: MarkupTool;
|
||||
decorTool: DecorTool;
|
||||
showHidden: boolean;
|
||||
/** «показать дальние» changes _baseVb, so the restored view rect is only
|
||||
* the same rect if the frame it was clamped against is the same one */
|
||||
showFar: boolean;
|
||||
selId: string | null;
|
||||
rszSel: string | null;
|
||||
decorSel: string | null;
|
||||
};
|
||||
const expiredWarmViewport = (vp: WarmViewport | null): WarmViewport | null => {
|
||||
if (!vp || vp.mode === 'view') return vp;
|
||||
return {
|
||||
...vp,
|
||||
mode: 'view',
|
||||
zoom: vp.snap?.space === vp.space ? vp.snap.zoom : vp.zoom,
|
||||
view: null,
|
||||
snap: null,
|
||||
tool: 'draw',
|
||||
decorTool: 'select',
|
||||
showHidden: false,
|
||||
selId: null,
|
||||
rszSel: null,
|
||||
decorSel: null,
|
||||
};
|
||||
};
|
||||
/** Which dialog was open and its draft (docs/WARM-REMOUNT.md §3). `data` is
|
||||
* the live draft OBJECT — the memo is module state, never serialised, so a
|
||||
* half-filled device dialog with its uploaded pdfs survives for free. */
|
||||
type WarmDialogKind = 'space' | 'marker' | 'settings' | 'opening' | 'decorText' | 'decorShape' | 'backdrop' | 'rules' | 'room' | 'info' | 'openingInfo';
|
||||
type WarmDialog = { kind: WarmDialogKind; space: string; mode: string; data: unknown; baseline?: string };
|
||||
/** AUD-159B1-01: one entry per CARD PLACEMENT, not per key. Two cards with an
|
||||
* identical config on one view share the key, so the key alone cannot say
|
||||
* whose viewport this is; `place`/`idx` (the parent element the card was
|
||||
* mounted in, and its position among that parent's children) identify the
|
||||
* DOM slot, and `owner` the live instance sitting in it. A re-mount into the
|
||||
* same slot inherits the entry; a different card never does. */
|
||||
type WarmEntry = {
|
||||
/** generation id of the instance that currently owns the slot */
|
||||
owner: number;
|
||||
/** HA route where this placement was captured. */
|
||||
path: string;
|
||||
/** the parent element the owner was mounted in (weak — never keep DOM alive) */
|
||||
place: WeakRef<Node> | null;
|
||||
/** the owner's index among that parent's children */
|
||||
idx: number;
|
||||
/** the owner is attached; a dead slot is a tombstone waiting for a successor */
|
||||
live: boolean;
|
||||
hdrH: number;
|
||||
stageH: number;
|
||||
vp: WarmViewport | null;
|
||||
/** Last complete visual frame for #73; safe only inside the same placement. */
|
||||
frameFingerprint: string;
|
||||
/** Metadata projection is immutable-by-replacement and avoids an empty
|
||||
* device layer on a same-document warm remount before registry refresh. */
|
||||
devices: readonly DevItem[] | null;
|
||||
dlg: WarmDialog | null;
|
||||
/** when the instance that wrote `dlg` detached; 0 = it is still alive */
|
||||
freed: number;
|
||||
/** the TTL timer that frees `dlg` once it can no longer be revived */
|
||||
evict: number;
|
||||
};
|
||||
const warmBoot = new Map<string, WarmEntry[]>();
|
||||
let warmGen = 0;
|
||||
/** `location.pathname` is the dashboard AND the view path: two Lovelace views
|
||||
* never share a key, so a card that comes back on another view boots cold
|
||||
* instead of inheriting a stranger's viewport (AUD-159B1-01). The hash is
|
||||
* deliberately out — `#space=` is OUR deep link, not another placement. */
|
||||
const warmBootKey = (config: unknown): string =>
|
||||
`${window.innerWidth}x${window.innerHeight}|${location.pathname}|${JSON.stringify(config ?? {})}`;
|
||||
/** A dialog is revived only if the instance that owned it died THIS long ago.
|
||||
* A Lovelace rebuild detaches and re-attaches within one task; a user who
|
||||
* walked off to another dashboard view and came back later must not be met
|
||||
* by a dialog they have long forgotten opening. */
|
||||
let WARM_REVIVE_MS = 10000;
|
||||
/** The memo gains a key on every window RESIZE and never loses one; the
|
||||
* values used to be two numbers, and now they can hold a dialog draft (a
|
||||
* plan pdf among it). Keep the last few viewports — a stale entry only
|
||||
* costs the next card at that size a cold boot. */
|
||||
const WARM_MAX_KEYS = 8;
|
||||
/** How many placements of ONE key are remembered. More identical cards than
|
||||
* this on one view and the oldest dead slot is dropped — it only costs that
|
||||
* placement a cold boot. */
|
||||
const WARM_MAX_SLOTS = 4;
|
||||
|
||||
/** Which slot of `list` belongs to the instance now claiming it, and may we
|
||||
* trust it with the viewport/dialog (`sure`) or only with the settled height?
|
||||
* Ranked, best first (AUD-159B1-01):
|
||||
* 4 — same parent, same index: literally the DOM slot we are standing in,
|
||||
* which is how Lovelace replaces a card (the predecessor may still be
|
||||
* attached for another task — that is the case the audit reproduced);
|
||||
* 0 — some OTHER placement whose owner is still attached: a neighbouring
|
||||
* card with an identical config, never ours to inherit;
|
||||
* 3 — same parent, shifted index, owner gone: still our placement;
|
||||
* 2 — a tombstone whose placement is gone with its subtree — the ordinary
|
||||
* Lovelace rebuild, where the container is rebuilt too.
|
||||
* A tie means two candidates are equally plausible: then only the settled
|
||||
* height is adopted, and it is the same for all of them anyway. */
|
||||
const warmMatch = (
|
||||
list: WarmEntry[],
|
||||
gen: number,
|
||||
place: Node | null,
|
||||
idx: number,
|
||||
): { slot: WarmEntry | null; sure: boolean } => {
|
||||
const score = (s: WarmEntry): number => {
|
||||
const same = !!place && s.place?.deref() === place;
|
||||
if (same && s.idx === idx) return 4;
|
||||
if (s.live) return 0;
|
||||
return same ? 3 : 2;
|
||||
};
|
||||
let best: WarmEntry | null = null;
|
||||
let bestScore = 0;
|
||||
let ties = 0;
|
||||
let newest: WarmEntry | null = null;
|
||||
for (const s of list) {
|
||||
if (s.owner === gen) continue;
|
||||
newest = s;
|
||||
const sc = score(s);
|
||||
if (sc <= 0) continue;
|
||||
if (sc > bestScore) { best = s; bestScore = sc; ties = 1; }
|
||||
else if (sc === bestScore) ties++;
|
||||
}
|
||||
if (!best || ties > 1) return { slot: best || newest, sure: false };
|
||||
return { slot: best, sure: true };
|
||||
};
|
||||
/** Rotation step of a decor text block — the same 5° a device icon turns in
|
||||
* (marker dialog). Shift affects angle precision only, never position. */
|
||||
const DT_ANGLE_STEP = 5;
|
||||
/** Line spacing of a multi-line label, in font sizes. */
|
||||
const DT_LINE = 1.2;
|
||||
const LS_KEY = 'houseplan_card_layout_v1';
|
||||
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
|
||||
const LS_ZOOM = 'houseplan_card_zoom_v1';
|
||||
const LS_NAV = 'houseplan_card_nav_v1'; // last space only; editor sessions never survive page navigation
|
||||
const LS_KIOSK = 'houseplan_card_kiosk_v1'; // per-SCREEN size multipliers (each wall tablet differs)
|
||||
const LS_VIEW = 'houseplan_card_view_v1'; // presentation preference per space, Labs-only
|
||||
const POINTER_HOVER_TARGET_SELECTOR = 'hp-dialog, hp-help, hp-color-opacity, hp-device-preview';
|
||||
const NORM_W = 1000; // side of the render space — the canvas is square (v1.48.0)
|
||||
/** Short semantic-event / direct-terminal-transition window. Event uses
|
||||
three sequential 1.1 s waves; motion cool-down itself never animates. */
|
||||
/**
|
||||
* How finely the lit region is traced where nothing blocks the light. 96 steps
|
||||
* put the chord error at 0.05% of the radius — under a tenth of a pixel on a
|
||||
* wall tablet, and cheap because only unobstructed directions use them.
|
||||
*/
|
||||
const GLOW_ARC_STEPS = 96;
|
||||
/**
|
||||
* Width of the lit→unlit ramp along a shadow edge, in SCREEN pixels: the eye
|
||||
* reads a perfectly geometric edge as a cut-out, and a real penumbra is never
|
||||
* wider than a hair at this scale. Measured on screen on purpose, so zooming in
|
||||
* does not turn a hairline into a smear.
|
||||
*/
|
||||
const GLOW_EDGE_FEATHER_PX = 2;
|
||||
/**
|
||||
* Radial profile of a pool, as [offset %, share of the calibrated alpha].
|
||||
* Monotonic all the way out: a lamp is brightest under itself and dies at its
|
||||
* radius. The centre keeps the full calibrated alpha, so nothing about the
|
||||
* brightness maths (docs/specs/067) changes — only where that alpha is spent.
|
||||
*/
|
||||
const GLOW_FALLOFF: readonly (readonly [number, number])[] = [
|
||||
[0, 1], [45, 0.88], [70, 0.62], [86, 0.32], [100, 0],
|
||||
];
|
||||
/** A source pool fades in/out without changing its final calibrated alpha. */
|
||||
const GLOW_FADE_MS = 500;
|
||||
|
||||
/** Smallest rectangle holding both (docs/CANVAS.md §4). */
|
||||
const unionRect = (a: Rect, b: Rect): Rect => {
|
||||
const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
|
||||
return { x, y, w: Math.max(a.x + a.w, b.x + b.w) - x, h: Math.max(a.y + a.h, b.y + b.h) - y };
|
||||
};
|
||||
|
||||
/** #313: one Thickness-tool hit — a room interval or independent masonry. */
|
||||
type WallThickSource = { kind: 'room' }
|
||||
@@ -664,10 +335,6 @@ type WallThickHit = {
|
||||
};
|
||||
|
||||
type MarkupTool = 'select' | 'draw' | 'column' | 'merge' | 'split' | 'resize' | 'opening' | 'wallthick' | 'delroom';
|
||||
type RoomFillFrame = {
|
||||
byRoom: Map<RoomCfg, ResolvedRoomFill | null>;
|
||||
byId: Map<string, ResolvedRoomFill | null>;
|
||||
};
|
||||
type WallFaceCandidate = WallGraphFace & {
|
||||
split?: { roomId: string; mainPoly: number[][]; newPoly: number[][] };
|
||||
consumeAllActive?: boolean;
|
||||
@@ -690,28 +357,6 @@ type WallFaceBatch = {
|
||||
activeCms: number[];
|
||||
activePartitionIds: string[];
|
||||
};
|
||||
/**
|
||||
* The floor a source can see, and nothing else. One region means one clip:
|
||||
* a beam through a doorway, the room it lands in and the shadow of a column
|
||||
* are all the same computation, so they can never disagree with each other.
|
||||
*/
|
||||
type GlowClipGeometry = { lit: string[] };
|
||||
const MARKUP_TOOLS = new Set<MarkupTool>([
|
||||
'select', 'draw', 'column', 'merge', 'split', 'resize',
|
||||
'opening', 'wallthick', 'delroom',
|
||||
]);
|
||||
/** Warm viewport is page-memory, so it may contain a tool name from the old bundle. */
|
||||
const normalizeMarkupTool = (value: unknown): MarkupTool => {
|
||||
// #173 replaces the public one-shot Partition tool with one Walls chain.
|
||||
// A warm page may still carry the old session token; reading it is inert.
|
||||
value = normalizeUnifiedWallTool(value);
|
||||
// Opening placement is valid only together with its explicit session-only
|
||||
// type preset. Warm viewport state does not persist that preset.
|
||||
if (value === 'opening') return 'draw';
|
||||
return typeof value === 'string' && MARKUP_TOOLS.has(value as MarkupTool)
|
||||
? value as MarkupTool
|
||||
: 'draw';
|
||||
};
|
||||
const MAX_WALL_CHAIN_POINTS = 500;
|
||||
const MAX_PARTITIONS = 2000;
|
||||
const MAX_ROOMS = 400;
|
||||
@@ -740,17 +385,6 @@ interface SpaceGeometryState {
|
||||
* (docs/FURNITURE.md): it opens a palette and places a symbol at real size. */
|
||||
type DecorTool = 'select' | 'backdrop' | 'line' | 'rect' | 'ellipse' | 'text' | 'furniture' | 'image' | 'erase';
|
||||
|
||||
const fireEvent = (node: EventTarget, type: string, detail?: unknown) => {
|
||||
const ev = new Event(type, { bubbles: true, composed: true }) as any;
|
||||
ev.detail = detail ?? {};
|
||||
node.dispatchEvent(ev);
|
||||
};
|
||||
|
||||
const navigate = (path: string) => {
|
||||
history.pushState(null, '', path);
|
||||
fireEvent(window, 'location-changed', { replace: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* Debounce with `flush()` and `pending`. Both are load-bearing: a pending
|
||||
* config write MUST be flushed before the card adopts a server revision,
|
||||
@@ -763,36 +397,6 @@ interface Debounced<T extends (...a: any[]) => void> {
|
||||
pending(): boolean;
|
||||
}
|
||||
|
||||
const debounce = <T extends (...a: any[]) => void>(fn: T, ms: number): Debounced<T> => {
|
||||
let t: number | undefined;
|
||||
let last: Parameters<T> | null = null;
|
||||
const wrapped = ((...a: Parameters<T>) => {
|
||||
clearTimeout(t);
|
||||
last = a;
|
||||
t = window.setTimeout(() => {
|
||||
t = undefined;
|
||||
const args = last;
|
||||
last = null;
|
||||
if (args) fn(...args);
|
||||
}, ms);
|
||||
}) as Debounced<T>;
|
||||
wrapped.flush = () => {
|
||||
if (t === undefined) return;
|
||||
clearTimeout(t);
|
||||
t = undefined;
|
||||
const args = last;
|
||||
last = null;
|
||||
if (args) fn(...args);
|
||||
};
|
||||
wrapped.cancel = () => {
|
||||
clearTimeout(t);
|
||||
t = undefined;
|
||||
last = null;
|
||||
};
|
||||
wrapped.pending = () => t !== undefined;
|
||||
return wrapped;
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture the pointer for a drag, tolerating an inactive pointerId.
|
||||
*
|
||||
@@ -825,8 +429,6 @@ interface DeviceInboxDialogState {
|
||||
busy?: string;
|
||||
}
|
||||
|
||||
type FixedFloorState = FixedFloorSelection | { kind: 'pending'; value: unknown };
|
||||
|
||||
export interface HouseplanEditorHostPort {
|
||||
_ackNewDevice: (id: string) => void;
|
||||
_activeWallChainId: string | null;
|
||||
@@ -6894,8 +6496,6 @@ public _applyWallFaceBatch(): void {
|
||||
abort('toast.wall_repair_changed');
|
||||
return;
|
||||
}
|
||||
const effectiveActivePath = this._activePathWithRepair(batch.activePath, repairs[0]);
|
||||
|
||||
if (!accepted.length) {
|
||||
if (!this._finalizeWallChainPartitions(batch.activePartitionIds)) return;
|
||||
this.host._path = [];
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
import { mapTopologyNodes, TOPOLOGY_STALE_MS } from './zigbee-topology';
|
||||
import type { DevItem } from './types';
|
||||
import type { HaRegistrySnapshot } from './ha-binding-status';
|
||||
import type {
|
||||
ZigbeeProviderState, ZigbeeTopologyHass, ZigbeeTopologyRuntimeSnapshot,
|
||||
} from './zigbee-topology-runtime';
|
||||
import type { ZigbeeTopologyHass, ZigbeeTopologyRuntimeSnapshot } from './zigbee-topology-runtime';
|
||||
|
||||
const EMPTY_RUNTIME: ZigbeeTopologyRuntimeSnapshot = { revision: 0, topologies: [], states: {} };
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* SVG volume layers so the main Lit shell does not also become a renderer.
|
||||
*/
|
||||
import { nothing, svg, type TemplateResult } from 'lit';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
import { clampScale, islandsOf, roomPoly, type SpaceDisplay } from './logic';
|
||||
import {
|
||||
ISO_CAMERA, ISO_FLOOR_EDGE_HEIGHT, ISO_OVERLAY_VISUAL_OFFSET,
|
||||
|
||||
@@ -100,18 +100,6 @@ const simplePolygon = (poly: number[][]): boolean => {
|
||||
return polygonArea(poly) > 1e-12;
|
||||
};
|
||||
|
||||
const simpleOpenPath = (points: number[][]): boolean => {
|
||||
for (let i = 0; i + 1 < points.length; i++) {
|
||||
if (samePoint(points[i], points[i + 1])) return false;
|
||||
for (let j = i + 2; j + 1 < points.length; j++) {
|
||||
if (segmentsProperlyCross(points[i], points[i + 1], points[j], points[j + 1])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const replacePoint = (point: number[], move: EndpointMove): number[] => (
|
||||
samePoint(point, move.from) ? [...move.to] : [...point]
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { html, type TemplateResult } from 'lit';
|
||||
import type { PdfDialogContext, PdfAssetReference } from './hp-pdf-dialog';
|
||||
import type { PdfDialogContext } from './hp-pdf-dialog';
|
||||
import './hp-pdf-dialog';
|
||||
import { buildPdfPage, type PdfExportOptions, type PdfRasterPlacement } from './pdf-scene';
|
||||
import { writePdf } from './pdf-writer';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
linearWallBody, linearWallJoinPatches, pairButtEndTrimWedges, wallCmToUnits,
|
||||
type LinearWallSegment,
|
||||
} from './wall-thickness';
|
||||
import type { PartitionCfg, SpaceModel, WallColumnCfg } from './types';
|
||||
import type { SpaceModel, WallColumnCfg } from './types';
|
||||
|
||||
export const COLUMN_MIN_CM = 1;
|
||||
export const COLUMN_MAX_CM = 150;
|
||||
|
||||
@@ -1192,7 +1192,6 @@ export function validateSafeResize(
|
||||
}
|
||||
}
|
||||
|
||||
const movingRoom = rooms.find((room) => room.id === plan.roomId)!;
|
||||
const movingPoly = result.polys[plan.roomId];
|
||||
const movingEdge = plan.edgeByRoom[plan.roomId];
|
||||
const ma = movingPoly[movingEdge];
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import {
|
||||
wallHatchStepUnits, wallIntervals, innerContourForRoom, HATCH_BASE_STEP_UNITS,
|
||||
type WallEntry,
|
||||
} from './wall-thickness';
|
||||
import { DEFAULT_ICON_RULES, compileIconRules, EXCLUDED_DOMAINS } from './rules';
|
||||
import { DEFAULT_ICON_RULES, compileIconRules } from './rules';
|
||||
import { t, type Lang } from './i18n';
|
||||
import { bgModeOf, resolveDayCycle } from './sun';
|
||||
import { dayCycleStageVars, renderDayCycleEnvironment } from './day-cycle-render';
|
||||
|
||||
@@ -820,11 +820,6 @@ export class LoadedSummaryPanelRuntime {
|
||||
return resolved === null ? { kind: 'unavailable' } : { kind: 'ready', text: resolved };
|
||||
}
|
||||
|
||||
private value(value: SummaryPanelValue): string {
|
||||
const state = this.valueState(value);
|
||||
return state.kind === 'ready' ? state.text : this.t('summary.unavailable');
|
||||
}
|
||||
|
||||
private ensureMetrics(): void {
|
||||
if (this.metricsModule || this.metricsLoad) return;
|
||||
const generation = this.lifecycleGeneration;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
* each guessing a floor. Nothing here touches the DOM, `hass` or storage.
|
||||
*/
|
||||
|
||||
import { FitParams, fitFromMatrix, initialFit, VacRoom } from './vacuum';
|
||||
|
||||
export type Affine = [number, number, number, number, number, number];
|
||||
|
||||
|
||||
@@ -4323,9 +4323,6 @@ export function paperRoomShapesWithWalls(
|
||||
const rawPaper = exterior.shell?.length
|
||||
? union(exterior.centre, exterior.shell)
|
||||
: exterior.centre;
|
||||
const multiWallNodes = multiWallNodesForGeometry(
|
||||
rooms, walls, openCuts, pitch, cellCm, gridPitch, coordScale,
|
||||
);
|
||||
const paper = rawPaper;
|
||||
const path = polyclipToPathD(paper);
|
||||
if (path) return [{ path }];
|
||||
|
||||
@@ -29,7 +29,11 @@ const CAPS = {
|
||||
// 2026-09-14, #564: the geometry/index/latch/DOM ownership implementation is
|
||||
// isolated in device-hit-owner.ts. These 81 lines are the remaining card
|
||||
// lifecycle invalidations and the presentation/action integration seams.
|
||||
'src/houseplan-card.ts': 13732,
|
||||
// 2026-09-23, #624: сняты 112 мёртвых делегатов и полей, которых не читает
|
||||
// ни карточка, ни харнесс (реализация живёт в рантайме и зовётся оттуда),
|
||||
// 190 неиспользуемых импортов и 9 мёртвых констант — 784 строки. Потолок
|
||||
// опущен на выигрыш, запас над фактом три строки.
|
||||
'src/houseplan-card.ts': 12951,
|
||||
// #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.
|
||||
@@ -38,7 +42,10 @@ const CAPS = {
|
||||
// — 1290 строк. Потолок опущен ровно на эту величину (14100 → 12810), запас
|
||||
// над фактом остался прежним, три строки: выигрыш зафиксирован, иначе ядро
|
||||
// отыграет его обратно первой же правкой диалога.
|
||||
'src/houseplan-editor-runtime.ts': 12810,
|
||||
// 2026-09-23, #624: снесены мёртвые копии объявлений карточки (warm-boot,
|
||||
// LS_*, GLOW_*, debounce, navigate, lruRead — 31 объявление и 6 типов) и
|
||||
// 196 неиспользуемых импортов — 400 строк. Потолок опущен на выигрыш.
|
||||
'src/houseplan-editor-runtime.ts': 12359,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,6 +80,7 @@ export function readHouseplanProductionSource() {
|
||||
.replace(/^public\b/, visibility)
|
||||
.replaceAll('this.host.', 'this.');
|
||||
replacements.push({
|
||||
name,
|
||||
from: member.getStart(cardFile),
|
||||
to: member.end,
|
||||
text: implementationText,
|
||||
@@ -91,6 +92,24 @@ export function readHouseplanProductionSource() {
|
||||
+ replacement.text
|
||||
+ reconstructed.slice(replacement.to);
|
||||
}
|
||||
// #624: мёртвые делегаты карточки удалены — реализация, у которой в
|
||||
// карточке больше нет заглушки, живёт только в рантайме. Логическая
|
||||
// поверхность продукта от этого не меняется, поэтому такие члены
|
||||
// дописываются к исходнику в том же виде, в каком раньше подставлялись на
|
||||
// место делегата. Контракт «что делает продукт» не должен зависеть от того,
|
||||
// есть ли у метода однострочная заглушка в карточке.
|
||||
const delegated = new Set(replacements.map((replacement) => replacement.name));
|
||||
const appended = [];
|
||||
for (const [name, implementation] of implementations) {
|
||||
if (delegated.has(name) || name === 'host') continue;
|
||||
const text = moved.has(name)
|
||||
? `public ${name}(): TemplateResult ${moved.get(name)}`
|
||||
: runtime.slice(implementation.getStart(runtimeFile), implementation.end);
|
||||
appended.push(text.replace(/^public\b/, 'private').replaceAll('this.host.', 'this.'));
|
||||
}
|
||||
if (appended.length) {
|
||||
reconstructed += `\n// --- editor runtime members without a card delegate (#624) ---\n${appended.join('\n\n')}\n`;
|
||||
}
|
||||
for (const path of SHARED_DIALOG_MODULES) {
|
||||
const shared = readFileSync(new URL(path, import.meta.url), 'utf8')
|
||||
.replaceAll('port.host.', 'this.')
|
||||
|
||||
+6
-2
@@ -319,9 +319,13 @@ test('issue 252 Optimize keeps internal ids out of the main orphan report', () =
|
||||
});
|
||||
|
||||
test('i18n: every literal help call has body and full aria keys in every language', () => {
|
||||
const allCalls = helpSource.match(/this\._help\(/g) || [];
|
||||
// #624: логический исходник теперь включает члены рантайма без делегата в
|
||||
// карточке; среди них — порт общего модуля `help: (key) => this._help(key)`.
|
||||
// Это проброс, а не вызов с ключом: литерал стоит у вызывающей стороны.
|
||||
const allCalls = (helpSource.match(/this\._help\(/g) || []).length
|
||||
- (helpSource.match(/this\._help\(key\)/g) || []).length;
|
||||
const helpKeys = [...helpSource.matchAll(/this\._help\('([^']+\.help)'\)/g)].map((match) => match[1]);
|
||||
assert.equal(helpKeys.length, allCalls.length, 'every _help call must use one string literal ending in .help');
|
||||
assert.equal(helpKeys.length, allCalls, 'every _help call must use one string literal ending in .help');
|
||||
assert.ok(helpKeys.length > 0, 'the help affordance pilot disappeared');
|
||||
for (const key of helpKeys) {
|
||||
for (const { code } of LANGUAGE_REGISTRY) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// #624: шесть чисел связности монолита и гейт мёртвого кода — один модуль
|
||||
// для `lint:unused` и `inventory`. Имена файлов монолита — через константы
|
||||
// модуля: этот тест не читает монолит и не должен попадать в заморозку якорей. Фикстуры с известными числами: делегаты во
|
||||
// всех формах, порт, `host.`, разбор диагностик, байты dist, храповик.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
BASELINE_FILE, BUNDLE_BYTES_BAND, CARD_FILE, METRIC_NAMES, RUNTIME_FILE, bundleBytes, classifyUnused, collectMetrics, compareWithBaseline, countDelegates,
|
||||
harnessMemberNames, hostReferences, portMemberNames, readBaseline,
|
||||
} from '../scripts/monolith-metrics.mjs';
|
||||
import { baselineFrom, decide } from '../scripts/unused-locals-gate.mjs';
|
||||
|
||||
const CARD = `
|
||||
import { html } from 'lit';
|
||||
class HouseplanCard {
|
||||
private _a = 1;
|
||||
// делегаты: return, async, без return, геттер, await
|
||||
private _saveMarker(): void { return this._editorRuntimeOrThrow()._saveMarker(); }
|
||||
private async _saveRoom(id: string): Promise<void> { return this._editorRuntimeOrThrow()._saveRoom(id); }
|
||||
private _open(): void { this._editorRuntimeOrThrow()._open(); }
|
||||
private get _tool(): string { return this._editorRuntimeOrThrow()._tool; }
|
||||
private async _apply(): Promise<void> { await this._editorRuntimeOrThrow()._apply(); }
|
||||
// не делегаты: два выражения, свой код, сам аксессор
|
||||
private _two(): void { this._a += 1; this._editorRuntimeOrThrow()._open(); }
|
||||
private _own(): number { return this._a * 2; }
|
||||
private _runtime() { return this._editorRuntimeOrThrow(); }
|
||||
private _editorRuntimeOrThrow(): any { return null; }
|
||||
}
|
||||
`;
|
||||
|
||||
const RUNTIME = `
|
||||
export interface HouseplanEditorHostPort {
|
||||
_a: number;
|
||||
_tool: string;
|
||||
_saveRoom(id: string): Promise<void>;
|
||||
readonly _model: unknown[];
|
||||
'quoted-name'?: string;
|
||||
}
|
||||
class HouseplanEditorRuntime {
|
||||
constructor(public host: HouseplanEditorHostPort) {}
|
||||
run() { return this.host._a + this.host._tool.length; }
|
||||
}
|
||||
`;
|
||||
|
||||
test('#624 делегаты считаются по AST во всех формах, свой код — нет', () => {
|
||||
assert.equal(countDelegates(CARD), 5);
|
||||
assert.equal(countDelegates('class HouseplanCard { private _x() { return 1; } }'), 0);
|
||||
assert.throws(() => countDelegates('class Other {}'), /HouseplanCard/);
|
||||
});
|
||||
|
||||
test('#624 члены порта и обращения host. считаются по именам', () => {
|
||||
const port = portMemberNames(RUNTIME);
|
||||
assert.deepEqual([...port].sort(), ['_a', '_model', '_saveRoom', '_tool', 'quoted-name']);
|
||||
const refs = hostReferences({
|
||||
[RUNTIME_FILE]: RUNTIME,
|
||||
'src/other.ts': 'const x = host._model; const y = port.host._a; ghost._z; hostile._q;',
|
||||
[CARD_FILE]: 'host._ignored; host._ignored2;', // карточка не считается
|
||||
});
|
||||
assert.equal(refs.count, 4, 'два в рантайме + два в other; ghost./hostile. — не host.');
|
||||
assert.deepEqual([...refs.names].sort(), ['_a', '_model', '_tool']);
|
||||
});
|
||||
|
||||
test('#624 харнесс: имена, которых смоки касаются как свойств', () => {
|
||||
const names = harnessMemberNames({
|
||||
'demo/smoke_a.mjs': "card._saveMarker(); c._glowClipCache.clear(); Number(c._until || 0); el['_bracket'] = 1; card.publicName();",
|
||||
});
|
||||
assert.deepEqual([...names].sort(), ['_bracket', '_glowClipCache', '_saveMarker', '_until']);
|
||||
});
|
||||
|
||||
test('#624 разбор диагностик: порт и харнесс разрешены и посчитаны, остальное — нарушение', () => {
|
||||
const cardText = [
|
||||
'class HouseplanCard {',
|
||||
' private _portOnly = 1;',
|
||||
' private _hostOnly(): void {}',
|
||||
' private _harnessOnly = new Map();',
|
||||
' private _dead = 2;',
|
||||
' publicDead = 3;',
|
||||
'}',
|
||||
].join('\n');
|
||||
const d = (line, name, file = CARD_FILE, code = 6133) => ({ file, line, code, message: `'${name}' is declared but its value is never read.` });
|
||||
const { allowed, violations } = classifyUnused([
|
||||
d(2, '_portOnly'), d(3, '_hostOnly'), d(4, '_harnessOnly'), d(5, '_dead'), d(6, 'publicDead'),
|
||||
d(1, '_portOnly', 'src/other.ts'), // не карточка — не разрешается, даже если имя из порта
|
||||
{ file: 'src/x.ts', line: 1, code: 6192, message: 'All imports in import declaration are unused.' },
|
||||
], {
|
||||
cardText, portNames: new Set(['_portOnly']), hostNames: new Set(['_hostOnly']), harnessNames: new Set(['_harnessOnly', '_dead_no']),
|
||||
});
|
||||
assert.deepEqual(allowed.port, ['_portOnly', '_hostOnly']);
|
||||
assert.deepEqual(allowed.harness, ['_harnessOnly']);
|
||||
assert.deepEqual(violations.map((v) => `${v.file}:${v.line}:${v.name}`), [
|
||||
`${CARD_FILE}:5:_dead`, `${CARD_FILE}:6:publicDead`, 'src/other.ts:1:_portOnly', 'src/x.ts:1:',
|
||||
]);
|
||||
});
|
||||
|
||||
test('#624 байты dist — сумма всех файлов, включая чанки; без сборки — null', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'hp-monolith-dist-'));
|
||||
try {
|
||||
mkdirSync(join(dir, 'dist', 'houseplan-assets'), { recursive: true });
|
||||
writeFileSync(join(dir, 'dist', 'houseplan-card.js'), 'a'.repeat(10));
|
||||
writeFileSync(join(dir, 'dist', 'houseplan-assets', 'chunk.js'), 'b'.repeat(1000));
|
||||
assert.equal(bundleBytes(join(dir, 'dist')), 1010);
|
||||
assert.equal(bundleBytes(join(dir, 'missing')), null);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('#624 храповик: рост на 1 любого числа — красный, снижение без записи базы — тоже, равенство — зелёный', () => {
|
||||
const base = { delegates: 10, portMembers: 20, hostRefs: 30, portPrivates: 4, harnessPrivates: 5, bundleBytes: 1000 };
|
||||
assert.deepEqual(compareWithBaseline({ ...base }, base), { grown: [], shrunk: [] });
|
||||
for (const name of METRIC_NAMES.filter((n) => n !== 'bundleBytes')) {
|
||||
const grown = compareWithBaseline({ ...base, [name]: base[name] + 1 }, base).grown;
|
||||
assert.deepEqual(grown.map((g) => g.name), [name], `${name} +1 виден как рост`);
|
||||
const decision = decide({ metrics: { ...base, [name]: base[name] + 1 }, violations: [], baseline: base });
|
||||
assert.equal(decision.fail, true, `${name} +1 — красный гейт`);
|
||||
assert.match(decision.lines.join('\n'), new RegExp(`связность выросла: ${name} `));
|
||||
}
|
||||
// bundleBytes — с полосой, как gzip-потолок #438: чужой коммит в dev меняет
|
||||
// dist на сотни байт, и точное число красило бы каждую ветку после ребейза.
|
||||
assert.deepEqual(compareWithBaseline({ ...base, bundleBytes: base.bundleBytes + BUNDLE_BYTES_BAND }, base).grown, []);
|
||||
assert.deepEqual(compareWithBaseline({ ...base, bundleBytes: base.bundleBytes + BUNDLE_BYTES_BAND + 1 }, base).grown.map((g) => g.name), ['bundleBytes']);
|
||||
assert.deepEqual(compareWithBaseline({ ...base, bundleBytes: base.bundleBytes - BUNDLE_BYTES_BAND - 1 }, base).shrunk.map((g) => g.name), ['bundleBytes']);
|
||||
assert.equal(decide({ metrics: { ...base, bundleBytes: base.bundleBytes + BUNDLE_BYTES_BAND + 1 }, violations: [], baseline: base }).fail, true);
|
||||
const shrunk = decide({ metrics: { ...base, delegates: 9 }, violations: [], baseline: base });
|
||||
assert.equal(shrunk.fail, true);
|
||||
assert.match(shrunk.lines.join('\n'), /база не опущена: delegates 10 → 9/);
|
||||
assert.equal(decide({ metrics: base, violations: [], baseline: base }).fail, false);
|
||||
// Отсутствие числа в базе — рост (нельзя обнулить метрику, удалив ключ).
|
||||
const { portPrivates, ...withoutOne } = base;
|
||||
void portPrivates;
|
||||
assert.deepEqual(compareWithBaseline(base, withoutOne).grown.map((g) => g.name), ['portPrivates']);
|
||||
});
|
||||
|
||||
test('#624 гейт: нарушения noUnusedLocals красят даже при равной базе; без dist — красный; без базы — красный', () => {
|
||||
const base = { delegates: 1, portMembers: 1, hostRefs: 1, portPrivates: 1, harnessPrivates: 1, bundleBytes: 1 };
|
||||
const violation = { file: 'src/x.ts', line: 3, code: 6133, message: "'y' is declared but its value is never read.", name: 'y' };
|
||||
const red = decide({ metrics: base, violations: [violation], baseline: base });
|
||||
assert.equal(red.fail, true);
|
||||
assert.match(red.lines.join('\n'), /src\/x\.ts:3 TS6133/);
|
||||
assert.equal(decide({ metrics: { ...base, bundleBytes: null }, violations: [], baseline: base }).fail, true);
|
||||
assert.equal(decide({ metrics: base, violations: [], baseline: null }).fail, true);
|
||||
assert.deepEqual(Object.keys(baselineFrom({ ...base, extra: 1 })), METRIC_NAMES);
|
||||
});
|
||||
|
||||
test('#624 живое дерево: база равна текущим числам, нарушений нет (gate:small и Validate судят то же)', () => {
|
||||
const root = fileURLToPath(new URL('../', import.meta.url));
|
||||
const baseline = readBaseline(root);
|
||||
assert.ok(baseline, `${BASELINE_FILE} обязан существовать`);
|
||||
// Компилятор гоняет вся программа (~8 с): один раз здесь, как и в гейте.
|
||||
const { metrics, violations } = collectMetrics(root);
|
||||
assert.deepEqual(violations, [], 'мёртвый код по noUnusedLocals вне порта и харнесса');
|
||||
const { grown, shrunk } = compareWithBaseline(metrics, baseline);
|
||||
// bundleBytes судит гейт после сборки (Validate: Unit tests идут до Build,
|
||||
// локально dist может быть от другого дерева) — здесь пять чисел исходника.
|
||||
const source = (list) => list.filter((s) => s.name !== 'bundleBytes');
|
||||
assert.deepEqual(source(grown), [], 'связность выросла — вернуть или обосновать');
|
||||
assert.deepEqual(source(shrunk), [], 'связность упала — опустить базу: node scripts/unused-locals-gate.mjs --update');
|
||||
assert.equal(typeof readFileSync(join(root, BASELINE_FILE), 'utf8'), 'string');
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
// #624: заморозка текстовых якорей монолита.
|
||||
//
|
||||
// 54 тест-файла читают `src/houseplan-card.ts` / `src/houseplan-editor-runtime.ts`
|
||||
// как текст (напрямую или через `test/houseplan-source.mjs`). Каждый такой
|
||||
// тест красится на переносе метода из карточки в модуль без единой регрессии
|
||||
// — и это главная причина, по которой монолит не разбирается: перенос стоит
|
||||
// дороже, чем оставить как есть. Новые утверждения о монолите доказываются
|
||||
// экспортом и `test-build` (PROCESS.md §2.7), поэтому список ниже может
|
||||
// только уменьшаться. Новое имя здесь — не «добавьте в список», а вопрос
|
||||
// «почему контракт нельзя проверить исполнением».
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { join } from 'node:path';
|
||||
|
||||
/** Признак якоря: имя монолита или общий хелпер-реконструктор в тексте теста. */
|
||||
export const MONOLITH_ANCHOR_RE = /houseplan-source\.mjs|houseplan-(?:card|editor-runtime)\.ts/;
|
||||
|
||||
/** Замороженный список — 2026-09-23, #624. Уменьшать свободно. */
|
||||
export const FROZEN_TEXT_ANCHOR_TESTS = [
|
||||
'bundle-assets.test.mjs', 'card-version.test.mjs', 'check-inputs.test.mjs', 'classify-changes.test.mjs',
|
||||
'coincident-partitions.test.mjs', 'color-picker.test.mjs', 'config-adoption-ownership.test.mjs',
|
||||
'coordinate-canonicalization.test.mjs', 'coordinate-write-barrier-guard.test.mjs', 'core-file-budget.test.mjs',
|
||||
'danger-confirmation.test.mjs', 'data-hp-contract.test.mjs', 'device-hit-owner-contract.test.mjs',
|
||||
'device-marker-polish-contract.test.mjs', 'device-position-echo.test.mjs', 'device-presentation-policy.test.mjs',
|
||||
'device-presentation.test.mjs', 'devices.test.mjs', 'draft-live-preflight.test.mjs', 'editor-dialog-modules.test.mjs',
|
||||
'editor-runtime-loader.test.mjs', 'fixed-floor-contract.test.mjs', 'furniture-assets.test.mjs',
|
||||
'furniture-stroke-contract.test.mjs', 'furniture-transform-contract.test.mjs', 'golden-matrix.test.mjs',
|
||||
'houseplan-panel.test.mjs', 'hp-dialog-contract.test.mjs', 'i18n.test.mjs', 'isometric-contract.test.mjs',
|
||||
'junction-limits.test.mjs', 'mutation-gate.test.mjs', 'native-select-contract.test.mjs', 'near-axis.test.mjs',
|
||||
'open-passage-contract.test.mjs', 'optional-space-model-contract.test.mjs', 'paper-scene-contract.test.mjs',
|
||||
'performance-contract.test.mjs', 'process-gate.test.mjs', 'rebase-on-dev.test.mjs', 'release-contract.test.mjs',
|
||||
'render-device-snapshot.test.mjs', 'resize-production-path.test.mjs', 'review-doc-guard.test.mjs',
|
||||
'room-temperature-renderers.test.mjs', 'source-fingerprint.test.mjs', 'summary-panel.test.mjs',
|
||||
'support-feedback.test.mjs', 'unified-wall-tool-source.test.mjs', 'version-recovery-card-contract.test.mjs',
|
||||
'view-accessibility.test.mjs', 'wall-segment-model.test.mjs', 'wall-union-isolation.test.mjs',
|
||||
'writer-fixed-point.test.mjs',
|
||||
];
|
||||
|
||||
/** Файлы, которых нет в замороженном списке, но которые читают монолит текстом. */
|
||||
export function newTextAnchors(actual, frozen = FROZEN_TEXT_ANCHOR_TESTS) {
|
||||
const allowed = new Set(frozen);
|
||||
return actual.filter((name) => !allowed.has(name)).sort();
|
||||
}
|
||||
|
||||
export function textAnchorTests(dir) {
|
||||
return readdirSync(dir)
|
||||
.filter((name) => name.endsWith('.test.mjs') && name !== 'monolith-text-anchors.test.mjs')
|
||||
.filter((name) => MONOLITH_ANCHOR_RE.test(readFileSync(join(dir, name), 'utf8')))
|
||||
.sort();
|
||||
}
|
||||
|
||||
test('#624 AC4: ни одного нового теста, читающего монолит как текст', () => {
|
||||
const dir = fileURLToPath(new URL('./', import.meta.url));
|
||||
const actual = textAnchorTests(dir);
|
||||
assert.deepEqual(newTextAnchors(actual), [],
|
||||
'новый контракт по монолиту доказывается экспортом и test-build, не regex по src/houseplan-card.ts (PROCESS.md §2.7, #624)');
|
||||
// Список — верхняя граница, не инвентарь: ушедшие имена не возвращаются.
|
||||
assert.ok(actual.length <= FROZEN_TEXT_ANCHOR_TESTS.length);
|
||||
});
|
||||
|
||||
test('#624 AC4: подмена имени и новый файл видны как новые якоря, а не как «столько же»', () => {
|
||||
const frozen = ['a.test.mjs', 'b.test.mjs'];
|
||||
assert.deepEqual(newTextAnchors(['a.test.mjs', 'b.test.mjs'], frozen), []);
|
||||
assert.deepEqual(newTextAnchors(['a.test.mjs'], frozen), [], 'уменьшение свободно');
|
||||
// Та же длина, другое имя: сравнение по множеству, не по числу.
|
||||
assert.deepEqual(newTextAnchors(['a.test.mjs', 'c.test.mjs'], frozen), ['c.test.mjs']);
|
||||
assert.deepEqual(newTextAnchors(['a.test.mjs', 'b.test.mjs', 'c.test.mjs'], frozen), ['c.test.mjs']);
|
||||
});
|
||||
@@ -28,7 +28,8 @@ test('#290 authoring, Optimize and renderer use one near-axis threshold source',
|
||||
assert.match(renderer, /import \{ NEAR_AXIS_MAX_DEGREES \} from '\.\/near-axis';/);
|
||||
assert.match(renderer,
|
||||
/export const MULTI_WALL_NEAR_ORTHOGONAL_MAX_DEGREES = NEAR_AXIS_MAX_DEGREES;/);
|
||||
assert.match(authoring, /import \{ snapNearAxisEndpoint \} from '\.\/near-axis';/);
|
||||
// #624: импорт живёт в редакторском рантайме — карточка держала мёртвую копию.
|
||||
assert.match(sourceOf('houseplan-editor-runtime.ts'), /import \{ snapNearAxisEndpoint \} from '\.\/near-axis';/);
|
||||
assert.match(authoring, /snapNearAxisEndpoint\(/);
|
||||
assert.match(optimizer, /import \{ repairNearAxisRoomWalls \} from '\.\/near-axis';/);
|
||||
assert.match(optimizer, /repairNearAxisRoomWalls\(space\)/);
|
||||
|
||||
@@ -97,8 +97,10 @@ const methodBody = (source, name) => {
|
||||
|
||||
test('atomic plan render paths do not bypass RenderDeviceSnapshot with this.hass', () => {
|
||||
const source = readHouseplanProductionSource();
|
||||
// #624: `_sunNow` был мёртвым методом (никто не звал) и снят; путь солнца
|
||||
// читает состояние через `_renderSunRays`, который остаётся в списке.
|
||||
for (const name of [
|
||||
'_roomLqi', '_resolvedRoomFills', '_sunNow', '_renderSunRays', '_renderGlowLayer',
|
||||
'_roomLqi', '_resolvedRoomFills', '_renderSunRays', '_renderGlowLayer',
|
||||
'_renderVacuums', '_renderVacFit', '_renderDevice', '_roomTemp', '_roomHum', '_openingAmt',
|
||||
'_renderOpeningLocks', '_renderDecorLayer',
|
||||
]) {
|
||||
|
||||
Reference in New Issue
Block a user