mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
R2-1 (high): plan replacement committed filesystem state before the config CAS.
The upload wrote the final name and unlinked the other extension, so a rejected
config write left the live plan already replaced — or the stored config
pointing at a deleted file. Uploads now go to <space>.<token>.<ext> and delete
nothing; houseplan/plan/cleanup runs only after the config write is accepted.
The '.' separator is load-bearing: a space id cannot contain one, so cleaning
'f1' can never reach the files of 'f1-attic'.
R2-2: the backend signs at most MAX_SIGN_PATHS (200) per request and ignores
the rest silently, while the card sent its whole cache in one call and trusted
any cached entry forever — past 200 attachments the later ones stopped being
refreshed and expired for good. Requests are chunked to the shared constant,
entries carry their issue time (aging urls keep rendering while a replacement
is fetched, expired ones are dropped), and the cache is pruned to urls the live
config still references.
R2-3: areaClimate() rescanned the whole registry per room and per measurement.
areaClimateMap() classifies once and returns Map<area,{temp,hum}>, memoized on
hass identity so fresh states are always observed. Smoke measurement: 133
registry scans per update with 44 rooms before, 2 after, flat in room count.
Also: smoke_ux_fixes wrote its screenshot to a hard-coded /tmp path and could
not run on Windows.
Tests: smoke_plan_upload_reject, smoke_sign_cap, smoke_climate_once (all fail
on v1.44.8), three backend tests for versioned plan names and cleanup scoping,
unit tests for chunk/referencedContentUrls and areaClimateMap.
Docs: CHANGELOG.md + CHANGELOG.ru.md + ARCHITECTURE.md + TESTING.md + STATUS.md.
32 KiB
Executable File
32 KiB
Executable File
История изменений
Русская версия docs/CHANGELOG.md. Переведены записи начиная с v1.42.0 (2026-07-26); более ранние доступны только в английском файле.
Правило проекта: оба файла пополняются в одном коммите с самим изменением — как и остальная документация (см. docs/STATUS.md).
v1.45.0 — 2026-07-27 (внешнее ревью v1.44.8: R2-1, R2-2, R2-3)
- Отвергнутое сохранение больше не может испортить рабочий план (R2-1,
high). Файл плана записывался под финальным именем — попутно удаляя вариант
с другим расширением — до проверки ревизии конфига. Если запись потом
отвергалась (конфликт ревизий, валидация, обрыв связи), живой план оказывался
уже подменён, а сохранённый конфиг мог ссылаться на удалённый файл. Теперь
загрузка идёт в версионированное имя (
<space>.<токен>.<ext>) и ничего не удаляется; карточка просит бэкенд убрать устаревшие файлы только после принятой записи конфига. Падение между шагами оставляет один осиротевший файл, который подберёт следующая успешная загрузка. - Подписанные ссылки больше не протухают навсегда на долгоживущих экранах (R2-2). Бэкенд подписывает не более 200 путей за запрос и молча отбрасывает остальные, а карточка отправляла весь кэш одним вызовом и считала любую запись годной вечно. Начиная с 201-го вложения поздние ссылки переставали обновляться и через 24 часа тихо ломались. Теперь запросы бьются на батчи по общему лимиту, записи помнят свой возраст (стареющая ссылка работает, пока едет замена, протухшая не отдаётся вовсе), а кэш чистится до ссылок, на которые конфиг всё ещё ссылается.
- Климат комнат считается один раз на обновление, а не на каждую комнату (R2-3). Каждая комната запрашивала температуру и влажность по отдельности, и каждый запрос заново обходил весь реестр сущностей: 60 комнат и 2000 сущностей — это ~120 обходов на рендер, целый кадр на метаданные, которые не менялись. Теперь один проход строит карту по всем зонам с привязкой к снимку Home Assistant: свежие состояния видны всегда, а посторонние перерисовки не стоят ничего. Замер в смоуке: 133 обхода реестра на обновление до, 2 после — и число больше не растёт с числом комнат.
smoke_ux_fixesписал скриншот по жёстко зашитому пути/tmpи не запускался на Windows — теперь берёт временную папку у ОС.- Новые тесты:
smoke_plan_upload_reject(чистка только после принятого сохранения),smoke_sign_cap(201 ссылка, батчи, чистка, срок годности),smoke_climate_once(число обходов не растёт с числом комнат), плюс backend-покрытие версионированных имён и юнит-тесты новых хелперов.
v1.44.8 — 2026-07-27
- Загруженная подложка действительно привязывается к пространству.
_saveSpaceDialogдержал ссылку на объект пространства черезawaitзагрузки картинки. Любое событиеhouseplan_config_updatedзапускает_reloadConfigOnly(), а оно заменяет_serverCfg— ссылка становилась осиротевшей, иplan_url,aspect, заголовок и все настройки отображения писались в отсоединённый объект, тогда как на сервер уходил нетронутый конфиг. Файл попадал на диск, подложка не появлялась, пересохранение не помогало. Создание пространства в этот момент теряло пространство целиком. Теперь загрузка идёт до обращения к конфигу, и ни одна ссылка не живёт через await. _saveConfigNowпомечает запись как выполняющуюся (_cfgWriting) — так же, как отложенный писатель, — поэтому чужая ревизия, пришедшая посреди сохранения, откладывает перечитывание вместо подмены конфига (аудит L2, расширен на этот путь).- Регрессионный тест
demo/smoke_plan_upload_race.mjsпадает на v1.44.7 и проходит здесь.
v1.44.7 — 2026-07-27
- Подложки снова отображаются (регрессия с v1.44.5). Эндпоинт с файлами
требует авторизации, поэтому карточка просит бэкенд подписать ссылку на план —
но подпись подставлялась внутри мемоизированной модели пространства, а она
кэшируется по отпечатку конфига. Неподписанная ссылка «замерзала» в кэше,
подпись до элемента
<image>не доезжала, и план не грузился никогда. Теперь ссылка вычисляется в момент отрисовки, вне кэша. (Ссылки на PDF не страдали — там она и так вычислялась при отрисовке.) - Больше нет «неудачной попытки входа» с собственного IP. Пока подложка была сломана, браузер продолжал дёргать неподписанный путь, тот отвечал 401, и Home Assistant поднимал предупреждение о неудачном входе с адреса самого зрителя. Теперь до получения подписи не рисуется ничего, и неподписанный запрос не уходит вовсе.
- Долгоживущие экраны не моргают. Переподписывание раз в 12 часов раньше сбрасывало все подписи и ждало новые; теперь текущие ссылки держатся до прихода замены, так что настенный планшет не показывает пустой план.
- Регрессионный тест
demo/smoke_plan_signed.mjsпадает на v1.44.6 и проходит здесь.
v1.44.6 — 2026-07-27
- Климатом комнаты считается только температура воздуха. После v1.44.5,
когда данные стали браться из реестра зон, а не с видимых значков,
кандидатами стали все скрытые датчики температуры в зоне — в том числе те,
что меряют вовсе не воздух. Перед усреднением теперь работают три фильтра:
пропускаются сущности с категорией диагностика/настройка, сущности
исключённых интеграций и сущности, в id которых назван не-воздушный носитель
(
water,coolant,flow_temp,return_temp,target,setpoint,chip,cpu,processor,board,device_temp,batter,freezer,fridge,oven,kettle,boiler). На живой установке с 60 зонами это убрало четыре реальных ложных срабатывания: температуру процессора NAS, воду в умном чайнике, сауну с 90 °C и виртуальныйbetter_thermostat, дублирующий настоящий датчик. - Новые правила иконок: чайники и термопоты получают
mdi:kettle, сауны (sauna,harvia,парная) —mdi:hot-tub. Раньше и те и другие попадали под общее правило термометра, из-за чего и учитывались в климате комнаты.
v1.44.5 — 2026-07-27
- Климат комнаты считается по всем датчикам зоны, включая устройства, которых нет на плане (скрыты курированием или вами). Раньше среднее бралось только по видимым значкам, поэтому скрытый термометр молча выпадал из карточки комнаты, подсказки и температурной заливки. Курирование сохранено (холодильники, термоголовки и розетки с температурой чипа не считаются), а явно выбранный источник в настройках комнаты по-прежнему главнее.
- Из подсказки к комнате убрана фраза «открыть зону» — клик по комнате перестал никуда вести ещё в v1.40.1, для перехода есть значок-ссылка у названия.
v1.44.4 — 2026-07-27 (доработка по аудиту: B2, B5, L4)
- Единая политика авторизации (B2). HTTP-загрузка по-прежнему разрешала
запись, когда запись о конфигурации недоступна, тогда как WebSocket-путь уже
отказывал — они разошлись. Теперь оба вызывают общий помощник
may_write, который в неопределённой ситуации пропускает только администраторов. - NaN/Infinity отвергаются во всех координатах (B5). Проверка на конечность
числа стояла только у позиций раскладки; прямоугольники комнат, вершины
полигонов,
view_boxи координаты проёмов принимали"NaN", который при записи превращается вnullи необратимо портит геометрию. ОграничениеMAX_OPENINGSбыло объявлено, но нигде не использовалось — список проёмов оставался безразмерным. - Укрепление перетаскивания (часть L4). Безопасная обёртка над
setPointerCaptureтеперь используется во всех сценариях перетаскивания (устройства, подписи, изменение размера), а не только у проёмов — «мёртвый» идентификатор указателя мог оборвать перетаскивание. Фигуры декора получили ограничение по границам: их больше нельзя утащить далеко за пределы плана и сохранить там.
v1.44.3 — 2026-07-27 (исправление: планы и инструкции снова загружаются)
- У аутентифицированной выдачи контента не было рабочего пути для браузера.
Версия v1.43.0 закрыла дыру правильно, но Home Assistant аутентифицирует
HTTP-запросы либо заголовком Bearer, либо подписанным путём
authSig— а<image href>внутри SVG и обычная ссылка<a href>не отправляют ни того, ни другого. На настоящем дашборде фоны планов и ссылки на PDF отдавали 401 (воспроизведено вживую до исправления). Теперь карточка просит бэкенд подписать то, что собирается показать (houseplan/content/sign, 24 часа, привязано к токену сессии, только для нашего эндпоинта), перерисовывается, когда подписи приходят, и обновляет их каждые 12 часов, чтобы настенные планшеты продолжали работать. Тест бэкенда скачивает подписанный адрес без заголовка авторизации и проверяет 200, а без подписи — 401.
v1.44.2 — 2026-07-27 (внешнее код-ревью: CR-1…CR-3)
Второе, состязательное ревью (версии v1.44.0) дало три находки — все закрыты.
- Правило про замки теперь сформулировано точно и проверяется (CR-1).
Рецензент справедливо отметил, что утверждение «замок нельзя открыть с плана»
было слишком абсолютным: кнопка в карточке двери действительно вызывает
сервис. Эта кнопка — осознанное продуктовое решение, поэтому правило
переписано там, где ему место («никогда случайным нажатием; ровно одна
подписанная поверхность»), отпирание теперь спрашивает подтверждение, а
новый смок-тест проверяет все пять путей управления и доказывает, что значки,
controls[]и карточка устройства по-прежнему отказывают замкам. - Перенос вложений стал транзакционным (CR-2). При смене привязки маркера
файлы раньше ПЕРЕМЕЩАЛИСЬ до сохранения конфига с проверкой ревизии: если
сохранение отклонялось, на сервере оставались старые ссылки, а файлы уже
уехали. Теперь сервер копирует, конфиг фиксируется, и только после этого
старая папка удаляется (
houseplan/files/cleanup). - Неудачный или частичный перенос больше не переписывает ссылки (CR-3). Копирование возвращает точное соответствие «исходное имя → записанное»; переписываются только подтверждённые копии, при совпадении имён файл получает уникальное имя вместо молчаливой ссылки на чужой файл, а ошибка переноса показывается тостом.
v1.44.1 — 2026-07-27
- Ссылка на чат сообщества добавлена везде, где её ищут: https://t.me/ha_houseplan (бейдж и строка в шапке обоих README, раздел «Помощь и обмен опытом», контакт-ссылки в шаблонах issue, CONTRIBUTING, STATUS и SCOPE).
v1.44.0 — 2026-07-27 (отзыв пользователя: сначала управление)
- Карточка устройства стала поверхностью управления. Она открывается со списка управляемых сущностей: лампы, розетки и вентиляторы переключаются прямо здесь кнопками под палец, шторы, замки и климат передают управление в штатный more-info Home Assistant. Модель, ссылки и PDF-инструкции ушли ниже — на настенном планшете эта карточка нужна для управления домом, а не для чтения документации (из полевого отзыва). Служебные (config/diagnostic) сущности в списке не показываются, замки по-прежнему не переключаются нажатием в карточке.
- «Это устройство — источник света» — новый флаг у устройства. Умный выключатель с обычными (не умными) светильниками теперь даёт ореол в заливке «Свет по источникам» без создания хелпера-группы: свечение следует за самим выключателем либо за лампами, привязанными в «Управляет источниками света».
v1.43.3 — 2026-07-27 (отзыв пользователя: обнаруживаемость и тач)
- Настройки комнаты невозможно было найти. Шестерёнка из v1.42.0 жила внутри подписи комнаты размером 0.9em от её шрифта и с прозрачностью 60% — несколько бледных пикселей на обычном плане. Теперь это кнопка-пилюля «⚙ Комната» фиксированного читаемого размера, не зависящая от масштаба карточки, и она появляется даже у комнат без имени (их там и называют). Заодно разблокировались слайдеры размеров шрифта, до которых никто не мог добраться.
- Строка показателей увеличена с 0.62 до 0.75 от размера названия — автор отзыва мог увеличить название, но строка датчиков оставалась нечитаемой на планшете. Множители комнаты и пространства работают поверх.
- Тултипы на тач-устройствах, вторая попытка. Проверки
(hover: none)оказалось мало: некоторые устройства, оболочки, стилусы и подключённые мыши сообщаютhover: hover, и подсказки продолжали висеть под пальцем. Теперь карточка запоминает первое же касание (touch/pen) и гасит открытую подсказку.
v1.43.2 — 2026-07-27 (внешний аудит: слой тестов)
- Смок-тесты наконец умеют падать (T1). Все 48 браузерных смоков печатали
булевы значения и всегда завершались с кодом 0 — регрессия была видна в их
собственном выводе, а прогон считался успешным.
demo/serve.mjsтеперь экспортируетcheck/checkAll/finish: каждый факт проверяется по имени, несовпадения и необработанные исключения внутри карточки дают ненулевой код возврата. Проверено намеренной поломкой блокировки редакторов в киоске — соответствующий смок покраснел. - Набор гоняется в CI (T2) отдельной джобой
smokeпослеfrontend, против свежесобранного бандла (закоммиченная копия вdemo/srv/assets— снимок, на нём легко получить «зелёный» отчёт о несуществующем коде), с выгрузкой логов при падении. docs/TESTING.mdприведён в соответствие (T3).[auto]теперь означает «существует именованная проверка, которая падает», и рядом написано, где она; 72 пункта, где автоматизация была намерением, честно помечены[manual]. Исправлены два давних противоречия: строка про «ноль кнопок редактирования в Просмотре» (неверна с v1.30.1) и строка про клик по проёму (снова верна с v1.43.1).- Три смока проверяли поведение, которого уже нет (ожидания времён v1.39.0 и v1.25); теперь они тестируют текущий контракт.
v1.43.1 — 2026-07-27 (внешний аудит: исправления P1)
- Стоимость отрисовки (L1). Home Assistant подменяет объект
hassпри любом изменении состояния в доме, и каждая такая отрисовка пересчитывала всю геометрию плана —_openPairs()вызывался по разу на комнату (кубическая математика коллинеарных наложений), модель пространства строилась дважды. Теперь и то, и другое мемоизируется по структурному отпечатку конфига и вынесено из цикла по комнатам; сброс кэша происходит синхронно в момент мутации, а не внутри дебаунса. - Тап против перетаскивания у проёмов (L4). У перетаскивания двери или окна не было порога движения, поэтому любое дрожание пальца считалось перетаскиванием: диалог свойств не открывался, а в конфиг писалось неизменённое состояние (что подпитывало гонку L2). Теперь порог 3 px, как во всех остальных сценариях перетаскивания, и запись только при реальном изменении геометрии.
- Вогнутые комнаты (G2). Вложенность определялась через среднее арифметическое
вершин — а оно лежит СНАРУЖИ U- и L-образных комнат, поэтому комнаты-острова
в них отвергались как пересечение, а дырка в заливке не рисовалась. Теперь
вычисляется настоящая внутренняя точка (
interiorPoint). - Дедупликация стен (G3).
segKeyсортировал концы по сырым float, а печатал округлённые, поэтому одна общая стена могла дать два ключа и рисовалась дважды. Сначала округление, потом сортировка. - Укрепление бэкенда (B2–B5). Проверка прав на запись теперь отказывает,
когда запись о конфигурации недоступна (раньше во время перезагрузки
интеграции запись разрешалась);
layout/setподдерживаетexpected_revи сообщает о конфликте так же, как хранилище конфига;config/setбезexpected_revповерх непустого хранилища пишет предупреждение в лог; координаты отвергают NaN/Infinity, а у пространств, комнат, маркеров, декора и раскладки появились щедрые ограничения размера.
v1.43.0 — 2026-07-27 (внешний аудит: исправления P0)
Внешний аудит кода версии v1.41.1 нашёл четыре критические проблемы. Все четыре исправлены и покрыты регрессионными тестами.
- Молчаливая потеря правок при сохранении (L2). Отложенная запись конфига
читала его в момент срабатывания, поэтому пришедшее в промежутке событие
houseplan_config_updatedподменяло конфиг, и правка пользователя исчезала без единой ошибки — воспроизводилось даже в одной вкладке. Теперь дебаунс умеетflush()/pending(), перезагрузка сперва дописывает отложенную запись и откладывается, пока запись в полёте, а неудачная перезагрузка наконец сообщает о себе вместо молчания. - Разрез разрушал геометрию комнаты (G1). Разрез, начинающийся и заканчивающийся на ОДНОЙ стене (вырезание ниши — совершенно естественное действие), давал две самопересекающиеся комнаты, суммарная площадь которых вдвое превышала исходную, и проверка пересечений это не ловила. Теперь такие разрезы корректно вырезают нишу, а инвариант разбиения (части в сумме дают исходную площадь) отклоняет всё остальное.
- Планы и загруженные файлы отдавались без авторизации (B1). Любой, кто мог достучаться до вашего Home Assistant, скачивал планы этажей и вложенные инструкции без входа в систему. Теперь их отдаёт аутентифицированный обработчик; сохранённые старые адреса переписываются на чтении, так что ничего не ломается. Старые публичные пути исчезают только после перезапуска Home Assistant.
- Диалоги могли воскреснуть и обнулить карточку (L3). Закрытие диалога во время неудачного сохранения превращало его состояние в пустую «оболочку», отрисовщик падал, и карточка оставалась пустой до перезагрузки страницы. Защита добавлена во все четыре процедуры сохранения, тост об ошибке по-прежнему показывается.
v1.42.2 — 2026-07-26
- На тач-устройствах подсказки при наведении больше не выскакивают при каждом касании (из отзыва: «на планшете при тапе вылезают доп. надписи — мешают»). Подсказки теперь только для мыши; на тач та же информация есть в карточках комнат и в карточке устройства по долгому нажатию.
v1.42.1 — 2026-07-26 (размеры шрифтов карточек комнат)
- Закрываем отзыв «нельзя настроить размер шрифта»: три слайдера. В настройках пространства появился базовый размер шрифта карточек комнат для всего пространства; в настройках комнаты — независимые размеры названия и строки показателей (50–300% каждый). Эффекты перемножаются и складываются с растягиванием карточки за уголки и множителем экрана в киоск-режиме.
- В обоих диалогах показывается живой пример карточки, который меняется прямо во время перетаскивания слайдеров.
v1.42.0 — 2026-07-26 (настройки комнаты — третий уровень)
- У настроек теперь четыре уровня: общие → пространство → комната → устройство; более конкретный уровень переопределяет более общий (решение владельца, зафиксировано в ARCHITECTURE). В этом релизе добавлен уровень КОМНАТЫ.
- У каждой карточки комнаты в редакторе плана появилась шестерёнка: переименовать комнату, сменить её зону HA, переопределить тип заливки только для этой комнаты (работает и в glow-пространствах — «без заливки» выводит комнату из темноты) и выбрать явный источник температуры и влажности — любое устройство или сущность HA вместо среднего по комнате. Источник питает карточку комнаты, всплывающую подсказку и температурную заливку и работает даже у комнат без зоны HA (случай из отзыва: собственный template-сенсор, привязанный к помещению).
- Тот же раздел настроек появляется в диалоге комнаты сразу после замыкания контура.