Перечисляются только реальные пересечения конечных границ, новые границы обрабатываются инкрементально, а дробные координаты дают только необходимые floor/ceil-соседи.
Issue: #585
User-Visible: no
Закрывает находки код-ревью r1 по #598.
M1 — неприкосновенный смок восстановлен. `demo/smoke_tap_run.mjs` вернулся
побайтово к версии `origin/dev`: он один из девяти `.srcrow`-смоков, которые
ТЗ объявило неизменяемыми, и правка в нём обесценивала собственное
доказательство AC2/AC3. Проверки карточек переехали в новый
`demo/smoke_settings_dialog_cards.mjs` — отдельного свидетеля ТЗ не запрещало.
M2 — сегментированный переключатель сделан там, где он обещан и уместен:
`space-bg-mode` и `space-zero-wall-style` переведены из `<select>` в
`segmented`. Остальные выборы в трёх диалогах остаются списками осознанно:
привязка маркера, роль света и режим свечения уже `role="radiogroup"`
(`.markerradios`), позиция значения закреплена смоком
`smoke_device_preview_parity`, списки сущностей и тем длинные. Сокращение
объявлено в ТЗ и в обоих changelog.
M3 — мутант `state-callout-hidden-under-help` патчит теперь оба состояния:
`marker.run_target_gone` и `gs.sun_missing`; гард — новый смок.
Заодно: `ensureFormKitStyles(this.host)` не вызывался ни в одном из трёх
диалогов — стили набора приезжали только из диалога комнаты, и без него
сегменты имели высоту 14 px вместо 44. Нашёл новый смок проверкой
`bgSegmentTargetIsBigEnough`.
Гейты: typecheck, test (2788 pass), build, bundle:sync, три смока,
mutation-gate --check, оба мутанта задачи.
Issue: #598
User-Visible: yes
«Общие настройки», «Пространство» и «Устройство на плане» были плоскими
столбцами. В «Общих» девять подписей-заголовков шли вперемешку с рядами и
абзацами; в «Устройстве» на 760 строк формы не было ни одного деления, и
«Название» лежало в одном списке с «Дополнительными действиями».
Теперь это карточки общего набора: семь в «Общих», четыре в «Пространстве»,
пять в «Устройстве». Набор взят как есть — он поднят в #594 и доказан на диалоге
комнаты и редакторе панели (#597).
Шесть пояснений переехали под «?» к своему заголовку, подзаголовку или строке;
текст не переписан. Два сообщения о СОСТОЯНИИ остались на виду — решение
владельца: `gs.sun_missing` и `marker.run_target_gone` говорят, что настройка
сломана прямо сейчас, а второе ещё и называет пропавшую цель подстановкой,
которой в статической подсказке взяться неоткуда.
`gs.hint` при этом переехал из ленивого support-словаря в основной: «?» читает
текст через `_t`, а не `supportT`. Тест мёртвых ключей сливает оба словаря, так
что копия в support удалена во всех четырёх локалях.
Классы-опоры сохранены намеренно: `.srcrow`, `.dispsection`, `.gsrow`,
`.colorrow`, `.namein`, `.areasel`, `.tempin` остались на своих элементах, а
карточки добавились вокруг. За эти имена держатся одиннадцать смоков, и потеря
любого превратила бы ревью в чтение переписанных свидетелей.
Изменён ровно один смок — `smoke_general_settings`, и изменён в сторону
строгости: вместо одного списка подписей он снимает теперь два (заголовки
карточек и подзаголовки внутри), требует непустоты каждой карточки, проверяет
что радиус свечения лежит ВНУТРИ карточки свечения (подпись такой границей не
была), что четыре переехавших пояснения доступны кнопкой и что их абзацев в
форме не осталось.
`src/houseplan-onboarding-runtime.ts` затронут двумя строками: он второй
потребитель переименованных ключей `space.hide_*_tip` → `space.hide_*.help`.
Один текст — один ключ; дублировать его ради неприкосновенности файла хуже.
Потолок стартового графа поднят 291 400 → 292 500. Выросли СТРОКИ, а не код:
12 новых ключей в четырёх локалях плюс переехавший `gs.hint`. Разметка диалогов
как была в ленивом редакторском графе, так там и осталась.
Свидетели: `state-callout-hidden-under-help` прячет сообщение о состоянии под
«?» и краснит новый блок `smoke_tap_run`; `dialog-card-loses-its-heading`
обнуляет заголовок карточки и краснит `smoke_general_settings` — карточка без
заголовка это снова плоский список, только с рамкой.
Issue: #598
User-Visible: yes
Набор контролов был поднят из редактора боковой панели в #594, но сама панель
осталась на своей копии тех же правил. Две копии расходятся молча: правка в
наборе не доедет до панели, и наоборот.
Просто вызвать `formKitCss` из панели нельзя. Общие правила в её листе не идут
подряд: между карточкой-группой и обводкой фокуса стоит сетка `.summary-general-grid`,
между фокусом и выключенным состоянием — `button { font: inherit }`, между ним и
рядом-переключателем — `svg`. Вставить один общий кусок значило бы переставить её
объявления, а перестановка правил равной специфичности — уже не подстановка:
проверять пришлось бы каждое правило по отдельности.
Поэтому генератор разрезан на пять именованных фрагментов — карточка-группа,
обводка фокуса, выключенное состояние, ряд-переключатель, подпись ряда, — и
панель подставляет каждый на своё место. `formKitCss` остаётся их композицией,
чтобы у диалогов не появилось пяти вызовов вместо одного.
Доказательство вместо обещания. У настроек панели нет ни одного golden-кадра: в
матрице нет сцены `dialog: 'summary'`, и «панель не изменилась» показать
пикселями нечем. Замена эталону — побайтовое сравнение собранного листа с
замороженным снимком с `origin/dev`:
длина было 14585 стало 14585 — ПОБАЙТОВО СОВПАЛО
Две находки ревью ТЗ закрыты тестами, а не формулировками:
- проверка «копии не осталось» ловила один фрагмент из пяти (`min-height: 54px`
встречается только в ряду-переключателе). Теперь цикл по всем пяти текстам;
проба — фрагмент карточки-группы, оставленный в комментарии, — краснит именно
этот цикл, а прежняя проверка была бы зелёной;
- «выход `formKitCss` остаётся байт в байт» опиралось на тесты, сравнивающие
подстроки и счётчики. Добавлены две фикстуры с полным текстом обеих форм листа
диалогов — с рядом-переключателем и без. Форма с переключателем сегодня не
используется никем, и её расхождение не поймал бы никто до первого потребителя.
Три замороженные фикстуры — осознанный долг: они удаляются на том шаге эпика,
который законно меняет вид панели. Записано рядом с фикстурой.
Issue: #597
User-Visible: no
Та же находка, что Medium 1 ревью, но в коде: заголовочный комментарий
`src/furniture-art-runtime.ts` до сих пор говорил «The 44 designer symbols carry
~10 KB gzip». После пакета 0.4.0 их 60, чанк весит 16 943 B gzip по манифесту
собранного бандла, а главное — исчез союз с примитивами, поэтому абзац про
`fallback` описывает поведение всей библиотеки, а не 48 предметов из 60.
Это же место читает всякий, кто разбирается, почему при отказе чанка на плане
ничего нет: раньше двенадцать предметов рисовались всегда, теперь — никто, и
сказать об этом надо там, где живёт механизм.
Бандл пересобран в трёх копиях (правка комментария меняет содержимое чанка и
его хеш).
Issue: #593
User-Visible: no
Встроенная библиотека мебели была смешанной: 44 предмета рисовал дизайнер, а
12 — код, прямоугольниками и эллипсами в unit box. Они выбивались из стиля, и
три категории меню (`computer`, `hood`, `oven`) вообще не открывались, потому
что вида сверху для них не было.
Пакет 0.4.0 перерисовывает все 56 существующих публичных ID и добавляет четыре
новых: `computer`, `hood`, `oven`, `cactus`. Каталог становится равен поставке:
60 символов, 32 непустые категории.
Совместимость. Ни один сохранённый план не мигрирует: положение, размер,
поворот, отражение, цвет, прозрачность и толщина линии не трогаются, а
default-размеры всех 56 прежних ID совпадают со старыми до числа — таблица
зашита в `test/furniture.test.mjs`, чтобы сдвиг было видно тестом, а не глазами.
Ловушка, ради которой примитивы удалены целиком. Библиотека собиралась союзом
каталога и `LEGACY_FURNITURE`, и обе половины несли ОДНИ И ТЕ ЖЕ публичные ID.
`BY_ID` — это `Map`, она оставляет последнюю запись, поэтому примитив молча
побеждал дизайнерский рисунок, который его заменял: задача выглядит выполненной,
12 предметов не обновились, ни один существующий тест этого не видит. Поэтому
`LEGACY_FURNITURE`, `RETAINED_IDS`, тип `Prim`, `box()` и `primitivePathD`
удалены, а на дубликаты в каталоге стоит тест и мутант.
Два продуктовых решения владельца (#593):
- `cactus` живёт в категории `plant`, а не `exercise`, как предлагал пакет:
плитка «Тренажёр» рисует тренажёр, и открывать её на кактус — врать о
содержимом палитры. `exercise` остаётся скрытой пустой категорией, непустых
становится 32, а не 33;
- при отказе ленивого чанка с рисунками не рисуется НИЧЕГО. Прежде 12
примитивов рисовались всегда, и `docs/FURNITURE.md` это обещал; обещание
снято. Это единственное место, где задача ухудшает наблюдаемое поведение, и
оно проверяется смоком, а не словом.
Бюджеты. Ревью ТЗ показало, что «бюджет ленивого графа защищён» было
заявлением без гейта: размеры печатались в отчёт и ни с чем не сравнивались.
Добавлены `lazyGraphCeilingViolation` и два настоящих потолка со своим мутантом.
Стартовый граф ушёл ниже полосы, поэтому `INITIAL_VIEW_GZIP_CEILING` опущен
292 400 → 291 400 — вторая половина храповика, без которой выигрыш отыграется.
Провенанс. Документ пакета воссоздан под 0.4.0: грант владельца, имя архива и
его SHA-256. Это единственная исполнимая защита от повторения истории #159, и
она не ослаблена — число проверок в тесте не уменьшилось, а мутант подменяет
хеш и обязан её покраснить.
Свидетели: пять новых мутантов, новая golden-сцена `furniture-new-symbols-light`
для четырёх новых ID, матрица 63 → 64. Шестой мутант правится, а не добавляется:
`furniture-placement-needs-art` ссылался на удалённое поле `g` и переставал
компилироваться — раннер честно отличал это от «пойман» и отчитывался
setup-failure. Ложь мутанта осталась прежней по смыслу, убрана только ветка про
примитив.
i18n: 7 ключей × 4 локали. `furn.cat_exercise` не добавлен — категория скрыта.
Issue: #593
User-Visible: yes
Validate вернул задачу с красным `no-new-any`: одна строка в переработанном
диалоге комнаты, `this.host._t(k as any)`. Послабление переноса (#592) её
справедливо не покрыло — вокруг строки код действительно изменился, а блок
короче порога переносом не считается.
Причина `any` была в форме данных: пара «значение, ключ» собиралась кортежами,
и ключ, полученный конкатенацией, терял литеральный тип. Теперь варианты
заливки объявлены явно — режим остаётся значением `_roomFill`, а ключ
проверяется компилятором как настоящий `I18nKey`. Приведение не понадобилось
вовсе: долг не обоснован, а снят.
Issue: #594
User-Visible: no
Шаг 1 эпика #591. Набор поднят из редактора боковой панели — он сделан по
макетам дизайнера и уже живёт в продукте, — а не собран заново по прототипу.
Стили набора выдаёт генератор, параметризованный именами: контейнер формы,
префикс классов, карточки, футер и токены цвета. Это не украшение архитектуры,
а условие переезда панели на общий источник: тест с замороженной фикстурой
показывает, что с параметрами панели генератор отдаёт её нынешние правила
дословно — карточку, обводку фокуса, выключенное состояние и ряд-переключатель.
После этого переезд станет подстановкой, а не редизайном, и пиксели панели не
дрогнут. Сама панель в этом коммите не тронута.
Диалог комнаты собран в четыре карточки: «Основное», «Заливка», «Источники»,
«Размеры шрифтов». Порядок полей прежний, записи прежние — те же ключи
черновика, те же условия «Сохранить», тот же `hp-color-opacity` с атомарной
записью цвета и прозрачности. Пояснения уехали под «?» у заголовков групп
(решение владельца 18.09), выбор источника стал сегментированным
переключателем — под капотом это по-прежнему радиогруппа, поэтому стрелки и
скринридер работают как раньше.
Два измерения определили форму решения, и оба стоило сделать до кода.
Первое: лист набора в синхронном графе дал +1459 Б gzip и пробил потолок
initial View на 415 Б. Диалоги — инструмент администратора, во View их не
открывают, поэтому лист уехал в ленивый редакторский граф и вносится в теневой
корень при открытии диалога — тем же способом, которым это делает боковая
панель. Потолок не поднимался.
Второе: `css` с интерполяцией запрещён минификатором сборки (он намеренно
падает, чтобы не зависеть от рантайма), поэтому лист собирается `unsafeCSS`.
Цена — несжатые пробелы генератора, замерено 59 Б gzip; копия литералом стоила
бы расхождения с генератором, то есть единого источника.
Оставшийся рост initial View — 291 356 → 291 872 Б (+516) — это четыре текста
подсказок в двух встроенных локалях. Тексты сокращены до одного предложения:
подсказка под «?» и должна быть короткой.
`_renderRoomSource` переехал в модуль комнаты вместе со стабом карточки: у него
остался ровно один потребитель.
Issue: #594
User-Visible: yes
Шаг 0 эпика #591. Видимых изменений нет — это подготовка: оба ядра стояли в
трёх строках от жёсткого потолка, а разметка диалогов была крупнейшей
перемещаемой частью редакторского рантайма.
Перенос механический и проверяемый. Тела всех четырёх методов уехали
побайтово: тип `this` объявлен параметром функции, поэтому ни одна строка
разметки, ни один обработчик и ни один якорь мутанта не переписаны, а в классе
остались однострочные делегаты. Сверка при подготовке коммита: 43 617, 16 795,
10 590 и 6 719 символов совпали посимвольно с оригиналом.
src/editors/marker-dialog.ts диалог устройства на плане
src/editors/space-settings-dialog.ts диалог пространства
src/editors/general-settings-dialog.ts общие настройки
src/editors/room-settings-dialog.ts настройки комнаты
Имена файлов пространства и комнаты уточнены против ТЗ: рядом уже живёт
`src/space-dialog.ts` с логикой того же диалога, и два `space-dialog.ts` в
одном дереве путали бы обоих — и человека, и grep.
Попутно из рантайма уехали два словаря ярлыков режима отображения (их
единственный потребитель — диалог устройства) и границы шага сетки: последние
теперь объявлены в модуле, который рисует поле, а кламп записи импортирует их
оттуда. Направление импорта то же, что у самой функции рисования, цикла нет.
`_radarSetup` стал публичным: диалог устройства обращается к контроллеру из
своего модуля, а остальной класс и так живёт на `public _x`.
Потолок ядра опущен 14100 → 12810 — ровно на 1290 вынесенных строк, запас над
фактом прежний. Незафиксированный выигрыш ядро отыграло бы первой же правкой.
Сборщик логического исходника для контрактных тестов (`test/houseplan-source.mjs`)
научен подставлять тело вынесенной функции вместо делегата: шесть тестов,
читающих разметку, продолжают видеть ровно то же, что и до переноса, — иначе
их пришлось бы переучивать на новые пути в тот самый момент, когда они должны
доказать, что перенос ничего не изменил.
Issue: #592
User-Visible: no
Пятый вариант «Отображение»: маркер показывает выбранное значение и при этом
никогда не меняет цвет — ни по состоянию, ни по тревоге, ни по недоступности,
ни под цвет RGB-света, ни по активности.
Четыре прежних режима задавали одним выбором две независимые вещи: что
нарисовано внутри маркера и красится ли он состоянием. Поэтому вместо
сравнения с одним токеном появились два производных предиката рядом с
`normalizeDeviceDisplay` — `displayWantsValue` и `displayIsNeutral`, — и их
спрашивают политика, слой пульсации, внешний бейдж, редактор и три ветки
живого слоя пылесоса.
Две ловушки, из-за которых режим не сводится к одной строке в словаре:
- быстрый путь «статичному маркеру источники не нужны» (`sourceDetails: false`)
— это основной путь рендера плана, карточки пространства и PDF. Он оставлен
только режиму без значения: иначе число пропало бы именно на плане и
осталось в предпросмотре редактора;
- три ветки живого пылесоса сравнивают режим строкой и не читают политику,
поэтому зелёная политика их не гарантирует. Свидетель держит две
конфигурации карты: сопоставленная доказывает puck и след, несопоставленная
— бейдж маршрута (при совпадающей калибровке маршрут `ready`, и бейджа не
было бы ни в одном режиме). Бейдж проверяется при наполненном буфере
позиций, иначе его отсутствие объяснялось бы первой веткой.
Потолок initial View перецентрирован 291_700 → 292_400 без изменения общего
бюджета: измеренный факт 291 346 Б оставлял под прежним центром 354 Б —
внутри шумовой полосы метрики.
Эталон: в `device-icon-state-table-{light,dark}` включённая RGB-лампа
переведена в новый режим. Кадры обязаны разойтись; приёмка — отдельным
коммитом класса D с полного линуксового артефакта Validate.
Issue: #588
User-Visible: yes
Аудит 14.09, §4 и §9. `golden-report.json` не нёс платформу вовсе, поэтому
приёмщик вызывал `captureEnvironment()` у себя и записывал СВОЮ платформу как
платформу кадров. На `ad4000f9` это дало `"platform": "win32"` у кадров,
снятых Linux-прогоном 34853080375, а причина осознанного обхода осталась в
stdout и в индекс не попала — хотя `AGENTS.md` обещает след в обоих местах.
Сами PNG были и остаются целы: 101 свидетель совпал байт-в-байт. Врал
провенанс.
Что сделано:
- отчёт съёмки получил раздел `capture`: платформа, архитектура, сборка
Chromium, отпечаток материала и — в CI — прогон с попыткой и SHA. Схема
отчёта поднята до 2;
- приёмка читает среду съёмки из отчёта. Гейт чужой среды теперь судит обе
стороны: съёмку (из отчёта) и приёмку (свою). Отказ — до единой записи;
- индекс эталонов поднят до схемы 2 и различает `capturedOn` и `acceptedOn`,
несёт раздел `capture` и причину осознанного обхода в `foreignCapture`;
- отчёт схемы 1 платформы съёмки не несёт физически: это отдельная явная
ветка, `capturedOn` уезжает `null`. Выдумывать платформу нельзя — ровно этим
задача и вызвана;
- `--baselines=<dir>` у приёмки: без него проверить «отказ произошёл ДО
записи» можно было бы только порчей рабочего дерева, то есть никак (#556).
Осознанно отменено решение #455 «не трогать run.mjs»: ради ГЕЙТА цена
фингерпринта не окупалась, ради ПРОВЕНАНСА окупилась — платформу кадров знает
только тот, кто их снял. Плата разовая: пересобран бандл, индекс скриншотов
документации переснят отдельным коммитом. Тест #455 переписан под новый
инвариант, а не удалён.
Свидетели: `test/golden-capture-provenance.test.mjs` — семь проверок, все пять
сценариев приёмки из issue, включая «отказ до записи» и «подмена PNG и неполный
артефакт по-прежнему fail-closed». Мутанты `golden-index-invents-capture-platform`
(возвращает платформу приёмщика) и `golden-report-provenance-optional`
(разрешает отчёт схемы 2 без провенанса) прогнаны лично: оба краснеют.
npm test 2762/2761/0 fail, typecheck чистый.
Issue: #571
User-Visible: no
Указанные ширина и глубина теперь совпадают с видимым габаритом предмета.
Раньше рисунок занимал внутри своего бокса около 88 % стороны, поэтому
«Кухонный напольный модуль» 60 × 60 выглядел меньше «Посудомоечной машины»
60 × 60 примерно на 14 % по стороне и на 30 % по площади: цифры совпадали,
картинка врала.
Пак `fix-584-1` от дизайнера (issue, комментарий от 16.09) проверен исполнением
своей метрикой, а не его отчётом: границы считаются аналитически по той же
математике, что у продакшен-экспорта в PDF. Все 44 плановых SVG заполняют
`viewBox` с худшим отклонением 0.000000 при допуске ТЗ 0,1; `pack.json` и 33
иконки меню побайтно прежние. Для сравнения, прежний набор в `dev` давал 65
нарушений того же контракта.
Что сделано в коде:
- `scripts/svg-path-bounds.mjs` — границы рисунка по настоящим экстремумам
кривых (дуги приводятся к кубикам). Контрольные точки в габарит не входят:
иначе «заполняет бокс» проверялось бы по выпуклой оболочке и поля проходили
бы мимо. Генератор — `.mjs` и обязан работать без сборки TypeScript, поэтому
разбор здесь свой; тест сверяет его с `parseSvgPath` продакшена на всей
библиотеке, чтобы реализации не разъехались молча.
- `scripts/furniture-path-join.mjs` — склейка нескольких `<path>` с
канонизацией только начального `moveto`. Приём подсказан патчем в поставке
дизайнера; реализация и свидетели свои. Прежний `paths.join(' ')` продолжал
координаты предыдущего пути: замер показал, что у иконок меню `stairs` и `tv`
части рисунка уезжали за `viewBox` 110 × 110 — до 119. Теперь склейка
побайтно воспроизводит «каждый путь из своего нуля».
- генератор проверяет плановый символ по контракту: один путь, подконтуры с
абсолютной `M`, рисунок заполняет `viewBox` (допуск 0,1).
- `src/furniture.ts` — legacy `plant` растянут от центра в 1/0.96. Это
единственный из двенадцати retained-примитивов с полями (0.02…0.98 unit box);
форма прежняя, габарит стал заявленным.
Свидетели: `test/furniture-visual-bounds.test.mjs` (AC1–AC3, AC5 и сверка двух
реализаций границ) и `test/furniture-path-join.test.mjs` (AC4, включая реальные
`stairs`/`tv`). У AC1 есть положительный контроль: та же функция обязана видеть
поля на геометрии из шапки issue — утверждение «полей нет» стоит ровно столько,
сколько стоит измерение. Мутанты `furniture-symbol-may-keep-inner-padding` и
`furniture-paths-joined-without-reset` возвращают прежнее поведение и краснеют;
прогнаны лично через `mutation-gate --id=`.
Миграции пользовательских данных нет: координаты, размеры, поворот и
зеркалирование сохраняются.
npm test 2743/2742/0 fail, typecheck чистый, furniture:check зелёный.
Issue: #584
User-Visible: yes
Promotion-only на вершине v1.76.0-beta.5: семь источников версии, снимки
собранного бандла и релизные метаданные. Продуктовый код в этом коммите не
двигается.
Линейка агрегируется от стабильной v1.75.0. Пользователю: щипковое
масштабирование в приложении Home Assistant больше не рвёт картинку — на
больших планах с фоном «Следует за Солнцем» пропали фиксированные белые
провалы (#582), план перестал переключаться между режимами композитинга
посреди жеста (#579), а щипок, начатый на значке устройства, не запускает его
действие и не открывает два окна сразу (#578, #563). Появился выбор источника
солнечных лучей — от внутренних или внешних углов окна (#577). «Как у
пространства» снова возвращает комнате цвет пространства и лечит застрявшие
(#581). Близко стоящие значки не перехватывают нажатие друг у друга, а
клавиатурный фокус получает ту же подсказку, что и мышь (#564, #565). План,
изменённый в другом браузере, не откатывается (#543), и при перетаскивании у
края не появляется пустая полоса (#544). Мельче: #561, #554, #545, #580.
Внутреннее в линейке: #583, #547, #556, #560, #566, #567, #568, #569.
Из тела исключён #580: этот дефект жил только внутри бета-линейки — режим
внешних углов окна (#577) не выходил ни в одном стабильном релизе, поэтому
пользователю он «исправлением» не является. В ченджлоге он остаётся.
Записано, а не замолчано: индекс свежести скриншотов документации устарел с
коммита 699ab471, а гейт Validate, который обязан был это поймать, лишь
предупреждает — режим выбирается сравнением с многострочным выводом CLI
(#586). Одиннадцать кадров документации от той правки измениться не могли: ни
одна из сцен не рисует скрытый 2.5D-вид, а отпечаток считается по всему
`src/**`. Пересъёмка требует Chromium, которого в этой песочнице нет.
npm test 2720/2719/1 fail (только bundle-tree до этого коммита), typecheck
чистый, контракт релиза зелёный на всех семи источниках, тело проходит #328
(`release:notes --verify`), check-docs зелёный кроме отпечатка скриншотов.
Issue: #582
User-Visible: yes
Release: v1.76.0
Разрешение коллизий 2.5D-подписей сканировало диск смещений с шагом 1 px и
для каждого кандидата спрашивало пространственный хэш и все стены. Замер
показал: на large-house-isometric-v1 580 размещений дают 2 523 652 смещения и
1 742 740 проверок отпечатка против стен, 98 % позиций отвергают стены.
Поиск переведён на решётку 4 px с уточнением 1 px вокруг найденного места
(и вокруг лучшего конфликтующего кандидата — без второго уточнения качество
разрешения падало с 308 до 304). Дешёвая часть кандидата (позиция, габариты,
конфликты, штраф) отделена от дорогой (комната-владелец, непрерывность
сегмента, зазор до стены); соседи считаются один раз на элемент; расстояния
таблицы смещений предвычислены; добавлены AABB комнаты-владельца и широкая
фаза по стенам.
Групповой проход 9584 → 839 мс, смещений 2 523 652 → 157 618, разрешено
308 / остаток 272 — как до правки.
Issue: #583
User-Visible: no
После первого движения камеры переносит фильтр контура на ограниченный размером viewport слой, сохраняя исходный статичный рендер без изменений.
Issue: #582
User-Visible: no
Режим заливки комнаты и её цвет — два поля конфига под одним переключателем.
«Как у пространства» снимало только режим; цвет оставался и продолжал
применяться, потому что `roomCustomFillOf` отдавал цвет комнаты независимо от
того, чей режим `custom` действует. Так возникало безымянное состояние «режим
наследую, цвет свой» — Cabinet на даче.
Теперь цвет комнаты участвует в раскраске только вместе с её собственным
`fill_mode: 'custom'` (одна функция — все поверхности: карточка, space-card,
PDF, черновик диалога). Диалог загружает цвет в черновик только при своём
режиме, обнуляет его при уходе с «Свой цвет» и показывает строку цвета только
под этим радио; сохранение пишет `custom_fill` только с `fill_mode: 'custom'`,
иначе удаляет — включая сироту от прежнего редактора. Чтение конфиг не
переписывает: застрявшие комнаты выздоравливают обновлением.
- `test/logic.test.mjs`: AC1 — сирота и любой чужой режим → цвет пространства
- `demo/smoke_room_settings.mjs` шаг 7: свой цвет → «Как у пространства» →
ни режима, ни цвета, во View цвет пространства; сирота открывается как
наследование, сохранение её удаляет (проверено красным на базе: 9 фактов)
- `demo/smoke_space_settings.mjs`: override с собственным режимом + сирота
- `demo/golden/harness.mjs`: `roomCustomFill` ставит комнате её режим —
кадры `lighting-custom-glow-*` не меняются
- мутант `room-orphan-colour-wins-again`
- docs: ARCHITECTURE (#56), CONFIG-COMPATIBILITY, USER-GUIDE ru/en, TESTING;
отпечаток скриншотов принят попиксельно (11 кадров)
Issue: #581
User-Visible: yes
Package the validated Stage 4 candidate while keeping the isometric experiment hidden and the default Flat experience unchanged.
Release: v1.76.0-beta.3
Issue: #570
User-Visible: yes
Гард `keepTwoPoint` решал, сохранять ли двухточечную калибровку, сравнением
`JSON.stringify`, то есть текста. Порядок ключей `sources` при этом меняет сам
билдер: `slots`/`ranges`/`zones`/`occupancy_entity`/`count_entity` он удаляет и
дописывает заново, а `availability_entity` остаётся на месте и уезжает в начало.
Конфиг, только что записанный этим же билдером, при следующем открытии
сравнивался неравным — и радар с `availability_entity` терял `refs` и `rms_cm`,
получая `method: manual`, при первом же обычном сохранении.
Сравнение стало каноничным по порядку ключей объектов и осталось чувствительным
к порядку элементов массивов: позиция слота — это его `target_N`. Проекция не
затронута — она считается от `mount`, `cell_cm` и `mirror`.
Issue: #567
User-Visible: no
Сводная панель теперь использует канонический порядок карточек Home Assistant и не переносит локальные настройки между визуальными колонками после remount.
Issue: #561
User-Visible: yes