mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 06:38:57 +00:00
Release v1.74.0 — слой пространства с ключом
Догоняем main после #534: смена пространства больше не сверяет двести маркеров поэлементно, чтобы затем всё выбросить. Issue: #534 User-Visible: no Release: v1.74.0
This commit is contained in:
@@ -1,109 +1,109 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039",
|
||||
"fingerprint": "b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 287242,
|
||||
"initialViewGzipBytes": 287302,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-card.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 289548,
|
||||
"initialPanelGzipBytes": 289606,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2306,
|
||||
"initialPanelOnlyGzipBytes": 2304,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/de-BAfQ3noX.js",
|
||||
"houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"houseplan-assets/fr-a4v8Ohtu.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-B_T0ilCj.js",
|
||||
"houseplan-assets/iso-scene-render-DtlvcvOT.js",
|
||||
"houseplan-assets/live-interaction-runtime-CjYDG1SP.js",
|
||||
"houseplan-assets/pdf-export-CY9TOlZ5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Cez46hoy.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/de-Cl_N0-YX.js",
|
||||
"houseplan-assets/editor-DAm0GfXr.js",
|
||||
"houseplan-assets/fr-D490TXwk.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-xOc6gz5D.js",
|
||||
"houseplan-assets/iso-scene-render-B6PFBRuR.js",
|
||||
"houseplan-assets/live-interaction-runtime-uOonwwaS.js",
|
||||
"houseplan-assets/pdf-export-CYH2TEQI.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js"
|
||||
],
|
||||
"lazyGzipBytes": 417431,
|
||||
"lazyGzipBytes": 417421,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/editor-DAm0GfXr.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 204999,
|
||||
"lazyEditorGzipBytes": 204997,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 13916,
|
||||
"lazyOnboardingGzipBytes": 13918,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-BAfQ3noX.js",
|
||||
"houseplan-assets/fr-a4v8Ohtu.js"
|
||||
"houseplan-assets/de-Cl_N0-YX.js",
|
||||
"houseplan-assets/fr-D490TXwk.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 54820,
|
||||
"lazyLocaleGzipBytes": 54821,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/iso-scene-render-DtlvcvOT.js"
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/iso-scene-render-B6PFBRuR.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 13460,
|
||||
"lazyIsometricGzipBytes": 13456,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 10254,
|
||||
"lazyFurnitureArtGzipBytes": 10251,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/pdf-export-CY9TOlZ5.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/pdf-export-CYH2TEQI.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 120128,
|
||||
"lazyPdfGzipBytes": 120123,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"sha256": "ce22e7599ea095db52bf13da52204107ed2d535fb57f69766e3669dfea1c9ef2",
|
||||
"path": "houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"sha256": "04f4f6ebe74df3384c918202679b3ff9380729b553ff798dafe33ba5231eaaa4",
|
||||
"rawBytes": 19956,
|
||||
"gzipBytes": 6879,
|
||||
"gzipBytes": 6880,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-BAfQ3noX.js",
|
||||
"sha256": "38d0ffe6551c85bc7a5686943679357a726928644136850dc646e0cdd5713bab",
|
||||
"path": "houseplan-assets/de-Cl_N0-YX.js",
|
||||
"sha256": "2ea60918c00765be8acb766982dcc9b58715271c62f2eef3dabc07218dd70f64",
|
||||
"rawBytes": 91402,
|
||||
"gzipBytes": 27694,
|
||||
"gzipBytes": 27695,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"sha256": "fd8b9731bc4a417cb0545e06677c836e98fa59a7b28e7cf0345d87c25f4aaed7",
|
||||
"path": "houseplan-assets/editor-DAm0GfXr.js",
|
||||
"sha256": "2524ee570c6d20502ae83cbacddc596882d293b27f7943ae59521c7b1923e796",
|
||||
"rawBytes": 3832,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1586,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-a4v8Ohtu.js",
|
||||
"sha256": "ccc6ccd7d3736f7567080f000de2dd04ecf60989163720e46d11063cd3ace9ee",
|
||||
"path": "houseplan-assets/fr-D490TXwk.js",
|
||||
"sha256": "8ff8d7ac1efd3359263752a7e11a26b3c34bc12ae874108c684a07ea2ff9590f",
|
||||
"rawBytes": 93667,
|
||||
"gzipBytes": 27126,
|
||||
"isEntry": false,
|
||||
@@ -111,199 +111,199 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"sha256": "d8e59b11da6c8f4a665b33cc6d92e9eb9e2e9ef94fed1071ba5988ce5c98e438",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"sha256": "016eabf8c967f1a22d567a35f83102a80b9d14ce864162308bb833855e440c40",
|
||||
"rawBytes": 33655,
|
||||
"gzipBytes": 10254,
|
||||
"gzipBytes": 10251,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/guard-BYWHhzjw.js",
|
||||
"sha256": "b81804f104d25d22dbc909341c48821dbe950aad94054b9512f5322a04fdfc8b",
|
||||
"path": "houseplan-assets/guard-zx2aaPuf.js",
|
||||
"sha256": "3455ec5c4d0ea4b31e517eb4aa029de858d0078fc6e59d49737612224440a98f",
|
||||
"rawBytes": 518,
|
||||
"gzipBytes": 369,
|
||||
"gzipBytes": 371,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"sha256": "d8cf65be7367f51ed4c8069825965c002036ec16321e3dc83dce1638ec7d2565",
|
||||
"rawBytes": 1020996,
|
||||
"gzipBytes": 286437,
|
||||
"path": "houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"sha256": "69a22d0bc488c22923dcc434d2e77fa2d9f12eb75a37c4b388507de7a4d9ce9d",
|
||||
"rawBytes": 1021194,
|
||||
"gzipBytes": 286498,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-BAfQ3noX.js",
|
||||
"houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"houseplan-assets/fr-a4v8Ohtu.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-B_T0ilCj.js",
|
||||
"houseplan-assets/iso-scene-render-DtlvcvOT.js",
|
||||
"houseplan-assets/live-interaction-runtime-CjYDG1SP.js",
|
||||
"houseplan-assets/pdf-export-CY9TOlZ5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js"
|
||||
"houseplan-assets/de-Cl_N0-YX.js",
|
||||
"houseplan-assets/editor-DAm0GfXr.js",
|
||||
"houseplan-assets/fr-D490TXwk.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-xOc6gz5D.js",
|
||||
"houseplan-assets/iso-scene-render-B6PFBRuR.js",
|
||||
"houseplan-assets/live-interaction-runtime-uOonwwaS.js",
|
||||
"houseplan-assets/pdf-export-CYH2TEQI.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"sha256": "b26691e5ac6338578a23ab3d6c0a774e36c4e708f2bb4a48894ab7e4b0ca7c96",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"sha256": "7433aa76bf0dd431661b9ca16546c81599493662392375ea421940a13e6c865f",
|
||||
"rawBytes": 668813,
|
||||
"gzipBytes": 178484,
|
||||
"gzipBytes": 178479,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-Cez46hoy.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js",
|
||||
"sha256": "e1fc28837937564eee4a602230cdaf3fbabe35703a7ecda43ce74def1159d0b7",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js",
|
||||
"sha256": "66085f37bd788c2f5e2d50d580655e93325a6b707b003500b953fdf815b926d8",
|
||||
"rawBytes": 28287,
|
||||
"gzipBytes": 7037,
|
||||
"gzipBytes": 7038,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-B_T0ilCj.js",
|
||||
"sha256": "d9f0676bc0034a3baed8c9108f8799aa261cbabbef86929ef812cd90e0e9e302",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-xOc6gz5D.js",
|
||||
"sha256": "c1007a28222b185c910284669d96f6dc941a2d7606b3194086f8c4fb29cdfd6f",
|
||||
"rawBytes": 10756,
|
||||
"gzipBytes": 3684,
|
||||
"gzipBytes": 3683,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Cez46hoy.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-DtlvcvOT.js",
|
||||
"sha256": "ad3e8c6b41caf6edc5e3b259dcffa3bff98c0110ae80cbd3bf736d5eccfb7d0f",
|
||||
"path": "houseplan-assets/iso-scene-render-B6PFBRuR.js",
|
||||
"sha256": "aa5903e34a2fd82469d195a19e4288c07b29d20130a9f99ed649903c2c6d6353",
|
||||
"rawBytes": 38562,
|
||||
"gzipBytes": 13091,
|
||||
"gzipBytes": 13085,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-CjYDG1SP.js",
|
||||
"sha256": "886197ec8207c347463a743dd3a7a2f95bb05efbe4545332a41c12891cc3242e",
|
||||
"path": "houseplan-assets/live-interaction-runtime-uOonwwaS.js",
|
||||
"sha256": "b26b6e1cadb17533b49d7d824cccc4a649def8557953e632b8075fa2da358594",
|
||||
"rawBytes": 6032,
|
||||
"gzipBytes": 2433,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-CY9TOlZ5.js",
|
||||
"sha256": "0fa7863db13ffe8d654f35cf25222639be2f6c4ed018941feffde0503da2b425",
|
||||
"path": "houseplan-assets/pdf-export-CYH2TEQI.js",
|
||||
"sha256": "1bf1783f9f3f80b37d38b5000a831860334f1a4cb1aacb58dc172b6c9f25f65b",
|
||||
"rawBytes": 236244,
|
||||
"gzipBytes": 109874,
|
||||
"gzipBytes": 109872,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-DeBQ1tcV.js",
|
||||
"sha256": "b1bbc6089d46f90c81c295e0305374ca2f2c5fca0322f27d4e9ecb9bb76ead28",
|
||||
"path": "houseplan-assets/summary-panel-editor-TASGFcvs.js",
|
||||
"sha256": "9e4e2bd331c2f5fdfb6cc6719f09795c5556d712fd961e2cd18d561f2dfee7e2",
|
||||
"rawBytes": 14824,
|
||||
"gzipBytes": 3401,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-B3ruGJoU.js",
|
||||
"sha256": "d68458815e4a7772cd87bf55f1448ba1794c54b1e6c749825f0672952051a93c",
|
||||
"path": "houseplan-assets/summary-panel-metrics-bIh_Q9NB.js",
|
||||
"sha256": "e50c875e83ee220fc99121b013f04f3417b8c1412d2d08265b94f4d2e3c74f57",
|
||||
"rawBytes": 2610,
|
||||
"gzipBytes": 1306,
|
||||
"gzipBytes": 1308,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js",
|
||||
"sha256": "d7d78a52ff91ce0dd1d07eb8e670e931814ef71c969f67069485d1313adeafe4",
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js",
|
||||
"sha256": "78d50efb02e10eaeb8a5c4d299e98f298606d1d95a419e6bfdfdf8dc37ff36a9",
|
||||
"rawBytes": 72272,
|
||||
"gzipBytes": 19728,
|
||||
"gzipBytes": 19727,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-DeBQ1tcV.js",
|
||||
"houseplan-assets/summary-panel-metrics-B3ruGJoU.js"
|
||||
"houseplan-assets/summary-panel-editor-TASGFcvs.js",
|
||||
"houseplan-assets/summary-panel-metrics-bIh_Q9NB.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-DitREaKv.js",
|
||||
"sha256": "d29bb086e6862441a6115d431ed71e4ee0b96201a10bdfedb93d3f11280264ca",
|
||||
"path": "houseplan-assets/zigbee-topology-70mzkKN3.js",
|
||||
"sha256": "789d0ec02c2b0133838b379975944bb2730ee6c4679c9262000861d8457535a5",
|
||||
"rawBytes": 18961,
|
||||
"gzipBytes": 7430,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-Cez46hoy.js",
|
||||
"sha256": "5bb047f1c003a211e0a57f2fa03d37e968144ca741d99b2f3e486ee70e4aa130",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js",
|
||||
"sha256": "04c8c7571c42d0659a412b4d560d3dbc504209631591f58ab7c86afd3580fedf",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1765,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "2cf259caacb83ef37c3e5e743f88bcd61a6f48131af71fa565528f409b2f1f82",
|
||||
"sha256": "6bf9432b3ce24211e3771939aca869d04bf03e4412c118129b3559d9561d6923",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 805,
|
||||
"gzipBytes": 804,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "02fa46808fbf68c9245ec2b518ec2047953678b13b054a923259f1a8d9e9cb9a",
|
||||
"sha256": "fbdee8bf86166bcbca6e899b1ffb00f840eda83bb4a7edb66390bd08982c5a7b",
|
||||
"rawBytes": 6124,
|
||||
"gzipBytes": 2306,
|
||||
"gzipBytes": 2304,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-card.js"
|
||||
|
||||
+6
-6
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,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{m as e,l as o,t,e3 as s,A as a,e4 as i,e5 as l,e6 as n,dU as r,b as c}from"./houseplan-card-DEVy5dLN.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__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{m as e,l as o,t,e3 as s,A as a,e4 as i,e5 as l,e6 as n,dU as r,b as c}from"./houseplan-card-bOJWMeFY.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 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{dS as r,dT as t,dU as s}from"./houseplan-card-DEVy5dLN.js";const e={},a=r(class extends t{constructor(){super(...arguments),this.ot=e}render(r,t){return t()}update(r,[t,e]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return s}else if(this.ot===t)return s;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,e)}});export{a as i};
|
||||
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{dS as r,dT as t,dU as a}from"./houseplan-card-bOJWMeFY.js";const s={},e=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return a}else if(this.ot===t)return a;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{e as i};
|
||||
+91
-91
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
+28
-28
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
+33
-33
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__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{b as a,A as e}from"./houseplan-card-DEVy5dLN.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-CB4AHgRN.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__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{b as a,A as e}from"./houseplan-card-bOJWMeFY.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-cFcfVYdb.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
|
||||
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
|
||||
<input id="summary-local-show" data-summary-local-show type="checkbox"
|
||||
-1
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{e9 as e,L as t,a2 as n,J as i,an as o,N as r,bo as s,bn as a,a1 as c,a0 as l,ax as d,ea as u,eb as f,bq as m,ec as g}from"./houseplan-card-DEVy5dLN.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 b(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*_(c,l,d=b){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 b=m.cellCm/o;u+=a(v)*b*b/1e4}return u}catch{return null}}function h(e,t,n=b){const i=_(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function p(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=u(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{_ as cleanFloorAreaSteps,v as representedHaDeviceIds,b as spaceWallGeometry,S as summaryEntityValue,p as summarySystemValue,h as totalCleanFloorAreaM2};
|
||||
+1
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{e9 as e,L as t,a2 as n,J as i,an as o,N as a,bo as r,bn as s,a1 as c,a0 as l,ax as d,ea as u,eb as f,bq as m,ec as g}from"./houseplan-card-bOJWMeFY.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 a=i.binding.slice(0,o),r=i.binding.slice(o+1),s="device"===a?e.registry.devices?.[r]:null,c="entity"===a?e.registry.entities?.[r]:null,l="device"===a?r:c?.device_id,d="device"===a?s?.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"===a&&n.liveEntities.has(r))&&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,a);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,a)}}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 s=t(e);if(!s)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,a,g.roomGeom,g.multiWallNodes)||s;v=y(v,r(c,m.physicalBodies)),yield}const _=m.cellCm/o;u+=s(v)*_*_/1e4}return u}catch{return null}}function p(e,t,n=_){const i=h(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function b(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=u(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{h as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,b as summarySystemValue,p as totalCleanFloorAreaM2};
|
||||
+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__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{n as t,b as n}from"./zigbee-topology-DitREaKv.js";import{B as e}from"./houseplan-card-DEVy5dLN.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),b=new Promise((t,n)=>{f=t,m=n}),w=[];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&&w.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&w.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(b,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of w)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{n as t,b as n}from"./zigbee-topology-70mzkKN3.js";import{B as e}from"./houseplan-card-bOJWMeFY.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";try{await import("./houseplan-assets/houseplan-card-DEVy5dLN.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__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";try{await import("./houseplan-assets/houseplan-card-bOJWMeFY.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
@@ -0,0 +1,141 @@
|
||||
// Стоимость растеризации дневного цикла — свидетель #532.
|
||||
//
|
||||
// Владелец: «в Firefox план тормозит; со статичным цветом фона тормоза
|
||||
// пропадают». Профиль подтвердил: наш JS спал 89 % времени, а в GPU-процессе
|
||||
// на кадр уезжало 23 МБ текстур. Виновником оказалась не сцена плана, а
|
||||
// тройной drop-shadow внешнего контура: группа .hp-paperg несёт одни бумажные
|
||||
// силуэты и при ховере и панораме не меняется, но фильтр жил в общем слое
|
||||
// плана — и каждая перерисовка плана заново прогоняла три прохода размытия по
|
||||
// габариту всего листа. Лечится одной подсказкой will-change: filter, которая
|
||||
// уводит отфильтрованную бумагу в свой композиционный слой.
|
||||
//
|
||||
// Смок проверяет две вещи, и вторая — не время, а ОТНОШЕНИЕ: абсолютные
|
||||
// миллисекунды зависят от машины и раннера, отношение «дневной цикл к
|
||||
// статичному фону» — нет. Оба замера идут в одном процессе, на одной странице,
|
||||
// подряд, поэтому делят между собой и прогрев, и загрузку машины.
|
||||
//
|
||||
// Замер аналитики на панораме демо-стенда: 7.9 без подсказки, 0.26 с ней.
|
||||
// Порог 2.0 стоит примерно посередине по логарифму и даёт запас в обе стороны.
|
||||
//
|
||||
// Движок здесь Chromium — он же движок CI и golden. Приговор по Gecko даёт
|
||||
// профиль Firefox с машины владельца: подсказка это совет движку, и Gecko
|
||||
// вправе отклонить её по бюджету памяти (layout.css.will-change.budget).
|
||||
// Смок нужен не вместо того профиля, а чтобы правку нельзя было молча снять.
|
||||
import { launch, check, finish } from './serve.mjs';
|
||||
|
||||
const PAN_STEPS = 60;
|
||||
const RATIO_CEILING = 2.0;
|
||||
|
||||
const { page, browser } = await launch({ width: 1400, height: 900 }, 1);
|
||||
const cdp = await page.context().newCDPSession(page);
|
||||
|
||||
const setBackground = async (mode) => {
|
||||
await page.evaluate(async (mode) => {
|
||||
const card = window.__card;
|
||||
card._serverCfg.settings = { ...(card._serverCfg.settings || {}), bg_mode: mode };
|
||||
// Пространство со своим bg_mode переопределяет глобальный — снимаем, иначе
|
||||
// смок мерил бы не то, что переключил.
|
||||
for (const space of card._serverCfg.spaces) {
|
||||
space.settings = { ...(space.settings || {}) };
|
||||
delete space.settings.bg_mode;
|
||||
}
|
||||
card.hass = { ...card.hass, states: { ...card.hass.states, 'sun.sun': {
|
||||
entity_id: 'sun.sun', state: 'above_horizon',
|
||||
attributes: { azimuth: 180, elevation: 40, rising: false },
|
||||
} } };
|
||||
card._cfgRev = (card._cfgRev || 0) + 1;
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
}, mode);
|
||||
// Переход фильтра длится 1100 мс: замер до его конца мерил бы анимацию.
|
||||
await page.waitForTimeout(1400);
|
||||
};
|
||||
|
||||
const outlineStyle = () => page.evaluate(() => {
|
||||
const root = window.__card.shadowRoot || window.__card.renderRoot;
|
||||
const stage = root.querySelector('.stage');
|
||||
const group = root.querySelector('.hp-paperg');
|
||||
if (!group) return null;
|
||||
const style = getComputedStyle(group);
|
||||
return {
|
||||
daycycle: stage.classList.contains('daycycle'),
|
||||
filter: style.filter,
|
||||
willChange: style.willChange,
|
||||
};
|
||||
});
|
||||
|
||||
const stageBox = () => page.evaluate(() => {
|
||||
const root = window.__card.shadowRoot || window.__card.renderRoot;
|
||||
const rect = root.querySelector('.stage').getBoundingClientRect();
|
||||
return { x: rect.x, y: rect.y, w: rect.width, h: rect.height };
|
||||
});
|
||||
|
||||
/** Одна панорама с зажатой кнопкой; возвращает суммарный RasterTask в мс. */
|
||||
const measurePan = async () => {
|
||||
const box = await stageBox();
|
||||
const events = [];
|
||||
const collect = ({ value }) => events.push(...value);
|
||||
cdp.on('Tracing.dataCollected', collect);
|
||||
await cdp.send('Tracing.start', {
|
||||
traceConfig: { includedCategories: ['disabled-by-default-devtools.timeline'] },
|
||||
transferMode: 'ReportEvents',
|
||||
});
|
||||
const cx = box.x + box.w / 2, cy = box.y + box.h / 2;
|
||||
await page.mouse.move(cx, cy);
|
||||
await page.mouse.down();
|
||||
for (let step = 0; step < PAN_STEPS; step += 1) {
|
||||
await page.mouse.move(cx + Math.sin(step / 6) * 140, cy + Math.cos(step / 9) * 90);
|
||||
// Два кадра на шаг: первый ставит кадр в очередь, второй ждёт, пока он
|
||||
// действительно отрисуется. Иначе замер считает не работу, а очередь.
|
||||
await page.evaluate(() => new Promise((resolve) => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
||||
}));
|
||||
}
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(200);
|
||||
const complete = new Promise((resolve) => cdp.once('Tracing.tracingComplete', resolve));
|
||||
await cdp.send('Tracing.end');
|
||||
await complete;
|
||||
cdp.off('Tracing.dataCollected', collect);
|
||||
let micros = 0, tasks = 0;
|
||||
for (const event of events) {
|
||||
if (event.name !== 'RasterTask' || event.ph !== 'X' || typeof event.dur !== 'number') continue;
|
||||
micros += event.dur;
|
||||
tasks += 1;
|
||||
}
|
||||
return { ms: micros / 1000, tasks };
|
||||
};
|
||||
|
||||
const out = {};
|
||||
|
||||
// 1. Подсказка стоит там и только там, где стоит фильтр.
|
||||
await setBackground('daynight');
|
||||
const dayStyle = await outlineStyle();
|
||||
out.dayCycleClassOn = dayStyle?.daycycle === true;
|
||||
out.dayCycleOutlineFiltered = /drop-shadow/.test(dayStyle?.filter || '');
|
||||
out.dayCycleOutlinePromoted = /filter/.test(dayStyle?.willChange || '');
|
||||
|
||||
await setBackground('static');
|
||||
const staticStyle = await outlineStyle();
|
||||
out.staticClassOff = staticStyle?.daycycle === false;
|
||||
out.staticOutlineUnfiltered = (staticStyle?.filter || 'none') === 'none';
|
||||
// Статичный фон не платит за чужую подсказку ни слоем, ни памятью.
|
||||
out.staticOutlineNotPromoted = !/filter/.test(staticStyle?.willChange || '');
|
||||
|
||||
// 2. Отношение растеризации на настоящей панораме.
|
||||
const staticPan = await measurePan();
|
||||
await setBackground('daynight');
|
||||
const dayPan = await measurePan();
|
||||
|
||||
out.rasterTasksObserved = staticPan.tasks > 0 && dayPan.tasks > 0;
|
||||
const ratio = staticPan.ms > 0 ? dayPan.ms / staticPan.ms : Infinity;
|
||||
out.rasterRatioWithinBudget = ratio <= RATIO_CEILING;
|
||||
|
||||
console.log(JSON.stringify({
|
||||
staticPanMs: Number(staticPan.ms.toFixed(1)), staticPanTasks: staticPan.tasks,
|
||||
dayCyclePanMs: Number(dayPan.ms.toFixed(1)), dayCyclePanTasks: dayPan.tasks,
|
||||
ratio: Number(ratio.toFixed(2)), ceiling: RATIO_CEILING,
|
||||
}, null, 1));
|
||||
|
||||
for (const [name, value] of Object.entries(out)) check(name, value);
|
||||
await finish(browser, out);
|
||||
@@ -148,7 +148,62 @@ const res = await page.evaluate(async () => {
|
||||
const onRealChange = runningAnimations();
|
||||
out.aRealDoorOpeningStillAnimates = onRealChange
|
||||
.filter((entry) => /op-leaf/.test(entry.cls)).length >= 1;
|
||||
// ---- 3) состав списка меняется ВНУТРИ пространства (#534) --------------
|
||||
// Внешний ключ `keyed(space.id, …)` снимает дорогой диф на смене
|
||||
// пространства, но внутри пространства состав списков всё равно ездит:
|
||||
// у маркеров — призраки редактора и живой синк, у проёмов — запись с
|
||||
// нерешённым хостом, которая живёт ТОЛЬКО в режиме plan. Если убрать
|
||||
// внутренний `repeat`, позиционное переиспользование вернётся через эту
|
||||
// дверь, и ни одна проверка выше не покраснеет.
|
||||
await settle();
|
||||
const byId = (nodes) => new Map(nodes.map((node) => [node.dataset.id, node]));
|
||||
const markerNodes = () => [...root().querySelectorAll('[data-hp="device"]')];
|
||||
const openingNodes = () => [...root().querySelectorAll('.opening')];
|
||||
|
||||
const markersKept = byId(markerNodes());
|
||||
const template = c._renderDevices.find((device) => device.space === c._space);
|
||||
out.markerTemplateFound = !!template;
|
||||
c._devices = [{ ...template, id: 'hp-534-probe', name: 'probe' }, ...c._devices];
|
||||
c._cfgEpoch++;
|
||||
c.requestUpdate();
|
||||
await c.updateComplete;
|
||||
await frame();
|
||||
const markersAfterInsert = byId(markerNodes());
|
||||
const movedMarkers = [...markersKept].filter(([id, node]) =>
|
||||
markersAfterInsert.has(id) && markersAfterInsert.get(id) !== node);
|
||||
out.listGrewInsideTheSpace = markersAfterInsert.size > markersKept.size;
|
||||
out.noMarkerNodeSwappedInsideTheSpace = movedMarkers.length === 0;
|
||||
out.swappedMarkerNodes = movedMarkers.map(([id]) => id);
|
||||
out.quietAfterTheInsert = runningAnimations().filter((entry) => !EXPECTED(entry)).length === 0;
|
||||
|
||||
// Проёмы: запись с нерешённым хостом отдаётся только в режиме plan, поэтому
|
||||
// вход в редактор добавляет её в список и сдвигает позиции остальных.
|
||||
await settle();
|
||||
const space = c._serverCfg.spaces.find((item) => item.id === c._space);
|
||||
const donor = space.openings[0];
|
||||
out.openingDonorFound = !!donor;
|
||||
space.openings = [
|
||||
{ ...donor, id: 'hp-534-orphan', host: { kind: 'partition', id: 'hp-534-missing' } },
|
||||
...space.openings,
|
||||
];
|
||||
c._cfgEpoch++;
|
||||
c.requestUpdate();
|
||||
await c.updateComplete;
|
||||
await settle();
|
||||
const openingsKept = byId(openingNodes());
|
||||
c._setMode('plan');
|
||||
await c.updateComplete;
|
||||
await frame();
|
||||
const openingsInPlan = byId(openingNodes());
|
||||
const movedOpenings = [...openingsKept].filter(([id, node]) =>
|
||||
openingsInPlan.has(id) && openingsInPlan.get(id) !== node);
|
||||
out.orphanAppearsOnlyInPlan = openingsInPlan.size > openingsKept.size;
|
||||
out.noOpeningNodeSwappedInsideTheSpace = movedOpenings.length === 0;
|
||||
out.swappedOpeningNodes = movedOpenings.map(([id]) => id);
|
||||
out.quietAfterTheModeSwitch = runningAnimations()
|
||||
.filter((entry) => /op-leaf|op-arc/.test(entry.cls)).length === 0;
|
||||
return out;
|
||||
});
|
||||
checkAll(res, { unexpectedAnimations: [], reusedMarkerNodes: [] });
|
||||
checkAll(res, { unexpectedAnimations: [], reusedMarkerNodes: [],
|
||||
swappedMarkerNodes: [], swappedOpeningNodes: [] });
|
||||
await finish(browser, res);
|
||||
|
||||
Vendored
+137
-137
@@ -1,109 +1,109 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039",
|
||||
"fingerprint": "b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 287242,
|
||||
"initialViewGzipBytes": 287302,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-card.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 289548,
|
||||
"initialPanelGzipBytes": 289606,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2306,
|
||||
"initialPanelOnlyGzipBytes": 2304,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/de-BAfQ3noX.js",
|
||||
"houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"houseplan-assets/fr-a4v8Ohtu.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-B_T0ilCj.js",
|
||||
"houseplan-assets/iso-scene-render-DtlvcvOT.js",
|
||||
"houseplan-assets/live-interaction-runtime-CjYDG1SP.js",
|
||||
"houseplan-assets/pdf-export-CY9TOlZ5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Cez46hoy.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/de-Cl_N0-YX.js",
|
||||
"houseplan-assets/editor-DAm0GfXr.js",
|
||||
"houseplan-assets/fr-D490TXwk.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-xOc6gz5D.js",
|
||||
"houseplan-assets/iso-scene-render-B6PFBRuR.js",
|
||||
"houseplan-assets/live-interaction-runtime-uOonwwaS.js",
|
||||
"houseplan-assets/pdf-export-CYH2TEQI.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js"
|
||||
],
|
||||
"lazyGzipBytes": 417431,
|
||||
"lazyGzipBytes": 417421,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/editor-DAm0GfXr.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 204999,
|
||||
"lazyEditorGzipBytes": 204997,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 13916,
|
||||
"lazyOnboardingGzipBytes": 13918,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-BAfQ3noX.js",
|
||||
"houseplan-assets/fr-a4v8Ohtu.js"
|
||||
"houseplan-assets/de-Cl_N0-YX.js",
|
||||
"houseplan-assets/fr-D490TXwk.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 54820,
|
||||
"lazyLocaleGzipBytes": 54821,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/iso-scene-render-DtlvcvOT.js"
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/iso-scene-render-B6PFBRuR.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 13460,
|
||||
"lazyIsometricGzipBytes": 13456,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 10254,
|
||||
"lazyFurnitureArtGzipBytes": 10251,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/pdf-export-CY9TOlZ5.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/pdf-export-CYH2TEQI.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 120128,
|
||||
"lazyPdfGzipBytes": 120123,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"sha256": "ce22e7599ea095db52bf13da52204107ed2d535fb57f69766e3669dfea1c9ef2",
|
||||
"path": "houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"sha256": "04f4f6ebe74df3384c918202679b3ff9380729b553ff798dafe33ba5231eaaa4",
|
||||
"rawBytes": 19956,
|
||||
"gzipBytes": 6879,
|
||||
"gzipBytes": 6880,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-BAfQ3noX.js",
|
||||
"sha256": "38d0ffe6551c85bc7a5686943679357a726928644136850dc646e0cdd5713bab",
|
||||
"path": "houseplan-assets/de-Cl_N0-YX.js",
|
||||
"sha256": "2ea60918c00765be8acb766982dcc9b58715271c62f2eef3dabc07218dd70f64",
|
||||
"rawBytes": 91402,
|
||||
"gzipBytes": 27694,
|
||||
"gzipBytes": 27695,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"sha256": "fd8b9731bc4a417cb0545e06677c836e98fa59a7b28e7cf0345d87c25f4aaed7",
|
||||
"path": "houseplan-assets/editor-DAm0GfXr.js",
|
||||
"sha256": "2524ee570c6d20502ae83cbacddc596882d293b27f7943ae59521c7b1923e796",
|
||||
"rawBytes": 3832,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1586,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-a4v8Ohtu.js",
|
||||
"sha256": "ccc6ccd7d3736f7567080f000de2dd04ecf60989163720e46d11063cd3ace9ee",
|
||||
"path": "houseplan-assets/fr-D490TXwk.js",
|
||||
"sha256": "8ff8d7ac1efd3359263752a7e11a26b3c34bc12ae874108c684a07ea2ff9590f",
|
||||
"rawBytes": 93667,
|
||||
"gzipBytes": 27126,
|
||||
"isEntry": false,
|
||||
@@ -111,199 +111,199 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"sha256": "d8e59b11da6c8f4a665b33cc6d92e9eb9e2e9ef94fed1071ba5988ce5c98e438",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"sha256": "016eabf8c967f1a22d567a35f83102a80b9d14ce864162308bb833855e440c40",
|
||||
"rawBytes": 33655,
|
||||
"gzipBytes": 10254,
|
||||
"gzipBytes": 10251,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/guard-BYWHhzjw.js",
|
||||
"sha256": "b81804f104d25d22dbc909341c48821dbe950aad94054b9512f5322a04fdfc8b",
|
||||
"path": "houseplan-assets/guard-zx2aaPuf.js",
|
||||
"sha256": "3455ec5c4d0ea4b31e517eb4aa029de858d0078fc6e59d49737612224440a98f",
|
||||
"rawBytes": 518,
|
||||
"gzipBytes": 369,
|
||||
"gzipBytes": 371,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"sha256": "d8cf65be7367f51ed4c8069825965c002036ec16321e3dc83dce1638ec7d2565",
|
||||
"rawBytes": 1020996,
|
||||
"gzipBytes": 286437,
|
||||
"path": "houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"sha256": "69a22d0bc488c22923dcc434d2e77fa2d9f12eb75a37c4b388507de7a4d9ce9d",
|
||||
"rawBytes": 1021194,
|
||||
"gzipBytes": 286498,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-BAfQ3noX.js",
|
||||
"houseplan-assets/editor-Ca9vnH-K.js",
|
||||
"houseplan-assets/fr-a4v8Ohtu.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-B_T0ilCj.js",
|
||||
"houseplan-assets/iso-scene-render-DtlvcvOT.js",
|
||||
"houseplan-assets/live-interaction-runtime-CjYDG1SP.js",
|
||||
"houseplan-assets/pdf-export-CY9TOlZ5.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js"
|
||||
"houseplan-assets/de-Cl_N0-YX.js",
|
||||
"houseplan-assets/editor-DAm0GfXr.js",
|
||||
"houseplan-assets/fr-D490TXwk.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-xOc6gz5D.js",
|
||||
"houseplan-assets/iso-scene-render-B6PFBRuR.js",
|
||||
"houseplan-assets/live-interaction-runtime-uOonwwaS.js",
|
||||
"houseplan-assets/pdf-export-CYH2TEQI.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BVcD08Op.js",
|
||||
"sha256": "b26691e5ac6338578a23ab3d6c0a774e36c4e708f2bb4a48894ab7e4b0ca7c96",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-B0CHFoUQ.js",
|
||||
"sha256": "7433aa76bf0dd431661b9ca16546c81599493662392375ea421940a13e6c865f",
|
||||
"rawBytes": 668813,
|
||||
"gzipBytes": 178484,
|
||||
"gzipBytes": 178479,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/zigbee-topology-runtime-Cez46hoy.js"
|
||||
"houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-0ApfaTVi.js",
|
||||
"sha256": "e1fc28837937564eee4a602230cdaf3fbabe35703a7ecda43ce74def1159d0b7",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-Dts4ebgR.js",
|
||||
"sha256": "66085f37bd788c2f5e2d50d580655e93325a6b707b003500b953fdf815b926d8",
|
||||
"rawBytes": 28287,
|
||||
"gzipBytes": 7037,
|
||||
"gzipBytes": 7038,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-yGeZ_1BL.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/backdrop-pick-BVE5pvXV.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-B_T0ilCj.js",
|
||||
"sha256": "d9f0676bc0034a3baed8c9108f8799aa261cbabbef86929ef812cd90e0e9e302",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-xOc6gz5D.js",
|
||||
"sha256": "c1007a28222b185c910284669d96f6dc941a2d7606b3194086f8c4fb29cdfd6f",
|
||||
"rawBytes": 10756,
|
||||
"gzipBytes": 3684,
|
||||
"gzipBytes": 3683,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-Cez46hoy.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-DtlvcvOT.js",
|
||||
"sha256": "ad3e8c6b41caf6edc5e3b259dcffa3bff98c0110ae80cbd3bf736d5eccfb7d0f",
|
||||
"path": "houseplan-assets/iso-scene-render-B6PFBRuR.js",
|
||||
"sha256": "aa5903e34a2fd82469d195a19e4288c07b29d20130a9f99ed649903c2c6d6353",
|
||||
"rawBytes": 38562,
|
||||
"gzipBytes": 13091,
|
||||
"gzipBytes": 13085,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/guard-BYWHhzjw.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/guard-zx2aaPuf.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-CjYDG1SP.js",
|
||||
"sha256": "886197ec8207c347463a743dd3a7a2f95bb05efbe4545332a41c12891cc3242e",
|
||||
"path": "houseplan-assets/live-interaction-runtime-uOonwwaS.js",
|
||||
"sha256": "b26b6e1cadb17533b49d7d824cccc4a649def8557953e632b8075fa2da358594",
|
||||
"rawBytes": 6032,
|
||||
"gzipBytes": 2433,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-CY9TOlZ5.js",
|
||||
"sha256": "0fa7863db13ffe8d654f35cf25222639be2f6c4ed018941feffde0503da2b425",
|
||||
"path": "houseplan-assets/pdf-export-CYH2TEQI.js",
|
||||
"sha256": "1bf1783f9f3f80b37d38b5000a831860334f1a4cb1aacb58dc172b6c9f25f65b",
|
||||
"rawBytes": 236244,
|
||||
"gzipBytes": 109874,
|
||||
"gzipBytes": 109872,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-CWJaBLIz.js",
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-d7Er55Or.js",
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-DeBQ1tcV.js",
|
||||
"sha256": "b1bbc6089d46f90c81c295e0305374ca2f2c5fca0322f27d4e9ecb9bb76ead28",
|
||||
"path": "houseplan-assets/summary-panel-editor-TASGFcvs.js",
|
||||
"sha256": "9e4e2bd331c2f5fdfb6cc6719f09795c5556d712fd961e2cd18d561f2dfee7e2",
|
||||
"rawBytes": 14824,
|
||||
"gzipBytes": 3401,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-B3ruGJoU.js",
|
||||
"sha256": "d68458815e4a7772cd87bf55f1448ba1794c54b1e6c749825f0672952051a93c",
|
||||
"path": "houseplan-assets/summary-panel-metrics-bIh_Q9NB.js",
|
||||
"sha256": "e50c875e83ee220fc99121b013f04f3417b8c1412d2d08265b94f4d2e3c74f57",
|
||||
"rawBytes": 2610,
|
||||
"gzipBytes": 1306,
|
||||
"gzipBytes": 1308,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-CB4AHgRN.js",
|
||||
"sha256": "d7d78a52ff91ce0dd1d07eb8e670e931814ef71c969f67069485d1313adeafe4",
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-cFcfVYdb.js",
|
||||
"sha256": "78d50efb02e10eaeb8a5c4d299e98f298606d1d95a419e6bfdfdf8dc37ff36a9",
|
||||
"rawBytes": 72272,
|
||||
"gzipBytes": 19728,
|
||||
"gzipBytes": 19727,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-DeBQ1tcV.js",
|
||||
"houseplan-assets/summary-panel-metrics-B3ruGJoU.js"
|
||||
"houseplan-assets/summary-panel-editor-TASGFcvs.js",
|
||||
"houseplan-assets/summary-panel-metrics-bIh_Q9NB.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-DitREaKv.js",
|
||||
"sha256": "d29bb086e6862441a6115d431ed71e4ee0b96201a10bdfedb93d3f11280264ca",
|
||||
"path": "houseplan-assets/zigbee-topology-70mzkKN3.js",
|
||||
"sha256": "789d0ec02c2b0133838b379975944bb2730ee6c4679c9262000861d8457535a5",
|
||||
"rawBytes": 18961,
|
||||
"gzipBytes": 7430,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-Cez46hoy.js",
|
||||
"sha256": "5bb047f1c003a211e0a57f2fa03d37e968144ca741d99b2f3e486ee70e4aa130",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-CE0rjXi5.js",
|
||||
"sha256": "04c8c7571c42d0659a412b4d560d3dbc504209631591f58ab7c86afd3580fedf",
|
||||
"rawBytes": 3941,
|
||||
"gzipBytes": 1765,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js",
|
||||
"houseplan-assets/zigbee-topology-DitREaKv.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js",
|
||||
"houseplan-assets/zigbee-topology-70mzkKN3.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "2cf259caacb83ef37c3e5e743f88bcd61a6f48131af71fa565528f409b2f1f82",
|
||||
"sha256": "6bf9432b3ce24211e3771939aca869d04bf03e4412c118129b3559d9561d6923",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 805,
|
||||
"gzipBytes": 804,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-DEVy5dLN.js"
|
||||
"houseplan-assets/houseplan-card-bOJWMeFY.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "02fa46808fbf68c9245ec2b518ec2047953678b13b054a923259f1a8d9e9cb9a",
|
||||
"sha256": "fbdee8bf86166bcbca6e899b1ffb00f840eda83bb4a7edb66390bd08982c5a7b",
|
||||
"rawBytes": 6124,
|
||||
"gzipBytes": 2306,
|
||||
"gzipBytes": 2304,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-card.js"
|
||||
|
||||
Vendored
+6
-6
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,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{m as e,l as o,t,e3 as s,A as a,e4 as i,e5 as l,e6 as n,dU as r,b as c}from"./houseplan-card-DEVy5dLN.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__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{m as e,l as o,t,e3 as s,A as a,e4 as i,e5 as l,e6 as n,dU as r,b as c}from"./houseplan-card-bOJWMeFY.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 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{dS as r,dT as t,dU as s}from"./houseplan-card-DEVy5dLN.js";const e={},a=r(class extends t{constructor(){super(...arguments),this.ot=e}render(r,t){return t()}update(r,[t,e]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return s}else if(this.ot===t)return s;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,e)}});export{a as i};
|
||||
+1
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{dS as r,dT as t,dU as a}from"./houseplan-card-bOJWMeFY.js";const s={},e=r(class extends t{constructor(){super(...arguments),this.ot=s}render(r,t){return t()}update(r,[t,s]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((r,t)=>r===this.ot[t]))return a}else if(this.ot===t)return a;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,s)}});export{e as i};
|
||||
Vendored
+91
-91
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
+28
-28
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
+33
-33
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__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{b as a,A as e}from"./houseplan-card-DEVy5dLN.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-CB4AHgRN.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__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{b as a,A as e}from"./houseplan-card-bOJWMeFY.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-cFcfVYdb.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
|
||||
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
|
||||
<input id="summary-local-show" data-summary-local-show type="checkbox"
|
||||
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{e9 as e,L as t,a2 as n,J as i,an as o,N as r,bo as s,bn as a,a1 as c,a0 as l,ax as d,ea as u,eb as f,bq as m,ec as g}from"./houseplan-card-DEVy5dLN.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 b(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*_(c,l,d=b){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 b=m.cellCm/o;u+=a(v)*b*b/1e4}return u}catch{return null}}function h(e,t,n=b){const i=_(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function p(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=u(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{_ as cleanFloorAreaSteps,v as representedHaDeviceIds,b as spaceWallGeometry,S as summaryEntityValue,p as summarySystemValue,h as totalCleanFloorAreaM2};
|
||||
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{e9 as e,L as t,a2 as n,J as i,an as o,N as a,bo as r,bn as s,a1 as c,a0 as l,ax as d,ea as u,eb as f,bq as m,ec as g}from"./houseplan-card-bOJWMeFY.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 a=i.binding.slice(0,o),r=i.binding.slice(o+1),s="device"===a?e.registry.devices?.[r]:null,c="entity"===a?e.registry.entities?.[r]:null,l="device"===a?r:c?.device_id,d="device"===a?s?.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"===a&&n.liveEntities.has(r))&&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,a);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,a)}}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 s=t(e);if(!s)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,a,g.roomGeom,g.multiWallNodes)||s;v=y(v,r(c,m.physicalBodies)),yield}const _=m.cellCm/o;u+=s(v)*_*_/1e4}return u}catch{return null}}function p(e,t,n=_){const i=h(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function b(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=u(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{h as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,b as summarySystemValue,p as totalCleanFloorAreaM2};
|
||||
+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__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";import{n as t,b as n}from"./zigbee-topology-DitREaKv.js";import{B as e}from"./houseplan-card-DEVy5dLN.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),b=new Promise((t,n)=>{f=t,m=n}),w=[];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&&w.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&w.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(b,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of w)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";import{n as t,b as n}from"./zigbee-topology-70mzkKN3.js";import{B as e}from"./houseplan-card-bOJWMeFY.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function g(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function m(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,m=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,m=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&m?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await g(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await g(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,m as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="dfcbb1b71778cc7ba07be84f07b876575d5c63600838b93cdb2db147b99ec039";try{await import("./houseplan-assets/houseplan-card-DEVy5dLN.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__="b3e08d50a47faaba7562dc080f267ca13de845113735892a9115a6cdd991392a";try{await import("./houseplan-assets/houseplan-card-bOJWMeFY.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
@@ -2,6 +2,23 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Switching between floors on a large plan is quick again. Every switch was
|
||||
rebuilding the device markers the slow way — comparing two hundred old
|
||||
markers against two hundred new ones only to throw all of them away — which
|
||||
cost about a tenth of a second of a frozen card each time. Nothing looks
|
||||
different, and the door that never moved still does not animate
|
||||
([#534](https://github.com/Matysh/houseplan-card/issues/534)).
|
||||
|
||||
- The “Follows the sun” background no longer slows the plan down. The plan's
|
||||
outer outline is a blur over the whole sheet, and it lived in the same layer
|
||||
as the plan: any mouse movement made the engine re-run it. The outline now
|
||||
keeps its own layer. Measured on a demo-stand pan, rasterization fell from
|
||||
fifteen times a static background to the same order as one. The view is the
|
||||
same: the outline around the plan matches pixel for pixel, and the hatch
|
||||
inside the walls is anti-aliased a shade differently — splitting the layer
|
||||
moves it by a sub-pixel
|
||||
([#532](https://github.com/Matysh/houseplan-card/issues/532)).
|
||||
|
||||
## v1.74.0 — 2026-09-11
|
||||
|
||||
- The plan is noticeably smoother, and in Firefox the difference is dramatic.
|
||||
|
||||
@@ -8,6 +8,22 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Переключение между этажами на большом плане снова быстрое. Каждое
|
||||
переключение пересобирало маркеры устройств самым долгим способом — сверяло
|
||||
двести старых маркеров с двумя сотнями новых, чтобы затем выбросить все, — и
|
||||
это стоило около десятой доли секунды подвисшей карточки. Внешне не меняется
|
||||
ничего, и дверь, которая никуда не двигалась, по-прежнему не анимируется
|
||||
([#534](https://github.com/Matysh/houseplan-card/issues/534)).
|
||||
|
||||
- Фон «Следует за Солнцем» больше не замедляет план. Внешний контур плана —
|
||||
это размытие по габариту всего листа, и оно жило в общем слое с планом: любое
|
||||
движение мыши заставляло движок прогонять его заново. Теперь контур держится
|
||||
отдельным слоем. Замер на панораме демо-стенда: растеризация упала с
|
||||
пятнадцатикратной к статичному фону до сопоставимой с ним. Вид прежний:
|
||||
ореол вокруг плана совпадает пиксель в пиксель, а штриховка внутри стен
|
||||
сглаживается чуть иначе — разделение слоёв сдвигает её на субпиксель
|
||||
([#532](https://github.com/Matysh/houseplan-card/issues/532)).
|
||||
|
||||
## v1.74.0 — 2026-09-11
|
||||
|
||||
- План стал заметно плавнее, а в Firefox разница разительная. Перетаскивание
|
||||
|
||||
+11
@@ -75,6 +75,17 @@ The public setting remains a two-value selector. `static` uses `bg_color`.
|
||||
05:00→13:00→21:00 fallback arc. It has zero visible opacity at night.
|
||||
- Four constant environment layers cross-fade for exactly 1100 ms with
|
||||
`cubic-bezier(.22,.61,.36,1)`. Reduced motion disables the transition.
|
||||
- The zero-offset outline is a filter over the grouped paper footprint, so it
|
||||
is kept on its own compositing layer (`will-change: filter`, scoped to the
|
||||
day-cycle stage). The group holds paper silhouettes only and does not change
|
||||
while the plan is hovered or panned; without the hint every repaint of the
|
||||
plan re-ran three blur passes over the whole sheet. Measured in headless
|
||||
Chromium (CDP tracing, summed `RasterTask`) on a demo-stand pan, that cost
|
||||
about fifteen times the rasterization of a static background; the owner's
|
||||
Firefox profile that opened #532 showed the same cause as 23 MB of texture
|
||||
uploads per frame and about nine frames per second, without a comparable
|
||||
ratio of its own. A static background carries neither the filter nor the
|
||||
hint.
|
||||
- Only the environment and the zero-offset alpha-aware outline outside the
|
||||
grouped plan-paper footprint change. The plan, paper, floors, room fills,
|
||||
Glow/spill, devices, labels, decor/backdrop, vacuum, hover, and window rays
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"fixture": "synthetic-only",
|
||||
"chromium": "151.0.7922.34",
|
||||
"oxipng": "oxipng 10.2.0",
|
||||
"sourceFingerprint": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceFingerprint": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"captureScriptSha256": "9e4b0ae533407fe003a129078aec020f2c83d67cdb6fc6da696ce3d829beaeb5",
|
||||
"command": "npm run build && node demo/docs/capture.mjs",
|
||||
"scenarios": {
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "76c35a87810e3dc6890bda7b43da133f57d8860031204a62cda7115c15950090"
|
||||
},
|
||||
"view-touch": {
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "795585acb09488236ebb8028a7fcbd918404c5fe01560bb9b4559de13d441625"
|
||||
},
|
||||
"space-create": {
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "9f0970de09772674c3156b9fc5b2d49de0ceb22be11ecf8b136047d37d100a84"
|
||||
},
|
||||
"room-contour-close": {
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "0c354a79f9c3660788b6c5e889a27b6bd78b1f209dc0eef6bb43e2c1344028c4"
|
||||
},
|
||||
"plan-context-tray": {
|
||||
@@ -59,7 +59,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "64169c543291bc4df806dcf8ff484b42c8ea55eb343323d945344e0e28f2ce97"
|
||||
},
|
||||
"device-editor": {
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "3c8703175605fcf0387d654cd30783ecfd33e21743e70b45190a5ae1a8d355c2"
|
||||
},
|
||||
"device-display-preview": {
|
||||
@@ -81,7 +81,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "5d6193202102f5e6d85567f1111fa105895b933979fbf369c6ba0b1029deb823"
|
||||
},
|
||||
"background-editor": {
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "375900a29aa62632883ede6727b7dbb8b48cabe9d7d0e16425f9779c837f3dd9"
|
||||
},
|
||||
"room-card": {
|
||||
@@ -103,7 +103,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "3d85424676c823ddca3fa7dab3cce089b6030e48c05d16d2d48d922c66c0cbd2"
|
||||
},
|
||||
"device-info": {
|
||||
@@ -114,7 +114,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "ac962820f8e1c5cb5b042d39b18dc95905fa840cb2d4749060d991be4f6977c2"
|
||||
},
|
||||
"pdf-export": {
|
||||
@@ -125,7 +125,7 @@
|
||||
},
|
||||
"theme": "light",
|
||||
"language": "en",
|
||||
"sourceSha256": "45f506aca7baceaa68ed03b4a7d97f05c8c22c42976287b6f3b42274f0122327",
|
||||
"sourceSha256": "d11c6433c3b10ec39a97f45ea5a1ea80da8443b80f8ab34a6e7bd4bd70aee64a",
|
||||
"imageSha256": "4537692fd57a3ed95528eddd748eee5cb6e456ffab7cc49169853d62459252cd"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
# CODE-REVIEW-532-r1
|
||||
|
||||
Вердикт: **жёлтый** · заход r1 · блокирующих циклов израсходовано 0 из 4 · High: 0 · Medium: 2 (в скоупе, возврат автору)
|
||||
|
||||
Материал: `bb04f8605f9003c0ec6643359fa47af9319247bc` (ветка приведена к `dev`
|
||||
конвейером; `origin/dev..HEAD` содержит один коммит, диплист диффа полный, не по
|
||||
дельте — цикл первый).
|
||||
|
||||
## Скоуп
|
||||
|
||||
Issue #532: тройной `drop-shadow` внешнего контура плана (`.hp-paperg`) живёт в
|
||||
общем композиционном слое с планом, поэтому любая перерисовка плана (ховер,
|
||||
панорама) заново прогоняет три прохода размытия по габариту всего листа —
|
||||
на машине владельца (Firefox) это ~9 к/с и 23 МБ текстур/кадр. Правка:
|
||||
`will-change: filter` на правиле `.stage.daycycle .hp-paperg,
|
||||
.hp-static-stage.daycycle .hp-paperg`, уводящее отфильтрованную бумагу в свой
|
||||
слой. Побочный эффект контракта (К2, принят на спек-ревью r2): промоушен делит
|
||||
SVG на два слоя, из-за чего диагональная штриховка стен антиалиасится иначе —
|
||||
4 golden-кадра `day-cycle-{dawn,day,dusk,night}-dark` расходятся и подлежат
|
||||
пересъёмке на кандидате беты, а не в этом коммите.
|
||||
|
||||
Продуктовая рамка: View-режим — продукт для двух из трёх персон (`SCOPE.md`),
|
||||
настенный планшет и телефон читают план именно там; ускорение растеризации
|
||||
дневного цикла закрывает именно этот сценарий, палитру/геометрию/слои
|
||||
окружения/блендинг не трогает (К3, К4 — вне скоупа, подтверждено кодом, см.
|
||||
ниже). Продукт кода вне `plan.styles.ts` не тронут.
|
||||
|
||||
Спек-ревью прошло два раунда (r1, r2 — оба зелёные); r2 переписал К2/AC3 после
|
||||
того, как автор сам обнаружил на реализации, что «golden останется зелёным» не
|
||||
подтвердилось, и вернул ТЗ на переработку вместо подгонки факта под старый
|
||||
текст. Код-ревью (этот документ) — первый заход, полный разбор.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Дешёвые гейты (`tsc`, `npm test`, `npm run build` + сверка 3 копий бандла,
|
||||
`check-docs.mjs`) подтверждены зелёным прогоном Validate на этом самом SHA
|
||||
(https://github.com/Matysh/houseplan-card/actions/runs/34622197915) — повторно
|
||||
не гонял, кроме случаев ниже, где перегон был нужен как побочный эффект.
|
||||
|
||||
Гейты, которые я прогнал сам (а не поверил хендоффу):
|
||||
|
||||
| Гейт | Команда | Результат |
|
||||
|---|---|---|
|
||||
| Смок-свидетель | `node demo/smoke_daycycle_raster.mjs` (после `npm run bundle:sync`, локальный ratio 0.73 — другая машина, тот же порядок, что у автора 0.76/0.82) | OK, 8/8 |
|
||||
| Мутант | `node scripts/mutation-gate.mjs --id=daycycle-outline-not-promoted` | `тест покраснел, как обязан` — поймано 1 из 1 |
|
||||
| Селектор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | НЕОПРЕДЕЛЁННОСТЬ (0 символов на изменённой строке) — совпадает с заявленным риском 5 |
|
||||
| Полная golden-матрица | `node demo/golden/run.mjs --mode=verify` (169 сценариев) | ровно 4 `different`: `day-cycle-{dawn,day,dusk,night}-dark`; всё остальное `passed`, включая 7 сценариев, которые в хендоффе автора были помечены как «шум песочницы» — здесь (пиновый Chromium) они все зелёные |
|
||||
| Мой собственный замер среднего цвета кадра по всем 4 кадрам (не только `night-dark`, как в хендоффе) | одноразовый скрипт через `createImageBitmap`/`getImageData`, baseline vs `artifacts/golden/actual/*`, удалён после прогона | `dawn` Δ−0.001, `day` Δ−0.014, `dusk` Δ+0.047, `night` Δ+0.080 из 255 — все четыре внутри заявленного допуска «не дальше 0.1» |
|
||||
| Ограничивающий прямоугольник расхождения по всем 4 кадрам | тот же одноразовый прогон | `[120‑121, 36, 867, 757‑758]` — идентичен по всем четырём кадрам и совпадает с рамкой, которую автор привёл для `night-dark`; расхождение не задевает ореол снаружи плана |
|
||||
| Три копии бандла | `diff -q dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` (+ `houseplan-assets.json`) | идентичны |
|
||||
|
||||
Не прогонял: `pytest tests_backend` (бэкенд не задет), `npm run invariants`
|
||||
(диффа геометрии/модели/`layout`/`marker.space`/`open_spans` нет — правка
|
||||
чисто в CSS), performance-профили Validate (не названы в AC), полный
|
||||
`npm test`/`tsc`/`build` заново как отдельный гейт (уже зелёные на этом SHA
|
||||
по Validate; `npm run build`+`bundle:sync` я всё равно выполнил как побочный
|
||||
эффект, чтобы поднять локальный смок/golden — красных мест не дали).
|
||||
`smoke_glow*`/`smoke_discovery_filters` не гонял: слабое совпадение по имени
|
||||
(«свет», «фильтр»), но К3/К4 прямо объявляют слои окружения и блендинг вне
|
||||
скоупа, а код-факт (`svgScreenBlendSupported` не читает `bg_mode`) это
|
||||
подтверждает — смотреть не было необходимости.
|
||||
|
||||
Рабочая копия оставлена чистой: все временные скрипты и `artifacts/golden/`
|
||||
удалены после использования, `git status` пуст.
|
||||
|
||||
## AC · чем доказан · чем краснеет (проверено самостоятельно)
|
||||
|
||||
| # | AC | Чем доказан | Чем краснеет | Проверено |
|
||||
|---|---|---|---|---|
|
||||
| AC1 | Подсказка `will-change: filter` стоит только при `daynight`, статичный фон — без неё | `demo/smoke_daycycle_raster.mjs`, часть 1 | мутант `daycycle-outline-not-promoted` | сам прогнал: смок зелёный на коде, красный на мутанте |
|
||||
| AC2 | Отношение растеризации на панораме ≤ 2.0 | тот же смок, часть 2 (CDP `RasterTask`) | тот же мутант (без декларации отношение ~15) | сам прогнал: 0.73 на коде, мутант красный |
|
||||
| AC3 | Изменились ровно 4 кадра `day-cycle-*-dark`; расхождение — антиалиасинг штриховки, не изменение вида (ореол цел, средний цвет кадра сдвинут ≤0.1/255) | полная golden-матрица + разбор пикселей | 5-й кадр либо сдвиг среднего цвета | сам прогнал полную матрицу (169 сценариев): ровно 4 different; сам посчитал средний цвет и bbox расхождения для **всех четырёх** кадров (не только `night-dark`, как в хендоффе) — все внутри допуска, bbox идентичен по всем четырём |
|
||||
| AC4 | Статичный фон не платит: ни `filter`, ни `will-change` | тот же смок | декларация без `.daycycle` | сам прогнал вместе с AC1 |
|
||||
| AC5 | `docs/SUN.md` описывает промоушен как часть контракта; оба чейнджлога на месте | построчная сверка ревьюером | расхождение документа и кода | сверил построчно — обе строки чейнджлога и абзац SUN.md на месте, но см. находки ниже |
|
||||
|
||||
AC3 закрыт по существу мной лично, а не на веру: спек-ревью r2 прямо
|
||||
отметило, что автор экстраполировал порог сдвига цвета с одного разобранного
|
||||
кадра (`night-dark`) на оставшиеся три и делегировало код-ревью обязанность
|
||||
проверить это «на реальном прогоне». В хендоффе автора эта проверка
|
||||
по-прежнему покрывала только `night-dark` количественно (три другие только
|
||||
помечены `different` в таблице). Я прогнал полную матрицу и лично посчитал
|
||||
средний цвет и bbox расхождения для `dawn`/`day`/`dusk`-dark — все три ведут
|
||||
себя идентично `night-dark` (тот же bbox, тот же порядок сдвига цвета), так
|
||||
что риск экстраполяции снят фактом, а не доверием.
|
||||
|
||||
## Находки
|
||||
|
||||
### Medium-1 (в скоупе) — чейнджлог утверждает то, что опровергает сам контракт задачи
|
||||
|
||||
`docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` (новая запись в `## Unreleased`):
|
||||
|
||||
> The picture is unchanged, to the pixel ([#532]).
|
||||
> Картинка не изменилась ни на пиксель ([#532]).
|
||||
|
||||
Это буквально неверно и противоречит К2/AC3 этой же задачи: 3.3–3.5 %
|
||||
пикселей на каждом из четырёх кадров дневного цикла расходятся (максимальное
|
||||
отклонение канала 81 из 255) — именно поэтому эталоны `day-cycle-*-dark`
|
||||
пересматриваются отдельным коммитом на кандидате беты. Сообщение самого
|
||||
коммита формулирует это точнее («the diagonal wall hatch anti-aliases
|
||||
differently and the four day-cycle baselines are re-taken»), а публичный
|
||||
чейнджлог — нет.
|
||||
|
||||
**Воспроизведение:** сравнить текст `docs/CHANGELOG.md` с любым из четырёх
|
||||
диффов golden-матрицы (`node demo/golden/run.mjs --mode=verify`,
|
||||
`day-cycle-night-dark`: 26 442 / 770 640 пикселей выше `maxChannelDelta: 10`).
|
||||
|
||||
**Правка:** перефразировать обе строки чейнджлога так, чтобы не утверждать
|
||||
побайтовую идентичность — например, в духе текста коммита («картинка читается
|
||||
так же; антиалиасинг штриховки стен смещается на несколько пикселей, эталоны
|
||||
дневного цикла пересматриваются отдельно, см. #532»).
|
||||
|
||||
### Medium-2 (в скоупе) — `docs/SUN.md` приписывает измерение в Chromium браузеру Firefox
|
||||
|
||||
`docs/SUN.md`, новый абзац (AC5):
|
||||
|
||||
> …without the hint every repaint of the plan re-ran three blur passes over
|
||||
> the whole sheet, which cost **a Firefox window** about **fifteen times**
|
||||
> the rasterization of a static background (#532).
|
||||
|
||||
Отношение «пятнадцать раз» — это измерение мутанта/смока через CDP-трассировку
|
||||
в headless **Chromium** (см. хендофф: «без правки… отношение 15.06»; тот же
|
||||
порядок в моём собственном прогоне мутанта). Профиль владельца в Firefox дал
|
||||
качественные цифры (23 МБ текстур/кадр, ~9 к/с, вклад drop-shadow 72–75 % от
|
||||
стоимости растеризации), но никогда не давал коэффициент «×15» — сам документ
|
||||
ТЗ и ревью r1/r2 явно фиксируют, что Gecko не профилировался этим свидетелем
|
||||
(«Chromium — не Gecko… приговор по Gecko даёт профиль владельца»). Формулировка
|
||||
SUN.md смешивает два источника числа под одной меткой, что противоречит
|
||||
собственной дисциплине проекта «одно число — один источник».
|
||||
|
||||
**Воспроизведение:** сверить абзац SUN.md с хендоффом/комментарием аналитики —
|
||||
цифра 15× нигде не связана с Firefox, только с Chromium/CDP.
|
||||
|
||||
**Правка:** заменить «a Firefox window» на «our Chromium/CI measurement» (или
|
||||
эквивалент), не приписывая коэффициент профилю владельца.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- Селектор и объявление `will-change: filter` в `src/styles/plan.styles.ts`
|
||||
дословно совпадают с К1 (в т.ч. вторая ветка селектора `.hp-static-stage`);
|
||||
комментарий над правилом даёт числа Chromium/CDP корректно (852 мс / 28 мс /
|
||||
108 мс) — в отличие от SUN.md, здесь атрибуция источника числа верна.
|
||||
- `.hp-paperg` действительно группа только бумажных силуэтов
|
||||
(`src/houseplan-card.ts:11534`, `_paperShapes(space.rooms)`), не меняется
|
||||
при ховере/панораме — механика из ТЗ подтверждена чтением, не только словом
|
||||
аналитика.
|
||||
- `scripts/mutation-gate.mjs`: патч `daycycle-outline-not-promoted` находит
|
||||
ровно ту строку, которая добавлена в диффе (сверено побайтово через `grep`),
|
||||
мутация подтверждена прогоном.
|
||||
- `demo/golden/matrix.mjs` содержит ровно 4 сценария с `bgMode: 'daynight'` —
|
||||
структурно подтверждает и «К2 живёт только под `.daycycle`», и «ровно четыре
|
||||
кадра» из AC3.
|
||||
- `svgScreenBlendSupported` (`src/glow-blend.ts`) — рантайм-проба, не читающая
|
||||
`bg_mode` — подтверждает К4 (блендинг света вне скоупа) кодом.
|
||||
- `docs/images/screenshots.json`: изменился только `sourceFingerprint`
|
||||
(отпечаток `src/**`), все 11 `imageSha256` не изменились — соответствует
|
||||
заявлению «`npm run docs:accept -- --identical`», документация не поехала.
|
||||
- Три копии бандла (`dist/`, `custom_components/houseplan/frontend/`)
|
||||
побайтово идентичны; сборка воспроизводится (`npm run build` +
|
||||
`bundle:sync` на этом дереве прошли чисто).
|
||||
- Коммит несёт `Issue: #532` и `User-Visible: yes`; обе строки чейнджлога — в
|
||||
том же коммите, что и поведение (не в отдельном).
|
||||
- Продуктовый вопрос автора («приемлемо ли платить пересъёмкой 4 эталонов за
|
||||
скорость») не новый: это ровно К2/AC3, уже разобранные и принятые зелёным
|
||||
спек-ревью r2 с полным раскрытием факта (byte-for-byte ореол, сдвиг
|
||||
среднего цвета, план пересъёмки с `Release:`/`Baseline-Reviewed:`). Код
|
||||
реализует то, что было одобрено; переоткрывать это как продуктовое решение
|
||||
на этапе код-ревью нет оснований — я лишь перепроверил цифру на всех
|
||||
четырёх кадрах вместо одного (см. AC3 выше).
|
||||
- Риск 5 из ТЗ (`smoke-select` не находит связь) подтверждён независимо:
|
||||
тот же вывод «НЕОПРЕДЕЛЁННОСТЬ» на моём прогоне, что и в хендоффе.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- `pytest tests_backend` — диффа в `custom_components/**/*.py` нет.
|
||||
- `npm run invariants` — диффа геометрии/модели (рёбра, `layout`,
|
||||
`marker.space`, `open_spans`) нет, только CSS-свойство.
|
||||
- Performance-профили Validate (`performance_smoke` и т.п.) — не названы в
|
||||
AC, диффа в этих путях нет.
|
||||
- Приговор по настоящему Firefox (профиль владельца до/после) — вне
|
||||
возможностей код-ревью; ТЗ явно оставляет это отдельным шагом суждения
|
||||
владельца, а не AC этой задачи.
|
||||
- Полный `smoke_*` матрица (244 файла) — не прогонял целиком; `smoke-select`
|
||||
честно вернул «неопределённость», просмотрел список смоков с похожими
|
||||
именами (`glow*`, `discovery_filters`) и решил не гонять — К3/К4 прямо
|
||||
выводят эти поверхности из скоупа, и код (`svgScreenBlendSupported`) это
|
||||
подтверждает.
|
||||
|
||||
---
|
||||
|
||||
**Материал раунда:** SHA `bb04f8605f9003c0ec6643359fa47af9319247bc`,
|
||||
`origin/dev..HEAD` = 1 коммит, диапазон диффа `origin/dev...HEAD` (53 файла,
|
||||
включая 3 копии бандла и golden-fingerprint документации). Первый заход —
|
||||
раздела «Унаследовано из r0» нет.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/532-daycycle-raster`, коммит `bb04f8605f90` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `959371418fa5d512b6314804fb4d9cf13e86efe4`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 959371418fa5
|
||||
```
|
||||
- Тело issue: `12e66588332b94c78100156f01e4832881b8163fb6f8fdca83acfcb8e90f4545`
|
||||
- Вердикт конвейера: `yellow` · High 0
|
||||
@@ -0,0 +1,181 @@
|
||||
# CODE-REVIEW-532-r2
|
||||
|
||||
Вердикт: **зелёный** · заход r2 · блокирующих циклов израсходовано 1 из 4 · High: 0 · Medium: 0
|
||||
|
||||
Материал: `1cfe7fa53c71b2ba595c92d1bdfc560f90242f40`. Ветка приведена к `dev`
|
||||
конвейером: поверх головы задачи (`a89bd1b8`, тот же коммит, что автор назвал
|
||||
хендоффом r2) легло 7 коммитов `dev` (релиз v1.74.0 и посторонняя задача
|
||||
#533), ветка перемотана на `a89bd1b8 → 1cfe7fa5`. Это другой код по §7.2 —
|
||||
разбор ниже полный, а не только по дельте, хотя `origin/dev..HEAD` содержит
|
||||
всего 3 коммита самой задачи.
|
||||
|
||||
## Скоуп
|
||||
|
||||
Issue #532 (не изменился с r1): тройной `drop-shadow` внешнего контура плана
|
||||
(`.hp-paperg`) жил в общем композиционном слое с планом, поэтому любая
|
||||
перерисовка плана (ховер, панорама) заново прогоняла три прохода размытия по
|
||||
габариту всего листа — в Firefox владельца ~9 к/с, 23 МБ текстур/кадр. Правка:
|
||||
`will-change: filter` на `.stage.daycycle .hp-paperg, .hp-static-stage.daycycle
|
||||
.hp-paperg`, уводящее отфильтрованную бумагу в свой слой. Побочный эффект
|
||||
контракта (К2, принят спек-ревью r2): промоушен делит SVG на два слоя, отчего
|
||||
диагональная штриховка стен антиалиасится иначе — 4 golden-кадра
|
||||
`day-cycle-{dawn,day,dusk,night}-dark` расходятся и пересматриваются отдельным
|
||||
коммитом на кандидате беты. Продуктовая рамка (View-режим как продукт для двух
|
||||
персон, `docs/SCOPE.md`) не тронута этим раундом — перечитал `SCOPE.md`,
|
||||
вывод не изменился.
|
||||
|
||||
Раунд r2 — это ровно исправление двух Medium-находок r1: оба чейнджлога и
|
||||
`docs/SUN.md`. Продуктовый код (`src/styles/plan.styles.ts`), смок
|
||||
(`demo/smoke_daycycle_raster.mjs`) и мутант (`scripts/mutation-gate.mjs`)
|
||||
байт-в-байт не изменились с r1 — доказано ниже, не заявлено.
|
||||
|
||||
## Что изменилось между r1 и HEAD (доказательство байт-в-байт для остального)
|
||||
|
||||
```
|
||||
git diff bb04f8605f9003c0ec6643359fa47af9319247bc ced9b416 -- \
|
||||
src/styles/plan.styles.ts demo/smoke_daycycle_raster.mjs \
|
||||
scripts/mutation-gate.mjs docs/images/screenshots.json
|
||||
```
|
||||
→ пустой вывод: коммит `ced9b416` (содержимое головы задачи после ребейза)
|
||||
идентичен коммиту `bb04f8605f90`, который разобрало r1, по всем файлам кроме
|
||||
самих чейнджлогов/`SUN.md`. Единственный новый коммит раунда —
|
||||
`1cfe7fa53c71b2ba595c92d1bdfc560f90242f40` («Say what the day-cycle layer
|
||||
change does to the picture...»), и он трогает только `docs/CHANGELOG.md`,
|
||||
`docs/CHANGELOG.ru.md`, `docs/SUN.md` (14 строк, `User-Visible: no`).
|
||||
|
||||
Это не молчаливое доверие: равенство деревьев проверено мной командой выше, а
|
||||
не взято со слов автора.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Validate на `1cfe7fa5` зелёный
|
||||
(https://github.com/Matysh/houseplan-card/actions/runs/34625301309) —
|
||||
`tsc`, `npm test`, `npm run build`+сверка бандла, `check-docs.mjs` не
|
||||
перегонял отдельно, кроме случаев ниже.
|
||||
|
||||
Прогнал сам (диф трогает рендер CSS через `ced9b416`, хоть и унаследованный
|
||||
от r1 — golden/смок/мутант не покрыты Validate):
|
||||
|
||||
| Гейт | Команда | Результат |
|
||||
|---|---|---|
|
||||
| Сборка + синхронизация 3 копий бандла | `npm run build && npm run bundle:sync` | чисто, `git status` пуст после — рабочее дерево воспроизводит закоммиченный `dist`/`custom_components/houseplan/frontend`/`demo/srv/assets` побайтово |
|
||||
| Смок-свидетель | `node demo/smoke_daycycle_raster.mjs` | OK, 8/8 (`ratio: 0.78`, другая машина, тот же порядок, что у автора 0.73–0.82) |
|
||||
| Мутант | `node scripts/mutation-gate.mjs --id=daycycle-outline-not-promoted` | «тест покраснел, как обязан» — поймано 1 из 1, рабочее дерево осталось чистым после |
|
||||
| Три копии бандла | `diff -q dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` + `houseplan-assets.json` | идентичны |
|
||||
| `check-docs.mjs` | `node scripts/check-docs.mjs` | passed (7 files, 12 external links) |
|
||||
|
||||
Golden-матрицу (169 сценариев) и построчный анализ 4 кадров дневного цикла
|
||||
**не перегонял** — код, который её результат определяет
|
||||
(`plan.styles.ts`), байт-в-байт тот же, что r1 уже прогнало полностью (ровно
|
||||
4 `different`, bbox и средний цвет посчитаны для всех четырёх кадров
|
||||
самостоятельно, см. `docs/reviews/CODE-REVIEW-532-r1.md`). Замена входа
|
||||
исключена диффом деревьев выше — прогон на неизменных байтах дал бы тот же
|
||||
результат, а сама матрица не из дешёвых.
|
||||
|
||||
Не прогонял: `pytest tests_backend` (бэкенд не задет), `npm run invariants`
|
||||
(диффа геометрии/модели/`layout`/`marker.space`/`open_spans` нет — раунд
|
||||
чисто документационный, а унаследованный код — чистый CSS), performance-
|
||||
профили Validate (не названы в AC), `smoke-select` заново (диф раунда — доки,
|
||||
не `src/**`; для унаследованного CSS-диффа r1 уже получило и задокументировало
|
||||
«НЕОПРЕДЕЛЁННОСТЬ» и обосновало решение не гонять смежные `smoke_glow*`/
|
||||
`smoke_discovery_filters` кодом К3/К4 — не переоткрываю).
|
||||
|
||||
## Закрытие раунда r1
|
||||
|
||||
| Находка r1 | Чем закрыта | Где это видно |
|
||||
|---|---|---|
|
||||
| Medium-1: чейнджлоги утверждали «picture is unchanged, to the pixel» / «не изменилась ни на пиксель», что противоречит К2/AC3 (3.3–3.5 % пикселей расходятся) | Коммит `1cfe7fa5` переписал обе записи: «The view is the same: the outline around the plan matches pixel for pixel, and the hatch inside the walls is anti-aliased a shade differently — splitting the layer moves it by a sub-pixel» (и симметрично по-русски) | `docs/CHANGELOG.md:9-12`, `docs/CHANGELOG.ru.md:15-17` |
|
||||
| Medium-2: `docs/SUN.md` приписывал коэффициент «×15» профилю Firefox, хотя измерен в headless Chromium через CDP | Коммит `1cfe7fa5` разделил источники: «Measured in headless Chromium (CDP tracing, summed `RasterTask`) on a demo-stand pan, that cost about fifteen times…; the owner's Firefox profile that opened #532 showed the same cause as 23 MB of texture uploads per frame and about nine frames per second, without a comparable ratio of its own» | `docs/SUN.md:82-88` |
|
||||
|
||||
Проверил не по заявлению автора, а построчным чтением обоих файлов после
|
||||
правки (см. выше) — формулировки соответствуют находкам r1 дословно, включая
|
||||
рекомендованную ревьюером r1 замену «a Firefox window» → «our
|
||||
Chromium/CI measurement».
|
||||
|
||||
## Унаследовано из r1
|
||||
|
||||
Без повторного прогона (дорогие гейты), на основании доказанного выше
|
||||
байт-в-байт равенства `src/styles/plan.styles.ts`, `demo/smoke_daycycle_raster.mjs`,
|
||||
`scripts/mutation-gate.mjs` между `ced9b416` (голова этого раунда) и
|
||||
`bb04f8605f9003c0ec6643359fa47af9319247bc` (материал r1):
|
||||
|
||||
- AC1/AC2/AC4 (подсказка стоит только под `.daycycle`, отношение растеризации
|
||||
≤ 2.0, статичный фон не платит) — доказаны в r1 смоком + мутантом; я
|
||||
независимо перепрогнал оба на текущем SHA (таблица выше) вместо чистого
|
||||
наследования.
|
||||
- AC3 количественно (полная golden-матрица 169 сценариев: ровно 4
|
||||
`different`; средний цвет и bbox расхождения посчитаны r1 лично для всех
|
||||
четырёх кадров `day-cycle-*-dark`, не только `night-dark`; семь «шумных»
|
||||
кадров подтверждены контролем на базе `origin/dev` тем же Chromium) —
|
||||
наследую без повторного прогона матрицы, см. документ
|
||||
`docs/reviews/CODE-REVIEW-532-r1.md`, SHA `bb04f8605f9003c0ec6643359fa47af9319247bc`.
|
||||
- Структурные факты кодом: `demo/golden/matrix.mjs` содержит ровно 4 сценария
|
||||
с `bgMode: 'daynight'`; `svgScreenBlendSupported` (`src/glow-blend.ts`) не
|
||||
читает `bg_mode` (К4 вне скоупа подтверждено кодом); `.hp-paperg` — только
|
||||
бумажные силуэты (`src/houseplan-card.ts`), не меняется на ховере/панораме.
|
||||
Файлы не изменились с r1 — наследую вывод.
|
||||
- Риск 5 ТЗ (`smoke-select` не находит связь для чисто-CSS диффа) —
|
||||
подтверждён независимо в r1, код не изменился — наследую.
|
||||
- Трейлеры и три копии бандла коммита `ced9b416` (`Issue: #532`,
|
||||
`User-Visible: yes`, оба чейнджлога в одном коммите с поведением) — уже
|
||||
проверены в r1 и не менялись; коммит `1cfe7fa5` несёт свои корректные
|
||||
трейлеры (`Issue: #532`, `User-Visible: no` — меняется только формулировка,
|
||||
не поведение), проверено мной заново.
|
||||
|
||||
## Что проверено и корректно (этот раунд)
|
||||
|
||||
- Оба чейнджлога и `docs/SUN.md` больше не содержат ни одного из двух
|
||||
утверждений, отмеченных r1 как противоречащих контракту задачи; новые
|
||||
формулировки проверяемы читателем и не смешивают источники чисел.
|
||||
- `1cfe7fa5` несёт `Issue: #532` и `User-Visible: no` — корректно: меняется
|
||||
только текст о поведении, не само поведение.
|
||||
- Рабочее дерево после `npm run build && npm run bundle:sync` совпадает с
|
||||
закоммиченными `dist/**`, `custom_components/houseplan/frontend/**`,
|
||||
`demo/srv/assets/**` побайтово (`git status` пуст) — сборка воспроизводима
|
||||
на этом SHA.
|
||||
- `docs/images/screenshots.json` не менялся в этом раунде (проверено диффом
|
||||
дерева) — отпечаток документации не поехал.
|
||||
- Продуктовый вопрос автора из хендоффа r1 («приемлема ли цена пересъёмки 4
|
||||
эталонов») закрыт зелёным спек-ревью r2 и не переоткрывается: этот раунд не
|
||||
трогает К2/AC3 по существу, только их описание в публичных доках.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- `pytest tests_backend` — `custom_components/**/*.py` не задет.
|
||||
- `npm run invariants` — геометрия/модель/`layout`/`marker.space`/
|
||||
`open_spans` не задеты; унаследованный код — чистый CSS без геометрии.
|
||||
- Полная golden-матрица заново — код, который её результат определяет, не
|
||||
изменился (доказано диффом деревьев); повторный прогон на тех же байтах не
|
||||
добавляет информации, а гейт дорогой.
|
||||
- Performance-профили Validate — не названы в AC.
|
||||
- `smoke-select` заново — диф раунда лежит только в `docs/**`, инструмент по
|
||||
документации не разрешает связей; для унаследованного CSS-диффа ответ r1
|
||||
(«НЕОПРЕДЕЛЁННОСТЬ») не переоткрываю.
|
||||
- Приговор по настоящему Firefox (профиль владельца до/после этой конкретной
|
||||
правки текста) — вне возможностей код-ревью и не AC этой задачи.
|
||||
|
||||
---
|
||||
|
||||
**Материал раунда:** SHA `1cfe7fa53c71b2ba595c92d1bdfc560f90242f40`,
|
||||
`origin/dev..HEAD` = 3 коммита (`ced9b416` — голова задачи после ребейза,
|
||||
байт-в-байт равна материалу r1; `3322c20c` — публикация документа r1;
|
||||
`1cfe7fa5` — исправление по r1, единственный содержательный коммит раунда).
|
||||
Полный разбор по указанию конвейера (ребейз через 7 чужих коммитов, §7.2), с
|
||||
опорой на доказанное byte-for-byte наследование дорогих гейтов из r1.
|
||||
|
||||
**Предыдущий раунд:** `docs/reviews/CODE-REVIEW-532-r1.md`, вердикт жёлтый,
|
||||
материал `bb04f8605f9003c0ec6643359fa47af9319247bc`.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/532-daycycle-raster`, коммит `1cfe7fa53c71` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `0da2d90f30f9e607782d065fe23fc1e770c7e676`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 0da2d90f30f9
|
||||
```
|
||||
- Тело issue: `12e66588332b94c78100156f01e4832881b8163fb6f8fdca83acfcb8e90f4545`
|
||||
- Вердикт конвейера: `green` · High 0
|
||||
@@ -0,0 +1,193 @@
|
||||
# CODE-REVIEW-534-r1
|
||||
|
||||
Issue: [#534](https://github.com/Matysh/houseplan-card/issues/534) · Материал: `5afdb644c78d60cdffa8f67a465b10af0d769c1e` (единственный коммит поверх `origin/dev`) · Заход r1 (первый код-ревью, ТЗ прошло r1→r3, зелёный вердикт на r3) · блокирующих циклов израсходовано 0/4
|
||||
|
||||
## Скоуп
|
||||
|
||||
Регресс `longTask.countP95` в линейке v1.74.0 (два профиля Full Performance:
|
||||
`large-house`, `plan-snap`), бисектом сведённый к #525: списки маркеров
|
||||
устройств и проёмов перевели с `map()` на `repeat(items, (o) => o.id, …)`,
|
||||
чтобы Lit не переиспользовал узлы по позиции при смене пространства. На плане
|
||||
с двумястами маркерами `repeat` на смене пространства честно строит две карты
|
||||
непересекающихся ключей, чтобы всё равно выбросить всё и создать заново — 80 мс
|
||||
на цикл, 6–13 длинных задач.
|
||||
|
||||
Правка: оба списка обёрнуты в `keyed(space.id, repeat(items, (item) => item.id,
|
||||
…))`. Внешний `keyed` выбрасывает поддерево целиком на смене пространства
|
||||
(дорогой диф не выполняется вовсе), внутренний `repeat` остаётся и защищает от
|
||||
позиционного переиспользования **внутри** пространства — состав обоих списков
|
||||
там тоже едет: у маркеров от призраков редактора устройств и живого синка
|
||||
конфига, у проёмов от orphan-записи, которая существует только в режиме Plan.
|
||||
|
||||
ТЗ прошло три раунда ревью ТЗ (r1/r2 — жёлтый, обе Medium-находки о том, что
|
||||
допущение о безопасности голого `map()` внутри пространства неверно — сначала
|
||||
для маркеров, затем для проёмов; r3 — зелёный). Код-ревью разбирает
|
||||
реализацию против финального текста ТЗ (тела issue) целиком, это первый заход
|
||||
код-ревью.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Дешёвые гейты подтверждены зелёным Validate на этом SHA
|
||||
([прогон 34639320867](https://github.com/Matysh/houseplan-card/actions/runs/34639320867),
|
||||
`workflow_dispatch`, событие проверено через `gh run view --json jobs`):
|
||||
job «Фронтенд: типы, юниты, мутанты, синхрон бандла» — success, job
|
||||
«Предполётные проверки» (docs/provenance/process-gate) — success, шесть
|
||||
шардов «Мутанты по диффу» — success. `smoke`/`golden`/`backend`/
|
||||
`performance_smoke` в этом прогоне **skipped** — классификатор не счёл его
|
||||
heavy (обычный push, не кандидат и не `full=true`), поэтому эти гейты я
|
||||
прогнал сам.
|
||||
|
||||
| Гейт | Команда | Результат |
|
||||
|---|---|---|
|
||||
| Пересборка бандла | `npm run build && npm run bundle:sync` | пересобранный бандл побайтово совпал с закоммиченным (`git status` пуст после пересборки) — сверх доверия к Validate |
|
||||
| `check-docs` (`src/**` тронут) | `node scripts/check-docs.mjs` | `Documentation checks passed (7 files, 12 external links)` |
|
||||
| Выбор смоков по диффу | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | 8 прямых совпадений (все на `_openingsR`) + 1 зарегистрированная связь (`smoke_space_switch_transitions.mjs` ← `_renderDevice`); широких/неопределённых связей нет |
|
||||
| Смок AC1/AC2/AC2а | `node demo/smoke_space_switch_transitions.mjs` | зелёный, все поля `true`, `unexpectedAnimations: []`, `swappedMarkerNodes: []`, `swappedOpeningNodes: []` |
|
||||
| 8 прямых смоков | `node demo/smoke_{glow,isometric_live_touch,open_passage,opening_binding,opening_entity_search,opening_preview,partition_openings,registryless_opening}.mjs` | все 8 зелёные |
|
||||
| Мутант AC2а (маркеры) | `node scripts/mutation-gate.mjs --id=device-markers-rendered-without-keys` | «покраснел, как обязан», 1 из 1 |
|
||||
| Мутант AC2а (проёмы) | `node scripts/mutation-gate.mjs --id=openings-rendered-without-keys` | «покраснел, как обязан», 1 из 1 |
|
||||
| Свидетель отклонения (см. находку/не-находку ниже) | ручная мутация: снят внешний `keyed(space.id, …)` в обоих местах, пересборка, `node demo/smoke_space_switch_transitions.mjs` | смок остаётся зелёным — подтверждает заявление автора, что внешний ключ не ловится ни одним функциональным свидетелем; дерево возвращено к исходному состоянию, `git status` чист |
|
||||
| AC6 (golden, диф трогает рендер) | `npm run golden:verify` | код возврата 0, все просмотренные сценарии `passed`, включая `day-cycle-*-dark` — расхождение, которое автор описал как окружение песочницы, в этой песочнице не воспроизвелось |
|
||||
| AC3/AC4 (перф) | не прогонял | см. «Чего не проверял» |
|
||||
| `npm test`, `npx tsc --noEmit`, `python -m pytest tests_backend` | не прогонял отдельно | покрыты зелёным Validate на этом SHA; бэкенд не тронут (diff не касается `custom_components/**/*.py`) |
|
||||
| Инварианты модели | не прогонял | diff не трогает геометрию/`layout`/`marker.space`/`open_spans` — только директиву рендера поверх тех же данных |
|
||||
|
||||
## Находки
|
||||
|
||||
Нет находок High или Medium.
|
||||
|
||||
**Одно отклонение от буквы ТЗ, заявленное автором открыто и проверенное мной
|
||||
как корректное, не находка.** Хендофф-комментарий сообщает: мутанты на внешний
|
||||
`keyed(space.id, …)` (`device-layer-loses-its-space-key`,
|
||||
`opening-layer-loses-its-space-key`) написаны, прогнаны и не ловятся ни одним
|
||||
тестом («0 из 1»), поэтому в реестр не добавлены. Я воспроизвёл это
|
||||
независимо (см. таблицу выше): сняв внешний `keyed` в обоих местах и
|
||||
пересобрав бандл, `smoke_space_switch_transitions.mjs` остаётся полностью
|
||||
зелёным — идентичность узлов после смены пространства одинакова что с
|
||||
внешним ключом, что без него, потому что идентификаторы маркеров и проёмов
|
||||
между пространствами и так не пересекаются (это гарантия #525, не этой
|
||||
задачи). Финальный текст AC5 в теле issue формулирует свидетеля внешнего
|
||||
ключа не как обязательный мутант, а как «красит свидетеля AC2 **либо**
|
||||
возвращает замер AC3 к 936 мс» — то есть ТЗ само называет перф-замер законной
|
||||
альтернативой мутационному гейту для этой конкретной защиты, и именно замер
|
||||
(бисект автора: 936,0 на `main` → 903,7/905,9 с `keyed`) и есть предъявленное
|
||||
доказательство. Решение принимаю: реестр мутантов не нуждается в
|
||||
заведомо неубиваемом мутанте — держать его там было бы хуже, чем не держать
|
||||
(врёт о покрытии), а сам факт «не ловится ничем, кроме числа» проверен, а не
|
||||
предположен.
|
||||
|
||||
Low, не блокирует, правки не требует: в `docs/CHANGELOG.md`/`.ru.md`
|
||||
экономия описана как «about a tenth of a second» / «около десятой доли
|
||||
секунды», хотя измеренная разница — около 80 мс (889,1 против 936,0 на
|
||||
`main`). Это качественное округление в пользовательском тексте, а не число с
|
||||
претензией на точность рядом с другим таким же числом (правило «одно
|
||||
число — один источник» здесь не задето: в UI это значение нигде не
|
||||
показывается вторично), так что не завожу как отдельную правку.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- **К1/К2 (форма).** `src/houseplan-card.ts`: оба места — список маркеров
|
||||
(было `repeat(devs, …)`) и список проёмов (было `repeat(items, …)`) — теперь
|
||||
`keyed(space.id, repeat(…, (x) => x.id, …))`. Один способ на оба списка, без
|
||||
исключений, как требует К2.
|
||||
- **К1 (узел не переживает смену пространства).** Подтверждено исполнением:
|
||||
`smoke_space_switch_transitions.mjs`, разделы 1–2, зелёные без изменений в
|
||||
самом тесте (AC1 не требовал правок и остался таким).
|
||||
- **К3 (пер-элементный ключ обязателен внутри пространства, для обоих
|
||||
списков).** Подтверждено и чтением, и исполнением:
|
||||
- `src/styles/devices.styles.ts:213` — `.device-shell-frame` действительно
|
||||
хранит `transition: border-color .15s, opacity .2s` (правильно: #524 снял
|
||||
только `box-shadow`, что и было находкой r1 ревью ТЗ);
|
||||
- `src/houseplan-card.ts` — `_openingsR` отдаёт запись с нерешённым хостом
|
||||
только когда `this._mode === 'plan'`, то есть переключение режима внутри
|
||||
пространства меняет состав списка проёмов (находка r2 ревью ТЗ, принятая и
|
||||
закрытая в r3);
|
||||
- оба триггера воспроизведены нагрузочно новым разделом 3 смока (AC2а):
|
||||
вставка устройства в начало списка и переключение Plan↔View с
|
||||
orphan-записью — оба сценария не переставляют существующие узлы и не
|
||||
запускают анимаций.
|
||||
- **Комментарий-ловушка на месте у обеих подсистем.** `plan.styles.ts:539-554`
|
||||
объясняет обе половины формы и оба триггера; `devices.styles.ts:211-213`
|
||||
ссылается на него рядом с живыми переходами — ровно как требует К3
|
||||
(«объясняется комментарием там же, где стоят анимации»).
|
||||
- **AC5, пер-элементная половина.** Оба существующих мутанта
|
||||
(`device-markers-rendered-without-keys`, `openings-rendered-without-keys`)
|
||||
переанкерены под новую форму (`scripts/mutation-gate.mjs:7434-7460`) и оба
|
||||
красные при снятии внутреннего ключа — проверено запуском, не заявлением.
|
||||
- **AC7.** Оба чейнджлога отредактированы в том же коммите, что и поведение;
|
||||
трейлеры `Issue: #534` и `User-Visible: yes` на месте; ветка
|
||||
`issue/534-space-switch-keyed-layer`. Комментарий К3 стоит рядом с
|
||||
анимациями (см. выше) — отдельного требования из АС7 выполнено.
|
||||
- **Бюджет строк.** `test/core-file-budget.test.mjs` поднимает потолок
|
||||
`src/houseplan-card.ts` на 1 (13650→13651) с датированной запиской
|
||||
«`import { keyed }`, переносить нечего» — соответствует диффу: единственная
|
||||
добавленная непустая строка кода вне самого рендера — это импорт.
|
||||
- **Бандл.** `docs/images/screenshots.json` меняет только
|
||||
`sourceFingerprint`/`sourceSha256` (пиксели те же, `imageSha256` не
|
||||
тронуты) — путь `docs:accept -- --identical`, как и предписывает §8 для
|
||||
правок без изменения кадра.
|
||||
- **Не найдено смежных регрессий.** 8 смоков, прямо завязанных на
|
||||
`_openingsR` по выбору `smoke-select.mjs`, зелёные без исключений; `npm run
|
||||
golden:verify` — 0 расхождений по всей матрице в этой песочнице.
|
||||
- **Скоуп.** Правка восстанавливает перф, которая была до #525, ничего не
|
||||
добавляет и не убирает в контракте #525 (дверь, которой не было, по-прежнему
|
||||
не анимируется) — новых экранов, полей или UX-контрактов нет; под
|
||||
`docs/SCOPE.md` подпадает как поддержание существующей отзывчивости плана
|
||||
(J1/J6), а не новая функциональность, так что вопрос «какую строку Core user
|
||||
jobs это закрывает» решается тривиально в пользу уже закрытых.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- **AC3 (`npm run benchmark:large-house`) и AC4 (Full Performance, 9
|
||||
профилей).** Не прогонял: перф-числа в этой песочнице не воспроизводят
|
||||
условия раннера (другое железо, шум), а само ТЗ называет их «ориентиром»
|
||||
и явно передаёт «окончательный приговор» гейту Full Performance на
|
||||
релизном раннере — это пре-релизный гейт по §8 PROCESS.md, не гейт
|
||||
ревью, и для этой линейки задачи он и есть источник истины (сама задача
|
||||
#534 началась с его красного прогона). Числа автора (`switchCycleMs`
|
||||
889,1 против 936,0 на `main` и 871,2 до #525, «2–3» длинных задачи вместо
|
||||
«7–13») внутренне согласованы с ходом бисекта в аналитике и не
|
||||
противоречат ничему, что я проверил чтением или исполнением.
|
||||
- **`python -m pytest tests_backend`.** Diff не касается
|
||||
`custom_components/**/*.py` — гейт неприменим.
|
||||
- **`node scripts/model-invariants.mjs`.** Diff не трогает геометрию, `layout`,
|
||||
`marker.space` или `open_spans` — только директиву рендера поверх тех же
|
||||
данных, гейт неприменим.
|
||||
- **Полная матрица `demo/smoke_*.mjs` (244 файла).** Прогнаны только
|
||||
выбранные диффом (8 прямых + 1 зарегистрированная связь) — задача не задевает
|
||||
всё дерево смоков, полный прогон это пре-релизная обязанность.
|
||||
- **Мутанты на внешний `keyed(space.id, …)`.** Не добавлял в реестр — см.
|
||||
раздел «Находки»: воспроизвёл заявление автора, что такой мутант не
|
||||
ловится ничем, кроме перф-замера, и посчитал реестр без него правильным
|
||||
решением, а не пробелом.
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- SHA материала: `5afdb644c78d60cdffa8f67a465b10af0d769c1e` (единственный
|
||||
коммит `issue/534-space-switch-keyed-layer` поверх `origin/dev`).
|
||||
- Диапазон: `git diff origin/dev...HEAD` (55 файлов: 3 файла класса A/B по
|
||||
содержанию — `src/houseplan-card.ts`, `src/styles/plan.styles.ts`,
|
||||
`src/styles/devices.styles.ts` — плюс `scripts/mutation-gate.mjs`,
|
||||
`demo/smoke_space_switch_transitions.mjs`, `test/core-file-budget.test.mjs`,
|
||||
оба чейнджлога, `docs/images/screenshots.json` и три копии бандла класса D).
|
||||
- Рабочая копия после всех локальных прогонов и ручной проверки отклонения
|
||||
возвращена в состояние коммита (`git status` пуст на момент публикации
|
||||
вердикта).
|
||||
|
||||
## Вердикт
|
||||
|
||||
Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/534-space-switch-keyed-layer`, коммит `5afdb644c78d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `10993a92cf52d54a6fe21138a74f514f535f2bef`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 10993a92cf52
|
||||
```
|
||||
- Тело issue: `9d969d777f9787e5a50d02b2e675ad1448e945a27b47899d2d37c445a612f30e`
|
||||
- Вердикт конвейера: `green` · High 0
|
||||
@@ -0,0 +1,246 @@
|
||||
# SPEC-REVIEW-534-r1
|
||||
|
||||
## Скоуп
|
||||
|
||||
Issue #534 (`bug`, `P1`, полный трек — критерий §5 «нет влияния на
|
||||
производительность» явно назван нарушенным в самой ТЗ). ТЗ живёт в теле
|
||||
issue под заголовком `## ТЗ` (решение владельца #517). Issue открыт, метка
|
||||
`S4-spec-review`, заход r1, бюджет циклов 0/4 не тронут.
|
||||
|
||||
Предмет: линейка v1.74.0 добавила 7 длинных задач P95 на профилях
|
||||
`large-house` и `plan-snap` — регресс внесён #525 (перевод списка маркеров
|
||||
устройств на `repeat(devs, (d) => d.id, …)`, чтобы Lit не переиспользовал
|
||||
узел позиционно и не проигрывал переход на неслучившемся событии). На 200
|
||||
маркерах `repeat` строит и обходит карту ключей на каждый рендер, а при
|
||||
смене пространства ключи не пересекаются вовсе — диф оплачивается напрасно.
|
||||
Решение ТЗ: обернуть слой маркеров в `keyed(space.id, …)`
|
||||
(`lit/directives/keyed.js`) вокруг обычного `map()`, оставить проёмы на
|
||||
`repeat` (там элементов на порядок меньше).
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Прочитаны `docs/SCOPE.md`, `PROCESS.md` (§1–§10, включая §7.1, §2.4, §2.10,
|
||||
§5, §8), тело issue #534 целиком и единственный комментарий (аналитика
|
||||
Codex, S2, с бисектом и измерением на пробе). Дополнительно, раз ТЗ
|
||||
опирается на конкретные строки, числа и поведение существующих механизмов,
|
||||
прочитан репозиторий на материале ревью (SHA `41ae5e99`, см. блок якорей):
|
||||
|
||||
- `src/houseplan-card.ts:11787` (`repeat(devs, (d) => d.id, …)`, слой
|
||||
маркеров) и `:12996` (`repeat(items, (o) => o.id, …)`, слой проёмов) —
|
||||
оба подтверждены чтением, ссылки в ТЗ точны;
|
||||
- `node_modules/lit/directives/keyed.js` — директива существует в составе
|
||||
уже установленной зависимости `lit`, ссылка К2 на неё не голословна;
|
||||
- `scripts/mutation-gate.mjs:7440` (`openings-rendered-without-keys`) и
|
||||
`:7446` (`device-markers-rendered-without-keys`) — оба существующих
|
||||
мутанта нацелены ровно на строки, которые правка меняет; find/replace
|
||||
`device-markers-rendered-without-keys` перестанет применяться после
|
||||
правки (текст `${repeat(devs, …)}` исчезнет) — ТЗ (AC5) корректно
|
||||
предвидит необходимость его переанкеровки;
|
||||
- `demo/smoke_space_switch_transitions.mjs` — прочитан целиком. Подтверждено:
|
||||
AC1/AC2 действительно можно закрыть существующим смоком без правок —
|
||||
проверка `noMarkerNodeReusedForAnotherDevice` (строки 124–130) судит по
|
||||
идентичности узла (`entry.node.isConnected && entry.node.dataset.id !==
|
||||
entry.id`), а не по побочному признаку, как и требует AC2;
|
||||
- `src/styles/plan.styles.ts:531–542` — комментарий-ловушка #525
|
||||
существует, называет `.device-shell-frame` (`box-shadow`) как причину
|
||||
для маркеров; ТЗ в разделе «Затронутые файлы» верно указывает, что этот
|
||||
комментарий нужно дополнить (К3);
|
||||
- `src/styles/devices.styles.ts:196-217` — проверена природа переходов на
|
||||
маркере (детали см. в находке ниже);
|
||||
- `demo/fixtures/large-house.mjs:10-11` — `DEVICE_COUNT = 200`,
|
||||
`OPENING_COUNT = 100` (на 3 этажа, т.е. ~33 на пространство) — цифры ТЗ о
|
||||
200 маркерах точны; слово «единицы» про проёмы (К3) — преувеличение
|
||||
(реально несколько десятков на этаж), но не влияет ни на один AC;
|
||||
- `docs/CHANGELOG.md`/`docs/CHANGELOG.ru.md` — записи #524/#525 в разделе
|
||||
«Unreleased»/«Не выпущено» существуют, формат черновика (AC7) совпадает
|
||||
со стилем файла;
|
||||
- `test/device-marker-polish-contract.test.mjs`, `test/space-order.test.mjs`,
|
||||
`test/device-face.test.mjs` — ни один не проверяет позиционную
|
||||
стабильность маркера при изменении состава списка внутри одного
|
||||
пространства (относится к находке ниже).
|
||||
|
||||
Гейты не гонялись: на этапе ревью ТЗ продуктовый код не менялся, дешёвые
|
||||
гейты (`typecheck`/`test`/`build`) к этому этапу неприменимы (§2.4, §8).
|
||||
|
||||
## Продуктовая рамка
|
||||
|
||||
Сценарий и «что человек увидит» сформулированы одной фразой без терминов
|
||||
реализации, как требует §7.1: «каждое переключение дороже примерно на
|
||||
80 мс главного потока… карточка перестаёт отвечать на ввод чуть дольше,
|
||||
чем должна» → «переключение возвращается к цене, которая была до #525».
|
||||
Персона — домочадцы/гости на большом плане, `docs/SCOPE.md` называет View
|
||||
mode продуктом для этих персон; регресс блокирует именно это. Полный трек
|
||||
обоснован явно названным нарушенным критерием §5 («нет влияния на
|
||||
производительность»), а не молчаливым выбором — соответствует требованию
|
||||
2026-08-27. i18n/миграция/бэкенд и touch закрыты явным «ничего» /
|
||||
«тот же путь» — ТЗ учло Low-находку из SPEC-REVIEW-525-r1 (там эти пункты
|
||||
пришлось поднимать отдельно), здесь они на месте с самого начала.
|
||||
|
||||
## Разбор AC
|
||||
|
||||
- **AC1** — «остаётся зелёным без правок»: подтверждено чтением, существующий
|
||||
смок уже проверяет то, что требуется, и правка К2/К3 его не задевает
|
||||
структурно (тест не завязан на `repeat` изнутри, только на видимое
|
||||
поведение DOM/CSS).
|
||||
- **AC2** — свидетель по идентичности узла уже существует (см. «Как
|
||||
проверялось»), формулировка ТЗ точно описывает то, что тест делает.
|
||||
- **AC3** — численный ориентир (910 мс) подтверждён измерением автора на
|
||||
`main` + патч (903,7 мс), не выдуман.
|
||||
- **AC4** — внешний гейт (Full Performance), критерий однозначен (девять
|
||||
профилей, включая оба проблемных).
|
||||
- **AC5** — корректно предвидит необходимость правки мутанта
|
||||
(подтверждено выше — старый find/replace перестанет матчиться).
|
||||
- **AC6/AC7** — golden и ревью кода, стандартные, без двусмысленности.
|
||||
|
||||
Двусмысленности, которая заставила бы гадать «что считается прохождением»,
|
||||
в текстах AC не нашёл.
|
||||
|
||||
## Находки
|
||||
|
||||
### Medium — ключевое техническое допущение К2/«Принято предположительно» №2 неверно, и это открывает непроверенный регресс той же природы, что чинит вся задача
|
||||
|
||||
**Утверждение ТЗ:** «Внутри одного пространства позиционное переиспользование
|
||||
маркеров безопасно: после #524 на маркере не осталось переходов, которые
|
||||
могла бы запустить смена значений.»
|
||||
|
||||
**Проверено чтением и опровергнуто:** `src/styles/devices.styles.ts:213`
|
||||
несёт `.device-shell-frame { … transition: border-color .15s, opacity .2s; }`
|
||||
— оба перехода живы. #524 убрал только переход `box-shadow` (см. комментарий
|
||||
на той же строке: «no box-shadow here: cqw-sized, restarts on every
|
||||
container resize (#524)»), а не «переходы» вообще — допущение спутало
|
||||
конкретный фикс с общим случаем. `--device-shell-stroke` (цвет рамки)
|
||||
переопределяется по состоянию устройства (`devices.styles.ts:296,312,322,421`
|
||||
— alert-цвета) — то есть у двух РАЗНЫХ устройств в одном списке этот цвет
|
||||
типично отличается.
|
||||
|
||||
**Почему это ломает контракт К2.** К2 предписывает конкретную реализацию:
|
||||
`keyed(space.id, …)` вокруг **обычного `map()`** для слоя маркеров — то есть
|
||||
внутри одного пространства элементы списка теряют пер-элементный ключ,
|
||||
который сейчас даёт `repeat(devs, (d) => d.id, …)`. Если состав `devs`
|
||||
меняется без смены `space.id` — а он меняется: `showGhosts` (`houseplan-
|
||||
card.ts:11307`, `this._mode === 'devices' && this._showAll`) переключает
|
||||
видимость части устройств в редакторе устройств, а само появление нового
|
||||
устройства в пространстве от лайв-синка конфигурации (J6, «new-device
|
||||
flag») тоже меняет состав `devs` без переключения пространства — Lit
|
||||
переиспользует DOM-узлы **по позиции**, не по устройству. Маркер, стоявший
|
||||
на позиции N, может получить данные другого устройства с другим
|
||||
`--device-shell-stroke`/`opacity`, и уже живой `transition: border-color
|
||||
.15s, opacity .2s` эти узла честно проиграет — ровно тот класс дефекта
|
||||
(«анимация события, которого не было»), который и #525, и эта самая задача
|
||||
называют главным риском (Риск 1 в ТЗ), только для другого триггера: не
|
||||
переключение пространства, а изменение состава списка внутри пространства.
|
||||
|
||||
**Почему это не поймано ни одним AC.** `demo/smoke_space_switch_transitions.mjs`
|
||||
(AC1/AC2) тестирует только переключение между двумя пространствами; ни один
|
||||
существующий тест (`test/device-marker-polish-contract.test.mjs`,
|
||||
`test/space-order.test.mjs`, `test/device-face.test.mjs`) не проверяет
|
||||
переход, вызванный изменением состава списка маркеров внутри одного
|
||||
пространства. Риски ТЗ (1–3) эту ситуацию не называют.
|
||||
|
||||
**Почему это в скоупе.** К2 — это ровно тот код, который правит задача;
|
||||
регрессия, если она проявится, будет внесена этим же коммитом, а не
|
||||
существует независимо от него сегодня (сейчас защиту даёт per-item ключ
|
||||
`repeat`, который К2 явно убирает).
|
||||
|
||||
**Что нужно для DoR (любое из трёх, решает автор):**
|
||||
1. исправить допущение и сохранить пер-элементную защиту внутри
|
||||
пространства — например, `keyed(space.id, () => repeat(devs, (d) =>
|
||||
d.id, …))`: смена `space.id` по-прежнему выбрасывает поддерево целиком
|
||||
без дорогого сравнения с 200 чужими ключами (устраняет весь измеренный
|
||||
регресс), а `repeat` внутри защищает от переупорядочивания в пределах
|
||||
одного пространства, как и сегодня; либо
|
||||
2. показать конкретным измерением/рассуждением, что состав `devs` в
|
||||
реальных сценариях (не только в фикстуре бенчмарка) не меняется без
|
||||
смены `space.id` так, чтобы порядок сдвигался — и явно принять остаточный
|
||||
риск в разделе «Риски», а не в «Принятые предположения» как решённый
|
||||
факт; либо
|
||||
3. добавить AC и свидетеля на сценарий «состав списка маркеров меняется
|
||||
внутри пространства» по образцу уже существующего свидетеля #525/#528.
|
||||
|
||||
**Решение ревьюера:** блокирует переход в `S5-ready` без правки одним из
|
||||
трёх способов выше — Medium в скоупе задачи, чинится в этом же issue
|
||||
(§2.4, §2.7 #202), отдельный issue не заводится.
|
||||
|
||||
### Low — «их единицы» (К3) неточно описывает число проёмов
|
||||
|
||||
К3 обосновывает, почему проёмы остаются на `repeat`: «их единицы, диф ничего
|
||||
не стоит». По фикстуре `large-house` (`DEVICE_COUNT=200`,
|
||||
`OPENING_COUNT=100` на 3 этажа) на пространство приходится порядка
|
||||
30 проёмов — не «единицы», хотя и на порядок меньше 200 маркеров, так что
|
||||
общий вывод (репит на проёмах дешевле) не меняется и ни один AC от этого не
|
||||
зависит.
|
||||
|
||||
**Решение ревьюера:** не блокирует, снимается этой записью; можно поправить
|
||||
формулировку заодно с остальной правкой К2, отдельного цикла не открываю.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- Обязательные продуктовые разделы §7.1 (сценарий, что человек увидит до и
|
||||
после) присутствуют и однозначны.
|
||||
- Полный трек обоснован явно названным нарушенным критерием §5.
|
||||
- i18n, миграция/бэкенд, touch — явные «нет»/«тот же путь», а не молчание
|
||||
(что в #525 пришлось поднимать Low-находкой — здесь уже учтено).
|
||||
- AC1–AC7 пронумерованы, у каждого указан способ доказательства; численные
|
||||
пороги AC3 (910 мс) и AC4 (девять профилей) проверяемы и не выдуманы —
|
||||
число AC3 сверено с измерением автора на пробе.
|
||||
- AC5 корректно предвидит поломку существующего мутанта новой формой кода
|
||||
и требует его переанкеровки — не тихое исчезновение защиты.
|
||||
- Технические ссылки (номера строк, наличие директивы `keyed` в
|
||||
зависимостях, наличие мутантов, содержимое свидетеля) подтверждены
|
||||
чтением репозитория, ни одна не оказалась пустой.
|
||||
- Откат — один revert, без миграции данных, корректно.
|
||||
- Риски 1 и 3 предметны и привязаны к AC/гейтам, которые их ловят.
|
||||
- Release-артефакты (changelog RU+EN, `User-Visible: yes`) названы верно.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Не запускал никакого кода и ни одного гейта — на этапе ревью ТЗ
|
||||
продуктовый код не менялся, дешёвые гейты (`typecheck`/`test`/`build`)
|
||||
неприменимы к этой стадии (§2.4, §8).
|
||||
- Не проверял golden-эталоны и реальные тайминги на Chromium/раннере —
|
||||
появятся только с реализацией; численные ориентиры ТЗ (903,7 мс, 910 мс)
|
||||
доверены измерению автора (внутренне согласованы с бисектом в
|
||||
комментарии) и будут окончательно подтверждены Full Performance по AC4.
|
||||
- Не проверял частоту реальных сценариев `showGhosts`/лайв-добавления
|
||||
устройства «в проде» количественно — риск в находке выше обоснован кодом
|
||||
(условие фильтра, наличие живых CSS-переходов, наличие state-зависимого
|
||||
цвета рамки), а не измерением частоты; для итогового решения (пункт 1–3
|
||||
находки) этого достаточно, эмпирическая частота не меняет того, что
|
||||
сценарий кодом не исключён и не покрыт тестом.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Жёлтый. Один Medium в скоупе задачи (допущение К2 о безопасности
|
||||
внутрипространственного позиционного переиспользования маркеров неверно и
|
||||
открывает непроверенный регресс той же природы, которую чинит вся задача);
|
||||
High нет. Возврат автору на правку одним из трёх названных способов;
|
||||
одна Low-находка о формулировке снята этой записью.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: подготовлено вручную ревьюером, конвейер §416 не запускался в этом окружении -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `dev`, коммит `41ae5e99868db27e3bfde9a40426c0a636c374bd`.
|
||||
- Дерево материала: `e87f2fb0f5a99184cd9795a5e59155b96b3ac2f6`
|
||||
```
|
||||
git log --all --format='%H %T' | grep e87f2fb0f5a9
|
||||
```
|
||||
- Тело issue (нормализовано `gh issue view 534 --json body -q '.body'`):
|
||||
`sha256:98e12a0f7bec0c050802b1caee9c139db3bbc7b7c1169744fb1ccd567e35babd`
|
||||
- Вердикт: `yellow` · High 0 · Medium 1 (в скоупе)
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `dev`, коммит `41ae5e99868d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `e87f2fb0f5a99184cd9795a5e59155b96b3ac2f6`
|
||||
```
|
||||
git log --all --format='%H %T' | grep e87f2fb0f5a9
|
||||
```
|
||||
- Тело issue: `3ba57688eafe3310c9e3d0157758ea519c3b89d980958e504d2c2b54b3a43cb3`
|
||||
- Вердикт конвейера: `yellow` · High 0
|
||||
@@ -0,0 +1,259 @@
|
||||
# SPEC-REVIEW-534-r2
|
||||
|
||||
## Скоуп
|
||||
|
||||
Issue #534, этап `spec` (ревью ТЗ, PROCESS.md §2.4), заход r2, до этого
|
||||
раунда израсходован 1/4 блокирующих цикла (r1 — жёлтый, Medium в скоупе).
|
||||
ТЗ живёт в теле issue (решение владельца #517).
|
||||
|
||||
Материал этапа spec — тело issue, не код. Между r1 и r2 продуктовый код не
|
||||
менялся: `git diff 41ae5e99..HEAD -- . ':!docs/reviews'` пуст, рабочая
|
||||
копия на `e40d3f18` содержит только коммит с документом
|
||||
`SPEC-REVIEW-534-r1.md`. Единственная дельта раунда — правка тела issue
|
||||
владельцем (комментарий-вердикт r1 и последующий комментарий Matysh от
|
||||
2026-09-11T18:58:53Z). Хэш нормализованного тела изменился
|
||||
(`sha256:18961b89…` в r2 против `sha256:98e12a0f…`, зафиксированного в
|
||||
материале r1) — дельта подтверждена, а не предполагается.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
1. Прочитан вердикт r1 (комментарий `IC_…`, 2026-09-11T18:53:19Z) и
|
||||
документ `docs/reviews/SPEC-REVIEW-534-r1.md` на коммите `41ae5e99`
|
||||
(дерево материала `e87f2fb0f5a9…`, зафиксировано в блоке «Материал
|
||||
раунда» того документа).
|
||||
2. Дельта тела issue объявлена (см. «Скоуп») и разобрана построчно:
|
||||
старые формулировки К2/К3/«Принятые предположения»/AC5/«Риски»
|
||||
реконструированы по цитатам r1-документа и сопоставлены с текущим
|
||||
текстом (`gh issue view 534 --json body -q '.body'`, сохранено в
|
||||
`/tmp/current_body.md`).
|
||||
3. Технические ссылки новой редакции проверены чтением репозитория на
|
||||
материале ревью (SHA `e40d3f18`, код не менялся с r1):
|
||||
- `src/houseplan-card.ts:11787` (`repeat(devs, (d) => d.id, …)`) и
|
||||
`:12996` (`repeat(items, (o) => o.id, …)`) — оба списка сегодня без
|
||||
`keyed`, ссылки К2 «оба списка… `keyed(…, repeat(…))`» описывают
|
||||
код, которого ещё нет, корректно как целевое состояние;
|
||||
- `scripts/mutation-gate.mjs:7434-7456` — оба существующих мутанта
|
||||
(`openings-rendered-without-keys`, `device-markers-rendered-without-keys`)
|
||||
сделаны точным `find`/`replace` по буквальной строке с `repeat(…)`;
|
||||
`applyPatches` (`mutation-gate.mjs:8910-8916`) бросает исключение,
|
||||
если якорь не найден ровно один раз — промах анкера не тихий, гейт
|
||||
красится сборкой мутанта, а не пропускает проверку молча;
|
||||
- `src/styles/plan.styles.ts:531-549` — комментарий-ловушка #525 уже
|
||||
сегодня объясняет `.op-leaf { transition: transform 0.6s ease }` и
|
||||
`.op-arc { transition: stroke-dashoffset 0.6s ease }` как причину,
|
||||
по которой список проёмов **уже с #525** рендерится через `repeat`,
|
||||
наравне с маркерами устройств — тем же абзацем, который К2/К3 этого
|
||||
раунда обязывают дополнить;
|
||||
- `src/houseplan-card.ts:8226-8260` (`_openingsR`) — при
|
||||
`!resolution.resolved` в режиме `plan` добавляется orphan-запись
|
||||
(`:8242-8243`), а вне `plan` эта запись выпадает из списка
|
||||
(`:8244`, `flatMap` возвращает `[]`) — состав `items` меняется от
|
||||
переключения режима Plan/View **без смены пространства**, длина
|
||||
массива и позиции последующих элементов сдвигаются;
|
||||
- `src/houseplan-card.ts:12987-12996` (`_renderOpenings`) — рендерит
|
||||
`items` из `_openingsR` тем же `repeat`, что и обсуждается в К2/К3.
|
||||
|
||||
Гейты не гонялись: этап spec, продуктовый код не менялся (§2.4, §8) —
|
||||
как и в r1.
|
||||
|
||||
## Закрытие раунда r1
|
||||
|
||||
| находка r1 | чем закрыта | где это видно |
|
||||
|---|---|---|
|
||||
| Medium — допущение К2 «внутри пространства позиционное переиспользование маркеров безопасно» опровергнуто чтением (`devices.styles.ts:213`, живые переходы) | Формулировка К2 переписана: оба списка теперь `keyed(space.id, repeat(items, id, …))` — внутренний `repeat` **сохраняется**, а не убирается. Добавлен явный контракт К3 «почему пер-элементный ключ обязан остаться» с тем же техническим обоснованием, которое привёл ревьюер (переходы `.device-shell-frame`, `--device-shell-stroke` по состоянию). Допущение №2 переписано из утверждения о безопасности в утверждение о цене (диф `repeat` идёт по быстрому пути, измерено 905,9 vs 903,7 мс) — корректность больше не зависит от истинности этого допущения, потому что `repeat` защищает по ключу независимо от того, совпадают ли ключи по порядку. Добавлен новый AC2а — свидетель именно на сценарий «состав списка маркеров меняется без смены пространства». Риск 2 переписан под новый сценарий и ссылается на К3/AC2а/мутант. | Тело issue: К2 (строка 48), К3 (50), AC2а (60), Риск 2 (90), допущение №2 (96). Выполнено способом 1 из трёх предложенных ревьюером r1 (сохранить пер-элементную защиту), усилено способами 2 (допущение переформулировано честно) и 3 (добавлен AC-свидетель) — все три сразу, не одно на выбор. |
|
||||
| Low — «проёмов единицы» (К3 в старой нумерации) неточно | Число исправлено на «порядка тридцати на этаж» | Допущение №3 (строка 97): «Проёмов на `large-house` порядка тридцати на этаж, не «единицы»» |
|
||||
|
||||
Обе находки r1 закрыты текстом ТЗ, а не заявлением в комментарии — проверено построчным чтением текущего тела issue, а не принято на слово.
|
||||
|
||||
## Унаследовано из r1
|
||||
|
||||
Без повторной проверки принято из `docs/reviews/SPEC-REVIEW-534-r1.md`
|
||||
(документ и SHA `41ae5e99868db27e3bfde9a40426c0a636c374bd`, дерево
|
||||
материала `e87f2fb0f5a9…`), потому что дельта r2 этих участков текста и
|
||||
кода не касается:
|
||||
|
||||
- Продуктовая рамка §7.1 (сценарий, «что человек увидит» до/после,
|
||||
выбор полного трека по явно названному критерию §5, i18n/миграция/
|
||||
бэкенд/touch как явные «нет» вместо молчания) — текст сценария в r2 не
|
||||
менялся;
|
||||
- AC1 (существующий смок остаётся зелёным без правок) — смок и его
|
||||
проверки не менялись, вывод r1 держится;
|
||||
- AC3/AC4 (численный ориентир 910 мс и полный перф-гейт из девяти
|
||||
профилей) — числа и формулировка не менялись между раундами;
|
||||
- AC6 (golden) и базовая часть AC7 (записи в оба changelog,
|
||||
`User-Visible: yes`) — не менялись;
|
||||
- Существование директивы `keyed` в зависимости `lit`, точность ссылок
|
||||
на номера строк `houseplan-card.ts:11787/12996` и на существующие
|
||||
мутанты в `scripts/mutation-gate.mjs` — репозиторий на этих участках
|
||||
не менялся с r1, перепроверка кода не требовалась (я всё же прочитал
|
||||
эти строки заново в рамках проверки новой находки ниже, они совпадают
|
||||
с тем, что зафиксировал r1).
|
||||
|
||||
## Разбор AC (только то, чего касается дельта)
|
||||
|
||||
- **AC2а** (новая) — формулировка однозначна, способ доказательства
|
||||
назван (`smoke`, вероятно расширение того же
|
||||
`demo/smoke_space_switch_transitions.mjs`, который назван в
|
||||
«Затронутые файлы»), критерий прохождения проверяем по идентичности
|
||||
узла, как и AC2. Двусмысленности нет. **Но её область — только
|
||||
маркеры устройств**, см. находку ниже.
|
||||
- **AC5** — обновлена под новую форму кода (два мутанта: снимающий
|
||||
внешний `keyed` и снимающий внутренний `repeat`). Явно называет
|
||||
переанкеровку `device-markers-rendered-without-keys`. **Не называет**
|
||||
`openings-rendered-without-keys`, хотя его якорь (`mutation-
|
||||
gate.mjs:7441`, буквальная строка с `repeat(items, (o) => o.id, …)`)
|
||||
тоже перестанет совпадать один-в-один после того, как К2 обернёт
|
||||
список проёмов в `keyed(…)` — та же механика, что и для маркеров.
|
||||
Практически это не тихий пропуск (`applyPatches` бросает исключение
|
||||
на несовпавшем якоре, PROCESS §8/находка выше), но ТЗ не проговаривает
|
||||
требование явно, оставляя его исполнителю угадывать.
|
||||
- **AC7** — ссылка на «комментарий К3» согласована с новой нумерацией
|
||||
контракта (проверено: К3 в новом тексте — это статья про
|
||||
пер-элементный ключ, а не про число проёмов, как в r1). «Затронутые
|
||||
файлы» требуют дополнить комментарий-ловушку **в обоих** файлах стилей
|
||||
(`plan.styles.ts` и `devices.styles.ts`) — см. находку ниже про то, что
|
||||
сам контракт К3 обосновывает это только для одного из двух.
|
||||
|
||||
## Находки
|
||||
|
||||
### Medium — К2 распространяет `keyed+repeat` на список проёмов «для единообразия», но обоснование (К3) и свидетель (AC2а) написаны только для маркеров устройств
|
||||
|
||||
**Что меняет К2.** «Оба списка — маркеры устройств и проёмы — рендерятся
|
||||
как `keyed(<идентификатор пространства>, repeat(items, (item) =>
|
||||
item.id, …))». Это новое требование к списку проёмов: до этого раунда
|
||||
(и в r1, и сегодня в коде) проёмы рендерятся голым
|
||||
`repeat(items, (o) => o.id, …)` без внешнего `keyed`. Причина
|
||||
унификации — допущение №3: «проёмов… не заслуживают отдельного режима,
|
||||
поэтому оба списка приводятся к одной форме» — обоснование
|
||||
единообразием, а не функциональной необходимостью.
|
||||
|
||||
**К3 и AC2а покрывают только один из двух списков.** К3 («почему
|
||||
пер-элементный ключ обязан остаться») целиком про маркеры: «Состав
|
||||
списка маркеров меняется без смены пространства: призраки в редакторе
|
||||
устройств (`showGhosts`) и появление устройства по живому синку
|
||||
конфига… на `.device-shell-frame`… живы переходы… Убрать внутренний
|
||||
ключ — значит вернуть тот же класс дефекта». AC2а — тоже буквально «свои
|
||||
детель К3… **состав списка маркеров** меняется… ни один существующий
|
||||
**маркер** не меняет свой DOM-узел». Ни К3, ни AC2а не упоминают проёмы.
|
||||
|
||||
**Тот же риск для проёмов подтверждён чтением, а не гипотетичен.**
|
||||
|
||||
1. Проёмы уже сегодня несут живые переходы того же типа, что маркеры, и
|
||||
это задокументировано прямо в комментарии-ловушке #525
|
||||
(`src/styles/plan.styles.ts:531-542`, тот самый абзац, который К2/К3
|
||||
в этом раунде требуют дополнить): `.op-leaf { transition: transform
|
||||
0.6s ease }`, `.op-arc { transition: stroke-dashoffset 0.6s ease }`.
|
||||
Комментарий прямо называет обе причины рядом: «the openings and the
|
||||
device markers… with this issue [#525]» — то есть исходная задача
|
||||
#525 уже трактовала оба списка как один класс риска, и текущее ТЗ
|
||||
(К2) следует этой логике для перфоманса, но не переносит её в
|
||||
контракт корректности К3.
|
||||
2. У проёмов есть собственный, отличный от маркеров, но настоящий
|
||||
триггер изменения состава списка **внутри одного пространства**:
|
||||
`_openingsR` (`src/houseplan-card.ts:8226-8260`) добавляет
|
||||
orphan-запись, только когда `this._mode === 'plan'`
|
||||
(`:8241-8243`), и убирает её, когда режим не `plan`
|
||||
(`:8244`, `flatMap` возвращает `[]` для нерешённого хоста). То есть
|
||||
переключение между режимами Plan/View **в одном и том же
|
||||
пространстве** меняет длину и, соответственно, позиции элементов
|
||||
`items`, который затем рендерится тем же `repeat` в
|
||||
`_renderOpenings` (`:12987-12996`) — прямой аналог `showGhosts` для
|
||||
маркеров, только с другим триггером.
|
||||
|
||||
**Почему это Medium, а не Low.** Если `repeat` для проёмов оставлен «по
|
||||
инерции единообразия» без записанной причины и без свидетеля, следующий
|
||||
читатель — ровно тот сценарий, которого боится сама К3 («иначе следующий
|
||||
читатель уберёт «лишнюю» обёртку») — с равным основанием сочтёт его
|
||||
избыточным именно для проёмов, потому что ни контракт, ни тест этого не
|
||||
запрещают. Регрессия при этом ловится AC1/AC2 только частично: они
|
||||
проверяют переключение *между* пространствами, а не смену режима
|
||||
*внутри* одного. Находка того же класса и в той же строке контракта
|
||||
(К2/К3), которую r1 уже поднимал для маркеров — только в r2 асимметрия
|
||||
переехала на второй список, который сама эта правка впервые
|
||||
подвела под общий режим.
|
||||
|
||||
**Что нужно для DoR (решает автор):**
|
||||
1. распространить К3 и AC2а на оба списка одним общим утверждением
|
||||
(единый триггер формулировки — «состав списка меняется без смены
|
||||
пространства» верно и для orphan-переключения Plan/View) и завести
|
||||
AC2б либо расширить AC2а на проёмы конкретным сценарием (например,
|
||||
вход/выход из режима Plan при наличии проёма с нерешённым хостом); либо
|
||||
2. явно обосновать, почему для проёмов регресс невозможен либо не имеет
|
||||
значения (например, если `.op-leaf`/`.op-arc` не проигрывают
|
||||
транзишн в застывшем Plan-виде, или если orphan-переключение всегда
|
||||
сопровождается прочим ре-рендером, который сбрасывает состояние) — и
|
||||
принять это как записанный риск, а не молчаливое умолчание;
|
||||
3. как минимум явно назвать в AC5 переанкеровку
|
||||
`openings-rendered-without-keys` наравне с `device-markers-
|
||||
rendered-without-keys` — это не закрывает риск (1)/(2), но убирает
|
||||
как минимум неполноту самого AC5 относительно кода, который сам же
|
||||
К2 меняет.
|
||||
|
||||
**Решение ревьюера:** блокирует переход в `S5-ready` без правки одним из
|
||||
способов 1 или 2 выше (способ 3 сам по себе недостаточен — устраняет
|
||||
только неполноту AC5, но не закрывает риск отсутствия контракта/AC для
|
||||
проёмов). Medium в скоупе задачи — К2 в этом самом раунде впервые
|
||||
распространила изменение на список проёмов, поэтому решение относится к
|
||||
предмету этой правки, а не к соседнему поведению; отдельный issue не
|
||||
заводится (§2.4, §2.7, #202).
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- Обе находки r1 (Medium и Low) закрыты текстом ТЗ, проверено построчным
|
||||
чтением, а не заявлением автора — таблица «Закрытие раунда r1» выше.
|
||||
- Допущение №2 больше не путает конкретный фикс (#524, `box-shadow`) с
|
||||
общим случаем: теперь это утверждение о производительности, а не о
|
||||
безопасности, и корректность К3 от его истинности не зависит — `repeat`
|
||||
защищает по ключу независимо от совпадения порядка.
|
||||
- AC2а — новый, недвусмысленный AC с указанным способом доказательства.
|
||||
- Нумерация контракта (К1–К5) внутренне согласована: перекрёстные ссылки
|
||||
AC7→К3, AC5→AC2а, Риск 2→К3/AC2а/АС5 указывают на верные, актуальные
|
||||
пункты нового текста.
|
||||
- Технические ссылки К2 (директива `keyed`, номера строк 11787/12996,
|
||||
существующие мутанты) точны — перепроверены на неизменном с r1 коде.
|
||||
- Число проёмов («порядка тридцати на этаж») теперь соответствует
|
||||
фикстуре `large-house` (было подтверждено ещё в r1).
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Не запускал код и гейты — этап spec, продуктовый код не менялся с r1
|
||||
(§2.4, §8), как и в прошлом раунде.
|
||||
- Не проверял, действительно ли `.op-leaf`/`.op-arc` физически
|
||||
проигрывают переход в статичном Plan-виде при переключении режима
|
||||
(например, если элемент в момент переключения не виден или
|
||||
transition подавлен другим правилом) — находка выше опирается на
|
||||
чтение CSS и `_openingsR`, а не на измерение в браузере; это ровно тот
|
||||
вопрос, который DoR-требование (пункт 2 находки) просит закрыть
|
||||
автору чтением/измерением, если он выберет этот путь вместо пункта 1.
|
||||
- Не проверял частоту реального использования Plan/View-переключения с
|
||||
ровно одним orphan-проёмом «в проде» — как и в r1 по аналогичному
|
||||
вопросу для маркеров, для решения находки (правка контракта либо
|
||||
обоснованный отказ) это не требуется: сценарий не исключён кодом и не
|
||||
покрыт тестом, этого достаточно, чтобы вернуть на уточнение.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Жёлтый. Один Medium в скоупе задачи: К2 в этом раунде распространила
|
||||
`keyed(space.id, repeat(…))` на список проёмов «для единообразия», но
|
||||
контракт К3 и его свидетель AC2а обосновывают и проверяют сохранение
|
||||
внутреннего ключа только для маркеров устройств, хотя тот же класс риска
|
||||
у проёмов подтверждён чтением (комментарий-ловушка #525 уже описывает их
|
||||
как один класс, а `_openingsR` меняет состав списка при переключении
|
||||
Plan/View внутри одного пространства). High нет. Обе находки r1 закрыты
|
||||
текстом, проверено построчно. Возврат автору на правку одним из двух
|
||||
названных способов (расширить К3/AC2а на проёмы, либо обосновать и
|
||||
записать отказ как риск).
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `dev`, коммит `e40d3f18d2fd` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `d42689f768439e92dac62df2f8055b5f2ed7d1db`
|
||||
```
|
||||
git log --all --format='%H %T' | grep d42689f76843
|
||||
```
|
||||
- Тело issue: `e9c78bcaf2429aff824dd80820d9b17d1a73c4b49c557df613d54a59d5590907`
|
||||
- Вердикт конвейера: `yellow` · High 0
|
||||
@@ -0,0 +1,218 @@
|
||||
# SPEC-REVIEW-534-r3
|
||||
|
||||
## Скоуп
|
||||
|
||||
Issue #534 (`bug`, `P1`, полный трек — критерий §5 «нет влияния на
|
||||
производительность» явно назван нарушенным в самой ТЗ). Этап `spec`
|
||||
(ревью ТЗ, PROCESS.md §2.4), заход **r3**, до этого раунда израсходовано
|
||||
**2/4** блокирующих циклов (r1 — жёлтый/Medium, r2 — жёлтый/Medium; оба в
|
||||
скоупе, оба вернули задачу на правку). Лимит цикла для полного трека — 4
|
||||
(§2.4), не 2 (2 — это лимит именно лёгкого трека).
|
||||
|
||||
Предмет спецификации не изменился с r1: регресс #525 (перевод списков
|
||||
маркеров и проёмов на `repeat(items, (x) => x.id, …)`) добавил лишний диф
|
||||
по непересекающимся ключам при смене пространства на `large-house`/`plan-
|
||||
snap`; решение — обернуть оба списка во внешний `keyed(space.id, …)`,
|
||||
сохранив внутренний `repeat` как защиту от переупорядочивания внутри
|
||||
пространства.
|
||||
|
||||
## Как проверялось (объём по дельте, §2.9/§2.10)
|
||||
|
||||
Материал предыдущего раунда объявлен в блоке «Материал раунда» документа
|
||||
`docs/reviews/SPEC-REVIEW-534-r2.md`: коммит `e40d3f18d2fd`, дерево
|
||||
`d42689f768439e92dac62df2f8055b5f2ed7d1db`, тело issue
|
||||
`sha256:e9c78bcaf2429aff824dd80820d9b17d1a73c4b49c557df613d54a59d5590907`.
|
||||
|
||||
1. Текущее тело issue снято `gh issue view 534 --json body -q '.body'` →
|
||||
`/tmp/current_body.md`, посчитан хеш:
|
||||
`sha256:18c0ed92ea4d832fcb8d3d4aeedf02d05af2e4c174d0f7e0b099d4b14e31ff8a`
|
||||
— отличается от хеша r2, дельта в теле issue подтверждена, а не
|
||||
предполагается.
|
||||
2. Дельта — правка тела issue владельцем, описанная в комментарии
|
||||
«Ответ на ревью ТЗ r2 — заход 3» (2026-09-11T19:09:03Z): К3 расширен на
|
||||
оба списка (маркеры и проёмы), AC2а расширен второй половиной для
|
||||
проёмов, AC5 явно называет переанкеровку обоих мутантов, Риск 2
|
||||
называет оба триггера. Сверено построчно с текущим телом issue — все
|
||||
перечисленные правки на месте (К3 — строки 50–53 тела, AC2а — 65–69,
|
||||
AC5 — 72, Риск 2 — 99).
|
||||
3. Продуктовый код на материале ревью (рабочая копия на `1fb84755`, тем
|
||||
же SHA, что был у r1/r2 — код с r1 не менялся, это ревью ТЗ) прочитан
|
||||
заново там, где дельта делает новые фактические утверждения:
|
||||
- `src/houseplan-card.ts:8226-8258` (`_openingsR`) — подтверждено
|
||||
построчно: `this._mode === 'plan' ? [{...fallback, orphanReason}] :
|
||||
[]`. К3/AC2а формулируют это как «Plan ↔ View»; в коде условие шире
|
||||
— `plan` против любого из **трёх** остальных режимов (`view`,
|
||||
`devices`, `decor`, `houseplan-card.ts:990`). «Plan ↔ View» —
|
||||
корректный частный случай общего триггера, демонстрирует и
|
||||
проверяет тот же механизм (изменение состава `items` без смены
|
||||
`space.id`); не искажает ни один AC и не расширяет риск — см. Low
|
||||
ниже;
|
||||
- `src/styles/plan.styles.ts:531-542` — комментарий-ловушка #525
|
||||
прочитан целиком: один блок на оба списка, сегодня называет
|
||||
`.op-leaf`/`.op-arc` (проёмы) и `.device-shell-frame, box-shadow`
|
||||
(маркеры, устаревшая причина — box-shadow снят #524). К3/АС7
|
||||
корректно требуют его переписать под новые триггеры и актуальные
|
||||
переходы;
|
||||
- `src/styles/devices.styles.ts:213` — `.device-shell-frame {
|
||||
transition: border-color .15s, opacity .2s; }` — переходы живы,
|
||||
как и утверждает К3 (маркеры);
|
||||
- `src/houseplan-card.ts:11787` (`repeat(devs, …)`) и `:12996`
|
||||
(`repeat(items, …)`) — оба списка сегодня без внешнего `keyed`,
|
||||
совпадает с описанием целевого состояния К2;
|
||||
- `node_modules/lit/directives/keyed.js` — директива по-прежнему
|
||||
доступна в установленной зависимости.
|
||||
|
||||
Гейты не гонялись — этап spec, продуктовый код не менялся с r1 (§2.4,
|
||||
§8), как и в r1/r2.
|
||||
|
||||
## Закрытие раунда r2
|
||||
|
||||
| находка r2 | чем закрыта | где это видно в текущем теле issue |
|
||||
|---|---|---|
|
||||
| Medium — К2 распространила `keyed(space.id, repeat(…))` на проёмы «для единообразия», но контракт К3 и свидетель AC2а обосновывали и проверяли сохранение внутреннего ключа только для маркеров устройств; тот же риск для проёмов подтверждён чтением (`_openingsR`, комментарий-ловушка #525) | К3 переписан заголовком «Почему пер-элементный ключ обязан остаться — **в обоих списках**» и содержит отдельный абзац «Проёмы» с тем же обоснованием, что привёл ревьюер: `_openingsR` включает orphan-запись только в режиме Plan, `.op-leaf`/`.op-arc` несут переходы `transform 0.6s`/`stroke-dashoffset`. AC2а расширен второй половиной сценария «проёмы: переключение Plan ↔ View при наличии проёма с нерешённым хостом». AC5 явно называет переанкеровку **обоих** существующих мутантов — `device-markers-rendered-without-keys` **и** `openings-rendered-without-keys`. Риск 2 переписан: «Триггеров два и они разные: для маркеров… для проёмов…», ссылается на «обе половины AC2а». | Тело issue: К3 (строки 50–53), AC2а (65–69), AC5 (72), Риск 2 (99). Выполнены сразу способ 1 (расширить К3/AC2а) и способ 3 (явно назвать оба мутанта в AC5) из трёх, предложенных r2 — не выбран один вариант, закрыты оба одновременно. |
|
||||
|
||||
Находка закрыта текстом ТЗ, проверено построчным чтением текущего тела
|
||||
issue, а не заявлением автора в комментарии.
|
||||
|
||||
## Унаследовано из r1 и r2
|
||||
|
||||
Без повторной проверки принято из `docs/reviews/SPEC-REVIEW-534-r1.md`
|
||||
(SHA `41ae5e99868db27e3bfde9a40426c0a636c374bd`) и
|
||||
`docs/reviews/SPEC-REVIEW-534-r2.md` (SHA `e40d3f18d2fdb1bc5f2f9f6dec969c490cddde1f`),
|
||||
поскольку дельта r3 этих участков текста не касается:
|
||||
|
||||
- Продуктовая рамка §7.1 (сценарий, «что человек увидит» до/после, выбор
|
||||
полного трека по явно названному критерию §5, i18n/миграция/бэкенд/
|
||||
touch как явные «нет») — не менялась с r1.
|
||||
- К1 («узел не переживает смену пространства») и К4/К5 (бюджет, вне
|
||||
объёма) — не менялись с r1.
|
||||
- AC1 (существующий смок остаётся зелёным без правок), AC3/AC4
|
||||
(численный ориентир 910 мс, полный перф-гейт на 9 профилях), AC6
|
||||
(golden), базовая часть AC7 (записи в оба changelog, `User-Visible:
|
||||
yes`) — числа и формулировки не менялись между r1/r2/r3.
|
||||
- Существование директивы `keyed` в зависимости `lit`, точность ссылок
|
||||
на номера строк `houseplan-card.ts:11787/12996` и на существующие
|
||||
мутанты в `scripts/mutation-gate.mjs:7434-7456` — репозиторий на этих
|
||||
участках не менялся с r1 (перепроверено заново в этом раунде наравне с
|
||||
новыми утверждениями К3, см. «Как проверялось» — совпадает с r1/r2).
|
||||
- Допущение №1 (`keyed` гарантированно выбрасывает поддерево) и №2
|
||||
(внутренний `repeat` идёт по быстрому пути внутри пространства,
|
||||
измерено 905,9 vs 903,7 мс) — не менялись с r2.
|
||||
|
||||
## Разбор AC (только то, чего касается дельта r3)
|
||||
|
||||
- **К3** — теперь однозначно покрывает оба списка отдельными абзацами с
|
||||
названным триггером, named CSS-классом и переходом для каждого.
|
||||
Двусмысленности, кто из двух списков защищён, а кто нет, больше нет.
|
||||
- **AC2а** — обе половины (маркеры/проёмы) сформулированы параллельно,
|
||||
способ доказательства общий (`smoke`), критерий прохождения проверяем
|
||||
по идентичности узла и отсутствию анимации для каждого списка отдельно.
|
||||
Единственное найденное уточнение — сценарий проёмов назван «Plan ↔
|
||||
View», хотя код переключает orphan-запись по условию «`plan` vs любой
|
||||
из трёх остальных режимов»; см. Low ниже, не блокирует.
|
||||
- **AC5** — оба существующих мутанта названы по имени, требование
|
||||
переанкеровки для обоих явное, не подразумеваемое. Неполнота,
|
||||
найденная в r2 (`openings-rendered-without-keys` не назван), закрыта.
|
||||
- **AC7** — «комментарий К3 стоит рядом с анимациями» (множественное
|
||||
число) теперь согласовано с К3, описывающим анимации для обоих
|
||||
списков; «Затронутые файлы» верно называют оба файла стилей
|
||||
(`plan.styles.ts`, где сегодня живёт единственный комментарий-ловушка
|
||||
на оба списка, и `devices.styles.ts`, рядом с самим
|
||||
`.device-shell-frame`) — технический вопрос «один комментарий или два»
|
||||
ТЗ не обязано решать (не наблюдаемо пользователем, решает автор по
|
||||
§7.1 «всё, чего пользователь не наблюдает»).
|
||||
|
||||
Остальные AC (AC1, AC3, AC4, AC6) дельта не задевает — наследуются из
|
||||
r1/r2 без повторной проверки.
|
||||
|
||||
## Находки
|
||||
|
||||
Блокирующих (High/Medium) находок нет.
|
||||
|
||||
### Low — АС2а/К3 называют триггер проёмов «Plan ↔ View», код переключает шире
|
||||
|
||||
`_openingsR` (`src/houseplan-card.ts:8241-8244`) убирает orphan-запись
|
||||
при **любом** режиме, отличном от `plan` (`view`, `devices`, `decor`), не
|
||||
только при `view`. «Plan ↔ View» — корректный и достаточный частный
|
||||
случай для смока: механизм (изменение состава `items` без смены
|
||||
`space.id`) один и тот же независимо от того, куда именно переключились
|
||||
из `plan`. Ни один AC от более узкой формулировки не становится
|
||||
неверным или непроверяемым — переключение именно в `view` даёт тот же
|
||||
эффект, что и в `devices`/`decor`.
|
||||
|
||||
**Решение ревьюера:** не блокирует. Формулировка достаточна для
|
||||
однозначного и проверяемого AC; если исполнитель захочет расширить смок
|
||||
на все три режима — это усиление сверх ТЗ, не требование ревью.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- Единственная Medium-находка r2 закрыта текстом ТЗ, проверено построчным
|
||||
чтением текущего тела issue (не заявлением автора) — таблица «Закрытие
|
||||
раунда r2» выше.
|
||||
- Технические факты нового текста К3 (переходы `.op-leaf`/`.op-arc`,
|
||||
условие `_openingsR` на `this._mode === 'plan'`, живые переходы
|
||||
`.device-shell-frame`) подтверждены прямым чтением исходников на
|
||||
материале ревью, а не приняты на слово.
|
||||
- AC2а, AC5, Риск 2, «Затронутые файлы» внутренне согласованы между
|
||||
собой и с новым текстом К3 — перекрёстные ссылки указывают на верные
|
||||
актуальные пункты.
|
||||
- Нумерация контракта (К1–К5) и AC (включая AC2а) не разъехалась между
|
||||
раундами.
|
||||
- Бюджет циклов не превышен: это второй жёлтый цикл из четырёх
|
||||
возможных для полного трека (§2.4), лимит 4, использовано было бы 2 —
|
||||
но этот раунд, если зелёный, бюджет не тратит (§4, #227).
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Не запускал код и гейты — этап spec, продуктовый код не менялся с r1
|
||||
(§2.4, §8).
|
||||
- Не проверял в браузере, действительно ли переключение `plan → devices`
|
||||
или `plan → decor` (а не только `plan → view`) физически воспроизводит
|
||||
тот же дефект — риск в Low-находке выше опирается на чтение условия
|
||||
`_openingsR`, а не на измерение; для вывода «AC2а достаточен» это не
|
||||
требуется, поскольку механизм (состав `items` меняется без смены
|
||||
`space.id`) один и тот же для всех трёх направлений.
|
||||
- Не проверял частоту реальных сценариев `showGhosts`/лайв-синка/
|
||||
Plan-Х-переключения «в проде» количественно — не требуется для этого
|
||||
раунда, вопрос был закрыт ещё в r1/r2 (сценарий кодом не исключён и не
|
||||
покрыт тестом — этого достаточно, чтобы Medium r2 был обоснованным, а
|
||||
его закрытие текстом ТЗ — достаточным).
|
||||
|
||||
## Вердикт
|
||||
|
||||
**Зелёный.** Обе предыдущие Medium-находки (r1 — допущение о безопасности
|
||||
внутрипространственного переиспользования маркеров, r2 — асимметрия
|
||||
контракта К3/AC2а между маркерами и проёмами) закрыты текстом ТЗ,
|
||||
проверено построчным чтением, а не заявлением автора. High нет. Одна
|
||||
Low-находка (формулировка триггера проёмов у`́же реального условия кода)
|
||||
не блокирует и не требует правки. Готово к разработке.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: подготовлено вручную ревьюером -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `dev`, коммит `1fb84755c22ba1fe990a96936399b579f0835a20` (рабочая
|
||||
копия на этом SHA; код с r1 не менялся, это ревью ТЗ).
|
||||
- Тело issue (нормализовано `gh issue view 534 --json body -q '.body'`):
|
||||
`sha256:18c0ed92ea4d832fcb8d3d4aeedf02d05af2e4c174d0f7e0b099d4b14e31ff8a`
|
||||
- Предыдущий материал: r1 — коммит `41ae5e99868db27e3bfde9a40426c0a636c374bd`,
|
||||
тело `sha256:98e12a0f7bec0c050802b1caee9c139db3bbc7b7c1169744fb1ccd567e35babd`;
|
||||
r2 — коммит `e40d3f18d2fdb1bc5f2f9f6dec969c490cddde1f`,
|
||||
тело `sha256:e9c78bcaf2429aff824dd80820d9b17d1a73c4b49c557df613d54a59d5590907`.
|
||||
- Вердикт: `green` · High 0 · Medium 0 · Low 1 (не блокирует)
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `dev`, коммит `1fb84755c22b` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `b5414fcc6281268a81bae04b9905f7199afa6242`
|
||||
```
|
||||
git log --all --format='%H %T' | grep b5414fcc6281
|
||||
```
|
||||
- Тело issue: `9d969d777f9787e5a50d02b2e675ad1448e945a27b47899d2d37c445a612f30e`
|
||||
- Вердикт конвейера: `green` · High 0
|
||||
@@ -2400,6 +2400,18 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: "const HANDLE_PAINT_ORDER = ['corners', 'edges'] as const;",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'daycycle-outline-not-promoted',
|
||||
guard: 'node demo/smoke_daycycle_raster.mjs',
|
||||
because: 'the day-cycle outline is a triple drop-shadow over the whole sheet; without its own '
|
||||
+ 'compositing layer every repaint of the plan re-runs three blur passes, and a demo-stand '
|
||||
+ 'pan costs fifteen times the rasterization of a static background (#532 AC1/AC2)',
|
||||
patches: [{
|
||||
file: 'src/styles/plan.styles.ts',
|
||||
find: ' will-change: filter;\n',
|
||||
replace: '',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'align-guides-exclude-dead-source',
|
||||
guard: 'node demo/smoke_align_guides.mjs',
|
||||
@@ -7426,8 +7438,9 @@ const MUTANT_DEFINITIONS = [
|
||||
+ 'animates a door that never moved (#525 AC1)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: ' return svg`<g class="openinglayer">${repeat(items, (o) => o.id, (o) => {',
|
||||
replace: ' return svg`<g class="openinglayer">${items.map((o) => {',
|
||||
find: " return svg`<g class=\"openinglayer\">${keyed(space.id, repeat(items,"
|
||||
+ " (o) => o.id, (o) => {",
|
||||
replace: " return svg`<g class=\"openinglayer\">${keyed(space.id, items.map((o) => {",
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -7438,8 +7451,8 @@ const MUTANT_DEFINITIONS = [
|
||||
+ 'painter, which finds the dragged marker by that attribute (#525 AC2)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: ' ${repeat(devs, (d) => d.id, (d) => this._renderDevice(',
|
||||
replace: ' ${devs.map((d) => this._renderDevice(',
|
||||
find: ' ${keyed(space.id, repeat(devs, (d) => d.id, (d) => this._renderDevice(',
|
||||
replace: ' ${keyed(space.id, devs.map((d) => this._renderDevice(',
|
||||
}],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { LitElement, html, svg, nothing, noChange, TemplateResult, PropertyValues, type PropertyDeclaration } from 'lit';
|
||||
import { cache as litCache } from 'lit/directives/cache.js';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
import { keyed } from 'lit/directives/keyed.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import './hp-dialog';
|
||||
import type { HpDialog } from './hp-dialog';
|
||||
@@ -11784,9 +11785,9 @@ export class HouseplanCard extends LitElement {
|
||||
view,
|
||||
(point) => this._scenePoint(point),
|
||||
)}
|
||||
${repeat(devs, (d) => d.id, (d) => this._renderDevice(
|
||||
${keyed(space.id, repeat(devs, (d) => d.id, (d) => this._renderDevice(
|
||||
d, view, showLqi, isoOverlays?.devices.get(d.id),
|
||||
))}
|
||||
)))}
|
||||
${this._renderVacuums(this._renderVacuumDevices, view, space.id)}
|
||||
${this._renderVacFit(view)}
|
||||
${this._renderOpeningLocks(view, isoOverlays?.locks)}
|
||||
@@ -12993,7 +12994,7 @@ export class HouseplanCard extends LitElement {
|
||||
const walls = this._spaceWalls;
|
||||
const openCuts = this._openCuts();
|
||||
const openingWallIndex = this._openingWallIndexFor(space, openCuts).value;
|
||||
return svg`<g class="openinglayer">${repeat(items, (o) => o.id, (o) => {
|
||||
return svg`<g class="openinglayer">${keyed(space.id, repeat(items, (o) => o.id, (o) => {
|
||||
if (o.orphanReason) return svg`<g class="opening orphan" data-hp="opening-orphan"
|
||||
data-id=${o.id} role="button" tabindex="0"
|
||||
aria-label=${this._t('opening.partition_orphan')}
|
||||
@@ -13045,7 +13046,7 @@ export class HouseplanCard extends LitElement {
|
||||
@pointerup=${(e: PointerEvent) => this._opPointerUp(e, o)}
|
||||
@pointercancel=${(e: PointerEvent) => this._opPointerUp(e, o)}></rect>
|
||||
</g>`;
|
||||
})}</g>`;
|
||||
}))}</g>`;
|
||||
}
|
||||
|
||||
/** Padlock badges for door-like openings with a lock entity. */
|
||||
|
||||
@@ -210,6 +210,9 @@ export const devicesStyles = css`
|
||||
background: transparent;
|
||||
box-shadow: var(--device-shell-shadow);
|
||||
/* no box-shadow here: cqw-sized, restarts on every container resize (#524) */
|
||||
/* these two are why the marker list keeps a per-item key under the
|
||||
space-level one (#525, #534): ghosts and live config sync shift
|
||||
positions inside one space — see plan.styles.ts for the full note */
|
||||
transition: border-color .15s, opacity .2s;
|
||||
pointer-events: auto;
|
||||
/* Normative production fallback: never add a per-marker backdrop blur. */
|
||||
|
||||
@@ -86,12 +86,21 @@ export const planStyles = css`
|
||||
opacity 1100ms cubic-bezier(.22, .61, .36, 1);
|
||||
}
|
||||
.stage.mode-transition .hp-day-cycle-env { transition: none; }
|
||||
/* #532: the outline keeps its OWN compositing layer. The group holds paper
|
||||
silhouettes only and does not change on hover or pan, but the filter used
|
||||
to live in the plan's own layer — so every repaint of the plan re-ran
|
||||
three blur passes over the whole sheet. Measured on a demo-stand pan
|
||||
(Chromium, CDP, summed RasterTask): 852 ms without the hint against 28 ms
|
||||
with it, where a static background costs 108 ms. The hint costs one layer
|
||||
of memory, so it is scoped to .daycycle and a static background never
|
||||
pays for it. */
|
||||
.stage.daycycle .hp-paperg,
|
||||
.hp-static-stage.daycycle .hp-paperg {
|
||||
filter:
|
||||
drop-shadow(0 0 1px var(--hp-day-cycle-outline-near))
|
||||
drop-shadow(0 0 5px var(--hp-day-cycle-outline-mid))
|
||||
drop-shadow(0 0 10px var(--hp-day-cycle-outline-far));
|
||||
will-change: filter;
|
||||
transition: filter 1100ms cubic-bezier(.22, .61, .36, 1);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@@ -530,7 +539,17 @@ export const planStyles = css`
|
||||
Anything animated that is rendered from a list needs a key. The Glow
|
||||
spots learned it first (glow-scene.ts, opacity), the openings and the
|
||||
device markers (.device-shell-frame, box-shadow) with this issue.
|
||||
Witness: demo/smoke_space_switch_transitions.mjs. */
|
||||
|
||||
#534: the shape is now keyed(space.id, repeat(items, (o) => o.id, ...)),
|
||||
and BOTH halves are load-bearing. The outer key does the space switch:
|
||||
there the key sets are disjoint, so repeat would build two key maps and
|
||||
walk both lists only to throw everything away — 80 ms of it on a plan
|
||||
with two hundred markers. The inner key does everything inside one
|
||||
space, where the list composition still changes underneath: an opening
|
||||
with an unresolved host is listed ONLY in plan mode (_openingsR), so
|
||||
entering and leaving the editor shifts the positions of the rest.
|
||||
Removing either half brings the phantom animation back through a
|
||||
different door. Witnesses: demo/smoke_space_switch_transitions.mjs. */
|
||||
.op-leaf {
|
||||
transition: transform 0.6s ease;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ const CAPS = {
|
||||
// model and rendering themselves remain in dedicated modules.
|
||||
// #500 moved config/layout identity and the adoption sequence into
|
||||
// src/config-adoption.ts; the ratchet keeps that win.
|
||||
'src/houseplan-card.ts': 13650,
|
||||
// 2026-09-11, #534: +1 for `import { keyed }`. The line buys back the 80 ms
|
||||
// that #525 put on every space switch, and there is nothing to move out —
|
||||
// the render itself did not grow by a character.
|
||||
'src/houseplan-card.ts': 13651,
|
||||
// #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.
|
||||
|
||||
Reference in New Issue
Block a user