docs: review document for #488

Issue: #488
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-08 07:27:14 +00:00
parent 2b2fc94479
commit b13a63f587
+157
View File
@@ -0,0 +1,157 @@
# CODE-REVIEW-488-r1
**Issue:** [#488](https://github.com/Matysh/houseplan-card/issues/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 в порядке.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/488-panel-host`, коммит `2b2fc94479d8` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `76c6fb8b02386f04bf88c536fdba9c2ab46effbc`
```
git log --all --format='%H %T' | grep 76c6fb8b0238
```