Files
houseplan-card/docs/reviews/CODE-REVIEW-488-r1.md
T
2026-09-08 07:27:14 +00:00

15 KiB
Raw Blame History

CODE-REVIEW-488-r1

Issue: #488 — панель /houseplan пуста в реальном HA (стейдж 0 px + hass не доходит до карточки при первом заходе). Заход: r1 · блокирующих циклов израсходовано 0 из 2 (лимит §4 для лёгкого трека — 2 цикла ревью ТЗ; для код-ревью — общий лимит 4, см. §4). SHA материала ревью: 2b2fc944 (проверено git rev-parse HEAD непосредственно перед вынесением вердикта — совпадает с SHA зелёного Validate, на который ссылается заказчик ревью: https://github.com/Matysh/houseplan-card/actions/runs/34198029189). Диапазон: git diff origin/dev...HEAD, 4 коммита (471527c4 fix, 28772133 test, e97e5681 docs, 2b2fc944 docs).

Скоуп

Диф правит два независимых, но совместно найденных дефекта монтирования кастомной панели HA:

  1. Стейдж 0 px. <ha-panel-custom> — блок без собственной высоты; :host с height:100% резолвится в auto, вся цепочка схлопывается. Правка берёт высоту от viewport: 100vh, затем calc(100dvh − safe-area-inset-top − safe-area-inset-bottom).
  2. hass не доходит до карточки при первом открытии (второй, ранее не описанный в теле issue корень — см. «Не заявлено в issue, но обосновано» ниже). HA присваивает panel/hass/narrow/route элементу сразу после load модуля, до того как top-level await import('./houseplan-card.js') даст классу определиться; значения оседают как собственные data-свойства инстанса и затеняют accessors. _adoptPreUpgradeProperties() в конструкторе и connectedCallback переносит их через accessors.

Правки: src/houseplan-panel.ts (+27/−… строк), новый сценарий в demo/smoke_houseplan_panel.mjs (реальный порядок монтирования HA + хост без высоты), 2 мутанта в scripts/mutation-gate.mjs, обновлённый test/houseplan-panel.test.mjs, docs/specs/486-house-plan-panel.md, CHANGELOG.md/CHANGELOG.ru.md, пересобранный dist/** + custom_components/houseplan/frontend/**, обновлённый docs/images/screenshots.json (только provenance, не сами PNG).

Не заявлено в issue, но обосновано. Тело issue в разделе «Причина» называет только высоту; про затенение accessors — ни слова, хотя раздел «Симптом» прямо описывает два состояния («первый заход — пустая шапка панели, ни заголовка карточки, ни плана» и «после возврата — шапка карточки есть, стейдж всё равно пуст») и сам помечает первое как догадку («вероятно, вуаль… проверить в исправлении»). Автор нашёл настоящую причину первого состояния (свойства затенены, hass физически не долетает до карточки), задокументировал её в docs/specs/486-house-plan-panel.md тем же коммитом и покрыл тестом и мутантом. Это не расширение скоупа: раздел «Приёмка» issue требует «план виден с первого захода» — без фикса затенения это требование не выполняется одной только высотой. Отношу к находкам не считаю; это корректное закрытие AC, а не самодеятельность помимо задачи.

Как проверялось

Дешёвые гейты не перегонялись бы (Validate на этом SHA зелёный), но диф трогает src/** и заявляет два защитных контракта дорогим гейтом (смок) — это явно требует мутанта и его прогона (§2.7), а «дешёвые» пришлось прогнать самостоятельно, т.к. npm run bundle:sync понадобился для смока (класс D пересобирается, но не входит в Validate из этого списка проверок отдельно).

Гейт Прогнан Результат
npx tsc --noEmit да (внутри npm run build) 0 ошибок
npm run build + npm run bundle:sync да dist ⟷ custom_components/.../frontend ⟷ demo/srv/assets — cmp всех трёх пар без вывода (идентичны)
npm test да tests 2248, pass 2247, fail 0, skipped 1 (пропуск — приватная фикстура #281, к делу не относится)
node scripts/check-docs.mjs да (diff трогает src/**) Documentation checks passed (7 files, 12 external links)
node scripts/no-new-any.mjs --base origin/dev --head HEAD да Новых any нет (26 добавленных строк в 1 файле)
node scripts/smoke-select.mjs --base origin/dev --head HEAD да Вывод: НЕОПРЕДЕЛЁННОСТЬ — символы PRE_UPGRADE_PROPERTIES, _adoptPreUpgradeProperties не зарегистрированы ни в одном смоке (инструмент не знает про добавленный в этом же диффе demo/smoke_houseplan_panel.mjs). Прямое соответствие «правка src/houseplan-panel.ts → смок demo/smoke_houseplan_panel.mjs» очевидно и без инструмента: единственный смок про панель — этот, и диф его же и расширяет. Прогнан.
node demo/smoke_houseplan_panel.mjs да все 63 проверки true/OK, включая новые haSequence* и autoHeightHost*
node scripts/mutation-gate.mjs --id=panel-ignores-pre-upgrade-properties да panel-ignores-pre-upgrade-properties: тест покраснел, как обязан (1/1)
node scripts/mutation-gate.mjs --id=panel-host-height-from-parent да panel-host-height-from-parent: тест покраснел, как обязан (1/1)
npm run golden:verify да (diff меняет CSS-высоту хоста панели, видимый результат под вопросом) 60/60 passed, 0 упавших; без панель-специфичного baseline (харнес и так фиксирует высоту хоста — подтверждено чтением demo/golden/)
python -m pytest tests_backend -q нет diff не трогает custom_components/houseplan/**/*.py
npm run invariants / model-invariants нет diff не трогает геометрию комнат, layout, marker.space, open_spans — панель это HTML-обвязка вокруг уже существующей карточки
performance-профили нет не названы в AC, diff не касается чувствительных к перфу путей

Таблица «чем краснеет» (§2.7, защитные AC)

AC/защита Чем доказан Чем краснеет
Хост панели берёт высоту от viewport, а не от <ha-panel-custom> (иначе стейдж 0 px) demo/smoke_houseplan_panel.mjs → autoHeightHostFillsViewportMinusInsets, autoHeightHostKeepsPositiveStage (оба true в реальном прогоне) node scripts/mutation-gate.mjs --id=panel-host-height-from-parent — мутант возвращает height:100%, смок краснеет (подтверждено прогоном)
Собственные pre-upgrade свойства (panel/hass/narrow/route) переносятся через accessors, hass доходит до карточки уже на первом открытии demo/smoke_houseplan_panel.mjs → haSequenceLeavesNoShadowingOwnProperties, haSequenceForwardsInitialHass, haSequenceForwardsLaterHass, haSequenceAdoptsNarrowThroughAccessor, haSequenceAdoptsRouteAndPanel (все true) node scripts/mutation-gate.mjs --id=panel-ignores-pre-upgrade-properties — мутант убирает перенос, смок краснеет (подтверждено прогоном)
Существующие golden-панели не меняются npm run golden:verify не мутировалось намеренно; регресс проявился бы падением одного из 60 сценариев — их нет

Находки

Нет ни High, ни Medium, ни Low.

Разобрано и не вызвало вопросов (детали — «Как проверялось» и код-чтение):

  • Порядок вызова _adoptPreUpgradeProperties() в массиве ['panel','hass','narrow','route'] не влияет на корректность: hass-setter вызывает _ensureShell() и не зависит от того, установлены ли уже narrow/route, а их setters не имеют побочных эффектов на карточку. Проверено чтением, подтверждено прогоном смока (haSequenceAdopts* все true).
  • Второй вызов _adoptPreUpgradeProperties() в connectedCallback — не находка: после апгрейда собственных свойств уже нет (hasOwnProperty ложный), вызов становится no-op. Не вредит, просто defensive.
  • delete на собственных data-свойствах, выставленных простым присваиванием до апгрейда элемента — стандартно configurable/writable, delete не может бросить исключение. Проверено чтением.
  • Регэксп-проверки в test/houseplan-panel.test.mjs (assert.doesNotMatch(panel, /:host \{[^}]*height: 100%;/) корректно ограничены блоком :host{...} классом символов [^}]* — не цепляют .page { height: 100%; } ниже по файлу. Проверено чтением; это вспомогательные регресс-тесты поверх поведенческого смока, а не единственное доказательство.
  • Трейлеры коммитов: Issue: #488 во всех четырёх, User-Visible: yes только в продуктовом коммите 471527c4 (единственном, меняющем видимое поведение), с правками в оба changelog в этом же коммите; остальные три — User-Visible: no (тест/доки), корректно.
  • Классы изменений (AGENTS.md) соблюдены: A — src/houseplan-panel.ts; B — demo/**, scripts/mutation-gate.mjs, test/**, все со ссылкой на #488; C — docs/**; D — dist/**, custom_components/houseplan/frontend/** меняются только вслед за источником, не сами по себе.
  • docs/images/screenshots.json: диф — только sourceFingerprint/sourceSha256, imageSha256 не менялся ни в одной записи; согласуется с check-docs.mjs зелёным и с коммитом 2b2fc944, прямо объясняющим, почему картинки не перевыпускались.
  • Продуктовая рамка (docs/SCOPE.md): правка закрывает J1 («show the whole home… live spatial overview») в самом буквальном виде — без неё вход в продукт через боковую панель не работает вовсе. В скоуп, приоритет P1 оправдан.

Чего не проверял

  • Реальный HA-инстанс (только Playwright-эмуляция через двойник <ha-panel-custom> в demo/smoke_houseplan_panel.mjs, включая порядок вызовов _createPanel/setCustomPanelProperties, который смок воспроизводит текстом, а не импортом настоящего HA frontend). Ручного тестирования в цикле ревью нет по правилам процесса; проверено чтением кода и поведенческим смоком с мутантами, не исполнением на живом HA.
  • python -m pytest tests_backend — не запускал, diff не трогает custom_components/houseplan/**/*.py.
  • node scripts/model-invariants.mjs — не запускал, diff не трогает геометрию/layout/marker.space/open_spans.
  • Полный ls demo/smoke_*.mjs матрицы кроме smoke_houseplan_panel.mjs — не прогонял; diff ограничен монтированием панели, smoke-select.mjs не назвал других кандидатов, а тема (панель) больше ни в одном смоке не фигурирует.
  • Performance-профили — не в AC, не затронуты.

Вердикт

Зелёный. AC issue выполнены и доказаны исполняемым смоком с двумя мутантами, подтверждённо краснеющими; побочный, изначально не описанный в issue корень (затенение accessors) корректно диагностирован, задокументирован и покрыт тем же гейтом; golden не регрессирует; трейлеры и changelog в порядке.


Материал раунда

  • Ветка: issue/488-panel-host, коммит 2b2fc94479d8 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 76c6fb8b02386f04bf88c536fdba9c2ab46effbc
    git log --all --format='%H %T' | grep 76c6fb8b0238