Та же находка, что 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
Promotion-only on top of v1.75.0-beta.1: seven version sources, the
generated bundle snapshots and the release metadata. No product source
code moves in this commit.
The line aggregates from the stable v1.74.0 and carries one user-visible
fix. The House Plan sidebar page could serve a previous version of the
card for hours: the panel entry fetched it through `./houseplan-card.js`,
a relative specifier, and relative resolution does not inherit the `?v=`
a dashboard gets from its Lovelace resource, while the entry files carry
no Cache-Control at all. The panel now imports the implementation by its
content-hashed name, so either the matching card arrives or the panel
says out loud that the page is stale (#535). Internal in the line: #536.
Known contradiction, recorded rather than silenced: the release contract
in scripts/release-contract.mjs requires the grouped "small fixes"
bullet unconditionally, while `npm run release:notes -- v1.75.0
--verify` rejects it because every user-visible issue of the range is
already itemised. The two rules deadlock any stable with a single
user-visible issue. The bullet stays, because the contract is the
automated gate that Validate enforces; the verifier's objection is
written down in STATUS and will get its own issue.
npm test 2532/2531/0 fail, release contract green on all seven sources,
docs strict green.
Issue: #535
User-Visible: yes
Release: v1.75.0
Seven version sources move together to 1.75.0-beta.1, both changelogs
close their section over what has landed since the stable v1.74.0, the
release notes carry the one shipped bullet on each side with the grouped
small-fixes bullet last, and STATUS says what this beta is.
What the user gets: the House Plan sidebar page can no longer serve a
previous version of the card. The panel entry fetched it through
`./houseplan-card.js` — a relative specifier, and relative resolution
does not inherit the `?v=` a dashboard gets from its Lovelace resource,
while the entry files carry no Cache-Control at all. A browser was free
to answer from its own heuristic cache for hours, and a stale loader
named a stale immutable chunk, so the panel ran an old card against the
current backend with nothing but the version banner to show for it —
and reloading could not help, because the address never changed (#535).
Internal in the line: #536 — the version banner now asks the host to
repaint when it drops the notice on disconnect, instead of leaving its
removal to whatever unrelated update happened to run next.
npm test 2532/2530/0 fail (the #349 manifest-tracking check goes green
with this commit), release contract green on all seven sources, bundle
287 323 B gzip inside the ceiling, docs strict green.
The `Release:` trailer is what asks CI for the heavy gates — smokes,
golden and the full performance comparison — on this exact SHA (#479).
Issue: #535
User-Visible: yes
Release: v1.75.0-beta.1
The controller dropped its banner on disconnect without asking the host
to repaint. Lit renders neither on disconnect nor on reconnect, so the
markup produced before the detach outlived it: the notice stayed on
screen while the controller no longer owned one, and it left only when
some unrelated update happened to run. Correctness rested on a
coincidence.
Measured on the built module with a counter: the sequence mismatch ->
disconnect -> versions agree -> connect asked for exactly one repaint,
the one that showed the notice. It now asks for two, and the second is
the one that takes the notice away.
Nothing else about the teardown changes. The field is still cleared
because a detached element cannot deliver animationend, reconnect still
rebuilds the notice from the retained input, and a disconnect with no
notice still asks for nothing.
Issue: #536
User-Visible: no
The sidebar page could serve a previous card for hours. It imported the
card through the stable facade, `./houseplan-card.js` — a relative
specifier, and relative resolution does not inherit a query. A dashboard
reaches the same file as `houseplan-card.js?v=1.74.0`, so an upgrade
changes its URL and the browser must refetch. The panel always asked for
the same address, and entries are served with no Cache-Control at all —
only ETag and Last-Modified — so the browser applies heuristic freshness
and may answer from cache without asking. A stale 1164-byte loader names
a stale chunk, chunks are immutable for a year, and the panel then ran a
previous card against the current backend without a single error. The
version banner was telling the truth; reloading could not help, because
the address never changed.
Rollup already emits the right edge: the panel's side-effect import
points straight at the shared implementation. The rewrite in
entryFallbackPlugin replaced it with the facade for a fallback that the
hashed name gives anyway — and better: a chunk the manifest no longer
serves now raises the panel's own "House Plan was updated" notice
instead of silently working on old code.
Two #486 assertions change meaning and are rewritten, not adjusted: the
panel no longer routes through the facade, and its initial graph no
longer contains it. The invariant they defended — the panel reuses the
exact card graph, never a second copy — is now stated over the
implementation, and a new test pins that no built entry reaches the card
by an address without a version.
Issue: #535
User-Visible: yes
#525 увёл оба списка сцены с `map()` на `repeat(items, (item) => item.id, …)`,
чтобы Lit не переиспользовал узлы по позиции и не проигрывал анимацию двери,
которой не было. Правка верная, но на плане с двумя сотнями маркеров она
оказалась дорогой ровно там, где пользы не приносит: при смене пространства
ключи не пересекаются вовсе, и `repeat` строит две карты ключей и обходит оба
списка, чтобы затем всё равно выбросить всё и создать заново.
Бисект по медиане `switchCycleMs` на `large-house`: 871,2 перед #525 → 953,5
после. На раннере эти 80 мс распадаются на шесть-девять дополнительных длинных
задач, и `longTask.countP95` вышел за порог стабильного гейта.
Теперь оба списка рендерятся как `keyed(space.id, repeat(…))`. Внешний ключ
делает смену пространства: поддерево выбрасывается целиком, дифа нет. Внутренний
остаётся, потому что состав списков едет и внутри пространства — у маркеров от
призраков редактора и живого синка, у проёмов от записи с нерешённым хостом,
которая живёт только в режиме plan, — а переходы на `.device-shell-frame`,
`.op-leaf` и `.op-arc` никуда не делись.
Замер после правки: `switchCycleMs` 889,1 против 936,0 на `main` и 871,2 до
#525. Потолок карточки поднят на одну строку — на `import { keyed }`;
переносить нечего, сам рендер не вырос ни на символ.
Issue: #534
User-Visible: yes
The "Follows the sun" background made the plan crawl in Firefox: 23 MB of
textures per frame, sixteen of about twenty picture-cache tiles thrown
away every frame, nine frames per second. The card's own JavaScript was
idle for 89 % of that.
The cost is the outer outline. It is a triple drop-shadow over the
grouped paper footprint, and although the group holds paper silhouettes
only and never changes on hover or pan, the filter lived in the plan's
own layer — so every repaint of the plan re-ran three blur passes over
the whole sheet. One hint moves the filtered paper into its own layer
and unhooks it from the plan's repaints.
Measured on a demo-stand pan (Chromium, CDP, summed RasterTask): 1456.6
ms against a static background's 96.7 ms before, 84.7 ms against 103.2
ms after. The new smoke measures that ratio and fails above two.
The picture does not change: the outline outside the plan matches byte
for byte and the frame's mean colour moves from 176.59 to 176.66 of 255.
Splitting the layer does move the remaining content onto a different
sub-pixel grid, so the diagonal wall hatch anti-aliases differently and
the four day-cycle baselines are re-taken on the beta candidate.
Issue: #532
User-Visible: yes
Seven version sources move together to 1.74.0; both changelogs gain the
stable section aggregated from the previous stable v1.73.0, the release
notes carry three itemised bullets on each side plus the grouped
small-fixes bullet, and STATUS says what this release is.
What the user gets since v1.73.0: the plan is noticeably smoother and in
Firefox dramatically so — a pan frame now moves what is already drawn by
a composited transform with the scene re-rasterizing on a budget (#531),
and a marker shadow sized in container units no longer restarts a
non-composited animation on every marker at once (#524); the printed plan
is about a third larger, because the in-plan dimension labels shrank and
the separate column of external dimensions is gone (#530); a plan left
over from the era of rooms without walls can be edited again, the
leftover cleaned up on the first save instead of refusing every change
(#529, reported in #527). Grouped under small fixes: alignment guides
follow the gesture again (#521), the summary panel neither freezes the
first frame nor claims an unavailable source (#509) and its settings
dialog scrolls again (#508), a space switch no longer replays a
neighbouring door's leaf (#525), and the card downloads 12.8 KB less on a
cold start (#526).
The speed verdict for #531 is deliberately not claimed here: headless
Firefox and Chromium hold 60 fps on both the old and the new path, so it
is owed by a profile from the owner's machine on the same pan.
`npm test` 2528/0/1 skip, `pytest tests_backend` 467 passed, release
notes pass the #328 verifier against v1.73.0..HEAD, bundle 287 242 B gzip
inside the ceiling, docs strict green.
Issue: #531
User-Visible: yes
Release: v1.74.0