Files
houseplan-card/docs/reviews/CODE-REVIEW-508-r1.md
T
2026-09-09 19:49:18 +00:00

124 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CODE-REVIEW-508-r1
Issue: #508 — «Сводная панель: в диалоге настроек не работает прокрутка — колесом мыши и touch на мобильных»
Материал: `768e1281f9119b12d018b416372984ff17eae62d` (ветка `issue/508-summary-dialog-scroll`, ребейзнута конвейером на `dev`: `7e5d394a → 768e1281`, +2 коммита dev).
Заход: r1 · блокирующих циклов израсходовано 0 из 2. Ребейз довёл ветку до другого дерева (§7.2) → разбор полный.
## Скоуп
Один коммит, класс A (`src/hp-dialog.ts`, `src/summary-panel-editor.ts`) + класс B (новый смок,
контрактный юнит-тест, два мутанта) + класс C (`docs/ARCHITECTURE.md`, оба CHANGELOG) + класс D
(пересобранный бандл во всех трёх копиях, `docs/images/screenshots.json` — только фингерпринт/`sourceSha256`,
`imageSha256` не изменились, что соответствует заявленному «11/11 попиксельно»).
Правка ровно та, что описана в ТЗ (docs зафиксированы в теле issue, `SPEC-REVIEW-508-r1.md` →
`SPEC-REVIEW-508-r2.md`, вердикт зелёный): `hp-dialog` получает opt-in булев атрибут `flex-content`
(`flexcontent` на `ha-dialog`, обе HA-ветки рендера — с `described-by` и без), диалог настроек сводной
панели его объявляет. Нативная ветка (`<dialog>`) не тронута ни строкой.
**J1** (`docs/SCOPE.md`) — обзорный диалог сводной панели входит в «show the whole home at a glance»;
это чистый баг-фикс регресса после #505, никакой новой функциональности и расширения скоупа нет.
## Как проверялось
Дешёвые гейты на этом SHA уже подтверждены Validate
(https://github.com/Matysh/houseplan-card/actions/runs/34395116095, `headSha=768e1281`, job
«Фронтенд: типы, юниты, мутанты, синхрон бандла» — success). Тем не менее часть из них прогнана
повторно вживую, потому что дёшево и диф трогает `src/**` (общий компонент `hp-dialog`, используемый
девятью другими вызывающими).
| Гейт | Прогнал | Результат |
|---|---|---|
| `npx tsc --noEmit` + `rollup` (`npm run bundle:sync`) | да | сборка чистая; пересобранный `dist/` и обе копии (`custom_components/.../frontend`, `demo/srv/assets`) байт-в-байт совпали с закоммиченными — `git status` после пересборки пуст |
| `npm test` | да | 2437 тестов, 2436 pass, 1 skip (известный `smoke_opening_measure` не запускался тут — не относится к юнитам), 0 fail |
| `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | да | «Новых any нет» (10 добавленных строк в 2 файлах) |
| `node scripts/check-docs.mjs` (диф трогает `src/**`) | да | «7 files, 12 external links» — зелёный, `sourceFingerprint` в `screenshots.json` уже обновлён коммитом |
| `npm run bundle:budget` | да | initial View 298979 B (потолок 299600±2000); предупреждение о запасе <15000 Б — существующий проектный долг (#367/#474), эта задача добавляет один атрибут разметки и не сдвигает бюджет заметно; не находка этой задачи |
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | да | «НЕОПРЕДЕЛЁННОСТЬ» — 2 файла, 0 символов на изменённых строках, порог «широкого» не достигнут. Решение ревьюера ниже |
| `node demo/smoke_summary_dialog_scroll.mjs` (AC1/AC2, новый) | да | `OK`, все 14 проверок зелёные (desktop wheel/footer, phone touch/viewport, witness без `flexcontent`) |
| `node demo/smoke_summary_panel_polish.mjs` (AC3, прямое совпадение — единственный смок диалога панели) | да | `OK`; файл смока не менялся (`git diff` пуст) — «без правок его ожиданий» выполнено |
| `node demo/smoke_dialog_modal_recovery.mjs`, `node demo/smoke_esc_dialogs.mjs`, `node demo/smoke_dialog_footer_width.mjs` (зарегистрированная связь: общий `hp-dialog`, фокус/эскейп/футер) | да | все три `OK` |
| `node scripts/mutation-gate.mjs --id=summary-dialog-drops-flex-content` | да | «чистый прогон» → зелёный, мутация → «тест покраснел, как обязан», поймано 1 из 1 |
| `node scripts/mutation-gate.mjs --id=hp-dialog-ignores-flex-content` | да | то же, поймано 1 из 1 |
| in-memory негативная проба AC4 (`test/hp-dialog-contract.test.mjs`, добавление `flex-content` в `hp-confirm.ts` только в памяти интерпретатора, без записи в дерево) | да | ассерт `=== 1` дал бы 2 → тест обязан покраснеть |
**Не прогонял:** полный `golden:verify`, `python -m pytest tests_backend`, полную матрицу смоков (237 шт.),
performance-профили.
- `golden` — диф не меняет ни один существующий рендер: атрибут opt-in, `?flexcontent=` в Lit убирает
атрибут целиком при `false`, поэтому разметка всех прочих `hp-dialog` не меняется ни на байт; golden
снимается в demo-стенде, где вообще используется нативная ветка (`<dialog>`), которую диф не трогает.
golden-сцен с префиксом `summary` в репозитории нет (снято ещё в SPEC-REVIEW r1 M1, AC3 переписан на
единственный смок). Риск нулевой — не прогонял намеренно, а не пропустил.
- `pytest tests_backend` — диф не касается `custom_components/houseplan/**/*.py` вовсе.
- Полная матрица смоков и перф-профили — не названы в AC; `smoke-select` вернул «НЕОПРЕДЕЛЁННОСТЬ», выбор
сузил вручную до прямого совпадения (диалог панели) и зарегистрированных связей (общий `hp-dialog`:
восстановление модального стека, Escape-стек, ширина футера). Остальные 231 смока к этому диффу не
привязаны сильнее «общий компонент» и по правилу прогона не требуют.
## AC → доказательство → чем краснеет
| AC | Доказано | Чем краснеет |
|---|---|---|
| AC1 (HA-ветка: колесо крутит `.summary-editor`, шапка/футер неподвижны) | `demo/smoke_summary_dialog_scroll.mjs` сценарий (a), прогнан лично — `OK` | оба мутанта `summary-dialog-drops-flex-content` и `hp-dialog-ignores-flex-content`, прогнаны лично: «покраснел, как обязан» |
| AC2 (touch 390×844 крутит редактор; диалог ≤ вьюпорта; футер доступен) | сценарий (b), прогнан лично — `OK` | те же два мутанта (общий guard — атрибут) |
| AC3 (нативная ветка не меняется, `smoke_summary_panel_polish` зелёный без правок ожиданий) | прогнан лично — `OK`; `git diff` файла смока пуст | не защитный AC (сравнение факта с ожиданием, не guard) — правило §2.7 не требует красноты |
| AC4 (прочие `hp-dialog` без `flex-content` не меняют раскладку) | `test/hp-dialog-contract.test.mjs` (в составе `npm test`, прогнан лично) + чтение: `?flexcontent=` в Lit опускает атрибут при `false`, значит разметка девяти прочих вызывающих идентична | in-memory негативная проба (см. выше, без записи в дерево): подмена показала бы `optIns.length === 2` → ассерт `=== 1` красный |
| AC5 (оба мутанта пойманы штатным раннером) | сами прогоны мутантов, см. выше | это и есть доказательство; вторичной пробы не нужно |
| AC6 (`User-Visible: yes`, оба changelog, i18n/модель/перф не задеты) | `git diff` коммита: `docs/CHANGELOG.md` + `docs/CHANGELOG.ru.md` в том же коммите; ни один `translations/**`/`i18n` файл не в диффе; в `src/hp-dialog.ts` нет новых обработчиков — только проброс булева атрибута в существующий рендер | не защитный AC (перечисление фактов), доказано чтением диффа |
## Что проверено и корректно
- Обе HA-ветки рендера (`describedBy` и без) получили `?flexcontent=${this.flexContent}` — прочитано в
`src/hp-dialog.ts:515,531`, подтверждено юнит-тестом и мутантом.
- Нативная `<dialog>`-ветка (`_usesHaDialog()` false) не содержит ни одного упоминания
`flexcontent`/CSS-селектора на `:host([flex-content])` — сброс атрибута не имеет побочных эффектов там,
где используется golden/demo.
- Диагноз (scroll chaining обрывается на height-unbound `overflow:auto + overscroll-behavior:contain`)
independently подтверждён автором на живом стенде HA 2026.9.1 (`ha-dialog.flexContent = true` →
`.summary-editor` 477/819, колесо работает) — это сильнее, чем воспроизведение только в стабе; стаб
смока копирует контракт `.body{overflow:auto}` + `:host([flexcontent]) .body{display:flex}`, о котором
говорит и уже существующий комментарий `demo/capture_summary_panel_505.mjs` (#505).
- `docs/TOUCH-SUPPORT.md:48` прямо называет «simple settings form» сводной панели гарантированной View-
touch-поверхностью — включение touch в скоуп задачи не расширение, а обязательное соответствие контракту.
- Терминология changelog («сводной панели», «настройки») совпадает с `docs/USER-GUIDE.ru.md:264`.
- `docs/images/screenshots.json`: только `sourceFingerprint`/`sourceSha256` изменились, все `imageSha256`
идентичны — соответствует заявленному в хендоффе `docs:accept --identical` (11/11 пиксель-в-пиксель, первое
боевое использование #512).
- Один коммит, трейлеры `Issue: #508` и `User-Visible: yes` на месте; `Release:`/`Baseline-Reviewed:` не
требуются — `demo/golden/baselines/**` не тронут.
- Скоуп не расширен: единственный вызывающий (`summary-panel-editor.ts`) объявляет атрибут, остальные восемь
— нет (проверено и тестом, и построчным чтением).
## Находки
Нет. High: 0, Medium: 0, Low: 0.
## Унаследовано из ревью ТЗ (для полноты трассировки)
Спек-ревью прошло два раунда (`SPEC-REVIEW-508-r1.md` → жёлтый, `SPEC-REVIEW-508-r2.md` → зелёный на
SHA `f02af9330b17a37eea6c8086f4335cbbc7265d36`): M1 (AC3 ссылался на несуществующий golden-префикс
`summary-*`) и M2 (не было раздела «откат») закрыты правкой тела issue. Код-ревью это не переоткрывает —
AC3 в теле issue на момент код-ревью уже опирается только на `smoke_summary_panel_polish`, а раздел
«Откат» присутствует и соответствует факту (opt-in атрибут, ревёрт коммита без миграций).
## Вердикт
Зелёный. Все AC доказаны исполнением (кроме AC3/AC6, не заявляющих защиты — доказаны прямым сравнением),
диагноз независимо подтверждён на реальном стенде HA, оба защитных мутанта лично прогнаны и красные без
патча/зелёные с ним, регресс на нативную ветку и остальные восемь `hp-dialog` исключён и тестом, и
построчным чтением кода. Находок нет.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/508-summary-dialog-scroll`, коммит `7e5d394a936a` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `1594677359b610e22fc5886db02f66bc3c943322`
```
git log --all --format='%H %T' | grep 1594677359b6
```
- Вердикт конвейера: `green` · High 0