Compare commits

...
Author SHA1 Message Date
claude[bot] 196be82d94 docs: review document for #655
Issue: #655
User-Visible: no
2026-09-27 13:05:34 +00:00
Sergey Matyunin f340157f59 test(backend): prove concurrent shutdown flush is idempotent (#655)
Issue: #655
User-Visible: no
2026-09-27 15:47:13 +03:00
claude[bot] 7acf5c74ad docs: review document for #655
Issue: #655
User-Visible: no
2026-09-27 12:43:54 +00:00
Sergey Matyunin 075a2d3fb1 fix(backend): flush deferred state on HA shutdown (#655)
Issue: #655
User-Visible: yes
2026-09-27 15:17:44 +03:00
claude[bot] 7a4032ba26 docs: review document for #655
Issue: #655
User-Visible: no
2026-09-27 12:07:59 +00:00
claude[bot] 9a7020ce42 docs: review document for #676
Issue: #676
User-Visible: no
2026-09-27 11:52:45 +00:00
claude[bot] 19edda83e3 docs: review document for #676
Issue: #676
User-Visible: no
2026-09-27 11:42:17 +00:00
claude[bot] 9fe7f88cb3 docs: review document for #676
Issue: #676
User-Visible: no
2026-09-27 11:28:14 +00:00
claude[bot] f1a87fbad9 docs(reviews): индекс после сдвига каталога (#658)
Issue: #658
User-Visible: no
2026-09-27 11:13:21 +00:00
claude[bot] ac8eac6034 docs: review document for #658
Issue: #658
User-Visible: no
2026-09-27 11:13:18 +00:00
Claude bdac4b4fdc ci: закрепить образ раннера, таймауты job и некруглые cron (#658)
`ubuntu-latest` с 19.10.2026 переезжает на Ubuntu 26, а golden, скриншоты
документации и перф-бюджеты сняты на текущем образе: все 43 job на раннере
теперь явно на `ubuntu-24.04`, один образ на все workflow. 26 job получили
`timeout-minutes` по наблюдённой длительности с запасом; гейт релиза — 180,
больше суммы собственных ожиданий (60 + 60 + 45). Расписания ушли с круглых
минут (ночь 02:17, мутанты 00:43, метрики 05:23, полный перф 04:11), ночь
пишет в summary сдвиг старта и предупреждает, если он больше часа.

test/workflow-hygiene.test.mjs держит все три правила по тексту workflow
(разбор `parseJobSettings` в scripts/workflow-jobs.mjs) и исполняет шаг
сдвига старта настоящим bash; порядок осознанного подъёма образа —
docs/DEVELOPMENT.md.

Issue: #658
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 13:57:21 +03:00
claude[bot] 6fc789e4c8 docs(reviews): индекс после сдвига каталога (#668)
Проверка (CI) / Классификация изменённых файлов (push) Successful in 26s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 50s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 18s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 26s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 49s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 42s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Successful in 1m51s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 11m41s
Проверка (CI) / Бандл головы dev для стенда (push) Skipped
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 36s
Issue: #668
User-Visible: no
2026-09-27 09:29:52 +00:00
claude[bot] 0a066177cf docs: review document for #668
Issue: #668
User-Visible: no
2026-09-27 09:29:49 +00:00
Sergey Matyuninandclaude[bot] 85cba3d8b4 docs: синхронизировать английское руководство (#668)
Английская версия догнана до структуры и содержания русского руководства. Структурный гейт защищает H2-H4 и маркер актуальной версии от нового рассинхрона.

Issue: #668
User-Visible: no
2026-09-27 09:18:50 +00:00
claude[bot] cbc39cea7b docs: review document for #668
Issue: #668
User-Visible: no
2026-09-27 09:18:50 +00:00
claude[bot] d24b0f0eac docs: review document for #668
Issue: #668
User-Visible: no
2026-09-27 09:18:50 +00:00
Claude 19587a797f Release v1.78.0-beta.5 candidate refresh
Пересборка кандидата на голове dev после слияния #669–#673 и #675:
бандл из текущих исходников, снимок STATUS и строка текущего цикла.

Issue: #663
Issue: #664
Issue: #665
Issue: #666
Issue: #667
Issue: #669
Issue: #670
Issue: #671
Issue: #672
Issue: #673
Issue: #675
User-Visible: no
Release: v1.78.0-beta.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 12:14:08 +03:00
claude[bot] 1ffb1c4eb3 docs(reviews): индекс после сдвига каталога (#675)
Issue: #675
User-Visible: no
2026-09-27 09:02:13 +00:00
claude[bot] b11d60eb4e docs: review document for #675
Issue: #675
User-Visible: no
2026-09-27 09:02:10 +00:00
Claude 6183119f1e ci: перекалибровать общий потолок spaceSwitchMs изометрии по ряду CI (#675)
spaceSwitchMs изометрии — одно холодное переключение этажа со сборкой
2.5D-геометрии; он растёт с каждой стадией 2.5D, и потолок 1800 мс из #89
stage 1 стал самим уровнем. Медиана perf-smoke на hosted-раннере:
1500 мс (09-09…09-12, 12 прогонов) → 1668 (09-19…09-25, 6) → 1798 после
#649 (9, σ 42, максимум 1867,7). Попытки одного SHA расходятся на 0,4 %,
разные прогоны одного SHA — до 5,5 %: больше образцов вердикт не меняют,
рычаг — потолок.

2200 мс: +17,8 % над наблюдённым максимумом и ниже единственной настоящей
регрессии окна (2719,6 мс, #583 до решётки, 09-16); удвоение уровня
краснеет с запасом. Одно число в трёх файлах: смок = полный профиль
(#473 AC4), плотный двойник = исторический (#160). Коэффициент и допуск
полного сравнения не тронуты. Обоснование — demo/performance/README.md,
тест закрепляет число и три точки ряда.

Issue: #675
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 08:50:34 +00:00
claude[bot] 7871efb0d6 docs(reviews): индекс после сдвига каталога (#673)
Issue: #673
User-Visible: no
2026-09-27 08:39:59 +00:00
claude[bot] e62dec74cd docs: review document for #673
Issue: #673
User-Visible: no
2026-09-27 08:39:55 +00:00
Matysh 2475403469 docs: обновить fingerprint после ребейза #673
После автоматического ребейза на dev все 11 кадров повторно проверены через docs:accept --identical: пиксели не изменились. Строгий docs-гейт теперь зелёный.

Issue: #673
User-Visible: no
2026-09-27 11:27:28 +03:00
claude[bot] 2aeda2dfa4 docs: review document for #673
Issue: #673
User-Visible: no
2026-09-27 08:24:17 +00:00
Matyshandclaude[bot] 855bede537 docs: обновить fingerprint скриншотов для #673
Все 11 кадров попиксельно совпали с закоммиченными (docs:accept --identical). PNG не изменялись; обновлён только отпечаток исходников.

Issue: #673
User-Visible: no
2026-09-27 07:51:54 +00:00
Matyshandclaude[bot] fc237f93be chore(golden): принять пять сцен Stage 6 (#673)
Issue: #673
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:adb377c40236510ed767a5a829dbbc88e51156e8d1f6eb206166fd3a7c61f921
2026-09-27 07:51:54 +00:00
Sergey Matyuninandclaude[bot] 15207dbacd test: добавить Stage 6 в golden-матрицу (#673)
Issue: #673
User-Visible: no
2026-09-27 07:51:54 +00:00
claude[bot] 2f857a0f6b docs(reviews): индекс после сдвига каталога (#669)
Issue: #669
User-Visible: no
2026-09-27 07:46:02 +00:00
claude[bot] 7e8605981b docs: review document for #669
Issue: #669
User-Visible: no
2026-09-27 07:45:59 +00:00
Claude 47c245b5ba perf: вычитать лестницы из площади пола только по запросу (#669)
Путь чистого пола строят четыре места рендера, а площадь читают только
подсказка комнаты и PDF. cleanFloorForRoom больше не вычитает лестницы при
построении пути: площадь считается при первом чтении и хранится в том же
закэшированном объекте. geometryMinusStairs передаёт в polyclip только
контуры лестниц, чей габарит пересекает вычитаемую геометрию, — результат
тот же, а комната на этаже с 250 лестницами больше не прогоняет все 250.

Issue: #669
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 09:48:02 +03:00
claude[bot] 2728a0ee54 docs(reviews): индекс после сдвига каталога (#670)
Issue: #670
User-Visible: no
2026-09-27 07:04:26 +00:00
claude[bot] df2455c530 docs: review document for #670
Issue: #670
User-Visible: no
2026-09-27 07:04:23 +00:00
Sergey Matyunin 9bc23f9871 fix: индексировать ревью стабильного релиза (#670)
Issue: #670
User-Visible: no
2026-09-27 09:43:51 +03:00
claude[bot] e45dcad47a docs: review document for #669
Issue: #669
User-Visible: no
2026-09-27 06:41:04 +00:00
claude[bot] 944adc65a9 docs(reviews): индекс после сдвига каталога (#672)
Issue: #672
User-Visible: no
2026-09-27 06:32:50 +00:00
claude[bot] 21555a5991 docs: review document for #672
Issue: #672
User-Visible: no
2026-09-27 06:32:49 +00:00
Sergey Matyuninandclaude[bot] 71c8a87e67 test: не считать README входами харнесса (#672)
Issue: #672
User-Visible: no
2026-09-27 06:32:49 +00:00
claude[bot] 8b775247a7 docs: review document for #669
Issue: #669
User-Visible: no
2026-09-27 06:28:44 +00:00
claude[bot] c9cd6cbf1f docs(reviews): индекс после сдвига каталога (#671)
Issue: #671
User-Visible: no
2026-09-27 06:02:37 +00:00
claude[bot] d36f79f156 docs: review document for #671
Issue: #671
User-Visible: no
2026-09-27 06:02:34 +00:00
Sergey Matyunin 05dfcd1c8a ci: guard asset inputs in Validate manifest
Issue: #671
User-Visible: no
2026-09-27 08:45:07 +03:00
Sergey Matyunin 098732512c Release v1.78.0-beta.5 candidate (rebuilt)
Проверка (CI) / Классификация изменённых файлов (push) Successful in 29s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 1m5s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 35s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 32s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 58s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 49s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 57s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 11m6s
Проверка (CI) / Бандл головы dev для стенда (push) Skipped
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 20s
Issue: #663
Issue: #664
Issue: #665
Issue: #666
Issue: #667
User-Visible: yes
Release: v1.78.0-beta.5
2026-09-27 03:05:37 +03:00
claude[bot] 88946f69db docs(reviews): индекс после сдвига каталога (#663)
Issue: #663
User-Visible: no
2026-09-26 23:59:05 +00:00
claude[bot] 7464d3e16c docs: review document for #663
Issue: #663
User-Visible: no
2026-09-26 23:59:02 +00:00
Matysh f2e114d71d docs: обновить fingerprint скриншотов после лестниц (#663)
Все 11 кадров попиксельно совпали с закоммиченными (docs:accept --identical, #512). PNG не изменялись; обновлён только отпечаток исходников.

Issue: #663
User-Visible: no
2026-09-27 02:26:19 +03:00
Matysh 9755b200e7 chore(golden): принять разметку обратной лестницы (#663)
Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:d6725147cfe96217ca01c22b86bc05eb4b69dd9c73402d88b57e071adfb4e181
2026-09-27 02:22:26 +03:00
Sergey Matyunin 8852207b18 test: стабилизировать проверку угла magnet лестницы (#663)
Issue: #663
User-Visible: no
2026-09-27 02:12:56 +03:00
claude[bot] 8176f1190f docs: review document for #663
Issue: #663
User-Visible: no
2026-09-26 23:09:27 +00:00
Sergey Matyunin d64338224d fix: учесть направление ступеней и защитить совместимость (#663)
Issue: #663
User-Visible: yes
2026-09-27 01:14:46 +03:00
claude[bot] 3f8aae838f docs: review document for #663
Issue: #663
User-Visible: no
2026-09-26 22:09:33 +00:00
Sergey Matyunin 59a60d526d test: сделать проверку magnet лестницы однозначной (#663)
Issue: #663
User-Visible: no
2026-09-27 00:17:55 +03:00
Matysh ac6f4bfe2e docs: обновить отпечаток скриншотов для лестниц (#663)
Issue: #663
User-Visible: no
2026-09-26 23:59:26 +03:00
Matysh 93876d7569 chore(golden): закрепить производительный расчёт лестниц (#663)
Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:fee8da264cf23f9cab943ced0d0c4c6f21d2c568daaa6d8890a13d6011626597
2026-09-26 23:53:08 +03:00
Matysh ea1f19c352 perf: разбить вычитание лестниц по кадрам (#663)
Issue: #663
User-Visible: no
2026-09-26 23:48:01 +03:00
Matysh bcc5481557 chore(golden): закрепить финальный resize smoke лестниц (#663)
Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:d5dabad3567794d874ee57e8b233990f1d801511c5e56ff08fdf6706c71d8464
2026-09-26 23:22:38 +03:00
Matysh ce26a051e6 test: стабилизировать pointer capture resize smoke (#663)
Issue: #663
User-Visible: no
2026-09-26 23:17:48 +03:00
Matysh e18a7c3385 chore(golden): закрепить финальный SHA лестниц (#663)
Эталоны 179 сцен совпали пиксель-в-пиксель; заменено 0 PNG.
Паспорт привязан к опубликованному дереву после исправлений Validate.

Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:77197ca53de1a025863f8213f5239f8122b2b844a8b77a8d485e27caa65a3a98
2026-09-26 23:01:38 +03:00
Matysh 16ede0c027 ci: очищать dist перед распаковкой бандла (#663)
Issue: #663
User-Visible: no
2026-09-26 22:51:14 +03:00
Matysh 42e7bc24a8 chore(golden): обновить аттестацию после CI-исправления (#663)
Эталоны 179 сцен совпали пиксель-в-пиксель; обновлён только паспорт
опубликованного SHA после исправления порядка Build и Unit tests.

Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:9c691b27fbe3c232afdf6ee513b4be1832e4f15de39c886a77089b84cd545741
2026-09-26 22:43:46 +03:00
Matysh 060d9ea547 ci: собирать dist до unit-контрактов бюджета (#663)
Issue: #663
User-Visible: no
2026-09-26 22:32:36 +03:00
Matysh efdc9df0c6 chore(golden): обновить аттестацию после синхронизации dev (#663)
Эталоны 179 сцен совпали пиксель-в-пиксель; обновлён только паспорт
финального опубликованного SHA после слияния актуального dev.

Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:86c19da2c36eb003ed5a8507c6924f2524e84b2f9ce41c3416cc0c215d91c5d4
2026-09-26 22:15:37 +03:00
Matysh ce4f820a03 test(mutation): сделать свидетели лестниц неэквивалентными (#663)
Issue: #663
User-Visible: no
2026-09-26 22:08:28 +03:00
Matysh b68aa216a9 chore(golden): обновить аттестацию после синхронизации dev (#663)
Эталоны 179 сцен совпали пиксель-в-пиксель; обновлён только паспорт
финального опубликованного SHA после слияния актуального dev.

Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:d0a9d150899e2d155fa350c05ad8c1d2270573d8e83c47b32957fef4e2ab5171
2026-09-26 21:52:19 +03:00
Matysh 19d2463dcc Merge origin/dev into issue/663-stairs
Issue: #663
User-Visible: no
2026-09-26 21:45:22 +03:00
Matysh 8aa7b9295e test(ci): учитывать локальную аттестацию эталонов (#663)
Issue: #663
User-Visible: no
2026-09-26 21:42:57 +03:00
claude[bot] cf876c712a docs(reviews): индекс после сдвига каталога (#667)
Issue: #667
User-Visible: no
2026-09-26 18:36:47 +00:00
claude[bot] 2d6f1e8be3 docs: review document for #667
Issue: #667
User-Visible: no
2026-09-26 18:36:44 +00:00
Matysh 6903f3320b chore(golden): принять сцены лестниц и актуальный dev (#663)
Проверены четыре новые сцены лестниц, перекомпоновка Plan-панели и уже выпущенные изменения активной вкладки и 2.5D-подписей.

Issue: #663
User-Visible: no
Release: v1.78.0-beta.5
Baseline-Reviewed-Local: sha256:a7995a1b977c613a5cde4a1f89b8efdb82129c36010b79d2a1ff7fb8e81f2428
2026-09-26 21:32:21 +03:00
Sergey Matyunin 847ebc91a9 test(golden): отделить стресс лестниц от кадров (#663)
Performance fixture по умолчанию сохраняет максимум 250 лестниц, а визуальные large-house сцены исключают стрессовую коллекцию, уже покрытую четырьмя отдельными кадрами.

Issue: #663
User-Visible: no
2026-09-26 21:22:14 +03:00
Claude 2fe20a095b docs: свести документацию с кодом и каноном (#667)
Пятнадцать расхождений из #667. Бюджет initial View назван одним
источником (scripts/bundle-budget.mjs). CONTRIBUTING описывает бандл после
#657, HA-харнесс после #630 и релиз через release-contract и release.yml.
Мутанты указывают на реестр scripts/mutation-registry.mjs. STATUS и
ROADMAP больше не держат PR в HACS «в очереди» и инструкции прежней
песочницы. SCOPE называет три редактора, форматы плана и радар #485.
README выводят первую комнату через «Стены». Русское руководство сверено с
v1.78.0-beta.5, таблица «Источник плана» склеена. UX-MODES и STYLING-HOOKS
следуют коду: проёмы в просмотре инертны, space-card рисует проёмы и
декор-изображения. Починены якорь в DEVICE-PRESENTATION и пол зума 1/3.
ADR 089/122/160 и ISOMETRIC помечают активацию через hp_alpha исторической.
Из раскладки ARCHITECTURE убран несуществующий src/data. legacy/README не
называет docs/superpowers действующими спецификациями.

Паритет английского руководства (п. 8) выделен в #668.

Issue: #667
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-26 18:19:52 +00:00
Sergey Matyunin 8eb97bcf46 test(golden): выбрать лестницу перед снимком (#663)
Сценарий включает инструмент выбора до клика по лестнице; существующие документационные кадры остаются побайтно прежними.

Issue: #663
User-Visible: no
2026-09-26 21:11:46 +03:00
Sergey Matyunin d03a68b88a feat: добавить лестницы между этажами (#663)
Прямые и винтовые лестницы получили отдельную модель, инструменты редактора, безопасную межэтажную навигацию, вычитание из чистой площади и плоское отображение в 2.5D.

Issue: #663
User-Visible: yes
2026-09-26 21:00:43 +03:00
Claude 781902b3a8 Release v1.78.0-beta.5 candidate
Проверка (CI) / Классификация изменённых файлов (push) Successful in 24s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 41s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 14s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 23s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 54s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 1m50s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 1m9s
Проверка (CI) / Бандл головы dev для стенда (push) Skipped
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 5m7s
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 18s
Issue: #664
Issue: #665
Issue: #666
User-Visible: yes
Release: v1.78.0-beta.5
2026-09-26 16:17:48 +03:00
claude[bot] f8a12f26b3 docs(reviews): индекс после сдвига каталога (#665)
Issue: #665
User-Visible: no
2026-09-26 13:14:54 +00:00
claude[bot] 2fdafb5bef docs: review document for #665
Issue: #665
User-Visible: no
2026-09-26 13:14:50 +00:00
claude[bot] d06cca2c19 docs: review document for #665
Issue: #665
User-Visible: no
2026-09-26 13:02:12 +00:00
Claude 8ffbdf8a28 test(2.5d): drive the label-metrics smoke through the public zoom controls (#665)
Validate on c93b3a90 failed no-new-private-writes: the smoke moved the
camera by writing card._view. It now presses the header zoom buttons
(data-hp zoom-fit / zoom-in) until the label font has at least doubled
(14 -> 38 px on the demo stand) and reads the private camera state only to
wait for the transition. Same assertions; the mutant
iso-room-label-44-box-centres-name still turns it red
(gapMatchesFlatAtEveryZoom, gapIsZoomIndependent).

Issue: #665
User-Visible: no
2026-09-26 15:23:52 +03:00
Claude c93b3a90ef fix(2.5d): room metrics keep their distance from the name at every zoom (#665)
The raised room label in 2.5D View sized its own box to the 44 px touch
minimum (min-height + justify-content: center). The name therefore sat
centred in 44 px while the metrics row, placed absolutely from the label
bottom, hung (44 - name height) / 2 below it: 18.3 px at a 9 px name and
6.4 px at a 39 px one on the demo stand, against a constant 0.1 name
height on the flat plan.

The 44 x 44 px floor moves into an invisible ::before (the door-lock
pattern), z-index -1 inside the label stacking context; the label box is
sized by its text again.

demo/smoke_iso_room_label_metrics.mjs measures gap / name height at two
cameras (font x4) in Flat and 2.5D, and the 44 px floor and area-link hit
targets on labels no raised device covers. Mutant
iso-room-label-44-box-centres-name.

Screenshots: 11 frames pixel-identical, fingerprint only.

Issue: #665
User-Visible: yes
2026-09-26 15:16:14 +03:00
claude[bot] 72d49040bf docs(reviews): индекс после сдвига каталога (#666)
Issue: #666
User-Visible: no
2026-09-26 12:11:55 +00:00
claude[bot] 288b9b41ab docs: review document for #666
Issue: #666
User-Visible: no
2026-09-26 12:11:51 +00:00
Claude 3bdd3c5163 fix(header): the active editor tab highlight covers its close X (#666)
The accent fill of the active Plan/Devices/Decor tab is now a pseudo-element
stretched over the 2 px group gap and the fixed 24 px X slot of #660, so
the X sits inside the highlighted zone without any box moving: the tab,
the slot, the X and the header keep their geometry. The X takes the tab
text colour, and the keyboard focus ring moves to the same pseudo-element
so it outlines the whole zone instead of cutting it at the tab edge.
.modes becomes its own stacking context so the z-index:-1 fill paints above
the group background. On a phone the mode tabs, and the fill, stay hidden.

smoke_toolbar_stable_width: painted-pixel probes for the gap, the slot edge
and top, and the first pixel after the slot, in every editor at every width
above 480 px; the X colour equals the tab colour. The existing geometry
and hit checks are unchanged and green. Mutant
toolbar-active-highlight-stops-at-tab.

Screenshots: 11 frames pixel-identical, fingerprint only.

Issue: #666
User-Visible: yes
2026-09-26 14:36:14 +03:00
claude[bot] 42f437cafe docs: review document for #665
Issue: #665
User-Visible: no
2026-09-26 11:20:43 +00:00
claude[bot] 942b9ede67 docs(reviews): индекс после сдвига каталога (#664)
Issue: #664
User-Visible: no
2026-09-26 10:25:16 +00:00
claude[bot] e53f9d4f77 docs: review document for #664
Issue: #664
User-Visible: no
2026-09-26 10:25:12 +00:00
Claude 40b73cc599 fix(space-card): nothing in the static schematic is hit-testable (#664)
The stage is pointer-events:none, but that is not an inherited ban: the
shared marker styles opt the 44 px floor (.dev::before) and the painted
capsule (.device-shell-frame) back in for the interactive plan (#564).
The static card imports them whole, so since v1.76.0-beta.2 it showed a
hand cursor over every marker and swallowed clicks that do nothing.

The card now closes every descendant opt-in (.hp-static-stage *, ::before,
::after). smoke_space_card asserts the browser hit test itself
(elementFromPoint over the marker and a 9x9 grid across the stage) and
the cursor, not the stage computed style that stayed green through the
regression. Mutant static-card-descendants-hit-testable.

Screenshots: 11 frames pixel-identical, fingerprint only
(npm run docs:accept -- --identical).

Issue: #664
User-Visible: yes
2026-09-26 13:02:51 +03:00
Sergey Matyunin 6cdcb1ea47 Release v1.78.0-beta.4 candidate refresh 2
Проверка (CI) / Классификация изменённых файлов (push) Successful in 24s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 52s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 34s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 34s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 48s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 34s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 36s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 8m43s
Проверка (CI) / Бандл головы dev для стенда (push) Skipped
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 20s
Повторная сборка кандидата после починки предрелизных unit- и mutation-гейтов.

Issue: #651
Issue: #654
Issue: #656
Issue: #657
Issue: #660
User-Visible: no
Release: v1.78.0-beta.4
2026-09-26 12:42:03 +03:00
Sergey Matyunin 85119fd9a2 test: восстановить защиту перехода редактора (#660)
Предрелизный гейт снова держит лимит монолитного файла, а защитный мутант #648 привязан к новой формуле общего бюджета сцены и панели. Мутант предметно краснит smoke_sections_resize.

Issue: #660
User-Visible: no
2026-09-26 12:42:03 +03:00
Sergey Matyunin f11c2a4c5d Release v1.78.0-beta.4 candidate refresh
Повторная сборка кандидата после исправления предрелизного smoke-гейта.

Issue: #651
Issue: #654
Issue: #656
Issue: #657
Issue: #660
User-Visible: no
Release: v1.78.0-beta.4
2026-09-26 12:35:19 +03:00
Sergey Matyunin bae6378006 fix: плавно менять высоту плана при входе в редактор (#660)
Переход снова интерполирует общий бюджет сцены и панели инструментов вместо измерения скрытого исходного кадра. Документационные кадры попиксельно не изменились.

Issue: #660
User-Visible: yes
2026-09-26 12:35:18 +03:00
Sergey Matyunin 467eaf873a chore(golden): эталоны beta.4 после 2.5D и панели
Приняты ровно 58 изменившихся сцен из Linux Validate; 117 остальных эталонов остались без изменений.

Issue: #651
Issue: #654
Issue: #660
User-Visible: no
Release: v1.78.0-beta.4
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36231891190
2026-09-26 12:17:29 +03:00
Sergey Matyunin 698f638969 docs: принять неизменные скриншоты для beta.4
Issue: #660
User-Visible: no
Release: v1.78.0-beta.4
2026-09-26 12:08:51 +03:00
Sergey Matyunin 3e48002c92 Release v1.78.0-beta.4 candidate
Issue: #651
Issue: #654
Issue: #656
Issue: #657
Issue: #660
User-Visible: yes
Release: v1.78.0-beta.4
2026-09-26 12:05:06 +03:00
claude[bot] 4c64189564 docs: review document for #663
Issue: #663
User-Visible: no
2026-09-26 09:04:16 +00:00
claude[bot] 9a19e17858 docs: review document for #662
Issue: #662
User-Visible: no
2026-09-26 08:56:02 +00:00
claude[bot] dc83a875f4 docs(reviews): индекс после сдвига каталога (#660)
Issue: #660
User-Visible: no
2026-09-26 08:51:15 +00:00
claude[bot] 5321c32ddc docs: review document for #660
Issue: #660
User-Visible: no
2026-09-26 08:51:15 +00:00
Sergey Matyuninandclaude[bot] 8906675879 test: вести smoke редакторов через публичный фасад (#660)
Issue: #660
User-Visible: no
2026-09-26 08:51:15 +00:00
Sergey Matyuninandclaude[bot] d3c88f06f9 feat: отполировать главную панель редакторов (#660)
Issue: #660
User-Visible: yes
2026-09-26 08:51:15 +00:00
claude[bot] cff83e43ec docs: review document for #663
Issue: #663
User-Visible: no
2026-09-26 08:47:57 +00:00
claude[bot] 422221fe93 docs: review document for #662
Issue: #662
User-Visible: no
2026-09-26 08:37:45 +00:00
claude[bot] 27517db8a4 docs: review document for #661
Issue: #661
User-Visible: no
2026-09-26 08:12:01 +00:00
claude[bot] 1ecf96d596 docs: review document for #660
Issue: #660
User-Visible: no
2026-09-26 07:43:22 +00:00
claude[bot] d65b9e39d5 docs: review document for #660
Issue: #660
User-Visible: no
2026-09-26 07:35:07 +00:00
claude[bot] 563fbe0759 docs: review document for #657
Issue: #657
User-Visible: no
2026-09-26 07:28:24 +00:00
Claudeandclaude[bot] b856dd33c8 infra(process): fast-forward merge rebuilds the review index too (#657 r1 H1)
The task branch no longer carries docs/reviews/INDEX.md (1b), and
merge-candidate rebuilt it only inside rebaseOnto. When dev did not move
the fast-forward pushed the stale index and reviews_index would turn dev
red. freshIndex(tip) commits the rebuilt index on top of the material
before the push; the merge stays a fast-forward.

Real-git test: fast-forward, then reviews-index --check on the dev head
is green. Mutant merge-ff-skips-review-index.

Issue: #657
User-Visible: no
2026-09-26 07:28:24 +00:00
claude[bot] 78d4da4abe docs: review document for #657
Issue: #657
User-Visible: no
2026-09-26 07:28:24 +00:00
Claudeandclaude[bot] 88c4e4e9ae infra(process): bundle and review index change only on the way to dev (#657)
Решения владельца: 1б — индекс ревью не пересобирается в ветке задачи,
только коммитами, идущими в dev; 2б — бандл меняет только кандидат
беты/релиза, стенд dev берёт его из артефакта Validate.

- scripts/bundle-policy.mjs: коммит, трогающий dist/** или
  custom_components/houseplan/frontend/**, обязан нести Release:
  (хук commit-msg и история в CI через validate-commit-provenance;
  коммиты с датой автора до 2026-09-27 не судятся); --verify судит
  целостность свежей сборки всегда, побайтовую сверку с закоммиченной
  копией — только на коммите, меняющем бандл, или кандидате; --clean.
- release-prerelease: публикация отказывает, если отпечаток исходников
  в закоммиченном манифесте не равен отпечатку дерева (хотфикс поверх
  кандидата без пересборки).
- bundle-sync: по умолчанию только demo/srv/assets; --release
  (npm run bundle:release) — ещё и custom_components.
- rebase-on-dev: конфликт в бандле берёт копию dev, без пересборки
  и amend.
- validate.yml: job dev_build публикует card-bundle головы dev в
  сиротскую ветку dev-build (scripts/dev-build.mjs); стенд накладывает
  её demo/stand/update-dev-bundle.sh.
- _process.yml: индекс ревью больше не пересобирается при приведении
  к dev и при публикации документа в ветку задачи.
- golden-wsl-artifact/golden-container: сборка перед съёмкой не
  считается правкой источника, после — bundle:clean.
- test/bundle-tree-committed: судит закоммиченный снимок, не диск.
- 11 мутантов в реестре; PROCESS/AGENTS/DEVELOPMENT/AUTHOR/REVIEWER.

Issue: #657
User-Visible: no
2026-09-26 07:28:23 +00:00
claude[bot] 4b8a82c08a docs: review document for #660
Issue: #660
User-Visible: no
2026-09-26 07:23:13 +00:00
claude[bot] 54f197209d docs: review document for #656
Issue: #656
User-Visible: no
2026-09-26 07:19:32 +00:00
Sergey Matyunin 43fab645b0 fix(ci): fail closed on newest full release proof (#656)
Issue: #656
User-Visible: no
2026-09-26 10:02:40 +03:00
claude[bot] 9287f798b3 docs: review document for #654
Issue: #654
User-Visible: no
2026-09-26 06:39:45 +00:00
Sergey Matyunin f5c6d70b7c fix: stabilize cold 2.5D first frame
Issue: #654
User-Visible: yes
2026-09-26 09:05:20 +03:00
claude[bot] 6d3738adef docs: review document for #654
Issue: #654
User-Visible: no
2026-09-26 05:29:35 +00:00
claude[bot] 6297af6701 docs: review document for #654
Issue: #654
User-Visible: no
2026-09-26 05:22:56 +00:00
claude[bot] 803c0f0eaa docs: review document for #651
Issue: #651
User-Visible: no
2026-09-26 01:54:48 +00:00
Sergey Matyunin e73e56dd94 test(2.5D): cover rigid group boundaries and fallback
Issue: #651
User-Visible: no
2026-09-26 04:34:49 +03:00
claude[bot] 7cc5423efe docs: review document for #651
Issue: #651
User-Visible: no
2026-09-26 01:30:54 +00:00
Sergey Matyunin c40266431c test(2.5D): retire superseded zoom mutant
Issue: #651
User-Visible: no
2026-09-26 03:54:57 +03:00
Sergey Matyunin 4ab7ecb4a2 fix(2.5D): keep device overlays stable while zooming
Issue: #651
User-Visible: yes
2026-09-26 03:41:32 +03:00
claude[bot] c6937f9fc7 docs: review document for #651
Issue: #651
User-Visible: no
2026-09-26 00:21:46 +00:00
claude[bot] 9d4f68a4ee docs: review document for #651
Issue: #651
User-Visible: no
2026-09-26 00:14:38 +00:00
Sergey Matyunin ec2b8c1522 fix(2.5D): preserve geometry on settings toggle
Проверка (CI) / Классификация изменённых файлов (push) Successful in 3m7s
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 6m52s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / HACS: валидация репозитория (push) Failing after 29s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 57s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 16s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 1m25s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 1m32s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 5m6s
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 18s
Issue: #649
User-Visible: no
2026-09-25 23:33:07 +03:00
Sergey Matyunin 8703727951 fix(2.5D): make floor shadow twins geometry-only
Render only the core and value-badge geometry needed by the common floor-shadow layer. Avoid mounting hidden icons, activity indicators and satellites for every 2.5D marker.

Issue: #649
User-Visible: no
2026-09-25 23:05:56 +03:00
Sergey Matyunin cc04d341d4 fix(2.5D): reuse projection snapshot during settings toggle
The beta performance gate exposed a duplicate 2.5D scene resolution in one
Lit update: willUpdate built the projection snapshot and immediately cleared
it before render. Invalidate first so the computed snapshot is reused.

Issue: #649
User-Visible: no
2026-09-25 22:44:37 +03:00
Sergey Matyunin 8501d10988 chore(golden): эталоны визуальных изменений beta.3
Съёмка — Linux CI, Validate 36177319720 на 142340ee; приёмка — Windows
владельца с HP_ALLOW_FOREIGN_CAPTURE (#455).

Приняты ровно 52 заявленных изменения: публичный 2.5D, мобильная шапка и
стабильная панель редактора; необъявленных расхождений нет.

Issue: #616
Issue: #647
Issue: #649
User-Visible: no
Release: v1.78.0-beta.3
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36177319720
2026-09-25 22:17:43 +03:00
Sergey Matyunin 142340ee1c Release v1.78.0-beta.3 candidate
Issue: #616
Issue: #626
Issue: #628
Issue: #638
Issue: #647
Issue: #648
Issue: #649
Issue: #650
User-Visible: yes
Release: v1.78.0-beta.3
2026-09-25 22:03:17 +03:00
claude[bot] 0e8e87303e docs: review document for #649
Проверка (CI) / Классификация изменённых файлов (push) Successful in 22s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 48s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 23s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 24s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 46s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 34s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 44s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 8m29s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 19s
Issue: #649
User-Visible: no
2026-09-25 16:51:38 +00:00
Claude 106fdc096d fix(2.5D): prove the sun beam is cut by bodies; review r1 lows (#649)
CODE-REVIEW-649-r1 M1: the 2.5D beam's cut by physical bodies (AC7) had no
witness. Unit: a column in the beam leaves the floor between window and
column lit and shades it behind the column; smoke: a Solid partition across
the south beam removes floor from the rendered wash. Mutants: occluders
extruded toward the sun, occluders ignored, card drops occluders.

Lows: L1 stale alpha wording (DEVELOPMENT, ARCHITECTURE, card comment,
benchmark); L3 dark-theme state edges computed per theme; L4 Alert beats a
plain Hover and a plain Selected; L5 a virtual marker has no dashed ring;
L6 tiles and shadows stay without walls (show_borders off).

Issue: #649
User-Visible: no
2026-09-25 18:55:28 +03:00
claude[bot]andClaude e1f1c7d908 docs: review document for #649
Issue: #649
User-Visible: no
2026-09-25 18:55:27 +03:00
Claude 2ed050311b test(mutants): stage3-w12 guard follows the renamed #649 contract test
The guard pattern "Labs iso is presentation-only" matched no test after #649
renamed it, so node --test ran the file with zero subtests and the mutant
survived (Validate 36151140394, shard 3/6). Point it at the test that still
asserts params.getAll('hp_alpha').

Issue: #649
User-Visible: no
2026-09-25 18:55:15 +03:00
Claude e5ab4659f6 feat: 2.5D stage 6 — public setting, raised tiles, soft sun, theme-free walls (#649)
- settings.volumetric_view (General settings › Display, third switch) replaces
  the alpha entry, the header projection toggle and the phone-menu item; one
  rule for card, sidebar page and kiosk; editors stay Flat; backend accepts
  only a boolean, support package copies it.
- Raised tiles in 2.5D View: no ring, rounded body min(0.275 D, 0.3 h), edge
  0.1 D with the lab filters, ×1.12 size also in layout/collision, lift
  0.075 D, one floor-shadow layer under all markers (theme × floor table),
  frames hugging tile and edge (Alert > Focus > Selected > Hover), forced
  colours without edge and shadow (src/iso-tiles.ts, styles/iso-tiles).
- Soft sun wash instead of projected Flat wedges (src/iso-sun.ts): same gates
  and windows as sun_rays, length from elevation, parallelogram along the sun,
  tone and streaks by floor lightness, sill line.
- Walls take the user's wall colour (top opaque, sides × .77/.68/.60); theme
  rules for walls/openings/labels removed; furniture uses the Flat stroke rule.
- Smokes smoke_iso_tiles/iso_sun/iso_theme_walls/volumetric_setting, 16 new
  mutants, acceptance scenes STAGE6_ACCEPTANCE_SCENARIOS (golden with their
  Linux CI baselines), ACCEPTANCE.md side by side.

Issue: #649
User-Visible: yes
2026-09-25 18:55:14 +03:00
465 changed files with 23554 additions and 4805 deletions
+9 -5
View File
@@ -52,7 +52,8 @@ jobs:
# commit/tree, а не самостоятельно читают dev в разное время.
material:
name: "Зафиксировать неизменяемый материал"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
sha: ${{ steps.identity.outputs.sha }}
tree: ${{ steps.identity.outputs.tree }}
@@ -112,7 +113,7 @@ jobs:
name: "Мутанты: каждый обязан красить тесты (шард ${{ matrix.shard }} из 6)"
needs: material
if: needs.material.outputs.reuse != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -208,7 +209,8 @@ jobs:
name: "Доказать единый material всех шардов"
needs: [material, mutants]
if: always() && needs.material.outputs.reuse != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
@@ -239,7 +241,8 @@ jobs:
name: "Записать маркер зелёного прогона"
needs: [material, mutants, evidence]
if: needs.material.outputs.reuse != 'true' && needs.mutants.result == 'success' && needs.evidence.result == 'success'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
@@ -275,7 +278,8 @@ jobs:
# является — её шарды пропущены намеренно. Любой другой пропуск шардов
# (упал material) по-прежнему заводит issue.
if: always() && github.event_name == 'schedule' && needs.material.outputs.reuse != 'true' && (needs.mutants.result != 'success' || needs.evidence.result != 'success')
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
actions: read
+21 -1
View File
@@ -32,9 +32,29 @@ permissions:
jobs:
dispatch:
name: "Запустить Validate на dev с полным набором и дождаться результата"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
# #658: при плане 02:30 UTC ночь фактически стартовала в 07:42–08:05 и
# кончалась в рабочее время владельца. Сдвиг старта больше часа — видимое
# предупреждение, а не молча съеденная ночь. `github.event.schedule` —
# строка cron вызывающего `nightly.yml`; NOW_EPOCH подставляет только тест.
- name: "Сдвиг старта ночи против расписания"
if: github.event_name == 'schedule'
env:
SCHEDULE: ${{ github.event.schedule }}
run: |
set -euo pipefail
read -r minute hour _ <<< "$SCHEDULE"
now=${NOW_EPOCH:-$(date -u +%s)}
planned=$(date -u -d "@$now" +%F)
planned=$(date -u -d "$planned $hour:$minute" +%s)
[ "$planned" -le "$now" ] || planned=$((planned - 86400))
lag=$(( (now - planned) / 60 ))
echo "- Старт по расписанию \`$SCHEDULE\` (UTC): сдвиг $lag мин" >> "$GITHUB_STEP_SUMMARY"
if [ "$lag" -gt 60 ]; then
echo "::warning::ночной прогон стартовал на $lag мин позже расписания ($SCHEDULE UTC) — очередь расписаний GitHub (#658)"
fi
- env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
+1 -1
View File
@@ -25,7 +25,7 @@ permissions:
jobs:
metrics:
name: "Снимок недели: issue, раунды, прогоны"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
# Код — из dev, как у reconcile: расписание читается из main, а исполняется
+1 -1
View File
@@ -19,7 +19,7 @@ permissions:
jobs:
reconcile:
name: "Один снимок S4/S7 без polling модели"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
concurrency:
group: process-reconcile
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
resume:
name: "Разбудить раунд, ждавший этот Validate"
if: github.event.workflow_run.event == 'workflow_dispatch' && startsWith(github.event.workflow_run.head_branch, 'issue/')
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
concurrency:
group: process-resume-${{ github.event.workflow_run.head_branch }}
+17 -13
View File
@@ -48,7 +48,8 @@ jobs:
# Только статусные метки этапов ревью запускают конвейер (#499). Остальные
# события помечаются skipped и не занимают место в группе concurrency.
if: github.event.label.name == 'S4-spec-review' || github.event.label.name == 'S7-code-review'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
concurrency:
group: process-issue-${{ github.event.issue.number }}
cancel-in-progress: false
@@ -282,7 +283,7 @@ jobs:
issues: write
needs: guard
if: needs.guard.outputs.stage != ''
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
concurrency:
group: process-issue-${{ github.event.issue.number }}
cancel-in-progress: false
@@ -446,11 +447,12 @@ jobs:
printf 'конфликтуют:\n%s\n' "${files:-(git не назвал файлы)}"
exit 0
fi
# #635 r2: ребейз мог принести в docs/reviews документы других задач,
# а закоммиченный INDEX.md — снимок каталога — их не знает. Свежесть
# держит тест «индекс свеж» в Validate, поэтому индекс пересобирается
# здесь же, коммитом конвейера (класс C), до фиксации материала.
node scripts/reviews-index.mjs --dir=docs/reviews --commit-if-stale --issue="$NUM"
# #657 (1б): индекс ревью в ветке задачи не пересобирается — ни здесь,
# ни при публикации документа. Он пересобирается только коммитами,
# которые идут прямо в dev: слиянием кандидата (merge-candidate.mjs)
# и публикацией документа ревью ТЗ. Коммит индекса в ветке конфликтовал
# с документами соседей по построению (#643) и возвращал зелёные
# задачи на повторное ревью; гейт «индекс свеж» судит только dev.
# --force-with-lease с явным ожидаемым значением обязателен: между
# fetch и push автор мог запушить коммит, и слепой --force потерял бы
# его молча. Расхождение lease — падение прогона, а не предупреждение:
@@ -851,7 +853,7 @@ jobs:
issues: write
needs: [guard, prepare]
if: needs.prepare.outputs.proceed == 'true' && needs.prepare.outputs.reuse != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
concurrency:
group: process-issue-${{ github.event.issue.number }}
cancel-in-progress: false
@@ -933,7 +935,7 @@ jobs:
- name: Установить Chromium
if: steps.pw.outputs.cache-hit != 'true'
# Без --with-deps: системные библиотеки Chromium предустановлены в
# образе ubuntu-latest, а apt при промахе кэша съедал минуты из бюджета
# образе раннера, а apt при промахе кэша съедал минуты из бюджета
# ревью и подолгу перебирал недоступное azure-зеркало (#175). Если
# библиотека когда-нибудь пропадёт из образа, Chromium не запустится с
# внятной ошибкой — тогда флаг вернуть.
@@ -1195,7 +1197,7 @@ jobs:
issues: write
needs: [guard, prepare, model_review]
if: always() && needs.guard.outputs.stage != ''
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
concurrency:
group: process-issue-${{ github.event.issue.number }}
cancel-in-progress: false
@@ -1375,9 +1377,11 @@ jobs:
# забрал бы всё, что там окажется, а после reset там не должно быть
# ничего постороннего — но полагаться на «не должно» здесь нельзя.
git add -- "$doc" 2>/dev/null || true
# #635: индекс ревью пересобирается тем же коммитом, что и документ —
# иначе он устаревает на первом же раунде. Генерируемый файл, класс C.
if [ -f "$doc" ]; then
# #635/#657 (1б): индекс пересобирается тем же коммитом, что и
# документ, только когда документ идёт прямо в dev (ревью ТЗ). В ветку
# задачи — один документ: индекс там конфликтовал бы с документами
# соседей в dev по построению; его пересоберёт слияние кандидата.
if [ -f "$doc" ] && [ "$target" = "dev" ]; then
node scripts/reviews-index.mjs --dir=docs/reviews
git add -- docs/reviews/INDEX.md
fi
+2 -1
View File
@@ -43,7 +43,8 @@ jobs:
telegram:
name: Оповещение в Telegram (только стабильные)
if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_call' && inputs.prerelease == false) }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out release notes for a reusable call
if: ${{ inputs.reusable == true }}
+2 -1
View File
@@ -31,7 +31,8 @@ permissions:
jobs:
capture:
name: Съёмка и сверка скриншот-индекса
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
+4 -3
View File
@@ -16,9 +16,10 @@ on:
required: false
default: dev
schedule:
# Каждую ночь, 01:00 UTC (04:00 MSK) — после суток правок и до ночного
# полного Validate (nightly.yml, 02:30 UTC), чтобы не делить раннеры (#513).
- cron: '0 1 * * *'
# Каждую ночь, 00:43 UTC (03:43 MSK) — после суток правок и до ночного
# полного Validate (nightly.yml, 02:17 UTC), чтобы не делить раннеры (#513).
# Минута не круглая: такие старты GitHub сдвигает меньше (#658).
- cron: '43 0 * * *'
permissions:
contents: read
+3 -1
View File
@@ -9,8 +9,10 @@ name: Ночной полный прогон dev
# держит копии равными.
on:
# 02:17 UTC, не на круглой минуте: старты «ровно в час/полчаса» GitHub
# откладывает на часы (факт — 07:42–08:05 при плане 02:30, #658).
schedule:
- cron: '30 2 * * *'
- cron: '17 2 * * *'
workflow_dispatch: {}
permissions:
+6 -2
View File
@@ -6,8 +6,12 @@ on:
push:
branches:
- main
paths-ignore:
- ".github/workflows/**"
- "docs/**"
schedule:
- cron: "0 4 * * 1"
# Минута не круглая (#658): доходит до main вместе со стабильным релизом.
- cron: "11 4 * * 1"
workflow_dispatch:
inputs:
comparison_ref:
@@ -28,7 +32,7 @@ jobs:
# Every profile keeps base and candidate sequential on one hosted runner.
# Independent profile pairs run in parallel: cross-profile timing is never
# compared, while serialising all profile pairs cannot fit the job timeout.
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
+3 -2
View File
@@ -10,8 +10,9 @@ name: Метрики процесса
on:
schedule:
# Понедельник 05:00 UTC — после ночных прогонов, до рабочего дня.
- cron: '0 5 * * 1'
# Понедельник 05:23 UTC — после ночных прогонов, до рабочего дня; минута
# не круглая, чтобы не стоять в очереди «ровных» расписаний (#658).
- cron: '23 5 * * 1'
workflow_dispatch:
inputs:
days:
+7 -3
View File
@@ -21,7 +21,9 @@ concurrency:
jobs:
gate:
name: "Гейт: зелёная Проверка и релизный контракт"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
# Больше ожидания зелёного Validate в release-gate.mjs (до 60 мин) (#658).
timeout-minutes: 75
outputs:
sha: ${{ steps.candidate.outputs.sha }}
tag: ${{ steps.candidate.outputs.tag }}
@@ -122,7 +124,8 @@ jobs:
publish:
name: Публикация тега и релиза
needs: gate
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
outputs:
url: ${{ steps.verify.outputs.url }}
newly_published: ${{ steps.release.outputs.newly_published }}
@@ -289,7 +292,8 @@ jobs:
close-merged:
name: Закрытие вошедших issue
needs: [gate, publish]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
actions: read
+4 -4
View File
@@ -46,7 +46,7 @@ concurrency:
jobs:
prepare:
name: "Ревью релиза: вход линии"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
@@ -110,7 +110,7 @@ jobs:
name: "Ревью релиза: работа модели"
needs: prepare
if: needs.prepare.outputs.proceed == 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
# Недоверенная стадия. Прав на запись нет никаких: ни в репозиторий, ни в
# issue. Документ публикует `publish`; находки в issue превращает владелец.
@@ -258,7 +258,7 @@ jobs:
publish:
name: "Ревью релиза: документ в dev"
needs: [prepare, model_review]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
@@ -307,7 +307,7 @@ jobs:
"$TAG" "$CANDIDATE" "${BASE:-—}" "${ISSUES:-—}" "$RUN_URL"
printf '```\n'
} > "$DOC"
node scripts/reviews-index.mjs --dir=docs/reviews
node scripts/reviews-index.mjs --dir=docs/reviews --strict
git add -- "$DOC" docs/reviews/INDEX.md
git diff --cached --name-only | node scripts/review-doc-guard.mjs
git -c user.name="claude[bot]" \
+13 -6
View File
@@ -48,7 +48,8 @@ jobs:
name: "Кандидат: точный SHA, режим и черновик"
# Публикация беты руками — не наш случай: у бет свой staged-путь.
if: ${{ github.event_name == 'workflow_dispatch' || !github.event.release.prerelease }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
sha: ${{ steps.resolve.outputs.sha }}
tag: ${{ steps.resolve.outputs.tag }}
@@ -142,7 +143,7 @@ jobs:
needs: candidate
if: ${{ needs.candidate.outputs.prerelease != 'true' }}
continue-on-error: true
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
actions: write
@@ -164,7 +165,10 @@ jobs:
gate:
name: "Гейт: контракт, Validate, Full Performance и E2E на точном SHA"
needs: candidate
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
# Больше суммы собственных ожиданий job: Validate и Full Performance ждутся
# до 60 мин каждый (release-gate.mjs), E2E — до 45 (e2e-gate.mjs): 165 < 180 (#658).
timeout-minutes: 180
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
@@ -234,7 +238,8 @@ jobs:
stage:
name: "Сборка: ассеты, SHA256SUMS и загрузка в черновик"
needs: [candidate, gate]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
@@ -374,7 +379,8 @@ jobs:
publish:
name: "Публикация и сверка публичных байтов"
needs: [candidate, gate, stage]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
outputs:
url: ${{ steps.verify.outputs.url }}
name: ${{ steps.verify.outputs.name }}
@@ -474,7 +480,8 @@ jobs:
# make that distribution failure impossible to miss in the release run.
if: ${{ needs.candidate.outputs.prerelease == 'true' }}
needs: [candidate, publish]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Verify the published tag is the prerelease HACS will discover
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
+80 -17
View File
@@ -50,7 +50,8 @@ jobs:
# действовать — на уровне шагов, с той же гранулярностью в логе.
preflight:
name: "Предполёт: документация, провенанс, процесс"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
# `actions: read` — чтение списка прогонов Validate (#388), `issues: read` —
# проверка 8 процессного гейта. Права перечислены явно, потому что job
# обращается к API сверх содержимого репозитория.
@@ -247,7 +248,8 @@ jobs:
# там делает код-ревью, которое гоняет гейты само (#127).
changes:
name: Классификация изменённых файлов
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
# Чтение списка прогонов Validate — единственное, что этой job нужно сверх
# содержимого репозитория (#387).
permissions:
@@ -406,7 +408,8 @@ jobs:
# performance_smoke включает набор, иначе glow-only результат засчитался бы
# прогону, которому нужен изометрический профиль.
needs: changes
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
smoke: ${{ steps.probe.outputs.smoke }}
golden: ${{ steps.probe.outputs.golden }}
@@ -555,7 +558,8 @@ jobs:
name: "HACS: валидация репозитория"
needs: changes
if: needs.changes.outputs.integration == 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: HACS validation
@@ -567,7 +571,8 @@ jobs:
name: "Hassfest: манифест интеграции"
needs: changes
if: needs.changes.outputs.integration == 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Hassfest validation
@@ -598,7 +603,7 @@ jobs:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -727,7 +732,8 @@ jobs:
name: "Фронтенд: типы, юниты, мутанты, синхрон бандла"
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 40
steps:
# Полная история без блобов (#342): гейт «новый код не добавляет any»
# diff-aware, ему нужен диапазон, а содержимое старых ревизий — нет.
@@ -780,14 +786,21 @@ jobs:
node scripts/no-new-any.mjs --base "$base" --head "$HEAD_SHA" || status=1
node scripts/no-new-private-writes.mjs --base "$base" --head "$HEAD_SHA" || status=1
exit "$status"
- name: Unit tests
run: npm test
- name: Build
run: npm run build
# Часть unit-контрактов проверяет размеры реально поставляемых графов в
# dist/ (#438, #593, #627). В issue-ветке закоммиченный dist законно
# отстаёт от исходников (#657), поэтому юниты обязаны читать свежую
# сборку — тот же порядок закреплён локальным gate:small.
- name: Unit tests
run: npm test
# Entry, manifest and every content-hashed chunk are one artifact (#337).
# #657: the committed bundle changes only in a release candidate, so the
# fresh build must equal it only there; elsewhere the committed copy
# legitimately lags and only the fresh dist is verified.
- name: Card bundle trees in sync
run: |
node scripts/bundle-tree.mjs dist custom_components/houseplan/frontend
node scripts/bundle-policy.mjs --verify HEAD
npm run bundle:budget
# #624: `tsc --noUnusedLocals` чист вне порта/харнесса, шесть чисел
# связности монолита не выше scripts/monolith-baseline.json. После
@@ -819,12 +832,48 @@ jobs:
retention-days: 1
if-no-files-found: error
# #657 (2б): бандл в дереве dev меняет только кандидат беты, поэтому стенд
# dev.houseplan.tech берёт его не из дерева, а из этого артефакта: после
# зелёного фронтенда на push в dev собранный `dist/` публикуется в служебную
# ветку `dev-build` одним коммитом без истории (scripts/dev-build.mjs), хост
# забирает его demo/stand/update-dev-bundle.sh. Опубликуется только голова:
# если dev ушёл вперёд, пушит следующий прогон. Не вход доказательства:
# стенд — не гейт, и сбой публикации не красит проверку кода.
dev_build:
name: "Бандл головы dev для стенда"
needs: frontend
if: github.event_name == 'push' && github.ref == 'refs/heads/dev' && needs.frontend.result == 'success'
runs-on: ubuntu-24.04
timeout-minutes: 10
continue-on-error: true
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 22
# download-artifact распаковывает поверх checkout и не удаляет старые
# content-hashed чанки закоммиченного release-бандла (#657). Свежий
# артефакт должен быть единственным содержимым dist, иначе bundle-tree
# справедливо видит оставшиеся файлы как orphan assets.
- name: Очистить закоммиченный dist перед артефактом
run: |
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
- name: Забрать собранный бандл
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: card-bundle
path: dist
- name: Опубликовать в dev-build
run: node scripts/dev-build.mjs --sha "$GITHUB_SHA" --dist dist
smoke:
name: Смоки в браузере (шард ${{ matrix.shard }} из 3)
# Gated on `frontend` so a typecheck failure does not burn browser minutes.
needs: [changes, frontend, reuse]
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.smoke != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
# Смоки шардируются: последовательный прогон занимал ~7.5 минут и был
# критическим путём всего Validate. Три шарда режут его примерно вдвое;
@@ -857,6 +906,9 @@ jobs:
# Бандл приходит артефактом из job `frontend` (#336): та же сборка, но
# один раз на прогон. Свежесть проверяет сам продукт — `assertFreshDemoBundle`
# сверяет вшитый отпечаток с исходниками этого дерева.
- name: Очистить закоммиченный dist перед артефактом
run: |
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
- name: Забрать собранный бандл
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
@@ -928,7 +980,8 @@ jobs:
name: "Смоки: все шарды зелёные"
needs: [changes, smoke, reuse]
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.smoke != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Записать маркер успеха
run: |
@@ -952,7 +1005,8 @@ jobs:
# inexpensive and catches a different class of regressions than timings.
needs: [changes, frontend, reuse]
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.golden != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
@@ -977,6 +1031,9 @@ jobs:
# Бандл приходит артефактом из job `frontend` (#336): та же сборка, но
# один раз на прогон. Свежесть проверяет сам продукт — `assertFreshDemoBundle`
# сверяет вшитый отпечаток с исходниками этого дерева.
- name: Очистить закоммиченный dist перед артефактом
run: |
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
- name: Забрать собранный бандл
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
@@ -1051,7 +1108,7 @@ jobs:
# prereleases. The expensive same-runner comparison lives in performance.yml.
needs: [changes, frontend, reuse]
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.performance_smoke != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
# 15 минут не хватало, когда установка браузера шла через apt: замер
# начинался на исходе окна (#206). Запас на холодный кэш — при попадании
# job укладывается в те же минуты, что и раньше. Диффозависимые профили
@@ -1081,6 +1138,9 @@ jobs:
# Бандл приходит артефактом из job `frontend` (#336): та же сборка, но
# один раз на прогон. Свежесть проверяет сам продукт — `assertFreshDemoBundle`
# сверяет вшитый отпечаток с исходниками этого дерева.
- name: Очистить закоммиченный dist перед артефактом
run: |
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
- name: Забрать собранный бандл
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
@@ -1144,7 +1204,8 @@ jobs:
name: "Геометрия: TS/Python parity исполнена"
needs: [changes, reuse]
if: needs.changes.outputs.geometry_parity == 'true' && needs.reuse.outputs.geometry_parity != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
@@ -1178,7 +1239,8 @@ jobs:
name: "Бэкенд: pytest в Home Assistant"
needs: [changes, reuse]
if: needs.changes.outputs.backend == 'true' && needs.reuse.outputs.backend != 'true'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
# Browser fixtures are generated by their real ESM factories and then
@@ -1264,7 +1326,8 @@ jobs:
name: "Доказательство выполненных проверок"
if: always()
needs: [preflight, changes, reuse, hacs, hassfest, changed_mutants, frontend, smoke, smoke_done, golden, performance_smoke, geometry_parity, backend]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
+19 -10
View File
@@ -110,7 +110,7 @@ Start with the spec?" is the correct answer, not a smaller patch.
| **A — product** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, i18n, `custom_components/**/translations/**` | yes |
| **B — gates and tooling** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, `.github/workflows/**`, `rollup.config.mjs`, `tsconfig*.json` | yes; may reuse the issue it covers |
| **C — documentation** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md` | not if it is part of its issue's DoD |
| **D — generated** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` | never changes on its own. The stand copy `demo/srv/assets/**` is no longer committed (#255): build the complete tree with `npm run bundle:sync` |
| **D — generated** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` | never changes on its own. The stand copy `demo/srv/assets/**` is no longer committed (#255): build it with `npm run bundle:sync`. The bundle (`dist/**`, `custom_components/houseplan/frontend/**`) changes only in a commit with a `Release:` trailer — the beta/release candidate, `npm run bundle:release` (#657); an ordinary task leaves it behind the sources and restores the build with `npm run bundle:clean` before committing |
The table above is a summary; `PROCESS.md` §1 is the authority and now covers the
configuration files this one omits — `package.json`, `package-lock.json`,
@@ -180,11 +180,11 @@ in between.
If the rebase conflicts the pipeline says so in the issue and sends the task back
to `S6-in-progress`. The verdict still stands: nothing needs reviewing again, the
remaining work is the rebase. When the conflict is only in the committed bundle
(`dist/**`, `custom_components/houseplan/frontend/**` — the usual case when two
tasks built it in parallel), run `node scripts/rebase-on-dev.mjs` (#479): it takes
`dev`'s copy through the rebase, rebuilds with `npm run bundle:sync` and amends
the result into your last commit; a conflict in `docs/reviews/INDEX.md` is
remaining work is the rebase. When the conflict is only in generated files, run
`node scripts/rebase-on-dev.mjs` (#479): a conflict in the committed bundle
(`dist/**`, `custom_components/houseplan/frontend/**` — possible only for a branch
started before #657, which stopped tasks from committing it) takes `dev`'s copy
and continues, with no rebuild and no amend; a conflict in `docs/reviews/INDEX.md` is
rebuilt from the directory (#643, the same helper the pipeline uses, so the
pipeline no longer bounces a task on it); a conflict anywhere else aborts and
leaves the tree as it was. Then push the branch and re-apply `S7-code-review`. When the
@@ -399,14 +399,23 @@ npm run inventory # the only correct way to get test counts
Never copy test counts into documents by hand; they go stale in days.
After building, keep the complete manifest-driven bundle trees in sync — CI
verifies every listed file byte-for-byte:
After building, lay the bundle out for the stand; only a candidate updates the
committed copy (#657):
```
npm run bundle:sync # dist → custom_components + demo/srv/assets (#255)
npm run bundle:budget # initial View graph <= 256000 B gzip (#337)
npm run bundle:sync # build + dist → demo/srv/assets (#255)
npm run bundle:clean # before an ordinary commit: dist back to the committed copy (#657)
npm run bundle:release # candidate only: build + dist → custom_components + demo/srv/assets
npm run bundle:budget # initial View graph within INITIAL_VIEW_GZIP_BUDGET (scripts/bundle-budget.mjs, #337/#367)
```
CI (`bundle-policy --verify`) checks the fresh build's integrity on every push
and compares it byte-for-byte with the committed copy only where the commit
changes the bundle or is a candidate. Publishing a beta additionally refuses a
committed bundle whose embedded source fingerprint differs from the tree. The
dev stand gets the head of `dev` from the Validate artifact via the `dev-build`
branch, not from the tree (`demo/stand/README.md`).
`npm run gate:small` runs the mandatory part of PROCESS §8 in one go (#479,
#576): build with typecheck, `no-new-any`, `no-new-private-writes` and `smoke-select` start in parallel;
unit tests follow the completed build because their bundle-contract witnesses
+15 -8
View File
@@ -104,9 +104,12 @@ tree repeatedly. Do **not** replace it with `--depth=1`: a shallow clone is abou
the same size but has no `merge-base`, so the process gate, `smoke-select` and
every `origin/dev..HEAD` range stop working.
The HA-harness backend tests (`tests_backend/test_ha_*.py`) need Python ≥3.13 and
`pytest-homeassistant-custom-component home-assistant-frontend`; CI runs them on
every push — locally they are skipped when `homeassistant` is not importable.
The HA-harness backend tests (`tests_backend/test_ha_*.py`) need the
repository-pinned Python (`.python-version`) and
`pytest-homeassistant-custom-component home-assistant-frontend`; their canon is
Linux CI or WSL (`bash scripts/wsl-setup.sh --verify`). Without an importable
`homeassistant` they are not collected at all — not skipped — and pytest prints
`HA harness NOT collected: …` (#630).
## Ground rules
@@ -114,8 +117,10 @@ every push — locally they are skipped when `homeassistant` is not importable.
`docs/STATUS.md` for state changes; `docs/DEVELOPMENT.md` for new gotchas.
- Every UI string goes through `src/i18n/<lang>.json`; follow the
[Translations](#translations) flow for registry and backend parity.
- The built card must be committed in sync: `cp dist/houseplan-card.js
custom_components/houseplan/frontend/` (CI compares them byte-for-byte).
- The committed bundle changes only in a release candidate: `npm run
bundle:release` in a commit with a `Release:` trailer (#657). An ordinary task
restores it with `npm run bundle:clean` before committing — the `commit-msg`
hook refuses a bundle change otherwise.
- Tap actions have a security model (locks/alarms never toggle from the plan) —
see `resolveToggleIntent` in `src/device-toggle.ts`; don't weaken it.
- Every commit follows the issue and trailer contract in `PROCESS.md`.
@@ -125,6 +130,8 @@ every push — locally they are skipped when `homeassistant` is not importable.
## Architecture
Start with `docs/ARCHITECTURE.md` (data model, WS API, coordinate system) and
`docs/STATUS.md` (current state). Release: bump the version in `package.json`,
`manifest.json`, `const.py`, `CARD_VERSION`, tag `vX.Y.Z`, publish a GitHub release —
the workflow attaches the card bundle.
`docs/STATUS.md` (current state). Release mechanics live in `docs/STATUS.md` › Workflow: the version sources are
the ones checked by `scripts/release-contract.mjs`, prereleases go through
`npm run release:prerelease -- <tag> --issues=… --yes` (or the manual
`Publish prerelease` workflow), and stable installable assets are published only
by `release.yml` (#540).
+32 -10
View File
@@ -50,7 +50,7 @@
| **A. Продукт** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, `src/i18n/*.json`, `custom_components/**/translations/*` | **Да, обязательно.** Только из «Готово к разработке» или дальше |
| **B. Гейты и инструменты** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, весь `.github/**`, `.githooks/**`, `rollup.config.mjs`, `tsconfig*.json`, `package.json`, `package-lock.json`, `pytest.ini`, `.gitignore`, `.gitattributes` | **Да.** Может использовать issue того изменения, которое покрывает; самостоятельная работа над гейтом получает свой issue (тип `tech-debt`) |
| **C. Документация** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md`, `CONTRIBUTING.md`, `PROCESS*.md`, `LICENSE`, `(CODE\|SPEC)-REVIEW-*.md` | Документирование A/B в том же коммите — часть DoD своего issue. Самостоятельная работа над документацией — свой issue |
| **D. Сгенерированное** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` (копия стенда `demo/srv/assets/houseplan-card.js` с #255 не коммитится вовсе) | Никогда не меняется само по себе. Коммит **только** класса D допустим лишь как релизный промоушен или как принятие эталонов с доказательством ревью |
| **D. Сгенерированное** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` (копия стенда `demo/srv/assets/houseplan-card.js` с #255 не коммитится вовсе) | Никогда не меняется само по себе. Коммит **только** класса D допустим лишь как релизный промоушен или как принятие эталонов с доказательством ревью. **Бандл** (`dist/**`, `custom_components/houseplan/frontend/**`) с #657 меняет только коммит с трейлером `Release:` — кандидат беты или релиза (`npm run bundle:release`); в обычной задаче закоммиченный бандл законно отстаёт от исходников, сборка в коммит не идёт (`npm run bundle:clean`). Судит `validate-commit-provenance.mjs` — хук `commit-msg` и история в CI |
Практический смысл таблицы: «я только поправил тест» и «я только пересобрал
бандл» перестают быть лазейками.
@@ -363,10 +363,13 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
**Индекс документов ревью** — `docs/reviews/INDEX.md` (#635): одна строка на
документ — issue, этап, раунд, вердикт, число High/Medium, заголовки находок,
файлы из находок (искать по имени файла: `grep form-kit docs/reviews/INDEX.md`).
Файл генерируется `node scripts/reviews-index.mjs` и пересобирается конвейером
тем же коммитом, что публикует документ, а также после каждого его ребейза
(приведение к dev перед ревью, слияние кандидата — `--commit-if-stale`, коммит
класса C); руками не правится. Конфликт ребейза, в котором **все** пути —
Файл генерируется `node scripts/reviews-index.mjs` и пересобирается **только
коммитами, идущими в `dev`** (#657, решение 1б): слиянием кандидата —
после ребейза, а если `dev` не двигался, то поверх материала перед
fast-forward (`--commit-if-stale`, коммит класса C) — и публикацией документа ревью ТЗ
прямо в `dev`. В ветке задачи индекс не пересобирается — ни при приведении к
dev, ни при публикации документа код-ревью: иначе две параллельные задачи
конфликтуют на нём по построению. Руками не правится. Конфликт ребейза, в котором **все** пути —
`INDEX.md`, отказом не считается (#643): `scripts/rebase-generated.mjs`
пересобирает индекс по каталогу на остановке и продолжает ребейз — так делают
приведение к dev, слияние кандидата и авторский `rebase-on-dev.mjs`; индекс
@@ -388,7 +391,7 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
перечисляет только живые. Перенос — часть чеклиста стабильного релиза, не
отдельная задача.
Дешёвые гейты (`typecheck`, `test`, `build` со сверкой копий бандла) гоняются в
Дешёвые гейты (`typecheck`, `test`, `build` с проверкой целостности сборки, `bundle-policy --verify`) гоняются в
каждом раунде: код изменился, а стоят они минуты. Тяжёлые — по дельте (§10.2).
**Разбор остаётся полным**, если дельта не локальна: ребейз на ушедший вперёд
@@ -707,8 +710,9 @@ issue #NN
```
npx tsc --noEmit
npm test
npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js \
# копия стенда собирается `npm run bundle:sync`, в репозитории её нет (#255)
npm run build && node scripts/bundle-policy.mjs --verify HEAD
# сборка цела; копии сверяются только на кандидате (#657).
# Копия стенда — `npm run bundle:sync` (#255); перед коммитом `npm run bundle:clean`
node scripts/smoke-select.mjs --base origin/dev --head HEAD # какие смоки относятся к диффу
node demo/smoke_<целевые>.mjs
node scripts/no-new-any.mjs --base origin/dev --head HEAD # новый код не добавляет any
@@ -732,7 +736,7 @@ npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
комментарии, строке или идентификаторе ложных срабатываний не даёт.
**Объём гейтов на код-ревью соразмерен задаче** (issue #127). Всегда:
`typecheck`, `npm test`, `npm run build` со сверкой трёх копий бандла, а при
`typecheck`, `npm test`, `npm run build` с `bundle-policy --verify` (копии сверяются на кандидате, #657), а при
любом diff'е по `src/**` — ещё и `node scripts/check-docs.mjs`. По
необходимости, определяемой diff'ом и AC: браузерные смоки (сколько их —
считает `ls demo/smoke_*.mjs | wc -l`, вшитое число здесь трижды отставало от
@@ -1067,7 +1071,7 @@ Matysh/houseplan-card/.github/workflows/_<имя>.yml@dev` с `secrets: inherit`
мутантов не запрашивают** (#601, решение владельца 20.09): мутационный гейт
проверяет тесты, а не продукт (#513), к бете каждая задача прогнана им дважды —
на ревью и на слитом после ребейза кандидате, — а ночью идёт полный реестр
(`mutation-gate.yml`, 01:00 UTC). Релизный гейт (#541) требует полного Validate,
(`mutation-gate.yml`, 00:43 UTC). Релизный гейт (#541) требует полного Validate,
но не mutant-jobs; для ревью и слияния шесть исполненных mutant-jobs остаются
обязательными.
@@ -1324,6 +1328,24 @@ Golden, браузерные смоки, performance и полный HA-харн
`gh workflow run release-review.yml --ref dev -f tag=vX.Y.Z [-f candidate=<sha>]`.
Беты шаг пропускают. Первый прогон — линия v1.78.0.
### 11.6 Повторные Validate на одном SHA перед релизом
Решение владельца 2026-09-26, issue #656.
Релизный гейт рассматривает прогоны одного SHA от нового к старому. Отменённый
прогон и proof, который не соответствует запрошенной политике (например,
лёгкий вместо полного), вердиктом не являются: гейт проходит мимо них к
следующему совместимому proof. **Среди совместимых полных прогонов решает
новейший.** Поэтому поздний полный `failed`, `missing` или `pending` блокирует
более ранний зелёный proof; новый полный зелёный прогон может обновить старый
красный.
Это fail-closed правило. Content-addressed proof доказывает, что конкретный
прогон относится к кандидату, но не даёт старому зелёному прогону права скрыть
более позднюю проверку той же политики, которая нашла отказ. Чтобы продолжить
выпуск после такого отказа, исправляют причину и получают новый совместимый
полный зелёный прогон на том же финальном SHA либо на новом SHA кандидата.
---
## 12. Запрещено
+6 -2
View File
@@ -42,6 +42,9 @@ sync across screens.
show temperature, humidity, light state and average LQI.
- **Light and environment.** Room fills, lamp Glow, wall shadows, a day-cycle
backdrop and sunlight through windows.
- **Flat or 2.5D.** One switch in General settings › Display shows the plan
with depth everywhere: raised device tiles with soft floor shadows and a soft
wash of sunlight; decor and wall colours stay as you set them.
- **Doors, windows, gates and vacuums.** Openings follow real contacts and locks;
a robot can show its position, dock and travelled path.
- **Several floors and screens.** Space tabs, swipe navigation, local viewport,
@@ -58,8 +61,8 @@ sync across screens.
1. Install the integration and open **House Plan** in the Home Assistant sidebar.
2. Create the first **space**: upload SVG/PNG/JPG/WebP, reuse an uploaded image,
or choose no image and draw the plan by hand.
3. In Plan, select **Room outline**, place vertices, and click the first point to
close the outline.
3. In Plan, select **Walls** and draw one continuous chain around the room: when
it closes an area, the room dialog opens.
4. Name the room and bind it to a Home Assistant area. Use “No area” for a room
that has no devices.
5. Open Device: devices from the bound area are already placed; drag their
@@ -183,6 +186,7 @@ concurrent clients, but avoid editing the same object in two browsers at once.
- [Full user guide](docs/USER-GUIDE.md)
- [Mouse/touch/keyboard matrix](docs/USER-GUIDE.md#6-navigation-zoom-and-input)
- [Plan tools](docs/USER-GUIDE.md#plan-tools-at-a-glance)
- [Stairs and floor links](docs/STAIRS.md)
- [Background editor](docs/DECOR-EDITOR.md)
- [Robot vacuums](docs/VACUUM.md)
- [Touch support](docs/TOUCH-SUPPORT.md)
+2 -2
View File
@@ -59,8 +59,8 @@ Assistant — и устройства появятся на плане авто
1. Установите интеграцию и откройте **House Plan** в боковом меню Home Assistant.
2. Создайте первое **пространство**: загрузите SVG/PNG/JPG/WebP либо выберите
вариант без изображения, чтобы нарисовать план вручную.
3. В редакторе «План» выберите **Контур комнаты**, поставьте вершины и замкните
контур нажатием на первую точку.
3. В редакторе «План» выберите **Стены** и нарисуйте непрерывную цепочку по
периметру комнаты: когда она замкнёт область, откроется диалог комнаты.
4. Назовите комнату и свяжите её с зоной Home Assistant. Для помещения без
устройств выберите «Без зоны».
5. Откройте «Устройства»: устройства связанной зоны уже размещены автоматически;
+40 -6
View File
@@ -6,6 +6,7 @@ import logging
from datetime import timedelta
from pathlib import Path
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.event import async_track_time_interval
@@ -39,6 +40,30 @@ from .store import (
_LOGGER = logging.getLogger(__name__)
async def _async_flush_runtime(
hass: HomeAssistant, entry: HouseplanConfigEntry,
) -> None:
"""Stop deferred writers and persist their latest state once.
Home Assistant stop and config-entry unload share this exact path. Both
writers are independently guarded so one failed store cannot prevent the
other from reaching disk during shutdown.
"""
recorder = hass.data.get(DOMAIN, {}).get("trail_recorder")
if recorder is not None:
try:
await recorder.async_teardown()
except Exception: # noqa: BLE001 - shutdown must continue with other stores
_LOGGER.exception("House Plan: flushing vacuum trails failed")
virtual_lights = getattr(entry.runtime_data, "virtual_lights", None)
if virtual_lights is not None:
try:
await virtual_lights.async_flush()
except Exception: # noqa: BLE001 - shutdown must continue with other stores
_LOGGER.exception("House Plan: flushing virtual-light state failed")
async def async_setup(hass: HomeAssistant, config) -> bool:
"""Register global handlers (survive config-entry reloads): WS commands, HTTP view."""
hass.data.setdefault(DOMAIN, {})
@@ -252,6 +277,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: HouseplanConfigEntry) ->
# sidebar entry after a migration, repair or initial housekeeping failure.
panel_path = Path(__file__).parent / "frontend" / "houseplan-panel.js"
await async_setup_panel_registration(hass, entry, panel_path)
async def _flush_on_stop(_event) -> None:
stop_listener[0] = None
await _async_flush_runtime(hass, entry)
stop_listener = [hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _flush_on_stop)]
def _remove_stop_listener() -> None:
if stop_listener[0] is not None:
stop_listener[0]()
stop_listener[0] = None
entry.async_on_unload(_remove_stop_listener)
return True
@@ -263,12 +301,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: HouseplanConfigEntry) -
entry is loaded. Static paths cannot be unregistered by design.
"""
remove_panel_registration(hass)
rec = hass.data.get(DOMAIN, {}).pop("trail_recorder", None)
if rec:
await rec.async_teardown()
virtual_lights = getattr(entry.runtime_data, "virtual_lights", None)
if virtual_lights is not None:
await virtual_lights.async_flush()
await _async_flush_runtime(hass, entry)
hass.data.get(DOMAIN, {}).pop("trail_recorder", None)
if entry.runtime_data.radar_coordinator:
entry.runtime_data.radar_coordinator.teardown()
entry.runtime_data.radar_coordinator = None
+1 -1
View File
@@ -57,7 +57,7 @@ SCHEDULED_GRACE_S = 30 * 24 * 3600
FILES_DIR = "houseplan/files"
ASSETS_DIR = "houseplan/assets"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.78.0-beta.2"
VERSION = "1.78.0-beta.5"
DECOR_ASSETS_API_VERSION = 1
SUMMARY_PANEL_API_VERSION = 1
@@ -158,6 +158,12 @@ def canonicalize_config_geometry(config: Any) -> Any:
_lattice_fields(decor, ("x", "y"))
_scalar_fields(decor, ("scale", "angle"))
for stair in _records(space.get("stairs")):
# #663: wall/stair magnet placement is continuous like furniture.
# Scalar cleanup removes JSON tails without moving an authored
# physical-face contact onto the plan grid.
_scalar_fields(stair, ("x", "y", "length", "width", "radius", "angle"))
# Legacy-only read boundary: v9 drafts are canonicalized before the
# wall-model migration turns their edges into current partitions.
for draft in _records(space.get("room_drafts")):
@@ -33,6 +33,7 @@ async def async_get_config_entry_diagnostics(
"rooms_with_area": sum(1 for r in s.get("rooms", []) if r.get("area")),
"partitions": len(s.get("partitions", [])),
"wall_columns": len(s.get("wall_columns", [])),
"stairs": len(s.get("stairs", [])),
}
for s in config.get("spaces", [])
],
@@ -1,129 +1,129 @@
{
"schema": 1,
"fingerprint": "5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051",
"fingerprint": "d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96",
"entry": "houseplan-card.js",
"panelEntry": "houseplan-panel.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 291761,
"initialViewGzipBytes": 299093,
"initialPanelFiles": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-panel.js"
],
"initialPanelGzipBytes": 293284,
"initialPanelGzipBytes": 300614,
"initialPanelOnlyFiles": [
"houseplan-panel.js"
],
"initialPanelOnlyGzipBytes": 2326,
"initialPanelOnlyGzipBytes": 2324,
"lazyFiles": [
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
"houseplan-assets/de-C7gyFGpd.js",
"houseplan-assets/editor-9cyNd7R7.js",
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/fr-BSn1zqYb.js",
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
"houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
"houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
"houseplan-assets/hp-zigbee-topology-overlay-D7siZQoq.js",
"houseplan-assets/iso-scene-render-xsesUj3A.js",
"houseplan-assets/live-interaction-runtime-DZWBYZP0.js",
"houseplan-assets/namespace-language-B2Cyshho.js",
"houseplan-assets/pdf-export-BYsRpVk_.js",
"houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js",
"houseplan-assets/zigbee-topology-CvQsfSze.js",
"houseplan-assets/zigbee-topology-runtime-A7rG7kis.js"
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
"houseplan-assets/de-B2abA_bU.js",
"houseplan-assets/editor-BLL8Ue28.js",
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/fr-Bho0FfMD.js",
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
"houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
"houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
"houseplan-assets/hp-zigbee-topology-overlay-BeNZqp-_.js",
"houseplan-assets/iso-scene-render-Soh6e8O4.js",
"houseplan-assets/live-interaction-runtime-D6A_AoBE.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js",
"houseplan-assets/pdf-export-De59asII.js",
"houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js",
"houseplan-assets/zigbee-topology-BbetEPvj.js",
"houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js"
],
"lazyGzipBytes": 449074,
"lazyGzipBytes": 453435,
"lazyEditorFiles": [
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
"houseplan-assets/editor-9cyNd7R7.js",
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
"houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
"houseplan-assets/namespace-language-B2Cyshho.js",
"houseplan-assets/zigbee-topology-CvQsfSze.js"
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
"houseplan-assets/editor-BLL8Ue28.js",
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
"houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js",
"houseplan-assets/zigbee-topology-BbetEPvj.js"
],
"lazyEditorGzipBytes": 231934,
"lazyEditorGzipBytes": 235435,
"lazyOnboardingFiles": [
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
"houseplan-assets/namespace-language-B2Cyshho.js"
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js"
],
"lazyOnboardingGzipBytes": 27899,
"lazyOnboardingGzipBytes": 27952,
"lazyLocaleFiles": [
"houseplan-assets/de-C7gyFGpd.js",
"houseplan-assets/fr-BSn1zqYb.js"
"houseplan-assets/de-B2abA_bU.js",
"houseplan-assets/fr-Bho0FfMD.js"
],
"lazyLocaleGzipBytes": 55699,
"lazyLocaleGzipBytes": 56230,
"lazyIsometricFiles": [
"houseplan-assets/iso-scene-render-xsesUj3A.js"
"houseplan-assets/iso-scene-render-Soh6e8O4.js"
],
"lazyIsometricGzipBytes": 17643,
"lazyIsometricGzipBytes": 17885,
"lazyFurnitureArtFiles": [
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js"
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js"
],
"lazyFurnitureArtGzipBytes": 16940,
"lazyFurnitureArtGzipBytes": 16941,
"lazyPdfFiles": [
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
"houseplan-assets/pdf-export-BYsRpVk_.js"
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
"houseplan-assets/pdf-export-De59asII.js"
],
"lazyPdfGzipBytes": 126827,
"lazyPdfGzipBytes": 126914,
"lazyNamespaceLocaleFiles": [
"houseplan-assets/settings-de-BSeiuZ78.js",
"houseplan-assets/settings-fr-Dqa7IIGC.js",
"houseplan-assets/settings-ru-DTjpLI7z.js",
"houseplan-assets/support-de-DkCHsIJX.js",
"houseplan-assets/support-fr-CF5yiuHl.js",
"houseplan-assets/support-ru-Bs7OI6GX.js",
"houseplan-assets/topology-de-BelPUGaW.js",
"houseplan-assets/topology-fr-FvLCzTYk.js",
"houseplan-assets/topology-ru-DG2M1Xdq.js"
"houseplan-assets/settings-de-BjfMuSw8.js",
"houseplan-assets/settings-fr-BKCX3R6z.js",
"houseplan-assets/settings-ru-D8xvcCw6.js",
"houseplan-assets/support-de-C9Oo6umO.js",
"houseplan-assets/support-fr-CRCSYq8q.js",
"houseplan-assets/support-ru-BcRlKfAd.js",
"houseplan-assets/topology-de-BjBB8qiK.js",
"houseplan-assets/topology-fr-DXFxoYd-.js",
"houseplan-assets/topology-ru-Di7X71yN.js"
],
"lazyNamespaceLocaleGzipBytes": 20887,
"lazyNamespaceLocaleGzipBytes": 21047,
"files": [
{
"path": "houseplan-assets/backdrop-pick-D3-yn5HD.js",
"sha256": "9979ee2d9318b8f709993cc91c77968c12ecfb3c3692a16d2ac3a268d5a752c2",
"rawBytes": 53888,
"gzipBytes": 17524,
"path": "houseplan-assets/backdrop-pick-CXaZKwRy.js",
"sha256": "001e8011f17ee8c1d3804304eb6734065b645a28b19beb8c18fa699f309de66c",
"rawBytes": 54017,
"gzipBytes": 17577,
"isEntry": false,
"imports": [
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/namespace-language-B2Cyshho.js"
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js"
],
"dynamicImports": [
"houseplan-assets/settings-de-BSeiuZ78.js",
"houseplan-assets/settings-fr-Dqa7IIGC.js",
"houseplan-assets/settings-ru-DTjpLI7z.js"
"houseplan-assets/settings-de-BjfMuSw8.js",
"houseplan-assets/settings-fr-BKCX3R6z.js",
"houseplan-assets/settings-ru-D8xvcCw6.js"
]
},
{
"path": "houseplan-assets/de-C7gyFGpd.js",
"sha256": "eed3905d3c370c5071e7074d4753a769efd3d37d263f21cee2a135ef0dbd6a2c",
"rawBytes": 93083,
"gzipBytes": 28075,
"path": "houseplan-assets/de-B2abA_bU.js",
"sha256": "63b70f53f23e194e16172c10b11dda1d19db0bebc23a5213444cab07da1e72be",
"rawBytes": 93993,
"gzipBytes": 28342,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-9cyNd7R7.js",
"sha256": "e49468af916efc53baab7dd87825f20b2b2b957dbcae134cfa493e0c9b08ae3a",
"path": "houseplan-assets/editor-BLL8Ue28.js",
"sha256": "6176410d822670169447e2324d7af4ffc7f500e914fd597b0318c41785adb31c",
"rawBytes": 3833,
"gzipBytes": 1583,
"gzipBytes": 1581,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/form-kit.styles-BJpA5NGj.js",
"sha256": "e07f6da05bf8bff00363bc559c8f9ac2c7acc93d0141d37a7570bd02ec1192ca",
"path": "houseplan-assets/form-kit.styles-CCrtJjwP.js",
"sha256": "e45b8023f44f1048304d5d43616f51ea797f9a3475a801f776f9c488b93a1435",
"rawBytes": 25600,
"gzipBytes": 5056,
"isEntry": false,
@@ -131,207 +131,207 @@
"dynamicImports": []
},
{
"path": "houseplan-assets/fr-BSn1zqYb.js",
"sha256": "f0b4b416865b978983c49e1b3321ca3d6f3956a43f7b1576d23e2d81b1a80148",
"rawBytes": 95424,
"gzipBytes": 27624,
"path": "houseplan-assets/fr-Bho0FfMD.js",
"sha256": "ae1939c095197487f36b8d34e06f976ac8e665f3542d9dc9cf06190963310ac3",
"rawBytes": 96369,
"gzipBytes": 27888,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
"sha256": "b5e9e8743651feb5c515d986581bd860a2184d7d64a7744b53acd0c6d7ed0327",
"path": "houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
"sha256": "ae4b691e03b0869b927ab76dfce816e0e50662bb9c78de68d2a66cdd2ead163e",
"rawBytes": 52035,
"gzipBytes": 16940,
"gzipBytes": 16941,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-BCYl6SkQ.js",
"sha256": "6df593e8f6479b0bedf505c29a41a20b4bf53a5977b45e0bf3165400fd3769e0",
"rawBytes": 1030968,
"gzipBytes": 290958,
"path": "houseplan-assets/houseplan-card-Ddo22-SB.js",
"sha256": "8746be63df29372816cf31f039e60165f1c7906565ab25cef8de52937cd88e89",
"rawBytes": 1055297,
"gzipBytes": 298290,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-C7gyFGpd.js",
"houseplan-assets/editor-9cyNd7R7.js",
"houseplan-assets/fr-BSn1zqYb.js",
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
"houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
"houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
"houseplan-assets/hp-zigbee-topology-overlay-D7siZQoq.js",
"houseplan-assets/iso-scene-render-xsesUj3A.js",
"houseplan-assets/live-interaction-runtime-DZWBYZP0.js",
"houseplan-assets/pdf-export-BYsRpVk_.js",
"houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js"
"houseplan-assets/de-B2abA_bU.js",
"houseplan-assets/editor-BLL8Ue28.js",
"houseplan-assets/fr-Bho0FfMD.js",
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
"houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
"houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
"houseplan-assets/hp-zigbee-topology-overlay-BeNZqp-_.js",
"houseplan-assets/iso-scene-render-Soh6e8O4.js",
"houseplan-assets/live-interaction-runtime-D6A_AoBE.js",
"houseplan-assets/pdf-export-De59asII.js",
"houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
"sha256": "c0a4a8e337a3e13b0df19eb57938c185d504e6ed89854d514585cd00b4f40197",
"rawBytes": 667315,
"gzipBytes": 186120,
"path": "houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
"sha256": "1a2c3ca19fd2ecdfe8e4fbbe2b0d187d44dc986cf97a7d5d13a47d0edf2baad0",
"rawBytes": 679701,
"gzipBytes": 189570,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/namespace-language-B2Cyshho.js",
"houseplan-assets/zigbee-topology-CvQsfSze.js"
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js",
"houseplan-assets/zigbee-topology-BbetEPvj.js"
],
"dynamicImports": [
"houseplan-assets/support-de-DkCHsIJX.js",
"houseplan-assets/support-fr-CF5yiuHl.js",
"houseplan-assets/support-ru-Bs7OI6GX.js",
"houseplan-assets/zigbee-topology-runtime-A7rG7kis.js"
"houseplan-assets/support-de-C9Oo6umO.js",
"houseplan-assets/support-fr-CRCSYq8q.js",
"houseplan-assets/support-ru-BcRlKfAd.js",
"houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js"
]
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
"sha256": "0d13b9bc17fc152738912ddfb30d0fd8247984c80286cbd6b82d6609c99bff48",
"path": "houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
"sha256": "08a26f899d0a5f8f71a2ed6f71d095388aa9eead14ce4a4593ce21ee2e54ea14",
"rawBytes": 15886,
"gzipBytes": 4909,
"gzipBytes": 4910,
"isEntry": false,
"imports": [
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/namespace-language-B2Cyshho.js"
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/hp-zigbee-topology-overlay-D7siZQoq.js",
"sha256": "0254f89b26d8b337c1c4939931cf72938089a0ea326199235320348ff6e085f9",
"path": "houseplan-assets/hp-zigbee-topology-overlay-BeNZqp-_.js",
"sha256": "5360f9165e07d274bc2e71634792f9d8868b20750f4565feeee98d48e9ac08fa",
"rawBytes": 10958,
"gzipBytes": 3765,
"gzipBytes": 3764,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/namespace-language-B2Cyshho.js",
"houseplan-assets/zigbee-topology-CvQsfSze.js",
"houseplan-assets/zigbee-topology-runtime-A7rG7kis.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js",
"houseplan-assets/zigbee-topology-BbetEPvj.js",
"houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/iso-scene-render-xsesUj3A.js",
"sha256": "4f41caad95b99d01013048852c1aa4c147959a8ef10a906dde51f45808e87905",
"rawBytes": 51983,
"gzipBytes": 17643,
"path": "houseplan-assets/iso-scene-render-Soh6e8O4.js",
"sha256": "192b514d86778182905c9ade6b2f2f28e8428b281b0190ebe9b159d3170eaf98",
"rawBytes": 52970,
"gzipBytes": 17885,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/live-interaction-runtime-DZWBYZP0.js",
"sha256": "aac5a7823e6fd097be076d62534e9b9f1c4290f36b92033d59bf1cac891da300",
"path": "houseplan-assets/live-interaction-runtime-D6A_AoBE.js",
"sha256": "8a3cd740cd67f85041975cf18d9819b6d44be34601e56d639664dce2b79b9873",
"rawBytes": 8216,
"gzipBytes": 3207,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/namespace-language-B2Cyshho.js",
"sha256": "5934f29831a1aecc9503f9afdc8559844dc39e09474b51d26cc917965f27faac",
"path": "houseplan-assets/namespace-language-CwX4Ljv1.js",
"sha256": "46a700cfd83daaaa2ddebe209e8395e08ae6c1e84f286e1f5807c31604e90130",
"rawBytes": 675,
"gzipBytes": 410,
"gzipBytes": 409,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/pdf-export-BYsRpVk_.js",
"sha256": "7a4a4d2130fb379cd99681cb6a843e10f81d954bf3c6146d1843bdc9b29027b9",
"rawBytes": 236264,
"gzipBytes": 109887,
"path": "houseplan-assets/pdf-export-De59asII.js",
"sha256": "4f1c72412bade0a94e89cc49fb44cce1b90c5304186506d0254fd0de25b26c31",
"rawBytes": 236608,
"gzipBytes": 109973,
"isEntry": false,
"imports": [
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/settings-de-BSeiuZ78.js",
"sha256": "e097b530727671f7473cc5288ae1d71124fa51afb4e4b26e35114d10b2b18e8d",
"rawBytes": 7248,
"gzipBytes": 2825,
"path": "houseplan-assets/settings-de-BjfMuSw8.js",
"sha256": "62cda042ab64811c685f49f5aa338308e592f464f39afc1440de0031cdd97cf0",
"rawBytes": 7378,
"gzipBytes": 2878,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/settings-fr-Dqa7IIGC.js",
"sha256": "b5597e3b95028ddd220c4bf7b2566455aa5e403d9a3ad2a55dc110cecdc6a33f",
"rawBytes": 7483,
"gzipBytes": 2786,
"path": "houseplan-assets/settings-fr-BKCX3R6z.js",
"sha256": "57246900bc095d6a84a2e7db9c96f7bb634aeb056957c431abbff760bed335db",
"rawBytes": 7620,
"gzipBytes": 2841,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/settings-ru-DTjpLI7z.js",
"sha256": "20b462a7368412ce5c2a5ad378177c963894c6afe8b2a642ae8686477f5e5771",
"rawBytes": 10128,
"gzipBytes": 3271,
"path": "houseplan-assets/settings-ru-D8xvcCw6.js",
"sha256": "cf1089c5af8bffe24f153ae8553b8eeb14577862cb34b3ee51e2061b1585091c",
"rawBytes": 10304,
"gzipBytes": 3325,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/summary-panel-editor-BVcF5FC3.js",
"sha256": "3b55c2b18035ea7d9861e5c1516d9c3a6443a6c042c2943dc2064ef2a51ed09e",
"path": "houseplan-assets/summary-panel-editor-CwTXBm1f.js",
"sha256": "6ccad563f28ed17d527b6e2a6efc167b696f478c7c23b114bf904ac4e695f566",
"rawBytes": 14862,
"gzipBytes": 3427,
"gzipBytes": 3423,
"isEntry": false,
"imports": [
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js"
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/summary-panel-metrics-D9g7eyWm.js",
"sha256": "ca53545e64213080520b779bd051d90e9a6e2d053656fdf2c2ca734b6b9e5982",
"rawBytes": 2611,
"gzipBytes": 1305,
"path": "houseplan-assets/summary-panel-metrics-Bd81K-sc.js",
"sha256": "20851cfa3b7d45452f558ac6ec231d6ba7a0a9b1459760580fa56ce7c5554a05",
"rawBytes": 2701,
"gzipBytes": 1344,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js",
"sha256": "352dc342aca3222c76a922e010001ddc35e22d04c412093d5b294e9b67ab36ff",
"rawBytes": 72956,
"path": "houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js",
"sha256": "1ed46af8f90254099376b7107163e5ee6dfb282924409bd5a5fa40036660c2eb",
"rawBytes": 72959,
"gzipBytes": 20242,
"isEntry": false,
"imports": [
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": [
"houseplan-assets/summary-panel-editor-BVcF5FC3.js",
"houseplan-assets/summary-panel-metrics-D9g7eyWm.js"
"houseplan-assets/summary-panel-editor-CwTXBm1f.js",
"houseplan-assets/summary-panel-metrics-Bd81K-sc.js"
]
},
{
"path": "houseplan-assets/support-de-DkCHsIJX.js",
"sha256": "9bcaa6f84b2df92ccf9b26714a4132d9973efa598b93b25a966ffd290f80c00f",
"path": "houseplan-assets/support-de-C9Oo6umO.js",
"sha256": "aeab7835cf28821f302612a778f0687b05cb006c5371bbfeed15976ac0aba3c1",
"rawBytes": 6167,
"gzipBytes": 2396,
"isEntry": false,
@@ -339,17 +339,17 @@
"dynamicImports": []
},
{
"path": "houseplan-assets/support-fr-CF5yiuHl.js",
"sha256": "967d72d645f5cd641e4a4d4fae7bf05a6b6a3752545a60c6981ae26c58c008b9",
"path": "houseplan-assets/support-fr-CRCSYq8q.js",
"sha256": "ff04381645035c564a6455ddfbfd81de57960e63977e4979ec1c04b4564f7885",
"rawBytes": 6333,
"gzipBytes": 2430,
"gzipBytes": 2429,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/support-ru-Bs7OI6GX.js",
"sha256": "63db0be43cdf00a66f24704ecba11224d6cba775fde57b8121a541be991360c3",
"path": "houseplan-assets/support-ru-BcRlKfAd.js",
"sha256": "0603d82d78e310ba8eaf26d4cc1b7ceeadf5cfabb40042199cb5b85b1089000c",
"rawBytes": 8376,
"gzipBytes": 2875,
"isEntry": false,
@@ -357,17 +357,17 @@
"dynamicImports": []
},
{
"path": "houseplan-assets/topology-de-BelPUGaW.js",
"sha256": "6278b6aa3c8ddbb36ebc7ce6fb2b506ab2e6071814e5c78cd6f5241ee81b646b",
"path": "houseplan-assets/topology-de-BjBB8qiK.js",
"sha256": "df225ea5dc6b1886537cd7a0cec055530773ba31c94714a9cc199fd6fae2beb4",
"rawBytes": 2698,
"gzipBytes": 1336,
"gzipBytes": 1334,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/topology-fr-FvLCzTYk.js",
"sha256": "40ba15894ef18ef7566796a2cdb21bc8ad82f855c27556e4868d53f39e2fb146",
"path": "houseplan-assets/topology-fr-DXFxoYd-.js",
"sha256": "4c1013cb61db49df76e856c34aea59266e065350210a5d964537f8dd42cf6774",
"rawBytes": 2718,
"gzipBytes": 1354,
"isEntry": false,
@@ -375,62 +375,62 @@
"dynamicImports": []
},
{
"path": "houseplan-assets/topology-ru-DG2M1Xdq.js",
"sha256": "e885da0310e6fe62f7b26a3fc8559d857bc03a5c1164146227b59a0532c6994f",
"path": "houseplan-assets/topology-ru-Di7X71yN.js",
"sha256": "daa7bcbbde34f363c8b114b2af050cc19264e85c7bcc6ba31a13e0b963f4f8ce",
"rawBytes": 3669,
"gzipBytes": 1614,
"gzipBytes": 1615,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/zigbee-topology-CvQsfSze.js",
"sha256": "e50d18bdc8333469cb81de65c7cbc1f7fc30f47820d90256835c20244e56c9fa",
"path": "houseplan-assets/zigbee-topology-BbetEPvj.js",
"sha256": "a204ece3a16a5af991ed3e6092e8ab2a3cd9d4d2bd79fdc1f056c504cdfa7798",
"rawBytes": 10958,
"gzipBytes": 4301,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/namespace-language-B2Cyshho.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js"
],
"dynamicImports": [
"houseplan-assets/topology-de-BelPUGaW.js",
"houseplan-assets/topology-fr-FvLCzTYk.js",
"houseplan-assets/topology-ru-DG2M1Xdq.js"
"houseplan-assets/topology-de-BjBB8qiK.js",
"houseplan-assets/topology-fr-DXFxoYd-.js",
"houseplan-assets/topology-ru-Di7X71yN.js"
]
},
{
"path": "houseplan-assets/zigbee-topology-runtime-A7rG7kis.js",
"sha256": "ff4fa95e61e5b87f04b9dbfdb5e4db560039b705ce85a78e0f88c5cc3fe752bf",
"path": "houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js",
"sha256": "bd75de4445ae8f036fbf89763dead2ce464783b566a6860fd9603e5c9251099b",
"rawBytes": 3983,
"gzipBytes": 1788,
"gzipBytes": 1789,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
"houseplan-assets/namespace-language-B2Cyshho.js",
"houseplan-assets/zigbee-topology-CvQsfSze.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js",
"houseplan-assets/namespace-language-CwX4Ljv1.js",
"houseplan-assets/zigbee-topology-BbetEPvj.js"
],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "da2699ffc51cdc95977a044aafa7ef32645f69eb58311b994d2d578b8697c2c0",
"sha256": "efc308c02a842ebafa05186a59d8ad2d734445445f1b9b746e1914706dca87cf",
"rawBytes": 1205,
"gzipBytes": 803,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
},
{
"path": "houseplan-panel.js",
"sha256": "07baa17cfdd3db6f04aa5027c5796f623976bf6a4e5282255123474d79cd35b2",
"sha256": "f820d606cf77d60252147bdf93075b2621d05e6cd3f014acdeb0c032b6b83d23",
"rawBytes": 6165,
"gzipBytes": 2326,
"gzipBytes": 2324,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
"houseplan-assets/houseplan-card-Ddo22-SB.js"
],
"dynamicImports": []
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,14 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-BCYl6SkQ.js";class f extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,f={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},h=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{aD as e,l as o,aG as t,ey as s,A as a,ez as i,eA as l,eB as n,E as r,c}from"./houseplan-card-Ddo22-SB.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
.computeLabel=${e=>f[e.name]||e.name}
.computeLabel=${e=>h[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(h.slice(0,3))}
${d(f.slice(0,3))}
${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a}
${d(h.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
${d(f.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{ej as e,ek as a,eg as n}from"./houseplan-card-BCYl6SkQ.js";function o(n,o){return new e([{code:"en",dictionary:n},{code:"ru",loadDictionary:o.ru},{code:"de",loadDictionary:o.de},{code:"fr",loadDictionary:o.fr}],"5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051",console.warn,a)}function r(e){return function(e,a){return{state:n=>{const o=e.state(n);return"pending"!==o&&a.some(e=>"pending"===e.state(n))?"pending":o},dictionary:a=>e.dictionary(a),ensure:n=>Promise.all([e,...a].map(e=>e.ensure(n))).then(()=>{})}}(n,e)}export{o as n,r as s};
@@ -0,0 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{eD as e,eE as n,eA as c}from"./houseplan-card-Ddo22-SB.js";function a(c,a){return new e([{code:"en",dictionary:c},{code:"ru",loadDictionary:a.ru},{code:"de",loadDictionary:a.de},{code:"fr",loadDictionary:a.fr}],"d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96",console.warn,n)}function d(e){return function(e,n){return{state:c=>{const a=e.state(c);return"pending"!==a&&n.some(e=>"pending"===e.state(c))?"pending":a},dictionary:n=>e.dictionary(n),ensure:c=>Promise.all([e,...n].map(e=>e.ensure(c))).then(()=>{})}}(c,e)}export{a as n,d as s};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{c as a,A as e}from"./houseplan-card-BCYl6SkQ.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-gtNYqb9U.js";import"./form-kit.styles-BJpA5NGj.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__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{c as a,A as e}from"./houseplan-card-Ddo22-SB.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-jjP-5y3_.js";import"./form-kit.styles-CCrtJjwP.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
<input id="summary-local-show" data-summary-local-show type="checkbox"
@@ -0,0 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{eG as e,N as t,b2 as n,L as i,ae as o,ac as r,ca as s,eH as a,c9 as c,b1 as l,b0 as d,b3 as u,eI as f,eJ as m,cc as g,eK as v}from"./houseplan-card-Ddo22-SB.js";function y(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),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 _(e,t){return e?.length?t?.length?v(e,t):e:t}function h(e,t){const n=l(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:d(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*p(l,d,u=h){try{let f=0;for(const m of d){const d=l.spaces.find(e=>String(e?.id)===m.id);if(!d)continue;const g=e(d,m),v=u(m,g);let y=null;for(const e of m.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(m.rooms,e.id,g.walls,g.openCuts,i,g.cellCm,o,r,v.roomGeom,v.multiWallNodes)||a;y=_(y,s(c,g.physicalBodies)),yield}const h=a(y||[],m.stairs);let p=h.next();for(;!p.done;)yield,p=h.next();y=p.value;const b=g.cellCm/o;f+=c(y)*b*b/1e4}return f}catch{return null}}function b(e,t,n=h){const i=p(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function S(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:g(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 C(e,t){const n=e?.states?.[t];if(!n)return null;const i=f(e,t);return i?m(i,String(n.attributes?.unit_of_measurement||"")):null}export{p as cleanFloorAreaSteps,y as representedHaDeviceIds,h as spaceWallGeometry,C as summaryEntityValue,S as summarySystemValue,b as totalCleanFloorAreaM2};
@@ -1 +0,0 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{em as e,N as t,b2 as n,L as i,ae as o,ac as r,bY as s,bX as a,b1 as c,b0 as l,b3 as u,en as d,eo as f,b_ as m,ep as g}from"./houseplan-card-BCYl6SkQ.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),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,u="device"===r?a?.area_id:c?.area_id||l&&e.registry.devices?.[l]?.area_id,d="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||u||"",f=d&&e.areaToSpace[d]||i.space||e.firstSpaceId;l&&e.registry.devices?.[l]&&e.spaceIds.has(f)&&(!n.devices.has(l)||"entity"===r&&n.liveEntities.has(s))&&t.add(l)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=c(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:l(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*p(c,l,u=_){try{let d=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=u(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(c,m.physicalBodies)),yield}const _=m.cellCm/o;d+=a(v)*_*_/1e4}return d}catch{return null}}function h(e,t,n=_){const i=p(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function b(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=d(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{p as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,b as summarySystemValue,h as totalCleanFloorAreaM2};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";var e={title:"Zigbee-Verbindungen",toggle:"Zigbee-Verbindungen beim Darüberfahren über ein Gerät anzeigen",hint:"Es werden nur beobachtete direkte Nachbarn angezeigt. Beim Darüberfahren werden keine Daten abgerufen.",help:"Zeigt, mit welchen Zigbee-Geräten jedes Gerät direkt verbunden ist. Die Linienfarbe ist die Verbindungsqualität (LQI) auf derselben Skala wie der LQI-Wert des Geräts: rot bei 40 und darunter, grün bei 180 und darüber. Eine gestrichelte Linie bedeutet, dass die Qualität nicht gemeldet wurde. Ein Pfeil zeigt auf das nächste Gerät auf dem Weg zum Koordinator: ein Pfeil führt hinaus, eingehende Pfeile sind die Geräte, die über dieses Gerät routen. Eine Linie ohne Pfeil ist ein Ersatznachbar. Das ist der Routenbaum, den House Plan bildet, nicht der Weg eines Pakets in diesem Moment: eine Beschriftung am Pfeilende bedeutet, dass das Ziel nicht auf diesem Plan liegt, und ein fehlender Pfeil, dass die Route unbekannt ist. Die Daten stammen aus dem letzten Laden und veralten; Verbindungen sind nur mit der Maus sichtbar.",help_aria:"Hilfe: Zigbee-Verbindungen",admin_only:"Topologiedaten sind nur für Home-Assistant-Administratoren verfügbar.",save_first:"Speichere diese Einstellung, bevor Topologiedaten geladen werden.",zha:"ZHA",zha_read:"ZHA-Daten lesen",zha_hint:"Liest den ZHA-Topologiecache; es wird kein Funkscan gestartet.",z2m:"Zigbee2MQTT",z2m_topics:"Basistopics (eines pro Zeile)",z2m_update:"Karte aktualisieren",z2m_warning:"Ein Netzwerkscan kann 10 Sekunden bis 2 Minuten dauern und Zigbee vorübergehend verlangsamen.",status_idle:"Daten nicht geladen",status_loading:"Wird geladen…",status_ready:"Empfangen: {time}",status_stale:"Empfangen: {time} · veraltet",status_partial:"Empfangen: {time} · einige Knoten fehlen",status_no_links:"Empfangen: {time} · keine beobachteten Verbindungen gefunden",error_permission:"Administratorrechte sind erforderlich.",error_unsupported:"Dieser Anbieter oder die Home-Assistant-API ist nicht verfügbar.",error_timeout:"Der Anbieter hat nicht rechtzeitig geantwortet.",error_invalid_topic:"Prüfe das Zigbee2MQTT-Basistopic.",error_invalid_payload:"Der Anbieter hat nicht unterstützte Topologiedaten geliefert.",error_provider:"Topologiedaten konnten nicht geladen werden.",remote_count:"+{n} in anderen Bereichen",route_device_not_on_plan:"Gerät ist nicht im Plan",route_coordinator_not_on_plan:"Koordinator ist nicht im Plan",route_other_space:"anderer Bereich"};const n="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";export{e as dictionary,n as fingerprint};
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";var e={title:"Zigbee-Verbindungen",toggle:"Zigbee-Verbindungen beim Darüberfahren über ein Gerät anzeigen",hint:"Es werden nur beobachtete direkte Nachbarn angezeigt. Beim Darüberfahren werden keine Daten abgerufen.",help:"Zeigt, mit welchen Zigbee-Geräten jedes Gerät direkt verbunden ist. Die Linienfarbe ist die Verbindungsqualität (LQI) auf derselben Skala wie der LQI-Wert des Geräts: rot bei 40 und darunter, grün bei 180 und darüber. Eine gestrichelte Linie bedeutet, dass die Qualität nicht gemeldet wurde. Ein Pfeil zeigt auf das nächste Gerät auf dem Weg zum Koordinator: ein Pfeil führt hinaus, eingehende Pfeile sind die Geräte, die über dieses Gerät routen. Eine Linie ohne Pfeil ist ein Ersatznachbar. Das ist der Routenbaum, den House Plan bildet, nicht der Weg eines Pakets in diesem Moment: eine Beschriftung am Pfeilende bedeutet, dass das Ziel nicht auf diesem Plan liegt, und ein fehlender Pfeil, dass die Route unbekannt ist. Die Daten stammen aus dem letzten Laden und veralten; Verbindungen sind nur mit der Maus sichtbar.",help_aria:"Hilfe: Zigbee-Verbindungen",admin_only:"Topologiedaten sind nur für Home-Assistant-Administratoren verfügbar.",save_first:"Speichere diese Einstellung, bevor Topologiedaten geladen werden.",zha:"ZHA",zha_read:"ZHA-Daten lesen",zha_hint:"Liest den ZHA-Topologiecache; es wird kein Funkscan gestartet.",z2m:"Zigbee2MQTT",z2m_topics:"Basistopics (eines pro Zeile)",z2m_update:"Karte aktualisieren",z2m_warning:"Ein Netzwerkscan kann 10 Sekunden bis 2 Minuten dauern und Zigbee vorübergehend verlangsamen.",status_idle:"Daten nicht geladen",status_loading:"Wird geladen…",status_ready:"Empfangen: {time}",status_stale:"Empfangen: {time} · veraltet",status_partial:"Empfangen: {time} · einige Knoten fehlen",status_no_links:"Empfangen: {time} · keine beobachteten Verbindungen gefunden",error_permission:"Administratorrechte sind erforderlich.",error_unsupported:"Dieser Anbieter oder die Home-Assistant-API ist nicht verfügbar.",error_timeout:"Der Anbieter hat nicht rechtzeitig geantwortet.",error_invalid_topic:"Prüfe das Zigbee2MQTT-Basistopic.",error_invalid_payload:"Der Anbieter hat nicht unterstützte Topologiedaten geliefert.",error_provider:"Topologiedaten konnten nicht geladen werden.",remote_count:"+{n} in anderen Bereichen",route_device_not_on_plan:"Gerät ist nicht im Plan",route_coordinator_not_on_plan:"Koordinator ist nicht im Plan",route_other_space:"anderer Bereich"};const n="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";export{e as dictionary,n as fingerprint};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";var e={title:"Liens Zigbee",toggle:"Afficher les liens Zigbee au survol d’un appareil",hint:"Seuls les voisins directs observés sont affichés. Le survol ne lance aucune requête.",help:"Montre avec quels appareils Zigbee chaque appareil communique directement. La couleur de la ligne est la qualité du lien (LQI) sur la même échelle que l'indicateur LQI de l'appareil : rouge à 40 et en dessous, vert à 180 et au-dessus. Une ligne pointillée signifie que la qualité n'a pas été communiquée. Une flèche pointe vers l'appareil suivant sur le chemin du coordinateur : une flèche part, les flèches entrantes sont les appareils qui passent par celui-ci. Une ligne sans flèche est un voisin de secours. C'est l'arbre de routes que construit House Plan, pas le trajet d'un paquet à cet instant : une étiquette au bout d'une flèche signifie que sa cible n'est pas sur ce plan, et l'absence de flèche que la route est inconnue. Les données proviennent du dernier chargement et vieillissent ; les liens ne sont visibles qu'à la souris.",help_aria:"Aide : liens Zigbee",admin_only:"Les données de topologie sont réservées aux administrateurs Home Assistant.",save_first:"Enregistrez ce réglage avant de charger les données de topologie.",zha:"ZHA",zha_read:"Lire les données ZHA",zha_hint:"Lit la topologie en cache de ZHA sans lancer d’analyse radio.",z2m:"Zigbee2MQTT",z2m_topics:"Topics de base (un par ligne)",z2m_update:"Actualiser la carte",z2m_warning:"L’analyse de la carte réseau peut durer de 10 secondes à 2 minutes et réduire temporairement la réactivité de Zigbee.",status_idle:"Données non chargées",status_loading:"Chargement…",status_ready:"Reçu : {time}",status_stale:"Reçu : {time} · obsolète",status_partial:"Reçu : {time} · certains nœuds sont omis",status_no_links:"Reçu : {time} · aucun lien observé trouvé",error_permission:"Les droits administrateur sont requis.",error_unsupported:"Ce fournisseur ou l’API Home Assistant n’est pas disponible.",error_timeout:"Le fournisseur n’a pas répondu à temps.",error_invalid_topic:"Vérifiez le topic de base Zigbee2MQTT.",error_invalid_payload:"Le fournisseur a renvoyé des données de topologie non prises en charge.",error_provider:"Impossible de charger les données de topologie.",remote_count:"+{n} dans d’autres espaces",route_device_not_on_plan:"appareil absent du plan",route_coordinator_not_on_plan:"coordinateur absent du plan",route_other_space:"autre espace"};const s="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";export{e as dictionary,s as fingerprint};
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";var e={title:"Liens Zigbee",toggle:"Afficher les liens Zigbee au survol d’un appareil",hint:"Seuls les voisins directs observés sont affichés. Le survol ne lance aucune requête.",help:"Montre avec quels appareils Zigbee chaque appareil communique directement. La couleur de la ligne est la qualité du lien (LQI) sur la même échelle que l'indicateur LQI de l'appareil : rouge à 40 et en dessous, vert à 180 et au-dessus. Une ligne pointillée signifie que la qualité n'a pas été communiquée. Une flèche pointe vers l'appareil suivant sur le chemin du coordinateur : une flèche part, les flèches entrantes sont les appareils qui passent par celui-ci. Une ligne sans flèche est un voisin de secours. C'est l'arbre de routes que construit House Plan, pas le trajet d'un paquet à cet instant : une étiquette au bout d'une flèche signifie que sa cible n'est pas sur ce plan, et l'absence de flèche que la route est inconnue. Les données proviennent du dernier chargement et vieillissent ; les liens ne sont visibles qu'à la souris.",help_aria:"Aide : liens Zigbee",admin_only:"Les données de topologie sont réservées aux administrateurs Home Assistant.",save_first:"Enregistrez ce réglage avant de charger les données de topologie.",zha:"ZHA",zha_read:"Lire les données ZHA",zha_hint:"Lit la topologie en cache de ZHA sans lancer d’analyse radio.",z2m:"Zigbee2MQTT",z2m_topics:"Topics de base (un par ligne)",z2m_update:"Actualiser la carte",z2m_warning:"L’analyse de la carte réseau peut durer de 10 secondes à 2 minutes et réduire temporairement la réactivité de Zigbee.",status_idle:"Données non chargées",status_loading:"Chargement…",status_ready:"Reçu : {time}",status_stale:"Reçu : {time} · obsolète",status_partial:"Reçu : {time} · certains nœuds sont omis",status_no_links:"Reçu : {time} · aucun lien observé trouvé",error_permission:"Les droits administrateur sont requis.",error_unsupported:"Ce fournisseur ou l’API Home Assistant n’est pas disponible.",error_timeout:"Le fournisseur n’a pas répondu à temps.",error_invalid_topic:"Vérifiez le topic de base Zigbee2MQTT.",error_invalid_payload:"Le fournisseur a renvoyé des données de topologie non prises en charge.",error_provider:"Impossible de charger les données de topologie.",remote_count:"+{n} dans d’autres espaces",route_device_not_on_plan:"appareil absent du plan",route_coordinator_not_on_plan:"coordinateur absent du plan",route_other_space:"autre espace"};const s="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";export{e as dictionary,s as fingerprint};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";var e={title:"Связи Zigbee",toggle:"Показывать связи Zigbee при наведении на устройство",hint:"Показываются только наблюдаемые прямые соседи. Наведение ничего не запрашивает.",help:"Показывает, с какими устройствами Zigbee каждое связано напрямую. Цвет линии — качество связи (LQI) по той же шкале, что у показателя LQI устройства: красный при 40 и ниже, зелёный при 180 и выше. Пунктир — качество не сообщено. Стрелка ведёт к следующему устройству по пути к координатору: исходящая одна, входящие — те, кто ходит через это устройство. Линия без стрелки — запасной сосед. Это дерево маршрутов, которое строит House Plan, а не путь пакета в эту секунду: подпись на конце стрелки значит, что цель не на этом плане, а отсутствие стрелки — что путь неизвестен. Данные из последней загрузки и устаревают, связи видны только при работе мышью.",help_aria:"Справка: связи Zigbee",admin_only:"Данные топологии доступны только администраторам Home Assistant.",save_first:"Сохраните настройку перед загрузкой данных топологии.",zha:"ZHA",zha_read:"Прочитать данные ZHA",zha_hint:"Читает сохранённую топологию ZHA и не запускает радио-сканирование.",z2m:"Zigbee2MQTT",z2m_topics:"Базовые топики (по одному в строке)",z2m_update:"Обновить карту",z2m_warning:"Сканирование карты сети занимает от 10 секунд до 2 минут и временно может снизить отзывчивость Zigbee.",status_idle:"Данные не загружены",status_loading:"Загрузка…",status_ready:"Получено: {time}",status_stale:"Получено: {time} · данные устарели",status_partial:"Получено: {time} · часть узлов не показана",status_no_links:"Получено: {time} · наблюдаемые связи не найдены",error_permission:"Нужны права администратора.",error_unsupported:"Провайдер или API Home Assistant недоступен.",error_timeout:"Провайдер не ответил вовремя.",error_invalid_topic:"Проверьте базовый топик Zigbee2MQTT.",error_invalid_payload:"Провайдер вернул неподдерживаемые данные топологии.",error_provider:"Не удалось загрузить данные топологии.",remote_count:"+{n} в других пространствах",route_device_not_on_plan:"устройства нет на плане",route_coordinator_not_on_plan:"координатора нет на плане",route_other_space:"другое пространство"};const a="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";export{e as dictionary,a as fingerprint};
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";var e={title:"Связи Zigbee",toggle:"Показывать связи Zigbee при наведении на устройство",hint:"Показываются только наблюдаемые прямые соседи. Наведение ничего не запрашивает.",help:"Показывает, с какими устройствами Zigbee каждое связано напрямую. Цвет линии — качество связи (LQI) по той же шкале, что у показателя LQI устройства: красный при 40 и ниже, зелёный при 180 и выше. Пунктир — качество не сообщено. Стрелка ведёт к следующему устройству по пути к координатору: исходящая одна, входящие — те, кто ходит через это устройство. Линия без стрелки — запасной сосед. Это дерево маршрутов, которое строит House Plan, а не путь пакета в эту секунду: подпись на конце стрелки значит, что цель не на этом плане, а отсутствие стрелки — что путь неизвестен. Данные из последней загрузки и устаревают, связи видны только при работе мышью.",help_aria:"Справка: связи Zigbee",admin_only:"Данные топологии доступны только администраторам Home Assistant.",save_first:"Сохраните настройку перед загрузкой данных топологии.",zha:"ZHA",zha_read:"Прочитать данные ZHA",zha_hint:"Читает сохранённую топологию ZHA и не запускает радио-сканирование.",z2m:"Zigbee2MQTT",z2m_topics:"Базовые топики (по одному в строке)",z2m_update:"Обновить карту",z2m_warning:"Сканирование карты сети занимает от 10 секунд до 2 минут и временно может снизить отзывчивость Zigbee.",status_idle:"Данные не загружены",status_loading:"Загрузка…",status_ready:"Получено: {time}",status_stale:"Получено: {time} · данные устарели",status_partial:"Получено: {time} · часть узлов не показана",status_no_links:"Получено: {time} · наблюдаемые связи не найдены",error_permission:"Нужны права администратора.",error_unsupported:"Провайдер или API Home Assistant недоступен.",error_timeout:"Провайдер не ответил вовремя.",error_invalid_topic:"Проверьте базовый топик Zigbee2MQTT.",error_invalid_payload:"Провайдер вернул неподдерживаемые данные топологии.",error_provider:"Не удалось загрузить данные топологии.",remote_count:"+{n} в других пространствах",route_device_not_on_plan:"устройства нет на плане",route_coordinator_not_on_plan:"координатора нет на плане",route_other_space:"другое пространство"};const t="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";export{e as dictionary,t as fingerprint};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{n as t,c as n}from"./zigbee-topology-CvQsfSze.js";import{aM as e}from"./houseplan-card-BCYl6SkQ.js";import"./namespace-language-B2Cyshho.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),v=[];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&&v.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&v.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const b=await m(w,s-Date.now()),_=d(b)?.status;if(_&&"ok"!==_)throw a("provider");return n(b,l)}finally{for(const t of v)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{n as t,c as n}from"./zigbee-topology-BbetEPvj.js";import{aM as e}from"./houseplan-card-Ddo22-SB.js";import"./namespace-language-CwX4Ljv1.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
@@ -1 +1 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";try{await import("./houseplan-assets/houseplan-card-BCYl6SkQ.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__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";try{await import("./houseplan-assets/houseplan-card-Ddo22-SB.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
+31 -3
View File
@@ -73,7 +73,7 @@ _LIVE_TEXT_ENTITY = re.compile(r"^[a-z0-9_]+\.[a-z0-9_]+$")
_LIVE_TEXT_ATTRIBUTE = re.compile(r"^[a-zA-Z0-9_.-]+$")
_PLAN_ONLY_DASH = "—"
_IMPORT_ID_NAMESPACES = {
"space", "room", "marker", "partition", "wall", "opening", "decor", "column",
"space", "room", "marker", "partition", "wall", "opening", "decor", "column", "stair",
}
_MAX_IMPORT_LINEAGE_DEPTH = 16
_REPORT_EXAMPLE_LIMIT = 24
@@ -294,6 +294,14 @@ def _project_plan_only_space(space: dict[str, Any]) -> dict[str, Any]:
projected["decor"] = [
_project_plan_only_decor(shape) for shape in space.get("decor") or []
]
if "stairs" in space:
projected["stairs"] = [
_pick_fields(stair, (
"id", "kind", "x", "y", "angle", "direction",
"target_space_id", "length", "width", "radius",
))
for stair in space.get("stairs") or []
]
return projected
@@ -742,6 +750,7 @@ def _counts(config: dict[str, Any], layout: dict[str, Any]) -> dict[str, int]:
"markers": len(config.get("markers") or []),
"openings": sum(len(sp.get("openings") or []) for sp in spaces),
"decor": sum(len(sp.get("decor") or []) for sp in spaces),
"stairs": sum(len(sp.get("stairs") or []) for sp in spaces),
"layout": len(layout),
}
@@ -1132,6 +1141,14 @@ def _repair_target_space_refs(
for space in spaces:
space_id = str(space.get("id", "?"))
for stair in space.get("stairs") or []:
target = stair.get("target_space_id") if isinstance(stair, dict) else None
mapped = replace(
f"{space_id}:{stair.get('id', '?')}", "stair.target_space_id",
target, resolve_space,
)
if mapped is not None:
stair["target_space_id"] = mapped
for room in space.get("rooms") or []:
room_id = str(room.get("id", "?"))
values = room.get("open_to")
@@ -1322,7 +1339,7 @@ def build_space_merge(
(sp,), sp.get("rooms") or [],
sp.get("partitions") or [], sp.get("wall_segments") or [],
sp.get("wall_columns") or [],
sp.get("openings") or [], sp.get("decor") or [],
sp.get("openings") or [], sp.get("decor") or [], sp.get("stairs") or [],
)
for item in collection
if isinstance(item, dict)
@@ -1338,7 +1355,7 @@ def build_space_merge(
(incoming_space,), incoming_space.get("rooms") or [],
incoming_space.get("partitions") or [], incoming_space.get("wall_segments") or [],
incoming_space.get("wall_columns") or [],
incoming_space.get("openings") or [], incoming_space.get("decor") or [],
incoming_space.get("openings") or [], incoming_space.get("decor") or [], incoming_space.get("stairs") or [],
)
for item in collection
if isinstance(item, dict)
@@ -1362,6 +1379,7 @@ def build_space_merge(
("rooms", "room"), ("partitions", "partition"),
("wall_segments", "wall"),
("wall_columns", "column"), ("openings", "opening"), ("decor", "decor"),
("stairs", "stair"),
):
for item in space.get(collection) or []:
if isinstance(item, dict) and item.get("id") is not None:
@@ -1405,6 +1423,16 @@ def build_space_merge(
reference_report, "incoming", "opening.host",
str(opening.get("id", "?")), old_host_id,
)
# A one-space export cannot carry a trustworthy external floor target.
# Self-links are invalid too because the imported source gets a new id.
for stair in space.get("stairs") or []:
target = stair.get("target_space_id") if isinstance(stair, dict) else None
if target is not None:
stair["target_space_id"] = None
_report_remap(
reference_report, "incoming", "stair.target_space_id",
str(stair.get("id", "?")), str(target),
)
space["id"] = new_space_id
space["title"] = _unique_title(
str(space.get("title") or old_space_id), current_config.get("spaces") or []
+1 -1
View File
@@ -20,5 +20,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [],
"single_config_entry": true,
"version": "1.78.0-beta.2"
"version": "1.78.0-beta.5"
}
+1 -1
View File
@@ -133,7 +133,7 @@ def create_data(hass: HomeAssistant) -> HouseplanData:
)
from .virtual_lights import VirtualLightController
data.virtual_lights = VirtualLightController(data.virtual_light_store)
data.virtual_lights = VirtualLightController(hass, data.virtual_light_store)
return data
@@ -155,6 +155,9 @@ def _global_settings(value: object) -> dict[str, Any]:
show_room_tooltip = value.get("show_room_tooltip")
if isinstance(show_room_tooltip, bool):
out["show_room_tooltip"] = show_room_tooltip
volumetric_view = value.get("volumetric_view")
if isinstance(volumetric_view, bool):
out["volumetric_view"] = volumetric_view
fill_colors = value.get("fill_colors")
if isinstance(fill_colors, dict):
palette = {
@@ -371,6 +374,17 @@ def _project_space(ids: _Pseudonyms, space: dict[str, Any], index: int) -> dict[
out["decor"] = [
_project_decor(ids, item) for item in space.get("decor") or [] if isinstance(item, dict)
]
out["stairs"] = []
for item in space.get("stairs") or []:
if not isinstance(item, dict):
continue
projected = {"id": ids.get("stair", item.get("id"))}
projected.update(_copy_keys(item, (
"kind", "x", "y", "angle", "direction", "length", "width", "radius",
)))
if item.get("target_space_id"):
projected["target_space_id"] = ids.get("space", item.get("target_space_id"))
out["stairs"].append(projected)
out["open_spans"] = [
{"a": _point(item.get("a")), "b": _point(item.get("b"))}
for item in space.get("open_spans") or [] if isinstance(item, dict)
@@ -433,6 +447,7 @@ def _summary(config: object, layout: object) -> dict[str, Any]:
"columns": sum(len(space.get("wall_columns") or []) for space in spaces if isinstance(space, dict)),
"openings": dict(sorted(kinds.items())),
"decor": dict(sorted(decor_kinds.items())),
"stairs": sum(len(space.get("stairs") or []) for space in spaces if isinstance(space, dict)),
"markers": {
"total": len(markers),
"lifecycle": dict(sorted(lifecycles.items())),
@@ -31,6 +31,7 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
"rooms": sum(len(s.get("rooms", [])) for s in config.get("spaces", [])),
"partitions": sum(len(s.get("partitions", [])) for s in config.get("spaces", [])),
"wall_columns": sum(len(s.get("wall_columns", [])) for s in config.get("spaces", [])),
"stairs": sum(len(s.get("stairs", [])) for s in config.get("spaces", [])),
"markers": len(config.get("markers", [])),
"layout_entries": len(layout_raw.get("layout", {})),
}
+56 -1
View File
@@ -1198,6 +1198,7 @@ MAX_ROOMS = 400
MAX_MARKERS = 2000
MAX_OPENINGS = 500
MAX_DECOR = 1000
MAX_STAIRS = 250
# v8 atomises room boundaries. The 2 MiB wire cap is the practical bound; this
# structural cap mirrors MAX_ROOMS * MAX_POLY_POINTS without depending on the
# later constant declaration.
@@ -1532,6 +1533,57 @@ DECOR_SCHEMA = vol.Any(
)
def _strict_stair(value: dict) -> dict:
"""A discriminated stair record carries dimensions for exactly one kind."""
if value["kind"] == "straight":
if "radius" in value:
raise vol.Invalid("radius is allowed only for spiral stairs")
if value["direction"] not in ("forward", "backward"):
raise vol.Invalid("straight stair direction must be forward or backward")
else:
if "length" in value or "width" in value:
raise vol.Invalid("length and width are allowed only for straight stairs")
if value["direction"] not in ("clockwise", "counterclockwise"):
raise vol.Invalid("spiral stair direction must be clockwise or counterclockwise")
return value
def _stair_dimensions(value: dict) -> dict:
"""Require the dimension fields owned by the selected stair kind."""
complete = (
value["kind"] == "straight" and "length" in value and "width" in value
or value["kind"] == "spiral" and "radius" in value
)
if not complete:
raise vol.Invalid("stair kind requires its dimensions")
return value
STAIR_SCHEMA = vol.All(
vol.Schema(
{
vol.Required("id"): vol.All(str, vol.Length(min=1, max=64)),
vol.Required("kind"): vol.In(["straight", "spiral"]),
vol.Required("x"): _NORM,
vol.Required("y"): _NORM,
vol.Required("angle"): vol.All(_finite, vol.Range(min=-360.0, max=360.0)),
vol.Required("direction"): vol.In([
"forward", "backward", "clockwise", "counterclockwise",
]),
vol.Optional("target_space_id"): vol.Any(
None, vol.All(str, vol.Match(SPACE_ID_RE.pattern))
),
vol.Optional("length"): _FURN_SIZE,
vol.Optional("width"): _FURN_SIZE,
vol.Optional("radius"): _FURN_SIZE,
},
extra=vol.ALLOW_EXTRA,
),
_stair_dimensions,
_strict_stair,
)
def _wall_endpoints_pair(entry: dict) -> dict:
"""Exact wall endpoints are useful only as a complete a/b pair."""
if ("a" in entry) != ("b" in entry):
@@ -1673,7 +1725,7 @@ OPENING_HOST_SCHEMA = vol.Any(PARTITION_OPENING_HOST_SCHEMA, WALL_OPENING_HOST_S
def _space_geometry_invariants(value: dict) -> dict:
"""All stored geometry shares ids; draft segments also have a space cap."""
seen: set[str] = set()
for key in ("rooms", "openings", "decor", "room_drafts", "partitions", "wall_columns", "wall_segments"):
for key in ("rooms", "openings", "decor", "stairs", "room_drafts", "partitions", "wall_columns", "wall_segments"):
for item in value.get(key, []):
item_id = item.get("id")
if not item_id:
@@ -1768,6 +1820,7 @@ SPACE_SCHEMA = vol.All(vol.Schema(
vol.Required("view_box"): _view_box,
vol.Required("rooms"): vol.All([ROOM_SCHEMA], vol.Length(max=MAX_ROOMS)),
vol.Optional("decor"): vol.All([DECOR_SCHEMA], vol.Length(max=MAX_DECOR)),
vol.Optional("stairs"): vol.All([STAIR_SCHEMA], vol.Length(max=MAX_STAIRS)),
vol.Optional("openings"): vol.All([
vol.Schema(
{
@@ -2254,6 +2307,8 @@ CONFIG_SCHEMA = vol.All(
vol.Optional("sun_rays"): bool,
vol.Optional("sun_ray_origin"): _SUN_RAY_ORIGIN,
vol.Optional("show_room_tooltip"): bool,
# #649: installation-wide 2.5D View; absent/false keeps Flat.
vol.Optional("volumetric_view"): bool,
vol.Optional("summary_panel"): SUMMARY_PANEL_WIRE_SCHEMA,
vol.Optional("radar"): vol.Schema({}, extra=vol.ALLOW_EXTRA),
# Removed from the UI/runtime in 2026-08-08. Keep accepting the
+17 -12
View File
@@ -7,6 +7,8 @@ import logging
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
from .store import HouseplanStore
@@ -81,12 +83,14 @@ def _snapshot_payload(
class VirtualLightController:
"""Runtime cache with coalesced durable writes for rapid toggles."""
def __init__(self, store: HouseplanStore) -> None:
def __init__(self, hass: HomeAssistant, store: HouseplanStore) -> None:
self.hass = hass
self.store = store
self._state: dict[str, Any] | None = None
self._dirty = False
self._save_task: asyncio.Task[None] | None = None
self._flush_event = asyncio.Event()
self._flush_lock = asyncio.Lock()
async def async_snapshot(
self, config: dict[str, Any], config_rev: int,
@@ -127,7 +131,7 @@ class VirtualLightController:
def _schedule_save(self) -> None:
if self._save_task is None or self._save_task.done():
self._save_task = asyncio.create_task(self._delayed_save())
self._save_task = self.hass.async_create_task(self._delayed_save())
async def _delayed_save(self) -> None:
try:
@@ -156,16 +160,17 @@ class VirtualLightController:
async def async_flush(self) -> None:
"""Persist the latest state before config transitions or unload."""
task = self._save_task
if task is not None:
self._flush_event.set()
await task
if self._dirty and self._state is not None:
payload = copy.deepcopy(self._state)
await self.store.async_save(payload)
self._dirty = False
if not self._dirty:
self._flush_event.clear()
async with self._flush_lock:
task = self._save_task
if task is not None:
self._flush_event.set()
await task
if self._dirty and self._state is not None:
payload = copy.deepcopy(self._state)
await self.store.async_save(payload)
self._dirty = False
if not self._dirty:
self._flush_event.clear()
def reset(self) -> None:
"""Forget cache after an external reconciliation wrote the store."""
@@ -439,7 +439,7 @@ def _thickness(space: dict[str, Any], atom: dict[str, Any], previous: dict[str,
def _non_catalog_ids(space: dict[str, Any]) -> set[str]:
result: set[str] = set()
for name in ("rooms", "openings", "decor", "room_drafts", "partitions", "wall_columns"):
for name in ("rooms", "openings", "decor", "stairs", "room_drafts", "partitions", "wall_columns"):
for item in space.get(name) or []:
if isinstance(item, dict) and isinstance(item.get("id"), str) and item["id"]:
result.add(item["id"])
@@ -661,7 +661,7 @@ def _migrate_room_drafts_to_partitions(space: dict[str, Any]) -> tuple[int, int]
return 0, 0
used = {
str(item["id"])
for name in ("rooms", "openings", "decor", "partitions",
for name in ("rooms", "openings", "decor", "stairs", "partitions",
"wall_columns", "wall_segments")
for item in space.get(name) or []
if isinstance(item, dict) and isinstance(item.get("id"), str) and item["id"]
@@ -1848,6 +1848,10 @@ def _space_delete_candidate(
item for item in candidate_config.get("spaces") or []
if item.get("id") != space_id
]
for remaining_space in candidate_config["spaces"]:
for stair in remaining_space.get("stairs") or []:
if isinstance(stair, dict) and stair.get("target_space_id") == space_id:
stair["target_space_id"] = None
for marker in candidate_config.get("markers") or []:
marker_id = str(marker.get("id")) if marker.get("id") is not None else None
marker_position = candidate_layout.get(marker_id) if marker_id is not None else None
+22 -5
View File
@@ -40,6 +40,7 @@ const requiresIsoStructuralBuildCounter = requiresIsometric
const requiresPlanSnap = planSnap && existsSync(resolve(targetRoot, 'src/plan-snap-overlay.ts'));
const requiresWallFace = planSnap && existsSync(resolve(targetRoot, 'src/wall-face-graph.ts'));
const requiresInteraction = interaction && existsSync(resolve(targetRoot, 'src/live-viewport.ts'));
const requiresStairs = existsSync(resolve(targetRoot, 'src/stairs.ts'));
const fixture = stage3Dense ? makeIsometricStage3DenseFixture() : makeLargeHouseFixture();
const fixtureCounts = stage3Dense ? fixture.counts : LARGE_HOUSE_COUNTS;
const sourceSha = (() => {
@@ -100,7 +101,7 @@ try {
const row = await page.evaluate(async ({
fixture, sample, cardContract, isometric, requiresIsometric, planSnap, requiresPlanSnap,
requiresWallFace, interaction, requiresInteraction, stage3Dense, requireStage3,
requiresIsoStructuralBuildCounter, profile,
requiresIsoStructuralBuildCounter, requiresStairs, profile,
}) => {
const frame = () => new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
const until = async (predicate, timeout = 10000) => {
@@ -142,7 +143,7 @@ try {
longTasks: await longTasks.stop(),
};
};
// Current candidates split the experimental Iso runtime out of the initial graph. Comparison
// Current candidates split the lazy 2.5D runtime out of the initial graph. Comparison
// bundles remain valid: their older monolithic renderer has no preload
// hook and is already available synchronously.
const ensureIsoRuntime = async (card) => {
@@ -181,7 +182,7 @@ try {
const overlaySelectors = {
device: '[data-hp="device"]',
'room-label': '[data-hp="room-label"]',
'opening-lock': '.oplock',
'opening-lock': '.oplock:not(.iso-tile-shadow)',
};
const totalRootsByKind = Object.fromEntries(Object.entries(overlaySelectors)
.map(([kind, selector]) => [kind, card.renderRoot.querySelectorAll(selector).length]));
@@ -291,6 +292,9 @@ try {
fixture.config.spaces.map((space) => [space.id, 'iso']),
)));
history.replaceState(null, '', '?hp_alpha=1');
// #649: current bundles read the installation setting; the alpha
// storage above stays for comparison bundles before it.
fixture.config.settings = { ...(fixture.config.settings || {}), volumetric_view: true };
} else history.replaceState(null, '', location.pathname);
const host = document.getElementById('host');
const card = document.createElement('houseplan-card');
@@ -386,7 +390,7 @@ try {
throw new Error('large-house editor runtime did not preload');
if (isometric) await ensureIsoRuntime(card);
window.__hpAssertCardContract(card, cardContract);
if (requiresIsometric && (typeof card._setProjection !== 'function'
if (requiresIsometric && (typeof card._effectiveProjection !== 'function'
|| !(card._isoGeometryCache instanceof Map))) {
throw new Error('large-house-isometric-v1 candidate has no renderer contract');
}
@@ -397,6 +401,10 @@ try {
await until(() => card._booting === false);
if (interaction && '_bootSoft' in card) await until(() => card._bootSoft === false);
await frame();
if (requiresStairs
&& card.renderRoot.querySelectorAll('[data-hp="stair"]').length !== fixture.counts.stairs) {
throw new Error(`${profile} did not render the bounded maximum stair collection`);
}
const firstStableRenderMs = Number((performance.now() - loadStarted).toFixed(2));
const bootDiag = {
updates: card.__diag.updates,
@@ -445,11 +453,20 @@ try {
}
const viewToggle = isometric ? await duration(async () => {
if (typeof card._setProjection === 'function') {
// Comparison bundles before #649: the per-device alpha preference.
card._setProjection('flat');
await card.updateComplete;
card._setProjection('iso');
await ensureIsoRuntime(card);
await card.updateComplete;
} else if (typeof card._syncVolumetricSetting === 'function') {
// #649: the installation-wide General settings switch, as a save applies it.
const cfg = card._serverCfg;
card._serverCfg = { ...cfg, settings: { ...(cfg.settings || {}), volumetric_view: false } };
await card.updateComplete;
card._serverCfg = { ...cfg, settings: { ...(cfg.settings || {}), volumetric_view: true } };
await ensureIsoRuntime(card);
await card.updateComplete;
} else {
// Comparison SHAs before #89 intentionally ignore the Labs operation.
card.requestUpdate();
@@ -1207,7 +1224,7 @@ try {
fixture, sample: measuredSample, cardContract: LARGE_HOUSE_CARD_CONTRACT,
isometric, requiresIsometric, planSnap, requiresPlanSnap, requiresWallFace,
interaction, requiresInteraction, stage3Dense, requireStage3,
requiresIsoStructuralBuildCounter, profile,
requiresIsoStructuralBuildCounter, requiresStairs, profile,
});
// #520: диагностика печатается в лог прогона и в запись не попадает.
const { bootDiag, ...measured } = row;
+22
View File
@@ -0,0 +1,22 @@
// #649 AC12: product frames for docs/design/649-25d-stage6/ACCEPTANCE.md.
// These scenarios are canonical golden entries (#673). This focused command
// remains a diagnostic helper for rebuilding the side-by-side design document;
// baseline capture and acceptance still use the complete golden matrix (#455).
//
// npm run build && npm run bundle:sync
// node demo/capture_stage6_acceptance_649.mjs [out-dir]
import { mkdirSync } from 'node:fs';
import { launch } from './serve.mjs';
import { goldenClip, prepareGoldenScenario } from './golden/harness.mjs';
import { STAGE6_ACCEPTANCE_SCENARIOS } from './golden/matrix.mjs';
const out = process.argv[2] || 'artifacts/stage6-acceptance';
mkdirSync(out, { recursive: true });
const { page, browser } = await launch({ width: 1000, height: 900 }, 1);
for (const scenario of STAGE6_ACCEPTANCE_SCENARIOS) {
await prepareGoldenScenario(page, scenario);
await page.waitForTimeout(2600); // the sun wash fades in over 2 s
await page.screenshot({ path: `${out}/${scenario.id}.png`, clip: await goldenClip(page, scenario.capture) });
console.log('captured', scenario.id);
}
await browser.close();
+21 -1
View File
@@ -12,6 +12,7 @@ const OPENING_COUNT = 100;
const PARTITION_COUNT = 60;
const COLUMN_COUNT = 40;
const DECOR_COUNT = 500;
const STAIR_COUNT = 250;
const round = (value) => Number(value.toFixed(6));
@@ -119,6 +120,23 @@ const makeDecor = (floor, count) => Array.from({ length: count }, (_, index) =>
};
});
const makeStairs = (floor, count) => Array.from({ length: count }, (_, index) => {
const column = index % 25;
const row = Math.floor(index / 25);
const common = {
id: `perf-stair-${floor}-${index}`,
x: round(0.025 + column * 0.039),
y: round(0.03 + row * 0.1),
angle: (index % 8) * 45,
target_space_id: 'perf-floor-2',
};
return index % 2 === 0
? { ...common, kind: 'straight', direction: index % 4 ? 'forward' : 'backward',
length: 0.12, width: 0.045 }
: { ...common, kind: 'spiral', direction: index % 4 ? 'clockwise' : 'counterclockwise',
radius: 0.05 };
});
const entityKinds = [
['light', 'on'],
['switch', 'off'],
@@ -188,9 +206,10 @@ export const LARGE_HOUSE_COUNTS = Object.freeze({
partitions: PARTITION_COUNT,
columns: COLUMN_COUNT,
decor: DECOR_COUNT,
stairs: STAIR_COUNT,
});
export const makeLargeHouseFixture = () => {
export const makeLargeHouseFixture = ({ includeStairs = true } = {}) => {
let openingsLeft = OPENING_COUNT;
let partitionsLeft = PARTITION_COUNT;
let columnsLeft = COLUMN_COUNT;
@@ -228,6 +247,7 @@ export const makeLargeHouseFixture = () => {
partitions: makePartitions(floor, rooms, partitionCount),
wall_columns: makeColumns(floor, rooms, columnCount),
decor: makeDecor(floor, decorCount),
...(includeStairs && floor === 0 ? { stairs: makeStairs(floor, STAIR_COUNT) } : {}),
};
});
const runtime = makeRuntime(spaces);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 69 KiB

+119 -89
View File
@@ -1,40 +1,70 @@
{
"schema": 3,
"matrixVersion": 64,
"acceptedAt": "2026-09-25T08:23:19.883Z",
"sourceFingerprint": "284838429ce80f96cfa10fb791c6aa3b837dcbbc3c2cf62cdc47d7e6bb6df108",
"matrixVersion": 66,
"acceptedAt": "2026-09-27T07:07:07.508Z",
"sourceFingerprint": "9f31bdc1556b108a5f8910aa9f246ba40507335bdeb00c8b1b2d960f72f84ce7",
"chromium": "151.0.7922.34",
"capturedOn": "linux",
"acceptedOn": "win32",
"acceptedOn": "linux",
"capture": {
"platform": "linux",
"arch": "x64",
"chromium": "151.0.7922.34",
"buildFingerprint": "284838429ce80f96cfa10fb791c6aa3b837dcbbc3c2cf62cdc47d7e6bb6df108",
"ci": {
"buildFingerprint": "9f31bdc1556b108a5f8910aa9f246ba40507335bdeb00c8b1b2d960f72f84ce7",
"ci": null
},
"localAttestation": {
"schema": "houseplan-golden-wsl/v1",
"sha256": "adb377c40236510ed767a5a829dbbc88e51156e8d1f6eb206166fd3a7c61f921",
"artifactSha256": "bbcaf4d2621c381224d34fa24d7c14bebb357d2de6342d4ba278a8b9f48556dd",
"createdAt": "2026-09-27T07:05:53.848Z",
"source": {
"repository": "Matysh/houseplan-card",
"run": 36111677966,
"attempt": 1,
"sha": "27d78801d0436f5bd496699a6ee201f8276956a9"
"branch": "issue/673-stage6-golden",
"commit": "c9a5df602becb3f402cf906c8d9b6b30898439fd",
"tree": "d7f59614ce1d307175670c39687e4988b41aaaf8",
"remoteSha": "c9a5df602becb3f402cf906c8d9b6b30898439fd",
"clean": true,
"status": ""
},
"environment": {
"platform": "linux",
"arch": "x64",
"kernel": "6.18.33.2-microsoft-standard-WSL2",
"wsl": true,
"distro": "Ubuntu",
"filesystem": "ext2/ext3"
},
"toolchain": {
"node": "22.23.2",
"npm": "10.9.8",
"playwright": "1.62.0",
"chromiumExecutableSha256": "0b20b130e7edd9dd51873be867761295fe0cfad490c2b9a64f95bd3cfc08fa71"
}
},
"localAttestation": null,
"foreignCapture": {
"reason": "visual acceptance of full Linux CI artifact 36111677966 for v1.78.0-beta.2 on Windows owner workstation"
},
"foreignCapture": null,
"witnesses": {
"count": 108,
"count": 126,
"floor": 10
},
"scenarios": {
"panel-wide-view-light-en": "2c93497871bc40f6401f6cb7e65e73bf98d0064bd2755ca2026135975bd2d9f3",
"panel-wide-plan-editor-dark-en": "9404d7e3aa84b4b40a8cfeed27eea8831e55e9e6236abbb7cb092c709ca0dfcd",
"isometric-stage6-light-lightfloor": "80ac5d4ed1535fbaa1420b5da1a35599cc8ef9a35e4fa723c89e25ef235f792e",
"isometric-stage6-light-darkfloor": "46e25d32a4e4f156586a327cdfbc6f59e79178733b6301c89843b8a8535a738a",
"isometric-stage6-dark-darkfloor": "5b76746846c13c2dbca9ade31d4e6cb891a9a486029ebcf4008539a4a92b47fc",
"isometric-stage6-dark-lightfloor": "157e0aaf68396b8ed5acc0d2caed935dc0d17c014c265cb2aaeda66944bba8c2",
"isometric-stage6-hover-light": "3f8dc5052e883a30e3cf89c1a0fdea0af3bae3c763b6ebaa50e41cba9cdb3bfd",
"stairs-flat-normal-light": "df6600545ffbc7611b764eb489e6e3f625ccf51b6ed2f8f83e4df5b7e1565d08",
"stairs-flat-hover-dark": "e642373ea6680a978219622105552d4afbf2311eebf101a7c8db280373c9eca3",
"stairs-flat-selected-light": "4b37e663d5689c3288ad7dc956b2ec0b42e406d418873c0ea6089c1caf92ec83",
"stairs-isometric-dark": "901bc1eee214d179aff13bc7f192a9e270688aa59b2744f3b361e653dc2d0af5",
"panel-wide-view-light-en": "275d09b3730361bb491a832fbb76723d90a7327086f042cbf0fdd79f4a002800",
"panel-wide-plan-editor-dark-en": "093a3fd1022755f5128261796e009383d7e7a23e9e50f193ab1987d30f9313b2",
"panel-narrow-read-only-empty-light-en": "99b8f1af82b1ad13f237125aab8f7e7da0563c138e9da2ddf06e54c2d56138c4",
"version-mismatch-desktop-dark-en": "b8059b768284208f605329618799d13491655e488e9d739a40b85cdc0eba5888",
"version-mismatch-touch-light-ru": "df42d0158025371b82eabd522e0dd7d0b9121a9ba685970db3d7839d97fce50f",
"version-mismatch-desktop-dark-en": "8981a02083caa27a163e9c59319bfdb3f55adf70ee319c9ac9a80739e08dd84e",
"version-mismatch-touch-light-ru": "855bb817ffd5628e8bdcc4e5e48367b140ff16e5040e674555fbb52c43d7867c",
"version-mismatch-kiosk-attempted-dark-de": "2e3eaf2fb13d1cca77abe97938374b3e25235fc7db87bb6f7a2e72f1cafe4a7e",
"hidden-wall-diagnostics-plan-light": "4113474796ccb1b7b8c876fcb463e97eb77bb1ec96129195a0dafdcc7ccca334",
"hidden-wall-diagnostics-plan-dark": "40c48868f0251aec3b1a14c0eef77a78cc7965420fb9b41f46f8190d1fdfd11a",
"hidden-wall-diagnostics-plan-light": "7bcb8a821b8e0712b1a6c9ad6f36965448db75575ffb02f5d5af0cd328d3c27f",
"hidden-wall-diagnostics-plan-dark": "a89b83c64cc7bf0fb4989eceaa4cd24297309221085603f676dc0398da344ecb",
"coincident-partition-before-dark": "32d6d7e32db1bb579b33c783833ce986d10a81589e0783c5e7fae4b8c1e2755e",
"coincident-partition-thin-dark": "a5bd00dfd6a085b81cf5e213b34669d60edf5c03451ec2943ce31c735497cf8c",
"coincident-partition-thick-dark": "848021570e9cdf98bbfa3e9035ac09fe70695ab6b48964f8f947e30ae5695141",
@@ -44,44 +74,44 @@
"split-corner-wall-thin-dark": "0a8da611ecb6061852cc26ee53c00d12433cf8539681bd91dac3adad1d7e9e67",
"split-corner-wall-thick-dark": "2efc65ddaa8777ebffe63205e2a961fa7053c91d3cd8c0af59810bef01ecd498",
"split-zero-divider-taper-dark": "f7f9ee435da00dcae043c693127561a5e050675ca4d6f6f0d916ac5dbe24a594",
"isometric-geometry-view-dark": "d02f39b803370d0b5b1ffe5c675591b0faf0797f0a34fcd9fee6e04bcc2f87c5",
"isometric-geometry-view-light": "aae5bb78d2d49be627d8cbc2ecd960ebeea7d35fe06f36eeef4674c49f136c4e",
"isometric-live-layers-dark": "17f41e1d18f7b16d824c437ec85c31e85300a97fc01a8a4200052f394e013d89",
"isometric-no-borders-dark": "ec8952f4bbd4011aed80cf66c9fa8345fe4f91fecfe0c4d9f33c5ae3e210805d",
"isometric-touch-kiosk-dark": "f478ac634e2367e2753fe2d44b516113425812e5724fd37c2b2ecb89b47c384b",
"isometric-large-warm-remount-dark": "354fc12ca28a58866d7ee58794485a87dcdc5887a6c0faa6afe36aaf4a4dd824",
"isometric-stage3-overlays-light": "a119a02a7e8755cb398f367a6fefeb6b4dceadd42d60453d11c5d3e83acae829",
"isometric-stage3-overlays-dark": "40355196c8578159e13aaa6b0086f6175a473f8f24ceefbde3e67791ffd26135",
"isometric-stage3-openings-dark": "702d1c159333a13483405f19bea5b0c580c717ac1c04428fdbaef6d97929c96c",
"isometric-stage3-forced-colors-dark": "79e0a916a3bec26c700a8eb280f161e6179685ea05ddf988b04bcb9e9c035747",
"isometric-stage3-no-filter-dark": "b0a9336db629283a61666e449cb1d70e537a6a3b85f4faf0a4da24e5877c8fe6",
"isometric-geometry-view-dark": "877cde0e1037071a23b3356141c1ddd1a74bcd33da5367ada2809ca6c3ee989b",
"isometric-geometry-view-light": "50164a84574bb81b860db94fd07cc5173eb676d4371deb90f3d2e16f5d252398",
"isometric-live-layers-dark": "1c3a54350b3bb07b4aa34bf853d22f12622616e1c8208f9d64c5e478c7d92bb2",
"isometric-no-borders-dark": "406fa7c7597724cba00df6451a1b81d5c306af0259cc9198e1985b6f325d619d",
"isometric-touch-kiosk-dark": "e88e95124300b723e6a3b4563961f7d178a2517f41f7b4a2c9fac3534a57c24f",
"isometric-large-warm-remount-dark": "d7932b8baea6b5689480a123698d562bcea2eb0d8536c3516272f5ebef4a4cc8",
"isometric-stage3-overlays-light": "3824e3c9901a53a19d2a5e069922e982bc2c8bdf21fc5ba6e8e6477bc2bb1f6d",
"isometric-stage3-overlays-dark": "adb042cf55b0c0e75412eea9167aec1df03e24cadb72e8e67d259927d687009b",
"isometric-stage3-openings-dark": "33c7c657229b44a3a52bbefe0197302d909b69dc0f1127352410d13f6d0d93f7",
"isometric-stage3-forced-colors-dark": "06675a2bf71d405c34dc430e1761f5cb78168d5b52207bb2ca7923f6dd8ce50b",
"isometric-stage3-no-filter-dark": "bdec8fce7628934e00a29b2c489226c8b0ca33b3e315fdd34c1f89a804dc125e",
"geometry-view-dark-fit": "454da1248f1fe13b8f60c589ae9ebee5e852995e9bc5175d3c5692c445f7cb94",
"geometry-view-light-fit": "de5f6a41552447ca909d20471ed6665f558fb6e111c1f333eeba55866517d5a5",
"pdf-export-geometry-light": "c403a141cefb73e4653f2ffb9c346b4cfaa3de339b6c39c227ef1552ebde0048",
"pdf-export-polish-light": "ed6685900355e20a4618e931d42d2e45a39ad44e4a2ab044fa5ee64ff56f12f4",
"pdf-export-stepped-dimensions-light": "b9732b8b9c38153c564d0c93fbfbf44a8dd348c8667d2f8c2d14492e08e04f3e",
"vacuum-trail-smoothing-dark": "f8eecc726a3e8bdaba414584ad6afd48908ee9d96ea969a1e37758f4e76843e2",
"room-label-parity-view-dark": "a188d4db84533d605cb6fe822d20c34d164e3bfc8458a55beaad564854eef169",
"room-label-parity-plan-dark": "07e896d4f6dad384bd52dc60f786f042e1cfcd26ddb1f7b12b72d40bb4824324",
"room-label-parity-view-light": "dd96a224c4d951720031ff1ba793a1ba560951e0441cb4f61ee82215aff1d8cc",
"room-label-parity-plan-light": "91b7aed42c3a677e387b26c405a04dcc327957f9368625c7c3ad3cca758f1fb1",
"room-label-parity-view-dark": "4c00ee3119d3886a5906d5880769558083eb06547a2e2c56e91ae84498416bc9",
"room-label-parity-plan-dark": "c0f08f586116c4070723ec89697aa5d966ea57f1c027e7740eeb5147d44d9958",
"room-label-parity-view-light": "ec2a1294b0f08019f49a392cdc94970c1545673ebe0d7f22652f602adf00d4e8",
"room-label-parity-plan-light": "2603d0c73b39fc72f8b31919fcee15d8b17ea3c6ba1dea74fc791efdd3cfafc6",
"washer-active-cycle-dark": "0d683619c27747b3dd6a1e6c7cdc5e15dd886f4f2db4b85b7ca5a5c529e9ec52",
"washer-idle-cycle-dark": "0138902daa8dabb0588904507ef5a44f5257f7859b4f1198191bfca3dd4130b7",
"day-cycle-dawn-dark": "96bd5ef84345976bb83d35d418e7d247503629eceb732b6e926f2217f3b9faa4",
"day-cycle-day-dark": "605849bbada8d3c3a02a2c88c7b21c81c675a7a89b72c4c560df635d63005e67",
"day-cycle-dusk-dark": "d19cc97b75014d9f0661705df50d5f30a3a574661957c04c5dab96e26c5e3364",
"day-cycle-night-dark": "39a6a959676295eb4ead02703533641f57901ad0ce1359933e1d47f48867196a",
"geometry-plan-editor-dark": "6fa29c51ac37449e53ff76afe17760a86325e160f53040c737619aae1095599d",
"safe-resize-handles-clamp-light": "4c2856edc3c2c8be740b052d747f144dd6f02e6737ef2df06880eb1a5ec634d4",
"safe-resize-handles-clamp-dark": "581f85ca93197ba796e72daa67402eebb7c64f13d4722b5d33943577837c8980",
"space-tab-drop-before-light": "2678c840a33ade95f8a038213a696132612cb9bae8290e467ae9c4ac2ee265e3",
"space-tab-drop-after-dark": "f4f3ab1bb986426f281753a101b4055fae1f5f1ed0c04c8d41071dfa260978f1",
"plan-snap-endpoint-light": "40f761e1a7a12e5113ec6e736c6d7176908d8a1585281c2c2f438bc1fee36e1d",
"plan-snap-line-gaps-dark": "e4e5e15a98e35ac26a5b0fe7ef674341fb55d8445587ccc25a81603e1ef92774",
"wall-junctions-plan-preview-light": "f6f450768912f37b3d91730cc38f43b399eab781e585bdbe6c9bb29458b56f4c",
"wall-junctions-plan-t-dark": "6c3748e931407079e3aad3ade77c46069ef75444c27405549d1610670a33ad95",
"geometry-plan-editor-dark": "5e7365ef5820a9c2ea51abd3be0c56900de7932e3da4f78fc8103f307a5fcf65",
"safe-resize-handles-clamp-light": "80b3cf9c755dbb77deceaf6deb4569bd1570093509b37e96beb32dd2a07d40da",
"safe-resize-handles-clamp-dark": "dd32a1034f349b27717a89516b91a85b916e70080cfeffb127b0b51a24683bff",
"space-tab-drop-before-light": "325225df7961482e81931989d1094ab232ca1afac8843febece2d2f124cff04b",
"space-tab-drop-after-dark": "c5b2e4089da3f1ddf22f8eb7cd224ddd12ca23680b3800d496f1d218706ed666",
"plan-snap-endpoint-light": "3b62f84beade690df3bf957cac80e7e1c66f2d30b2587d3c81419f617f23d31a",
"plan-snap-line-gaps-dark": "652277b925c0e4931094e323b14498ff0d44ed19abdde4e26f1e7a28256c5443",
"wall-junctions-plan-preview-light": "3d4b493e86ea3b702b326d2dd9db146170faf4ed3810eeb5f02dda8d50b2fd3e",
"wall-junctions-plan-t-dark": "395155d3ea4a432b05e81372dd0a039cb47cfe9355ff0fad7a7ab7b9c613bce8",
"wall-junctions-view-dark": "b3385dd544e87aeacbc85020621de96ea20feecccf907bc5de3d2c045860a1cd",
"junction-patch-resilience-plan-dark": "8ad87deb245097cd090f301d259710323cf4ea577cd8edaf16607d6ad8a3d29d",
"junction-patch-resilience-plan-dark": "e35fca8310d84fa0b4b5fb87641d0c3b550270a7fd8b735080d4d08d79801d1d",
"junction-patch-resilience-view-dark": "db72b14098dd26132521b71adc87cf5798e5d48faa1f449c294ca2df81077849",
"wall-union-isolation-view-light": "2fffbcef2001b24b4ce7d104f0457231044be7ee0718e7f31b6fc13b024f2bc9",
"wall-union-isolation-view-dark": "7dce51897ce87e96b7f43d62d68ac859aa662a2aa1227cc853bc86e5bbf321e4",
@@ -89,34 +119,34 @@
"orthogonal-strip-cell-5-view-dark": "267a3eab1f4ad7d8d484eb2a58b3e38031b0516ca8c46c93e272bfaf9ac409b8",
"orthogonal-strip-cell-1-view-dark": "7c9bab0789f9ec9019edf3c49176415af83e8a325e833f98a0e6324e7f06fe4c",
"wall-key-roundtrip-view-dark": "74fd46cffefd32d805296d844c162dbe6f15b13e86c4d6c7f6974293b253e1b4",
"isometric-wall-junctions-dark": "fe69b10e313298b9cffdd5bc6266e6e9314c4c3b4da85a22af277f31314cca1b",
"opening-placement-door-thick-wall-dark": "9d587eff0c2561ef5f84e9280cd812fa6e9af7cf6ff00c922d275796774ba513",
"opening-placement-passage-thick-wall-dark": "6ed48b39c0e11fbf4b0df7d3e4dad8078e5b17694f1a780936a864bebd0c1c57",
"opening-placement-passage-thick-wall-light": "3d4e3a5d9b67f2450fab9ccc0f5dc91f4485080582377de904f16ea1d62fbafa",
"isometric-wall-junctions-dark": "e6bf156d2afb70a631ceade26d6431caf92721a28a661fffd1654f49711fcb3e",
"opening-placement-door-thick-wall-dark": "cef2c75a6c60197c24a848fab7e47919437c751aae79ec4d88d744eb70abed43",
"opening-placement-passage-thick-wall-dark": "8d089364f6f9aace8a0cccad952731e6d295440b9eb1c8cb75b6e2264b8fa5c5",
"opening-placement-passage-thick-wall-light": "14c06cd119a476f8a4edb63db37d55c6d4000f6fbb3ed412cb88e225574896a6",
"opening-symbol-room-wall-light": "dc3f6513a08b809a77df6822f59fdb30c912f320e55d47d749963678fbb8f417",
"opening-symbol-diagonal-partition-dark": "fe264aa0a5926d904ea0a2d15e5f0567a536c9251a72bf96a287d7bf9c480aa1",
"opening-symbol-flip-pairs-light": "693fe97bae8375c6a16244fbb4ddaa1ed447732382a45d4629e1711e0aa0d45a",
"isometric-opening-symbol-parity-dark": "cad79850e1477f4a014cd2cf42c4d8d29e96e3cf3190afc6f3336841eff26bfb",
"geometry-devices-editor-dark": "e273c4fe49619a5856a9212ff6261803640e5afa79b841edfccf5a4056e0264d",
"device-inbox-desktop-en-light": "f90cdc008a7fdadb7fa859776089ce1f0667ef2f764d61e7bd31ae3f4c1033c1",
"device-inbox-desktop-ru-dark": "0c45e46ff313540c428ae57e70d0c56e43eeb7a13bf730ecb22ace7bb0b2788e",
"device-inbox-narrow-ru-dark": "7b760c9cf4bee09066ee6fde1dc90a93da97d9eaa96a12cee773b8fd596fe5d8",
"geometry-decor-editor-dark": "2f8a3742166c10c0a5aa1239c9b6b7d908e7e578dc43229274b17d015c47503c",
"tray-wide-selection-en": "5a535b457b8b274ff25085ee992aebd95d756415afe1321d0aadee70610d5801",
"tray-wide-tool-ru": "99abefe9c5b0f21356e87b46fc6d6d615cf9b799614c14bf8fffa0261fd9059a",
"tray-medium-group-en": "afdb651c687cabda48e62d426b9ef4926a23caa86904fe6fa2c163930070b2bd",
"tray-medium-selection-ru": "5d78f5d818514ab80a4fb2949274edffd372e0d2e41bc3ccf39a22fd7c993f6b",
"tray-narrow-palette-en": "100d7a87eebf6665bf67cdd53b7f23ade885827af8dc6cc7ae27cebc971b8fb9",
"furniture-categories-light": "36676ada243391e7b3b27974df5db1fd33da45508bdcb879b01be239521b830d",
"furniture-variants-dark": "7988bb13215657886a1e08ada67317f030c7f99d6d1f483b5d61cebc492cb46b",
"furniture-variants-light": "d025401492c808b11fc84e862d3154cd7ccd57095436488575d4b23f7b2debff",
"furniture-placement-preview-light": "eeaea99b86807fcf61960ce7c688d929721a3a3ee47867d564079d86d089f7e5",
"isometric-opening-symbol-parity-dark": "5dfcd219d9cdc41c382d6e436f15665c0a265ba2b7138b2856e1a50f0e252062",
"geometry-devices-editor-dark": "1cc18ac3250a0cc20981b090ef7f964555b237e085e3ddbe10cc7b6081342c8c",
"device-inbox-desktop-en-light": "3a1229f1f5134c329ee457a62e555896ffd664d1d6cbd217e4e8248dc0c07dc7",
"device-inbox-desktop-ru-dark": "2b0981e7cc134a8f2cfe107c58833e33b862a00a89cadc5033f3b633562f9379",
"device-inbox-narrow-ru-dark": "7694119b368baf49fe8c0aeeec46a75bd77c0987266e4a64f5d13a2099e5f098",
"geometry-decor-editor-dark": "0fddb5ba477ecad15827215d04d22e9ba1adfacbbfc853712b1e76832182fa5f",
"tray-wide-selection-en": "e544ee6b4cde64aae188bac9be9e52c1232a3de8e98e1f68af42d8c7064f0a38",
"tray-wide-tool-ru": "d3c49be18142f280c21062612cf06dc8df6e5a076c53aeb2d62da8e3975c8003",
"tray-medium-group-en": "6ba416e0ca3cb2fba4a420703318e62d8651053d76ae1936a37537ab486af4a8",
"tray-medium-selection-ru": "d5a82d474f75f438c06a340897a9e009e7df0c95c5ef07edd3952f0fe43a0f40",
"tray-narrow-palette-en": "d2147c10ec130bead031db16090b0638188a548163ca8dfbfa86e68e71865051",
"furniture-categories-light": "8ffc0d18edc76dad428190f7c041ce9fdf9c5cc71a8329116bc1c7ae040a7fd8",
"furniture-variants-dark": "368f863d070b8f2f02cf657731aab0e7d80bdf5a817193709bb2838d429ac4e1",
"furniture-variants-light": "a702b38b8bd42919a5ed8753b87489545eb6d6538726e5cc3cb856a586834974",
"furniture-placement-preview-light": "8a78103d7cdd2808496f6d73b8a65859c4e6b57ee53a0c18f4d90580945673ba",
"furniture-plan-art-dark": "e8d3ab381dd33e28c3c05271afeb66b110483e7fcf6e9c660d578c9ae3b4072f",
"furniture-new-symbols-light": "51535124c8a68cae621c4fbfc4398edd5bf820caaf6be9a5d2b5fdf962e0b628",
"furniture-corrected-art-light": "e7b62ae426c12fa4850429af9d92ca1dd03eedd583d7412112dd30bddf21a7d8",
"furniture-transform-light": "ca1db454c8ffe5eb9558bb08f29615b804a4f7b002d90bf51f010b2189d450af",
"furniture-transform-light": "ae0772d4a450646f24460a20016f42ea9dda78e7d696c27aee5387d23089f314",
"furniture-transform-dark": "04ccddce94dbd35bd3da7e5a40dc5f80adb189e3b5ed6e5e99142258558c207f",
"tray-narrow-tool-ru": "fa8f7125ea9f5c43f47ad090cf78ab970950a03dc5a9fa1c348c880ced7c0c4c",
"tray-narrow-tool-ru": "62765f588d404e3889f1bbc58e40446535e8b4f322fec3b5dae856c076273511",
"geometry-diagonal-45-opening-dark": "a083c9a5b39124f8f6fa8123562f1a5e24c884fa47c54c99786b24164e8bfa78",
"openings-thick-wall-dark": "10b672a9965a994928111911760c987b45c7166c60e6bb0cbe02b2c11c59c211",
"openings-filled-tunnel-dark": "87edc3afb2720f8b75deb02c2b2817bee9534b7908e4dab8e8fee26cc6daf99d",
@@ -170,36 +200,36 @@
"large-house-zoom-040-dark": "84f9e93b5fd25caaf031dc7758553c29459d4dda31e086b7d79e904c9b3d6230",
"large-house-zoom-250-dark": "fa901d097023320a599c1c80be9f9c65a0ce6245bb7182bc0f3d90d679668c17",
"large-house-warm-remount-dark": "6ed51a1c76cd7c62875eadafc169f7c6fc25604e64edabd9594a3664c886987d",
"device-dialog-desktop-en": "8e85d6f89bb3665e1588d3382f66c6c063d16a40a19472beec03f571899bf1ac",
"device-dialog-desktop-en": "f5d660f9244712f016c02c560a4bd301f3729edd4ca1e63b3d2cf0fab9c71802",
"device-dialog-mobile-ru": "2486302565fb8dadb7758384e592b059df45f6f8b92195738e5a0bf62758c713",
"device-dialog-desktop-de": "d743bf734ad12f2282e262048eeed97f90c3ca5de2af84bd4d7304794f143351",
"room-temperature-dialog-desktop-en": "dd42f01dec3f5c69bae9777c39abed5b8e9e3c076f0046779f9b8a65fec4a90c",
"device-dialog-desktop-de": "972d56b0a0307ad7a061175aa0f6147c3170fc64eadb88bafd038835fd2ce331",
"room-temperature-dialog-desktop-en": "cea714dbe38185ea3d936fdb7cb71a0205feb74687d3ade517aed28407abd4a9",
"room-temperature-dialog-mobile-ru": "5ca1c4f18eab3fb4c0667f76b0d0340df94f24e0457baca89234e0ef159f62d6",
"room-discard-dialog-mobile-ru": "9c85af0cdbd2842808ef5ac8fdb306f621a726b0ac77c1449722c85399b4cb3f",
"german-view-mobile-light": "8c079d1574a6fbec478eacc872a46800f4348a9ce6d4dc1b1a11f88df8688e19",
"toggle-entity-dialog-desktop-en": "d2bc74705c9687d11bd55d22140635c2fb55de6496c7e85dfbb67ac049483f55",
"german-view-mobile-light": "1c14ef6e97f0f7c616fe61ad72f4dfa7d94c605efc3d8b26931016c8c13a4282",
"toggle-entity-dialog-desktop-en": "075a714a4f013c99614c4941b1202d9ae411e6382f72cb0d0a98e0f669b3388b",
"toggle-entity-dialog-mobile-ru": "c5bf5077a33c1acfa5f744e0ab1c15ce6c2147bc3622a7e8754d972aeedb555b",
"device-help-popover-light-ru": "0c2f92e0da2546222e8f870cbdadc90775016f9b5f77ae5b5159b790b706b65e",
"decor-color-popover-mobile-ru": "de167d5a363758b26108f08f7e3f0ba7a44c8daf7461d1e54d719698d20ab208",
"decor-color-popover-desktop-en": "ce6e85df6417dc8912ff7119c9851a7e15c4f28ed4097aa21e973e9da8d1ca0e",
"general-color-popover-desktop-en": "9aca14f12cd35f51df24ee2af87d61dff5b6e75926439c054adcc334b1ea60d8",
"device-help-popover-light-ru": "7645a419f8214dacc6b97be894643d12b2a0486342e568b0b3ee42d74b8cf878",
"decor-color-popover-mobile-ru": "bdf90e0e265d41d032db948a737eeedd6e60cdacc9f85c9356aa809894654960",
"decor-color-popover-desktop-en": "46b38745da10d4467b510c5a257102395a797e0d3229e7ce8c89e4bbf3b937e7",
"general-color-popover-desktop-en": "b54ef50909c2e7b667d321457b17205d5de38dd39b219eef05bee5ef8fb6bef2",
"settings-help-zoom-200-en-light": "9d9cdc0d849afc821efdf4451be40a1d32ecdff06a91b1d517b46c3f45cbcff0",
"settings-help-zoom-200-ru-dark": "cb2a9f210b19057e26d42ca8975c3a888fb95de3f6cbd03515d1eccff39de387",
"support-desktop-empty-light-en": "ec60104cb98b646db22089c93f70f78c3a13cdb7001782b2981ce19800ce3cee",
"support-desktop-preview-dark-en": "37707b251c117eb381f7ddd6ad13a235be82c139a4d92dc07789a76cc425e946",
"support-phone-validation-light-ru": "1699dde634aa583ce6aaa30252f6cc01c9d822ff2718d0d77b179c7c38216ea2",
"support-phone-success-dark-en": "21146b3a221010cdc45e1a83c299395772a9f214550a296b78c5ecb72ddab332",
"support-relay-error-light-en": "e63e4e3aec111aa8044f6c99a8d3331801505a0024399293d8d5d53776346b76",
"support-tablet-preview-dark-ru": "969398341e722f54d56a14084d18c4943f644a98b7772222ee7b601d46ddd866",
"support-desktop-empty-light-en": "55ffd7a63b8e4d65b8cb695a7b166e0c4bb2e719ce925b8d28b8e8595897750d",
"support-desktop-preview-dark-en": "326c1b1ecbe71e0bdaed6445137ee4145418317bf5d5faf10faa297707fdb924",
"support-phone-validation-light-ru": "acc540f9459c9dc38981ccb70e75a5415f17c849d3840340648448884bd2ab31",
"support-phone-success-dark-en": "b2edd4415043d673d22dfe89ee16b9c190e7f3f9537371f01665e8e41444a00e",
"support-relay-error-light-en": "adf30f96a926a1730c9ad34d18b4234d9b19140f11d56c8fb7508976f366c11d",
"support-tablet-preview-dark-ru": "a69ba86bf069d9beba94eedf6cc3b385b026caeee66348e4ef42c1a072b6df6a",
"device-ripple-color-popover-mobile-ru": "8fc31025d32d87e5d3d3df896937d182577cd5169d520e7cf5fc267e7d080776",
"space-room-color-popover-desktop-ru": "031dcd90ca0f66e41876f87b76fc5241d1b1fd99135605b26ab8ce88dfb12111",
"backup-full-preview-desktop-en": "935adb062c28385a89d34716616406e2434d8cdf3daf9cb7abead13df3c6cb81",
"backup-plan-only-export-desktop-en": "b017b78e75968123011777c27550bf82da6e50267086339b963d8571e8dca679",
"backup-space-preview-mobile-ru": "5ee8c2651d9814be9d7c76b7692fcf65f4ce341fb4cde1fc3e854df4568994dc",
"optimize-preflight-dialog-dark-en": "be8c9ddaf4eb07d83594d836ea20f5fb0c142d3c0f53a5a5b32e69814df27ae8",
"optimize-preflight-dialog-light-ru": "75a42a6dfb8134d7030466a730bd32ed08b7f50a72d2e9e081f9a3d853898856",
"optimize-orphan-references-dark-en": "d8a33ed94935d3b09fd9317729dc014c38c9baf4ef425947365d8aab186c45c2",
"optimize-orphan-references-light-ru": "d68e22a3cd9b2f047dc017217593a9e1a9df79fb9014070890d4f2df52f38cc2",
"space-room-color-popover-desktop-ru": "0e4654978e004cb9242ccf4add8022b57459b6a8524f141160ae49aa46787fe9",
"backup-full-preview-desktop-en": "2a71dd52bce193a5ddca1ee0404f91754787206cbe01af0b0bfb3871016ed289",
"backup-plan-only-export-desktop-en": "d7ae8031e1147ee8d3d15f35d8cd5bc65aa24fc880ad2feafdd7c8713b76c0c5",
"backup-space-preview-mobile-ru": "363aaa313d4b1f6f0b2de66b497390ca876b69cb36dc022b135d33299482465f",
"optimize-preflight-dialog-dark-en": "09e2d582f6e9591ac7dc7f11520eed51127c06bf97221a1b5340b04227302a87",
"optimize-preflight-dialog-light-ru": "5b679e352bcdbef8437e6098aadd69b27ff8564fd4bb6d04050bd468f60a20f6",
"optimize-orphan-references-dark-en": "6a9ab05787bc789af6181e6f2abca19d3cf92ac166aac661ad8deb7c7a3c88bb",
"optimize-orphan-references-light-ru": "332e56edb151a5a820fc307d69ffd4060348978572e48704b0a2c14ba9802720",
"card-editor-invalid-default-floor-light-en": "dbf251ece014cfee252e0dcb2d7c00eda66aa4b5bd125e4c83e248d4319d24b3",
"card-editor-invalid-default-floor-dark-ru": "422b74bb391b87e383350247ef1bc1f10ca48c8f6fe418f75bde94e62e3b8bc0"
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Some files were not shown because too many files have changed in this diff Show More