# История изменений

> Русская версия [docs/CHANGELOG.md](CHANGELOG.md). Переведены записи начиная
> с v1.42.0 (2026-07-26); более ранние доступны только в английском файле.
>
> **Правило проекта:** оба файла пополняются в одном коммите с самим
> изменением — как и остальная документация (см. docs/STATUS.md).

## v1.53.1 — 2026-07-30

- **Фикс: поиск «что запускать» не показывал результатов.** Результаты были —
  список это прокручиваемый блок, и как flex-элемент внутри тела диалога он
  схлопывался в полоску 1px: 26 подходящих автоматизаций отрисовывались в
  ничто. Найдено владельцем через минуты после v1.53.0. Теперь список держит
  высоту; смок измеряет эту высоту, а не просто считает строки в DOM —
  именно поэтому он и пропустил сломанную сборку.

## v1.53.0 — 2026-07-30

**Нажатие может запускать вашу автоматизацию** (ТЗ владельца)

- **Новое действие по нажатию: «Запустить автоматизацию/скрипт/сцену».** Мы
  не можем знать каждое экзотическое устройство, но вы знаете, что должен
  делать тап по нему: выберите любую автоматизацию, скрипт или сцену из
  списка с поиском — тап её запустит (`automation.trigger`,
  `script.turn_on` или `scene.turn_on` по типу цели, с тостом «Запущено»).
  Скрипт — идиоматичное «действие» HA, поэтому предлагаются все три
  запускаемых типа: не нужны автоматизации-пустышки без триггера.
  Сохранение без цели блокируется; удалённая позже цель подсвечивается в
  диалоге и безопасно отвечает тостом при тапе.
- **Чекбокс «Спрашивать подтверждение»** — защита от случайных нажатий для
  любого действия, меняющего состояние: и toggle, и запуск, включая
  настенные выключатели с привязанными целями. Диалог собственный (Esc,
  клик мимо или «Отмена» — ничего не происходит), поэтому работает и на
  настенном планшете.
- **Шторы переключаются нативно:** cover и valve добавлены в домены
  card-wide toggle — с одним осознанным исключением: гаражные ворота, двери
  и калитки (классы garage/door/gate) остаются ВНЕ дефолтного переключения —
  случайный тап не должен открыть гараж. Явный per-device toggle остаётся
  осознанным выбором владельца, а замки и охранные панели, как и всегда,
  с плана неприкосновенны.

## v1.52.2 — 2026-07-29

**По ревью v1.52.1** (рантайм не менялся — качество теста и формулировок)

- Проверка регресса в редакторе плана целится ИМЕННО в лампу (HP-1521-01):
  старый ассерт принимал любой жёлтый значок, и горящая розетка из той же
  фикстуры прошла бы его даже без фикса лампы. Проверено мутацией: откат
  гейта v1.52.1 валит смок.
- Последняя формулировка «жёлтый в любом режиме» — пункт чек-листа и
  комментарий в _stateClass — приведена к фактическому контракту: состояние
  источника считается условием glow-пула, а жёлтая подложка показывается
  только там, где пятно не рисуется (HP-1521-02).

## v1.52.1 — 2026-07-29

**По ревью v1.52.0**

- **У горящей лампы всегда ровно один индикатор (HP-1520-01).** В редакторе
  плана glow-слой скрыт, но подавление жёлтого там всё равно действовало —
  горящая лампа не показывала ни пятна, ни подложки. Гейт подавления теперь
  равен фактической видимости слоя: где пятно не рисуется, жёлтая подложка
  возвращается.
- **Статичная карточка уважает размер и поворот маркера (HP-1513-01).** Один
  и тот же маркер рендерился на read-only карточке базовым и неповёрнутым.
  Теперь она зеркалит --dev-scale и угол полной карточки — только геометрия,
  по-прежнему схема.
- Документация догнала цветовой контракт v1.52.0 (HP-1520-02): ожидания
  RGB-окраски в TESTING/UX-MODES и устаревший комментарий в коде.

## v1.52.0 — 2026-07-29

**Один вид источников света, чем бы их ни переключали** (правило владельца)

- **Цвет лампы живёт только в её свечении.** RGB-окраска иконки, рамки и
  тени убрана: в зависимости от того, пришли ли с состоянием данные о
  цвете, одна и та же лампа попадала то в ветку «цветная иконка на тёмной
  подложке», то в «жёлтая подложка» — выключение одной лампы тапом и
  остальных выключателем выглядело по-разному. Ветки больше нет.
- **В режиме glow индикатор — само пятно света.** Подложка источника всегда
  стандартная, горит он или нет — всё говорит свет вокруг. Розетка,
  вентилятор или чайник остаются жёлтыми и в glow: они не светят, правило —
  только для источников.
- **В остальных режимах горящий источник — просто жёлтый**, как греющая
  термоголовка; RGB и обычные лампы одинаково.
- Морфинг иконки (контур «лампа с лучами») остаётся во всех режимах, а цвет
  пульсации по-прежнему берёт фолбэком цвет свечения лампы.

## v1.51.3 — 2026-07-29

- **Множитель «размер значка» масштабирует и сам глиф, а не только
  подложку.** Изменение размера устройства растило подложку, пульсацию и
  бейджи значений, но иконка внутри оставалась дефолтной — большая пустая
  рамка вокруг маленького глифа (репорт пользователя). Теперь глиф считается
  от того же поустройственного размера, что и всё остальное, и держит
  пропорцию при любом множителе и зуме.

## v1.51.2 — 2026-07-29

**По ревью v1.51.1**

- **Авто-сетка одинакова на обеих карточках (HP-1511-01).** Полная карточка
  резервирует клетки сетки за скрытыми устройствами (их призраки держат
  место в редакторе); статичная уплотняла сетку по одним видимым — свежее
  устройство без сохранённой позиции вставало на разных местах двух
  карточек. Теперь статичная передаёт сетке полный список и по-прежнему
  рисует только видимых.
- **Ripple-призрак сохраняет базовую иконку (HP-1511-02).** Скрытый маркер с
  отображением «пульсация» рендерился безликим неподвижным пульсом — в
  редакторе его нельзя было узнать. Призрак теперь полностью снимает
  «одежду» отображения: базовая иконка и имя, каким бы ни был режим.

## v1.51.1 — 2026-07-29

**По ревью v1.51.0**

- **Статичная карточка снова учитывает скрытые устройства в LQI комнаты
  (HP-1510-01).** Её фильтр видимости незаметно стал фильтром агрегации: одна
  и та же комната показывала разное Zigbee-здоровье на двух карточках. Теперь
  агрегация и отрисовка используют разные списки — скрытые устройства
  считаются в сигнале на обеих карточках, не рисуются ни на одной и
  по-прежнему не дают света.
- **Призрак не показывает живых чисел (HP-1510-02).** Скрытое устройство в
  «Показать скрытые» подавляло цвета состояний, но по-прежнему рисовало
  значение, температуру, влажность, бейдж LQI и морф иконки. Всё это убрано:
  у призрака только базовая иконка и имя — достаточно, чтобы узнать
  устройство и открыть диалог.

## v1.51.0 — 2026-07-29

**Скрытие — теперь явная галка** (docs/FILTERING.md)

- **В диалоге каждого устройства — включая виртуальные — есть галка «Скрыть
  устройство с плана».** Старый фильтр «на лету» остался только сеятелем
  этих галок: при первом открытии редактором конфиг один раз
  материализуется — нефизические устройства (мосты, сцены, служебные
  интеграции, лампы, свёрнутые в световую группу) получают галку, и дальше
  она принадлежит вам. Снятая галка — навсегда: сеятель не возвращается к
  устройству, о котором вы уже решили. Новые нефизические прячутся молча;
  физические — с красной точкой, как раньше.
- **«Показать все» стала «Показать скрытые»** — локальный инструмент
  редактора устройств (на настенных планшетах ничего не мигает): скрытые
  рисуются полупрозрачными СИНИМИ пунктирными призраками — не спутать с
  серым недоступным — и совсем без живой индикации: призрак — это
  конфигурация, а не статус. Клик — и галку можно снять. «Удалить с плана»
  у привязанных устройств убрана (галка — единственный путь); «Удалить» у
  виртуального по-прежнему удаляет.
- Скрытые устройства учитываются в Zigbee-сигнале комнаты, но не дают ни
  пятна света, ни заливки — не видно устройства, не видно и света. Климат
  комнат без изменений. Старые конфиги ведут себя по-старому, пока их не
  материализует клиент с правом записи.

**Жёлтый значит «работает прямо сейчас»**

- Один принцип для светящегося значка: лампа светит, розетка подаёт,
  вентилятор крутится, медиа играет, пылесос убирает — или термоголовка
  РЕАЛЬНО греет (hvac_action), а не просто включена на зиму. Раньше
  термоголовка могла светиться жёлтым из-за включённой защиты от накипи, а
  реально греющая оставалась тёмной: выбор главной сущности пропускал
  служебный свитч производителя вперёд видимого climate. Исправлено —
  служебная сущность больше никогда не побеждает видимую основную функцию
  (это чинит и tap-переключение, и морфинг иконок у таких устройств).
- Пятно света и цвет значка задают один и тот же вопрос: горящая лампа
  желтит значок в любом режиме заливки ровно тем условием, что зажигает её
  пятно. README (en+ru) описывает язык цветов.

**Редакторы на телефоне**

- Пинч-зум и панорамирование жестами работают во всех редакторах: рисование
  кликовое, так что они совместимы — палец с движением панорамирует, два
  пальца зумируют, отпускание после жеста не ставит точку, чистый тап —
  ставит.

**Кнопка настроек комнаты**

- Отвязана от (передвигаемого) названия: стоит в ВИЗУАЛЬНОМ центре комнаты —
  центр наибольшей вписанной окружности с тяготением к центроиду площади,
  поэтому вытянутая комната центрирует её по обеим осям, а Г-образная
  держит в середине широкой части и не утаскивает в узкую.
- Вдвое меньше прежней, размер от иконки устройства (70% бокса) и зумится
  ВМЕСТЕ с планом, а не держит постоянный экранный размер.
- Мелкие подписи под названием комнаты (температура, влажность, сигнал,
  свет) теперь видны и в редакторе плана, а название рендерится ровно в том
  же месте в просмотре и в редакторе.

## v1.50.4 — 2026-07-29

**По ревью v1.50.3**

- **Обе карточки строят модель одним кодом (HP-1503-01).** Полная карточка
  носила рукописную копию общего построителя модели, и копия не получила
  фолбэки для legacy-store из v1.50.3 — один и тот же битый store в
  статической карточке рендерился починенным, а в основной — пустым
  `viewBox="0 0 0 0"`. Дубликат удалён: полная карточка вызывает общий
  построитель и лишь подменяет сырой url плана, который нужен её подписи.
  Новый смок прогоняет точный вектор аудита через обе модели и оба
  DOM-дерева и проверяет паритет.

## v1.50.3 — 2026-07-29

**По ревью v1.50.2**

- **Размер — не координата (HP-1502-01).** Граница ±4 из v1.50.2 мерила все
  четыре элемента view_box и w/h комнат одинаково, поэтому `[0, 0, 0, 0]` и
  отрицательные размеры всё ещё проходили — а нулевая ось сериализуется в
  `viewBox="0 0 0 0"`: пустой план у всех клиентов, и `aspect-ratio: 0 / 0` у
  статичной карточки сверху. У размеров теперь свой validator: строго
  положительные, пол — одна тысячная холста; координаты по-прежнему могут
  быть отрицательными — начало кропа законно выходит за край. А поскольку в
  сторе битый viewport может уже лежать, обе карточки падают на полный холст
  вместо пустого экрана, и legacy-прямоугольник с отрицательным размером
  читается как тот же прямоугольник, нарисованный из другого угла.

**Также в этом релизе**

- Кнопка настроек комнаты переехала в самый низ карточки комнаты, а имя
  комнаты отображается ровно в одном и том же месте в просмотре и в
  редакторе плана — кнопка и метрики больше не участвуют в центрировании.

## v1.50.2 — 2026-07-29

**По ревью v1.50.1**

- **Величины геометрии ограничены на обоих слоях (HP-1501-01).** v1.50.1
  ограничила позиции устройств, но прямоугольники комнат, вершины полигонов,
  view_box и координаты проёмов всё ещё принимали любое конечное число — одна
  проходящая схему вершина 1e100 растягивала кадр так, что план становился
  точкой у всех клиентов, и сервер хранил это как вполне корректную
  конфигурацию. Теперь схема конфига ограничивает геометрию ±4 (углы ±360°),
  а рамка содержимого применяет к вершинам комнат тот же конверт холста, что
  уже применяла к позициям устройств — конфиг, где абсурдная координата уже
  лежит с прежних времён, всё равно рендерится: точка рисуется, где стоит,
  просто кадром больше не командует. Вершина чуть за краём холста работает
  как раньше.
- **Пустой repair больше не съедает бэкап (HP-1501-02).** Опечатка в space_id
  «успешно» отвечала moved: 0 — и её пустой результат заменял бэкап глубиной
  один, уничтожая единственный путь назад ровно тогда, когда он нужнее всего:
  сразу после починки не того пространства. Теперь «нечего чинить» — ошибка
  (`nothing_to_repair`): ничего не пишется, ревизия не растёт, предыдущий
  repair по-прежнему отменяем.

## v1.50.1 — 2026-07-29

**По ревью v1.50.0**

- **Карточка ниже другого контента дашборда снова получает сцену
  (HP-1500-02).** Замер высоты в v1.50.0 брал абсолютную координату документа:
  высокая карточка перед этой записывалась в «шапку», и сцена схлопывалась в
  ноль. Теперь меряется только собственная обвязка плюс ограниченная поправка
  на то, что дашборд держит сверху; перемер — по resize окна, слушатель
  снимается при демонтаже.
- **Рамка содержимого больше не бывает вырожденной или абсурдной
  (HP-1500-03).** Одинокий значок в пустом пространстве давал viewBox нулевой
  площади — пустую сцену; одна сохранённая координата вида 1e100 (любое
  конечное число проходило проверку) растягивала рамку так, что план
  становился точкой — у всех зрителей пространства. Почти нулевая ось теперь
  раскрывается до минимального кадра вокруг значка, точки далеко за холстом
  рамкой не командуют (рисуются, где стоят), а сервер отклоняет координаты вне
  ±4 — щедрый запас для значка, утащенного за край, но не для абсурда. Узкая
  комната сохраняет тесный кадр, датчик калитки чуть за краём по-прежнему
  учитывается.
- **Путь восстановления для установок, застрявших в окне миграции v1.48
  (HP-1500-01).** Если старая миграция упала между двумя записями, значки
  пространства остаются в старых координатах, и по данным это недоказуемо — а
  повторное преобразование правильных координат их испортит, поэтому ничего
  автоматического тут быть не может. Явный ответ —
  `houseplan/geometry/repair {space_id, aspect}`: `dry_run` показывает точные
  перемещения, прежние позиции уезжают той же записью хранилища как резервная
  копия глубиной один, `undo` их возвращает, и обычные перетаскивания эту
  копию больше не затирают. Протокол `geom_pending` из v1.50.0 уже защищает
  все будущие миграции; это — для тех, кому он опоздал.

## v1.50.0 — 2026-07-28

**Задачи владельца**

- **Масштаб по умолчанию считает устройства содержимым.** Им можно стоять вне
  комнат — датчик калитки у забора, камера на столбе — и стартовый вид теперь
  включает их, даже в пространстве совсем без комнат.
- **Вход в редактор больше не сдвигает план.** Высота сцены считалась как
  «экран минус 118px шапки», а шапка редактора выше: сцена уезжала вниз на
  разницу, низ пропадал за краём. Карточка измеряет, где сцена начинается на
  самом деле, и отдаёт ей остаток экрана.
- **Масштаб теперь и отдаляется.** До 0.4×; в отдалении план висит по центру,
  а не прилипает к углу.

**По ревью v1.49.0**

- **Миграция на квадратный холст переживает сбой между двумя записями
  (HP-1490-01).** Конфиг и позиции живут в разных хранилищах, пишутся по
  очереди, и первая запись удаляла именно те поля, которые нужны второй — сбой
  между ними навсегда оставлял значки в старых координатах. Теперь намерение
  миграции сохраняется до того, как что-либо меняется, и снимается той же
  записью, что сохраняет позиции: какая половина не успела — ту следующий
  запуск и доделает, ровно один раз.
- **Параллельные загрузки не проскакивают квоту вместе (HP-1490-02).** N
  загрузок мерили хранилище до того, как любая из них записала файл, и все
  проходили предел, под который помещалась одна. Замер и запись — один
  атомарный шаг под отдельным замком; отдельным — чтобы медленный обход папки
  не тормозил сохранения конфига.
- **Редакторы снова видят весь холст (HP-1490-03).** Рамка содержимого
  ограничивала и панорамирование, и координаты указателя, так что после первой
  комнаты рисовать вторую было негде. Режимы редактирования меряют от полного
  квадрата; просмотр остаётся по содержимому, а смена режима пересчитывает
  вид, вместо того чтобы тащить его прижатым не к той основе.
- **Сохранение ждёт пропорции выбранного плана (HP-1490-04).** Save до ответа
  картинки записывал пропорции ПРЕДЫДУЩЕГО файла, и новый план навсегда
  оставался в чужой форме. Выбор плана сразу стирает старое значение, Save
  дожидается ограниченного по времени чтения; не дождался — записывается
  «неизвестно»: честный квадрат лучше унаследованной формы.
- Гигиена релиза из §5: package-lock.json догнал версию пакета, задвоенный
  комментарий в space-geometry.ts убран.

## v1.49.0 — 2026-07-28

**Холст стал квадратным** (см. v1.48.0, выпущена вместе с этой).

- **Масштаб открывается по нарисованному, а не по всему холсту.** Пространство
  без подложки теперь вписывается по границам своих комнат с полями 5%: маленький
  план на большом холсте заполняет экран, а не сидит посередине точкой. С
  подложкой ничего не меняется — картинка и есть план, и обрезать её по комнатам
  значило бы спрятать то, что ещё не обведено.
- **Переключение пространств свайпом и в киоске стало с анимацией.** План
  уезжает туда, куда пошёл палец, следующий приходит с другой стороны.
  Уважает системную настройку «уменьшить движение».
- Кнопка настроек комнаты подписана «Настройки комнаты», а не просто «Комната»,
  и слегка светлеет под курсором.
- Слово «курирование» заменено на «фильтрацию» — в интерфейсе, документации и
  коде.

**По ревью v1.47.0**

- **Только что выбранный план больше нельзя удалить из того же диалога
  (HP-1470-02).** Он ещё не сохранён, поэтому сервер справедливо считал его
  свободным — а сохранение потом записывало ссылку, за которой нет файла.
  Кнопка заблокирована, и, поскольку два клиента могут сделать это в любом
  порядке, сервер сверяет с диском каждую внутреннюю ссылку, которую
  конфигурация добавляет, и отказывает, если файла нет. Ссылку, уже записанную
  в конфигурации, он пропускает: файл может исчезнуть и мимо Home Assistant, а
  отказ заблокировал бы ровно ту правку, которая его отцепляет. Чужие ссылки не
  трогаются.
- **Загрузки ограничены (HP-1470-01).** По возрасту не удаляется ничего — это
  дважды стоило настоящих планов, — поэтому предел стоит там, где решение и так
  принимается: загрузка отклоняется, если хранилище перевалит за 256 МБ или 200
  планов (1 ГБ и 1000 для вложений) либо если на диске останется меньше 512 МБ.
  Список планов отдаётся по 60 самых свежих, миниатюры грузятся лениво.
- **Выбор сохранённого плана читает его настоящие пропорции (HP-1470-03).**
  Карточка ничего не ждала и, если подпись для защищённой ссылки ещё не пришла,
  записывала пропорции «по умолчанию» — квадратный план получался растянутым.
  Теперь она дожидается подписи, привязывает результат к тому диалогу, который
  спрашивал, а превью в диалоге подписывается, как и всё остальное.

## v1.48.0 — 2026-07-28 (холст всегда квадратный)
- **У пространства больше нет собственных пропорций.** Область рисования —
  квадрат, а картинка плана сохраняет свою форму и вписывается в него по
  центру: у широкого плана появляются поля сверху и снизу, у вытянутого — по
  бокам. Выбирать нечего, поэтому настройка ориентации холста для пространств
  без картинки убрана.
- **Существующие планы переносятся один раз, при обновлении.** В самом рисунке
  ничего не меняется: коробка дополняется до квадрата, и все координаты
  пересчитываются относительно неё — комнаты, двери и окна, декор, позиции
  значков и сохранённая область просмотра. Углы, пропорции комнат и взаимное
  расположение сохраняются точно. Для вытянутых планов заодно пересчитывается
  масштаб в сантиметрах на клетку: сетка привязана к ширине, и без этого стены
  молча стали бы короче.

## v1.47.0 — 2026-07-28 (выбор из уже загруженных планов)
- **Диалог пространства показывает планы, сохранённые на сервере.** Отцепление
  плана оставляет картинку на диске — так с v1.46.4, но вернуть её можно было
  только найдя исходный файл у себя и загрузив заново. «Уже загруженные»
  показывают, что есть: миниатюра, размер файла и то, какое пространство его
  использует. Клик прикрепляет, пропорции читаются из самой картинки — так же,
  как при загрузке.
- **Там же план и удаляется.** Файл плана никогда не удаляется автоматически —
  ни за отцепление, ни за возраст, — и это разумная политика ровно до тех пор,
  пока видно, что именно хранится, и есть способ убрать это осознанно. Кнопка
  корзины делает это и отказывает, пока план используется пространством: ответ
  на вопрос «можно ли удалить» даёт сохранённая конфигурация, а не браузер.
- Документация догнала код: несколько комментариев всё ещё описывали удаление по
  возрасту, убранное в v1.46.6.

## v1.46.6 — 2026-07-28 (обещание про отцепление, теперь выполненное)
- **Переключение пространства в «нарисовать» больше не удаляет его картинку.**
  v1.46.4 и v1.46.5 утверждали, что не удаляет, и плановая уборка действительно
  отцеплённые планы не трогала — но само сохранение удаляло файл в тот момент,
  когда снималась ссылка, ещё до всех этих проверок. Причина: файл, покинувший
  конфигурацию, считался «заменённым», а по одной этой разнице замену плана,
  отцепление и удаление пространства различить невозможно. Удалением, о котором
  просили, является только первое. Теперь переход классифицируется по
  пространству-владельцу, и та же разница применяется к вложениям: убрали файл у
  существующего устройства — он удаляется, удалили устройство — его инструкции
  остаются.
- **План удалённого пространства сохраняется**, а не тридцать дней, как обещала
  v1.46.5: тридцать дней по возрасту файла всё равно бессмысленны — обычно он
  загружен месяцы назад.
- **По возрасту больше не удаляется ничего**, кроме промежуточной папки диалога.
  Правило, которое вычищало «отвергнутые загрузки», оказалось в гонке с
  повторной попыткой: уборка удаляла файл неудавшегося сохранения ровно тогда,
  когда следующая попытка коммитила ссылку на него. Правило, способное удалить
  файл, на который кто-то вот-вот сошлётся, не стоит освобождаемого места.
  Файлы уходят по действию, в остальных случаях остаются.

## v1.46.5 — 2026-07-28 (ревизия всех автоматических удалений)
- **Отцеплённый план не удаляется никогда, ни в каком возрасте.** В v1.46.4 ему
  давался месяц; теперь это навсегда, и причина записана там, где её увидит
  следующая правка. Правило, оно же в docs/SCOPE.md: компонент вправе удалить
  файл только тогда, когда об этом говорит действие пользователя — замена
  плана, удаление вложения, удаление устройства. «На это больше никто не
  ссылается» таким действием не является. Ошибки несимметричны: занятое зря
  место видно, стоит копейки и обратимо; удалённый файл — ничего из этого.
- **`houseplan/files/cleanup` больше не сносит папку по слову клиента.** После
  перепривязки устройства файлы копируются под новый id, а старая папка
  удалялась — через `rmtree`, по тому id, который прислала карточка. Два плохих
  исхода: при частичном копировании часть ссылок продолжает указывать внутрь
  этой папки (миграция намеренно их не переписывает — то есть это были живые
  ссылки на удаляемые файлы), а неверный или устаревший id от любого клиента
  уничтожил бы инструкции существующего устройства. Теперь сервер сам сверяется
  с сохранённой конфигурацией, под её блокировкой, и удаляет только то, на что
  никто не ссылается.
- **План удалённого пространства ждёт тридцать дней вместо часа.** Удаление
  пространства осознанно, но час — короткое окно, чтобы заметить промах.

## v1.46.4 — 2026-07-28 (потеря данных: отцеплённые планы убирались как мусор)
- **Отцеплённый план больше не удаляется через час.** Переключение пространства
  в режим «нарисовать» снимает ссылку и, как редактор всегда и говорил,
  оставляет картинку на диске, чтобы её можно было вернуть. Уборка, добавленная
  в v1.46.0, этой разницы не делала: считала «на файл сейчас никто не
  ссылается» синонимом «файл брошен» и применяла часовое правило. На установке
  автора плановый проход в итоге удалил два плана этажей, отцеплённых
  несколькими неделями раньше, — восстановить их было нечем. Если вы отцепляли
  план после v1.46.0 и инстанс перезапускался или проработал сутки — загляните в
  `config/houseplan/plans/` и напишите в Telegram-чат, если файла нет.
  Правило теперь такое: **коммит по-прежнему удаляет ровно то, что заменил**, —
  это он знает наверняка. Дальше вопрос в том, означает ли «непривязан»
  «брошен», и ответ зависит от случая. У пространства, у которого плана нет
  вовсе, его отцепили — и, возможно, вернут: его файлы не удаляются никогда. У
  пространства, у которого план есть, лишние файлы могут быть только его же
  отвергнутыми загрузками — они по-прежнему уходят через час. Вложения вне
  промежуточной папки диалога ждут месяц; сама промежуточная папка, где по
  построению лежит только загрузка из несохранённого диалога, сохраняет часовое
  правило.

## v1.46.3 — 2026-07-28 (перепроверка v1.46.2: HP-1462-01)
- **Уборка при старте действительно убирает.** Она искала свои же runtime-данные
  по домену, а во время запуска Home Assistant ещё не считает интеграцию
  загруженной — поэтому поиск возвращал пустоту, и проход тихо вырождался в
  удаление незавершённых передач, оставляя настоящую работу таймеру через
  24 часа. При перезапусках чаще, чем раз в сутки, она не выполнялась вообще.
  Теперь используется объект, который у неё и так был на руках.
- **Тест, который должен был это доказать, проходил по неверной причине.** Он
  создавал лишние файлы *до* сохранения конфигурации, а сохранение тоже
  собирает мусор — так что к моменту перезапуска убирать было уже нечего.
  Переписан: файлы создаются после сохранения; добавлен второй тест, который
  дёргает плановый таймер отдельно, и третий, который запускает перезапуск и
  сохранение одновременно и проверяет, что принятая конфигурация никогда не
  ссылается на удалённый уборкой файл.

## v1.46.2 — 2026-07-28 (перепроверка v1.46.1: HP-1461-01, -02)
- **Файл, который в итоге никому не понадобился, убирается, даже если больше
  ничего не сохраняют (HP-1461-01).** Сборка привязана к записи конфигурации —
  это верно для того, что запись вытесняет, но оставляет зазор: отмените диалог
  после того, как файл уже загрузился, потеряйте связь сразу после, или
  вызовите API загрузки напрямую — и на файл никто не ссылается, а будущей
  записи, которая бы это заметила, нет. Добавленное в v1.46.1 ежедневное
  подметание убирало только незавершённые передачи, поэтому обещание «отменённое
  вложение исчезнет через час» не выполнялось там, где никто ничего не правит.
  Теперь подметание сверяется с сохранённой конфигурацией — под той же
  блокировкой, что и запись, — и собирает устаревшие непривязанные вложения и
  планы тоже.
- **Перетаскивание больше не отменяется чужим перемещением (HP-1461-02).** Когда
  в v1.46.1 полная карточка научилась следить за позициями, она защищала те,
  что вы подвинули, но ещё не отправили, — вот только читала этот список *после*
  сброса отложенной записи, а сброс его первым делом опустошает. При настоящем
  перетаскивании, когда запись уже запланирована, список оказывался пустым, и
  старая серверная позиция закрашивала ваше движение. Теперь снимок снимается до
  сброса, и вдобавок удерживаются позиции, отправленные, но ещё не
  подтверждённые: пока сервер не подтвердил позицию, авторитет по ней — та
  карточка, которая её подвинула.
- Два теста доросли до своих же описаний: тест загрузки теперь действительно
  отменяет задачу запроса, а не только проходит по путям ошибок, а смоук
  синхронизации позиций планирует настоящую отложенную запись и задерживает её —
  именно этот порядок и терял перетаскивание.

## v1.46.1 — 2026-07-28 (перепроверка v1.46.0: HP-1460-01 … -03)
- **Две загрузки с одинаковым именем больше не сталкиваются (HP-1460-01).**
  v1.46.0 перестала затирать вложения, но выбор свободного имени и его занятие
  были двумя шагами: две загрузки, попавшие между ними, сходились на одном
  имени, обе рапортовали успех, и одни байты заменяли другие. Теперь имя
  занимается атомарно в момент выбора — двадцать одновременных загрузок
  `manual.pdf` дают двадцать файлов. Тот же механизм используется при переносе
  файлов на перепривязке, где был ровно такой же зазор.
  Заодно там же: имя предельной длины теряло расширение, а суффикс коллизии
  выталкивал его за предел, и вложение сохранялось под именем, которое сервер
  обратно не отдаёт — вечный 404 на файл, который интерфейс считал
  прикреплённым.
- **Прерванная загрузка больше не оставляет временный файл навсегда
  (HP-1460-02).** Уборка стояла в `except Exception`, мимо которого отменённый
  запрос проходит насквозь, а сборщик заглядывает только в папки маркеров —
  поэтому оборванная передача оставляла `.upload-*`, и убрать его было некому.
  Теперь убирает любой путь выхода, запрос с двумя файлами отклоняется сразу, а
  брошенные временные подметаются при старте и раз в сутки. Запись идёт
  порциями по мегабайту, а не отдельной задачей на каждые 64 КБ.
- **Две полные карточки рядом держат одинаковые позиции (HP-1460-03).** v1.46.0
  научила следить за перемещениями статическую карточку и оставила позади
  полную, поэтому перетаскивание иконки в одном окне не двигало её в другом до
  перезагрузки. Теперь следит — и не мешает собственному перетаскиванию: чужая
  ревизия, пришедшая в его разгар, сливается, а не накатывается сверху, и
  карточка не перечитывает то, что записала сама.

## v1.46.0 — 2026-07-28 (полный внешний аудит v1.45.4: HP-1454-01 … -10)

**Безопасность**

- **Загруженный SVG-план больше не является живым документом origin вашего
  Home Assistant (HP-1454-01, high — блокер релиза).** Внутри карточки план
  подключён через `<image>`, где скрипты не выполняются; но тот же URL,
  открытый напрямую, становился документом верхнего уровня в origin самого HA,
  и `<script>` в нём мог читать `localStorage` сессии и обращаться к API. Для
  загрузки нужно право записи, а оно по умолчанию есть у каждого
  аутентифицированного пользователя, и подписанную ссылку несложно передать
  администратору. Ответы с SVG теперь несут заголовок Content-Security-Policy
  `sandbox`, который помещает документ в opaque origin. Только SVG — CSP на PDF
  способен сломать встроенный просмотрщик браузера, а растровая картинка ничего
  выполнить не может. Для существующих планов ничего не меняется: карточка
  рисует их ровно как раньше.

**Целостность данных**

- **Инструкция, приложенная к устройству, больше не затирает предыдущую
  (HP-1454-02).** Загрузка писала прямо в `<маркер>/<имя файла>`, вне
  транзакции конфигурации: отмена диалога или отвергнутое сохранение оставляли
  сохранённую ссылку указывающей на новые байты. А все новые иконки грузили в
  одну общую папку, поэтому две с файлом `manual.pdf` начинали ссылаться на
  один физический файл. Теперь загрузка занимает свободное имя и никогда не
  перезаписывает, новая иконка получает собственную промежуточную папку, файлы
  из которой переезжают к настоящей иконке при принятом сохранении, а загрузка,
  которую никто не сохранил, убирается через час. Имя, на которое уходит
  коллизия, сменилось с `manual (2).pdf` на `manual-2.pdf`: старое санитайзилось
  на обратном пути, поэтому переименованное вложение записывалось и больше не
  отдавалось (нашёл новый тест; до этого релиза так же ломались коллизии при
  перепривязке).
- **Две быстрые правки больше не теряют вторую (HP-1454-03).** Debounce
  разносил старты сохранения, а не сами сохранения. Если одно длилось дольше
  полусекунды — занятый сервер, медленная связь, — следующая правка уходила с
  той же ревизией, сервер принимал первую и отклонял вторую, а обработчик
  конфликта перечитывал серверную копию поверх локальной. Правка исчезала, и
  сообщение винило «другое окно», которого не было. Записи сериализованы: по
  одной за раз, каждая с ревизией, которую вернула предыдущая.

**Корректность и пределы**

- **Открытые границы снова следуют за геометрией (HP-1454-04).** Их кэш
  ключевался только по id комнат и связям, поэтому смена пропорций
  пространства или перетаскивание вершины оставляли открытые границы — и свет,
  который через них проходит, — в старых координатах до перезагрузки. Ключом
  стала сама отрисованная модель, из которой они и вычисляются.
- **Конфигурацию больше нельзя сделать сколь угодно тяжёлой (HP-1454-05).**
  Внешние коллекции были ограничены, вложенные — нет. Полигон на 150 000 точек
  или список из 100 000 идентификаторов проходили валидацию, а потом каждый
  рендер по ним ходил. Появились пределы на вершины полигона, связи открытых
  границ, управляемые сущности, вложения, длину текста и ссылок, плюс общий
  предел размера сериализованной конфигурации. Устаревшее поле `segments`
  отбрасывает сервер, а не надежда на современный клиент.
- **Большие файлы передаются потоком, а не через память (HP-1454-06).**
  Инструкция на 50 МБ целиком читалась в память на приёме и ещё раз на отдаче;
  пара параллельных скачиваний — заметная нагрузка на слабый хост Home
  Assistant. Загрузка пишется во временный файл потоком, отдача идёт с диска.

**Согласованность**

- **Статическая карточка пространства учитывает настройки заливки комнаты
  (HP-1454-07).** Она строит модель другой функцией, и настройки комнаты в неё
  не переносились — комната, которой вы выключили заливку, всё равно
  закрашивалась.
- **Перемещение иконки сразу видно на статической карточке (HP-1454-08).**
  Layout — отдельное состояние без ревизии и без события: перетаскивание на
  полной карточке оставляло соседнюю статическую со старой позицией до
  изменения конфигурации или перезагрузки страницы. Теперь записи layout хранят
  ревизию, возвращают её и сообщают о себе — заодно оптимистическая блокировка
  на полной записи layout начала что-то значить, ведь точечная запись раньше
  сбрасывала счётчик.
- **Предупреждение о пропавшем плане исчезает вместе с пространством
  (HP-1454-09).** Уборка смотрела только на существующие пространства, поэтому
  удаление или переименование оставляло предупреждение в «Ремонте» навсегда.
- Сборка: `serialize-javascript` поднят выше двух advisory (HP-1454-10). В
  рантайме недостижим, production-зависимости и так были чисты, но это одна
  строка.

## v1.45.4 — 2026-07-28 (ревью v1.45.3: R5-1, R5-2)
- **Частично успешный ответ на подпись больше не пропускает выдержку (R5-1).**
  Бэкенд подписывает каждый путь независимо: тот, что подписать не удалось,
  логируется, пропускается, и вызов всё равно завершается успешно с остальными
  ссылками. Карточка считала любой успешный вызов «весь батч готов», сбрасывала
  выдержку для всех путей в нём и записывала только вернувшиеся ссылки — и путь,
  который бэкенд стабильно пропускал, запрашивался заново на каждом рендере, то
  есть ровно то усиление, ради которого выдержка и вводилась в v1.45.2. Теперь
  путь считается подписанным, только если в ответе действительно есть ссылка на
  него; остальные уходят в выдержку по отдельности, ключи, которых не просили,
  игнорируются, а перерисовка запускается лишь при появлении хотя бы одной новой
  подписи.
- **Снимок состояния больше не противоречит репозиторию (R5-2).** Там всё ещё
  было написано, что в `main` лежат релизы только до v1.40.1, и приводились
  счётчики тестов на несколько релизов назад — при том что строка версии рядом
  исправно обновлялась. Читающий его человек или агент получал неверную
  модель веток и заниженное представление о покрытии. Роли веток описаны точно,
  а счётчики убраны: `npm run inventory` печатает их из дерева, и устаревать
  больше нечему.

## v1.45.3 — 2026-07-27
- **«Значение вместо иконки» невозможно было сохранить (issue #3).** Опция
  появилась в редакторе устройств ещё в v1.26.0, но серверная схема всё это
  время принимала только `badge`, `ripple` и `icon_ripple`. При её выборе
  сохранение падало с
  `not a valid value for dictionary value @ data['config']['markers'][n]['display']`,
  а поскольку один отвергнутый маркер валит всю запись конфигурации, план не
  сохранялся вообще, пока настройку не отменишь. Спасибо @RemyRoux за отчёт и
  точный текст ошибки.
- **Списки опций теперь в одном месте и сверяются между языками.**
  `DISPLAY_MODES`, `TAP_ACTIONS`, `SPACE_FILL_MODES` и `ROOM_FILL_MODES`
  экспортируются из карточки, и backend-тест читает их, проверяя, что схема
  принимает каждое значение, которое пользователь реально может выбрать.
  Теперь добавить опцию в редактор и забыть про схему — значит уронить тесты, а
  не узнать об этом из чужого сообщения об ошибке.

## v1.45.2 — 2026-07-27 (закалка по ревью v1.45.1: R4-1, R4-2)
- **Сбой уборки больше не превращает принятое сохранение в ошибку (R4-1).**
  Сборка вытесненных файлов плана идёт уже после того, как конфигурация
  сохранена, но ошибка при обходе каталога — он может исчезнуть или стать
  недоступным между проверкой и обходом — вылетала наружу из `config/set`.
  Клиент видел неудачу для ревизии, которую сервер закоммитил, а его повтор
  возвращался с конфликтом. Теперь сборщик сообщает «ничего не убрано» вместо
  исключения, а `config/set` пишет в лог и продолжает: событие уходит, новая
  ревизия возвращается.
- **Один запрос подписи на ссылку вместо одного на рендер (R4-2).** Множество
  ожидающих очищалось в момент отправки батча, а не по возвращении, поэтому
  пока запрос `content/sign` был в полёте, каждая перерисовка ставила ещё
  один — шесть вызовов там, где нужен один, и куда хуже на медленном (а не
  просто занятом) сокете. Состояния «в очереди» и «в полёте» теперь разделены,
  после ошибки включается выдержка (2 с с удвоением до 60 с) вместо повтора на
  следующем кадре, а запрос, который так и не завершился, перестаёт блокировать
  повторы через 15 с. Поздний ответ, пришедший после размонтирования карточки,
  больше не вызывает перерисовку.
- Тесты: восемь юнит-тестов подписывателя с ручным разрешением promise (четыре
  падают на v1.45.1), backend-тест на то, что сломанный сборщик оставляет
  сохранение успешным с рабочей ревизией, и проверка исчезнувшего каталога в
  тестах чистого сборщика.

## v1.45.1 — 2026-07-27 (повторное ревью v1.45.0: R3-1, R3-2)
- **Уборка старых файлов плана перенесена внутрь транзакции конфига (R3-1,
  high).** v1.45.0 сделала загрузку безопасной, но отдала удаление клиенту:
  после успешного сохранения карточка просила бэкенд убрать всё, кроме только
  что закоммиченного файла. Два открытых редактора невозможно упорядочить —
  задержавшийся запрос одного клиента удалял план, который только что сохранил
  другой, и принятая конфигурация оставалась со ссылкой в пустоту, то есть
  ровно с тем ущербом, ради которого вводился copy-on-write. Команда
  `houseplan/plan/cleanup` убрана. Теперь `config/set` убирает сам, под своей
  блокировкой, сравнивая конфигурацию, которую заменил, с той, которую принял:
  файл, на который ссылалась старая ревизия и не ссылается новая, удаляется, а
  любая другая непривязанная загрузка не трогается, пока ей не исполнится час —
  свежая может принадлежать ещё не завершённой чужой транзакции.
- **Статическая карточка пространства снова показывает подложку (R3-2).** Она
  подписывала URL и выбрасывала результат — `getCardSize()` правил временную
  модель, а `render()` строил свою заново из конфига, — поэтому `<image>`
  запрашивал защищённый путь и на каждом рендере получал 401. Обе карточки
  теперь используют один подписыватель, и статическая заодно получила батчи,
  учёт срока годности и периодическое переподписывание, которые были только у
  основной. Её множество ожидающих запросов освобождается в `finally`, так что
  одна неудача больше не блокирует ссылку до конца жизни страницы.
- Новые тесты: пять backend-сценариев чередования двух клиентов из отчёта
  (поздний коммит, незакоммиченная загрузка, устаревший сирота, чужие файлы,
  отвергнутое сохранение), сборщик вынесен в чистый модуль и покрыт юнит-
  тестами, плюс `smoke_space_card_bg` на подписанный фон — он падает на
  v1.45.0, где в DOM попадает сырой URL.

## 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-сенсор, привязанный к помещению).
- Тот же раздел настроек появляется в диалоге комнаты сразу после замыкания
  контура.
