mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -0,0 +1,126 @@
|
||||
# CODE-REVIEW-594-r1
|
||||
|
||||
Issue: #594 (шаг 1 эпика #591) · Ветка `issue/594-form-kit-room` · Материал: `c54a7770d92469916dedecac7d4595c55bea0cbe` (родитель `073c45b0`) · Заход r1 · блокирующих циклов до этого раунда: 0/4
|
||||
|
||||
## Скоуп
|
||||
|
||||
Диапазон `origin/dev...HEAD` (`073c45b0..c54a7770`), 2 коммита:
|
||||
|
||||
- `bd0cf829` feat: общий набор контролов формы и диалог комнаты по нему — `User-Visible: yes`
|
||||
- `c54a7770` refactor: типизировать ключи заливки вместо `any` — `User-Visible: no`
|
||||
|
||||
Файлы вне генерируемого (класс D) и трейлеры проверены командой `git diff origin/dev...HEAD` и `git log --format=full`. Классы: A — `src/editors/form-kit.ts` (новый), `src/styles/form-kit.styles.ts` (новый), `src/editors/room-settings-dialog.ts`, `src/houseplan-editor-runtime.ts`, `src/houseplan-card.ts`, `src/i18n/{ru,en,fr,de}.json`; B — `demo/smoke_room_settings.mjs`, `scripts/mutation-registry.mjs`, `test/*.test.mjs`; C — `docs/CHANGELOG*.md`, `docs/USER-GUIDE*.md`. Всё укладывается в скоуп ТЗ (`## ТЗ` → «Скоуп / не-скоуп»); `src/houseplan-card.ts` теряет только тонкую обёртку `_renderRoomSource`, прямое следствие переноса, а не расширение скоупа. Оба коммита несут `Issue: #594` и по одному `User-Visible:`; `bd0cf829` (yes) правит `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` в том же коммите — трейлеры и §11 PROCESS соблюдены.
|
||||
|
||||
ТЗ прошло ревью зелёным на r3 (`SPEC-REVIEW-594-r3.md`, High: 0, Medium: 0), обе Low-находки закрыты, i18n-таблица полна. Материал кода не редактировал тело issue после этого — вопросов к материалу ТЗ нет.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Инструкция разрешила не перегонять `typecheck`/`test`/`build` — Validate на `c54a7770` зелёный (https://github.com/Matysh/houseplan-card/actions/runs/35394685119). Проверено отдельно, что это действительно материал ревью: `headSha` прогона == `c54a7770d92469916dedecac7d4595c55bea0cbe`. Тем не менее часть работы требовала свежего локального дерева (браузерные смоки, golden, docs), поэтому `npm run build`/`npm run bundle:sync` выполнялись как побочный эффект — без отклонений от закоммиченного бандла (`git status` после сборки — чисто, дерево дистрибутива побайтово совпало).
|
||||
|
||||
| Гейт | Результат | Почему |
|
||||
|---|---|---|
|
||||
| `npx tsc --noEmit`, `npm run build` | не гонял отдельно — уже в Validate `c54a7770`; но `npm run build`/`bundle:sync` выполнялись для смоков/golden и прошли чисто (`tsc --noEmit && rollup`) | дешёвый гейт подтверждён CI |
|
||||
| `npm test` (весь набор) | не гонял целиком — подтверждён Validate (`Фронтенд: типы, юниты, мутанты, синхрон бандла` = success); прогнал вручную целевой поднабор | см. ниже |
|
||||
| `node --test test/i18n.test.mjs test/i18n-dead-keys.test.mjs test/form-kit.test.mjs test/styles-split.test.mjs` | 43/43 pass | ключевые тесты этой задачи, включая замороженную фикстуру AC11 |
|
||||
| `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | «новых any нет» | подтверждает решение коммита `c54a7770` |
|
||||
| `npm run bundle:budget` | initial View 291872 Б (потолок 292400±2000), запас 528 Б; сверено с `origin/dev` (291356 Б, запас 1044 Б) — рост ровно +516 Б, как заявлено | AC8 |
|
||||
| `node scripts/mutation-gate.mjs --id=form-kit-writes-to-a-neighbour-key` | `поймано 1 из 1` | AC2 защитный столбец «чем краснеет» |
|
||||
| `node scripts/mutation-gate.mjs --id=form-kit-segment-drops-radio-semantics` | `поймано 1 из 1` | AC5/К7 защитный столбец |
|
||||
| `node demo/smoke_room_settings.mjs` | все ключи `out` truthy, `OK` | AC1/AC2/AC5/AC6, включая новые `nameWritesOnlyItsOwnKey`, `scaleWritesOnlyItsOwnKey`, `humiditySourceWritesOnlyItsOwnKey`, `everyGroupHasHelp`, `sourceSegment*` |
|
||||
| `node demo/smoke_color_picker_consumers.mjs`, `smoke_room_temperature_thresholds.mjs`, `smoke_help_affordance.mjs`, `smoke_summary_panel.mjs`, `smoke_summary_panel_polish.mjs`, `smoke_font_scales.mjs`, `smoke_editor_tabs.mjs` | все `OK` | AC3, AC4, AC6 (доп.), AC9, регресс на соседей из «прямого совпадения» `smoke-select` |
|
||||
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | 19 «прямых совпадений» (все выше плюс 12 геометрических/декоровых смоков, не относящихся к форме — не гонял: символы `_curSpaceCfg`/`_nameSel` совпадают случайно, диалог настроек комнаты их не трогает по логике письма) + 31 «слабых связей» по `_curSpaceCfg` (не гонял — общий контекстный символ, форма не меняет геометрию/decor) | выбор смоков соразмерно диффу |
|
||||
| `npm run golden:verify` (полный линуксовый набор, Chromium 151.0.7922.34 — совпадает с индексом эталонов) | **два расхождения сверх объявленных** — см. находку M1 | AC7 |
|
||||
| `npm run docs:accept -- --identical` (диагностика, изменения отменены) | все 11 кадров документации побайтово совпали; принимать нечего, кроме отпечатка исходников | AC/Release-артефакты, см. находку M2 |
|
||||
| `python -m pytest tests_backend` | не гонял — диф не касается `custom_components/**/*.py` | не применимо |
|
||||
| перф-профили | не гонял — диф не касается `src/iso-*`, `src/live-*`, `src/render-*`, `houseplan-render-lifecycle.ts`, `houseplan-card.ts` рендер-путей (только удаление тонкой обёртки); лист набора — в ленивом графе, новых проходов по данным нет (AC8 подтверждает размер, не время) | вне AC, вне названных путей |
|
||||
| `git worktree` на `origin/dev` (073c45b0) — контрольный `bundle:budget` и `golden:verify` | воспроизвёл то же самое: заголовок headroom-предупреждения и «different» `device-icon-state-table-{light,dark}` уже были на `dev` до этой задачи | атрибуция находок ниже |
|
||||
|
||||
Не гонял: полную матрицу смоков (250 файлов) — задача не задевает всё; `pytest`; perf-профили (не применимо по путям). Обоснование выбора приведено в таблице.
|
||||
|
||||
## Находки
|
||||
|
||||
### Medium (в скоупе задачи) — M1: AC7 не закрыт до конца — эталоны не приняты
|
||||
|
||||
`npm run golden:verify` на полном линуксовом артефакте (Chromium 151.0.7922.34, совпадает с `baselineManifest.chromium` в эталонах — то самое несоответствие 152/151, которое автор не смог обойти в песочнице, здесь не воспроизводится) даёт:
|
||||
|
||||
```
|
||||
different device-icon-state-table-light
|
||||
different device-icon-state-table-dark
|
||||
different room-temperature-dialog-desktop-en
|
||||
different room-temperature-dialog-mobile-ru
|
||||
```
|
||||
|
||||
Две последние — ровно объявленные AC7 сцены, и визуально они корректны (см. `artifacts/golden/actual/room-temperature-dialog-{desktop-en,mobile-ru}.png` — карточки «Basics/Fill/Sensor sources», «Основное/Заливка», сегмент источника, значения и порядок полей совпадают с ТЗ). Две первые (`device-icon-state-table-*`) я атрибутировал отдельно: контрольный прогон `golden:verify` на чистом `origin/dev` (`073c45b0`, до этой задачи) даёт **то же самое** расхождение по этим двум сценам — это уже существующий, не связанный с #594 дефект (сцена добавлена под #588; регрессия чужая, трогать её в этой задаче нельзя, а заводить отдельный issue — не моя роль корректировать чужой скоуп находкой этого ревью, если только автор сам не поднимет вопрос; это pre-existing и не входит в блокирующий счёт).
|
||||
|
||||
Проблема в другом: **AC7 требует не только «диф ограничен объявленными сценами» (это так), но и того, что эталоны для этих двух сцен пересняты и приняты** («Release-артефакты»: «эталоны двух объявленных сцен»). В диффе `git diff origin/dev...HEAD --stat` **нет ни одного изменения `demo/golden/baselines/**`** — новые кадры не приняты, коммит не несёт `Release:`/`Baseline-Reviewed:`. Если эту ветку слить как есть, `room-temperature-dialog-{desktop-en,mobile-ru}` останутся красными на первом же тяжёлом прогоне (кандидат беты, nightly) — притом что расхождение полностью предсказано и ожидаемо, не «дефект, который не мог проявиться раньше» (§11.4 сюда не подходит).
|
||||
|
||||
**Чем краснеет:** `npm run golden:verify` на полном Linux-артефакте — уже красное на этом материале.
|
||||
**Правка:** принять новые эталоны `npm run golden:accept -- --reviewed <ссылка на зелёный тяжёлый Validate/Docs-прогон>` с коммитом, несущим `Release:`/`Baseline-Reviewed:` (§10.1, правило 13/PROCESS.md), и запушить перед повторным заходом на код-ревью.
|
||||
|
||||
### Medium (в скоупе задачи) — M2: отпечаток скриншотов документации не обновлён
|
||||
|
||||
`node scripts/check-docs.mjs` красит: `ERROR screenshot source fingerprint is stale`. Диф трогает `src/**`, поэтому по правилу «выбирать тут нечего» (PROCESS.md §8, прецеденты #230/#234/#237) это ожидаемо и обязательно к починке **до** слияния — иначе `dev` получает красный job `docs` до следующей задачи, как уже дважды случалось. Проверил, что дело именно в отпечатке, а не в реальном визуальном расхождении: `npm run docs:accept -- --identical` (прогнал как диагностику, изменения в рабочей копии затем отменил) — «Все 11 кадров попиксельно совпали с закоммиченными: принят только отпечаток исходников». Диалог настроек комнаты действительно не открывает ни один из 10 сценариев `demo/docs` (ТЗ это верно называет), поэтому фикс тривиален и не требует пересъёмки на CI.
|
||||
|
||||
**Чем краснеет:** `node scripts/check-docs.mjs` — красное сейчас.
|
||||
**Правка:** `npm run docs:accept -- --identical` и закоммитить обновлённый `docs/images/screenshots.json` (класс C, не требует отдельного issue — часть DoD этой задачи).
|
||||
|
||||
### Medium (в скоупе задачи) — M3: AC1 доказан у́же, чем заявлено
|
||||
|
||||
ТЗ обещает для AC1 «новый unit `test/form-kit.test.mjs` — таблица «контрол → ключ → записанное значение» на фейковом хосте» для восьми полей: имя, область, режим заливки, цвет, пороги, два источника, два масштаба. По факту `test/form-kit.test.mjs` содержит три теста другого назначения (дословность CSS-фикстуры панели — AC11, параметризация имён генератора, радиосемантика сегмента — AC5/К7); таблицы «控трол → ключ» там нет вовсе. Контракт «пишет только своё поле» вместо этого проверяет расширенный `demo/smoke_room_settings.mjs`, но только для 3 из 8 полей: `name`, `nameScale`, `humSrc` (`nameWritesOnlyItsOwnKey`, `scaleWritesOnlyItsOwnKey`, `humiditySourceWritesOnlyItsOwnKey`). Цвет и пороги закрыты отдельно неизменными `smoke_color_picker_consumers.mjs`/`smoke_room_temperature_thresholds.mjs` (AC3/AC4 — это честно). Но **область (`_areaSel`) и режим заливки (`_roomFill`, включая только что перетипизированный `FILL_CHOICES`) не имеют исполнимого oracle вообще** — ни unit, ни smoke не кликает по `<select class="areasel">` или по радио заливки и не проверяет, что меняется ровно ожидаемое поле. `tempSrc` тоже не проверен напрямую (только `humSrc`, хотя обработчик один и тот же для обоих `kind`, риск частично перекрыт мутантом `form-kit-writes-to-a-neighbour-key`, который ловит именно перепутывание temp/hum).
|
||||
|
||||
Прочитал код (не исполнением) для `_areaSel` и `FILL_CHOICES`: логика письма побайтово перенесена из прежней версии (`this.host._areaSel = ...`, `this.host._roomFill = value; if (value !== 'custom') this.host._roomCustomFill = null;`), только тип пары `[value, key]` заменён на явный `RoomFillChoice`; риск регрессии от этого рефакторинга оцениваю как низкий, но это моё суждение по чтению, а не доказанный автотестом факт, а соразмерный исполнимый oracle здесь дёшев — по образцу уже добавленных в тот же смок трёх проверок.
|
||||
|
||||
**Чем краснеет:** сейчас ничем — пробел, а не красный тест.
|
||||
**Правка:** либо добавить в `demo/smoke_room_settings.mjs` аналогичные `areaWritesOnlyItsOwnKey`/`fillWritesOnlyItsOwnKey` (пара строк по образцу уже написанных), либо явно закрыть AC1 записью «проверено чтением, не исполнением» для этих двух полей в хендоффе — сейчас в хендоффе об этом сужении не сказано ни слова, и заявлено «AC1 | три теста» так, как будто покрытие полное.
|
||||
|
||||
### Low — не блокирует
|
||||
|
||||
- Автор в хендоффе не упомянул `npm run docs:accept` вовсе (ни как сделанное, ни как «не сделано») — при том что это явно требование Release-артефактов ТЗ. Раздел «Не прогонял» перечисляет `golden:verify`, полный HA-харнесс, перф-профили и полную матрицу смоков, но не docs. Снимаю как Low (входит в M2 по существу), фиксирую отдельно только как процессное наблюдение: список «не сделано» в хендоффе не был полным.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- **К1 (запись не меняется).** Прочитан весь новый `renderRoomSettingsDialog` (`src/editors/room-settings-dialog.ts`) построчно против версии на `origin/dev`: все восемь ключей черновика (`_nameSel`, `_areaSel`, `_roomFill`/`_roomCustomFill`, `_roomTempMin`/`_roomTempMax` через неизменный `roomTemperatureControls`, `_roomTempSrc`/`_roomHumSrc` через вынесенный `renderRoomSource`, `_roomNameScale`/`_roomLabelScale`) читают и пишут те же поля хоста, что и до рефакторинга — код по существу перенесён, а не переписан. Новых ключей конфигурации нет.
|
||||
- **К2 (условия сохранения).** `canSaveNew` и `?disabled=${!this.host._nameSel.trim() || !tempValid}` — байт в байт как в `origin/dev`; «активно только при изменениях» в диффе не появилось.
|
||||
- **К3 (цвет).** `hp-color-opacity` не подменён, событие `hp-color-opacity-change` и атомарная запись цвета+прозрачности — без изменений; `colorRow()` — чисто раскладочная обёртка. `smoke_color_picker_consumers.mjs` (без правок) зелёный.
|
||||
- **К4 (пояснения).** Все четыре заголовка групп используют `this._help('room.group_*.help'|'room.sizes_section.help')`, чья сигнатура (`houseplan-editor-runtime.ts:1263`) принимает только `${string}.help` и сама достраивает `.aria` — паттерн `hp-help` соблюдён, подтверждено `smoke_help_affordance.mjs` (без правок) и новой проверкой `everyGroupHasHelp` в `smoke_room_settings.mjs`.
|
||||
- **К5 (набор общий, не карточка-специфичный).** `form-kit.ts` не содержит ни одной ссылки на `_room*`; все операции параметризованы (`FormCardOptions`, `FormRowOptions`, `SegmentedOptions<T>`, `ColorRowOptions`). Стили — чистый генератор строки (`formKitCss(options, flags)`), без побочных эффектов.
|
||||
- **К6 (панель не тронута).** `git diff --stat origin/dev...HEAD` не содержит ни одного `src/summary-panel-*`; `smoke_summary_panel.mjs` и `smoke_summary_panel_polish.mjs` (без правок) зелёные.
|
||||
- **К7 (доступность сегмента).** `segmented()` рендерит настоящую `role="radiogroup"` с `<input type="radio" name=...>`; `test/form-kit.test.mjs` статически проверяет разметку, `demo/smoke_room_settings.mjs` — реальные размеры цели (≥44px) и фокус в браузере; мутация `type="radio"→"checkbox"` ловится (`поймано 1 из 1`).
|
||||
- **AC8 (бюджет).** Число совпадает с заявленным (+516 Б до 291872, потолок 292400±2000); лист набора действительно не входит в `cardStyles` (`test/styles-split.test.mjs`, «issue 266 the aggregator is exactly the five surface files» — не тронут) и вносится в теневой корень отдельно (`ensureFormKitStyles`, `adoptedStyleSheets`/фолбэк на `<style>`); новый тест «adds selectors instead of overriding» подтверждает отсутствие коллизий с существующими правилами каскада.
|
||||
- **AC11.** Тест сверяет фикстуру с живым файлом `src/summary-panel-editor-style.ts` (а не с самим собой), и это осмысленно устраняет риск «фикстура тихо разошлась с панелью» — прогнал отдельно, зелёный.
|
||||
- **`no-new-any`.** Причина рефакторинга во втором коммите (типизация `FILL_CHOICES` вместо `_t(k as any)`) реальна и устраняет `any`, а не прячет его; гейт подтверждён локально.
|
||||
- **i18n.** Все 4 локали (ru/en/fr/de) содержат все 11 новых ключей и не содержат удалённого `room.settings_section`; `test/i18n-dead-keys.test.mjs` обновлён с явным обоснованием числа (20→24); `test/i18n.test.mjs` получил осознанный тест на «единый хозяин help у карточек набора» вместо ослабления старой проверки.
|
||||
- **Changelog/USER-GUIDE.** Оба changelog и оба User-Guide правлены в коммите `bd0cf829` (`User-Visible: yes`), терминология («Основное», «Заливка», «Источники», «Размеры шрифтов») совпадает с i18n-ключами и с фактическим текстом на золотых кадрах.
|
||||
- **Классы риска §2.6.** async — не применимо (нет асинхронных операций). Данные/права — запись в те же ключи черновика, прав не касается (см. К1). Геометрия — не применимо. Визуал — главный риск, покрыт AC7 (см. M1: диф ограничен объявленными сценами, но эталоны не приняты). Объём/perf — AC8 подтверждён числом. Host/input — клавиатура и цели нажатия подтверждены смоком в браузере.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Полную матрицу из 250 golden-сцен и всех 250/~19 «слабо связанных» смоков — диф не задевает геометрию, decor, junction-логику; выборка по `smoke-select.mjs` и прямому совпадению признана достаточной.
|
||||
- `pytest tests_backend` — диф не касается `custom_components/**/*.py`.
|
||||
- Perf-профили — диф не в путях, которые их запускают, и лист стилей — в ленивом графе без новых проходов по данным.
|
||||
- Полный HA-харнесс (WSL) — не требуется этим диффом, автор тоже не прогонял.
|
||||
- Причину существующего расхождения `device-icon-state-table-{light,dark}` не расследовал за пределами атрибуции «не введено этой задачей» (подтверждено идентичным результатом на `origin/dev` до #594) — это чужой, добавленный под #588 дефект, вне скоупа этого ревью.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Жёлтый. High: 0, Medium: 3 (все в скоупе задачи — M1 приёмка эталонов, M2 отпечаток скриншотов, M3 неполное покрытие AC1 для двух полей). Реализация сама по себе соответствует ТЗ построчно и по поведению (К1–К7, AC1–AC6, AC8, AC9, AC11 подтверждены исполнением или чтением с явной пометкой), но задача не может считаться завершённой, пока не закрыты M1/M2 (иначе `dev` получает предсказуемо красные `golden`/`docs` гейты — ровно тот сценарий, из-за которого #230/#234/#237 попали в процесс) и не уточнено покрытие M3.
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- SHA материала: `c54a7770d92469916dedecac7d4595c55bea0cbe` (branch `issue/594-form-kit-room`, родитель `073c45b0`)
|
||||
- Дерево: рабочая копия совпадает с этим SHA на момент вывода вердикта (`git rev-parse HEAD` сверен непосредственно перед публикацией)
|
||||
- Диапазон: `origin/dev...HEAD` = `073c45b0..c54a7770`
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/594-form-kit-room`, коммит `c54a7770d924` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `59dc67066a10f550047f3d33e6da0fb2a8ed5976`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 59dc67066a10
|
||||
```
|
||||
- Тело issue: `594c7771e678dae203db75069c75806da3eec9c1d9b155a3d7ba2236d338052a`
|
||||
- Вердикт конвейера: `yellow` · High 0
|
||||
Reference in New Issue
Block a user