Files
houseplan-card/docs/reviews/CODE-REVIEW-594-r2.md
T
2026-09-19 11:08:06 +03:00

202 lines
20 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-594-r2
Issue: #594 · Этап: code · Заход: r2 · блокирующих циклов израсходовано 1 из 4
Материал: `fd33a0dce3b9d7ffdcf37068dc5afc17dccbe39d` (рабочая копия уже на нём,
HEAD detached). `git diff origin/dev...HEAD` — 66 файлов, ветка `issue/594-form-kit-room`,
пять коммитов: `fb361c49` (feat), `bd3f0019` (refactor: типизация ключей заливки),
`63059739` (review-документ r1), `bbaa469c` (test: M2/M3), `fd33a0dc` (test: приёмка
четырёх эталонов).
## Почему разбор полный, а не по дельте
Между применением `S7-code-review` в прошлый раз (комментарий владельца от
2026-09-19 05:49) и этим запуском ветка была ребейзнута конвейером на ушедший
вперёд `dev`: поверх легло 4 чужих коммита (`dcd66575`, `2167d439`, `5b6d5d1d`,
`41722e15` — доработки issue #593 и #595), исходные SHA задачи переписаны
(`c54a7770→bd3f0019`, `ec23a351→bbaa469c`, `0585c1ba→fd33a0dc`, плюс появился
промежуточный `073c45b0/fb361c49`). Это другой код (§7.2 PROCESS.md), поэтому
разбор — полный: прочитан весь `git diff origin/dev...HEAD`, а не только три
находки r1.
Проверил отдельно, что чужие коммиты не задели предмет ревью: `git diff
origin/dev...HEAD` содержит исключительно файлы задачи #594 (`form-kit.*`,
`room-settings-dialog.ts`, i18n, тесты/смоки, changelog, эталоны, dist/D-классы) —
никаких посторонних правок в дифф не просочилось, значит ребейз не изменил
семантику того, что писал автор, только базу.
## Закрытие раунда r1
Код-ревью r1 (комментарий от 2026-09-18T21:36:30Z, документ
`docs/reviews/CODE-REVIEW-594-r1.md`) на материале `c54a7770` дал жёлтый
вердикт, три Medium в скоупе:
| # находка r1 | Чем закрыта | Где это видно | Перепроверено сейчас |
|---|---|---|---|
| M1 — `npm run golden:verify` показывал диф по объявленным сценам, но `demo/golden/baselines/**` в диффе не было: приёмки не произошло | Коммит `fd33a0dc` (после ребейза — прежний `0585c1ba`): 4 PNG + `baselines-index.json` | `git show fd33a0dc --stat`: только класс D; трейлеры `Release: v1.76.0-beta.5`, `Baseline-Reviewed: .../runs/35407468491` на месте | Прогнал `npm run golden:verify` сам (Chromium в песочнице — 151.0.7922.34, совпадает с индексом эталонов, в отличие от песочницы автора). **exit 0, все сцены `passed`**, включая обе объявленные AC7 (`room-temperature-dialog-{desktop-en,mobile-ru}`) и посторонний долг #588 (`device-icon-state-table-{light,dark}`) |
| M2 — отпечаток `docs/images/screenshots.json` устарел (диф трогает `src/**`) | Коммит `bbaa469c`: `npm run docs:accept -- --identical` | `git diff` по `docs/images/screenshots.json`: изменился только `sourceFingerprint`/`sourceSha256` во всех 10 сценариях, ни один `imageSha256` не сдвинулся — пиксели те же | Прогнал `node scripts/check-docs.mjs --screenshots=strict` сам — **зелёный**: «Documentation checks passed» |
| M3 — свидетель AC1 доказывал «пишет только своё поле» лишь для 3 из 8 контролов; `_areaSel`/`_roomFill` не имели исполнимого oracle | Коммит `bbaa469c`: добавлены `areaWritesOnlyItsOwnKey`, `fillWritesOnlyItsOwnKey` в `demo/smoke_room_settings.mjs`, по образцу трёх уже существующих | `git diff` по `demo/smoke_room_settings.mjs` — оба факта на месте, используют тот же снимок-черновика (`draft()`/`changedFields`) | Прогнал `node demo/smoke_room_settings.mjs` сам — **зелёный**, все 13 булевых полей `true`, включая оба новых |
Находка r1 была узко сформулирована («добавить 1–2 проверки по образцу...» — именно
для `_areaSel`/`_roomFill`); `tempSrc` и `labelScale` r1 не называл дефектом
(перепутывание temp/hum ловит мутант `form-kit-writes-to-a-neighbour-key`,
`labelScale` структурно идентичен уже проверенному `nameScale`) — фикс не расширяет
скоуп находки, а закрывает её ровно как сформулировано.
Все три находки закрыты и подтверждены исполнением, а не чтением заявления автора.
## Что проверялось и как (полный AC-разбор)
Материал изучен диффом (`git diff origin/dev...HEAD`), затем гейты прогнаны
руками (сборка `dist`/`custom_components/houseplan/frontend` через
`npm run bundle:sync` пересобралась байт-в-байт идентично закоммиченному —
заодно подтверждает `tsc --noEmit`/`build`, поверх подтверждённого Validate на
этом SHA):
| Команда | Результат |
|---|---|
| `npm run bundle:sync` (build+tsc, затем сверка дерева) | `git status` после — чисто, ни одного расхождения |
| `npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test test/form-kit.test.mjs test/i18n.test.mjs test/i18n-dead-keys.test.mjs test/styles-split.test.mjs` | 43/43 |
| `node --test test/editor-dialog-modules.test.mjs` | 2/2 (набор остаётся в ленивом графе) |
| `node --test test/bundle-assets.test.mjs` | 31/31 |
| `node --test test/single-source-numbers.test.mjs` | 3/3 (диф не заводит второй источник числа) |
| `node scripts/mutation-gate.mjs --id=form-kit-segment-drops-radio-semantics` | поймано 1 из 1 |
| `node scripts/mutation-gate.mjs --id=form-kit-writes-to-a-neighbour-key` | поймано 1 из 1 |
| `npm run bundle:budget` | initial View 291872 Б / потолок 292400 Б, запас 9194 Б (совпадает с цифрой автора день-в-день); предупреждение про низкий общий запас — старый долг #367/#474, к #594 не относится |
| `npm run golden:verify` (Chromium 151.0.7922.34, совпадает с индексом) | **все сцены `passed`**, включая AC7 |
| `node scripts/check-docs.mjs --screenshots=strict` | зелёный |
| `node demo/smoke_room_settings.mjs` | зелёный, все поля включая новые `areaWritesOnlyItsOwnKey`/`fillWritesOnlyItsOwnKey`/`humiditySourceWritesOnlyItsOwnKey` |
| `node demo/smoke_color_picker_consumers.mjs` (AC3, без правок) | зелёный |
| `node demo/smoke_room_temperature_thresholds.mjs` (AC4, без правок) | зелёный |
| `node demo/smoke_help_affordance.mjs` (AC6, расширенный) | зелёный |
| `node demo/smoke_summary_panel.mjs`, `smoke_summary_panel_polish.mjs` (AC9, без правок) | зелёные |
| `node demo/smoke_font_scales.mjs`, `smoke_editor_tabs.mjs` (план автотестов, без правок) | зелёные |
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | 19 «прямых совпадений» — прогнаны названные в ТЗ плюс `smoke_render_parity` (упоминает `cardStyles` напрямую); остальные 15 не прогонялись (см. ниже) |
| `node demo/smoke_backup_transfer.mjs`, `smoke_binding_ui.mjs`, `smoke_general_settings.mjs`, `smoke_size_angle_parity.mjs`, `smoke_space_scale_defaults.mjs` | зелёные (см. отдельное расследование ниже) |
### Отдельное расследование: смоки, использующие общие классы
`smoke-select.mjs` матчит по JS-символам и не увидел совпадения по строковым
CSS-селекторам. Грепом по `demo/*.mjs` нашёл пять смоков, дёргающих классы,
которые диалог комнаты раньше рисовал (`.dispsection`, `.gsrow`, `.colorrow`,
`.dropbtn`, `.droppanel`): `smoke_backup_transfer.mjs`, `smoke_binding_ui.mjs`,
`smoke_general_settings.mjs`, `smoke_size_angle_parity.mjs`,
`smoke_space_scale_defaults.mjs`. Прочитал каждый: все пять открывают ДРУГИЕ
диалоги (настройки, бэкап, привязка устройства, диалог устройства/пространства)
— классы общие по имени, но не по диалогу, поэтому правка `room-settings-dialog.ts`
их не касается структурно. Прогнал все пять — зелёные. Это не было в списке ТЗ
и не входило в «прямое совпадение» инструмента, но стоило проверить именно
потому, что инструмент матчит по символам, а не по строкам-селекторам.
### AC-таблица (все — свежая проверка исполнением или чтением, не наследование)
| AC | Результат | Как подтверждено |
|---|---|---|
| AC1 | 8/8 ключей черновика подтверждены: name/nameScale/humSrc (было в r1), area/fill (добавлено в r2-фиксе), color/thresholds — отдельными AC3/AC4, tempSrc — мутантом (перепутывание с humSrc ловится) | чтение + исполнение смока и мутанта |
| AC2 | запись каждого поля через снимок-черновика; условия «Сохранить» не менялись (код диалога идентичен по условиям `canSaveNew`/`?disabled`) | смок зелёный, диф читаем |
| AC3 | `hp-color-opacity` не подменён, нативных пикеров 0 | смок без правок зелёный |
| AC4 | пороги температуры не изменились | смок без правок зелёный |
| AC5 | сегмент — радиогруппа, цель ≥44px, фокус берётся | смок + unit-тест `form-kit.test.mjs` |
| AC6 | 4 заголовка, у каждого «?» с текстом и aria-label, абзацев не осталось, мёртвых ключей нет | смок + `i18n-dead-keys.test.mjs` (24, было 20) |
| AC7 | ровно 2 объявленные сцены изменились и приняты, остальные (включая 3 сцены панели и все прочие диалоги) — побайтово прежние | `golden:verify` — все `passed`, эталоны в индексе принятые |
| AC8 | размер назван (+516 Б до initial View), лист набора ушёл в ленивый граф, а не в `cardStyles` | `bundle:budget` зелёный под потолком; `grep form-kit` — не встречается в `src/styles.ts`/`houseplan-card.ts`; `styles-split.test.mjs` подтверждает пятёрку каскада неприкосновенной |
| AC9 | `src/summary-panel-*` в диффе отсутствуют | `git diff --stat` — подтверждено; оба смока панели зелёные без правок |
| AC10 | полный набор зелёный | tsc/test/build — подтверждено Validate на этом SHA (ссылка в задаче ревью) + сборка вручную идентична; unit/смоки/мутанты/бандл/golden/docs — прогнаны вручную выше, все зелёные |
| AC11 | генератор с параметрами панели воспроизводит её текущие правила дословно | `test/form-kit.test.mjs` зелёный, плюс лично сверил фрагменты `PANEL_FRAGMENTS` с текстом `src/summary-panel-editor-style.ts` — совпадают |
К1–К7 (контракт поведения) проверены чтением `room-settings-dialog.ts` (см. диф
в анализе): ключи черновика те же, `hp-color-opacity`/событие те же,
`_renderRoomSource` переехал в модуль комнаты как единственный потребитель,
`FILL_CHOICES` типизирован без `as any` (закрывает красный `no-new-any` из
первого пуша), генератор параметризован скоупом/префиксом.
Трейлеры: все 5 коммитов несут `Issue: #594`; `User-Visible: yes` только на
`fb361c49`, и в нём же правки обоих changelog (`docs/CHANGELOG.md`,
`docs/CHANGELOG.ru.md`) и обоих `USER-GUIDE` — проверено `git show --stat` и
чтением текста, терминология («карточки-группы», «?», переключатель
«среднее/выбранный датчик») совпадает с формулировками ТЗ и UX-разделом.
Коммит с эталонами несёт `Release:`/`Baseline-Reviewed:` со ссылкой на реальный
прогон.
## Что проверено и корректно
- Полный контракт К1–К7 и AC1–AC11 — построчно, включая обе фиксовые правки r1.
- Golden — впервые в этом раунде подтверждён личным прогоном (не чтением
утверждения автора): AC7 закрыт полностью, регрессий вне двух объявленных
сцен нет.
- Отпечаток документации — тоже личным прогоном в строгом режиме.
- Пять «скрытых» смоков на общих CSS-классах — прогнаны, регрессий нет.
- Сборка (`bundle:sync`) воспроизводится байт-в-байт из исходников этого SHA —
дифф в `dist/**`/`custom_components/houseplan/frontend/**` не является
посторонним артефактом.
- Размер и архитектурное решение (лист в ленивом графе, `unsafeCSS`,
параметризованный генератор) соответствуют цифрам, названным в хендоффе, и
не расходятся с кодом.
## Чего не проверял и почему
- `npx tsc --noEmit` / `npm test` (весь набор) / `npm run build` как отдельные
команды — не перегонял: Validate на этом SHA (`fd33a0dc`) зелёный, дешёвые
гейты уже подтверждены; вместо этого пересобрал `bundle:sync`, что тоже
прогоняет `tsc --noEmit`+`build` и byte-for-byte сверило дерево — то же самое
доказательство другим путём.
- Полная матрица браузерных смоков (250 файлов) — не прогонял; прогнаны
названные в ТЗ, `smoke_render_parity` (прямое совпадение по `cardStyles`) и
пять найденных вручную по общим CSS-классам. Остальные 15 «прямых
совпадений» инструмента (`smoke_room_autoclose`, `smoke_merge_split`,
`smoke_plan_drawing_repairs`, `smoke_unified_wall_tool`,
`smoke_v8_draft_write`, `smoke_feedback_v2`, `smoke_island_rooms`,
`smoke_junction_limits`, `smoke_wall_face_overlap`,
`smoke_wall_thickness_transition`, `smoke_draw_wall_thickness`,
`smoke_split_polyline`, `smoke_zero_divider_taper`, `smoke_split_nonsnap`) не
прогонял: символы совпадения — это состояние хоста комнаты (`_nameSel`,
`_areaSel`, `_curSpaceCfg`, `_saveRoom`, `_pendingSplit`, `_wallFaceBatch`,
`_roomDialogCancel`), а не что-то, что этот диф меняет — обработчики кнопок
и сигнатуры методов в диффе идентичны прежним (см. `diff2.patch`/`diff3.patch`
анализа), меняется только обёртка разметки внутри `.body`. 31 «слабых связей»
инструмент сам не считает основанием для прогона.
- Полный HA-харнесс, перф-профили — не названы в AC и диф не касается
чувствительных к перфу путей (только разметка/CSS одного диалога).
- `python -m pytest tests_backend` — диф не трогает `custom_components/**/*.py`.
- `npm run invariants` — диф не трогает геометрию/`layout`/`marker.space`/
`open_spans`/записи толщины.
## Находки
Нет. Все три Medium из r1 закрыты и подтверждены исполнением; новых дефектов
в правках r1→r2 (типизация `FILL_CHOICES`, свидетель полей, отпечаток
документации, приёмка эталонов) и в ребейзе не найдено.
## Унаследовано из r1
Формально этот раздел почти пуст: инструкция для ребейза (§7.2) требует полный
разбор, поэтому весь код перепроверен заново своими руками (см. выше), а не
принят на слово документа r1. Единственное, что не переоткрывается сознательно —
продуктовые решения ТЗ, которые не относятся к предмету код-ревью:
- Само распределение полей по четырём группам, названия групп, решение не
трогать ширину диалога и не менять «Сохранить только при изменениях» —
приняты на этапе спецификации (`SPEC-REVIEW-594-r3.md`, зелёный вердикт,
материал — тело issue #594 на момент коммита `073c45b0`/`63059739`). Код-ревью
не переоценивает эти решения, только соответствие им реализации — оно
проверено выше по каждому AC.
## Вердикт
Зелёный. Все находки r1 закрыты и подтверждены исполнением; полный повторный
разбор после ребейза новых дефектов не выявил.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/594-form-kit-room`, коммит `fd33a0dce3b9` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `8fefb135dda0fc5fae81091314c8a16df4fec42c`
```
git log --all --format='%H %T' | grep 8fefb135dda0
```
- Тело issue: `594c7771e678dae203db75069c75806da3eec9c1d9b155a3d7ba2236d338052a`
- Вердикт конвейера: `green` · High 0