26 KiB
CODE-REVIEW-594-r1
Issue: #594 (шаг 1 эпика #591) · Ветка issue/594-form-kit-room · Материал: c54a7770d92469916dedecac7d4595c55bea0cbe (родитель 073c45b0) · Заход r1 · блокирующих циклов до этого раунда: 0/4
Скоуп
Диапазон origin/dev...HEAD (073c45b0..c54a7770), 2 коммита:
bd0cf829feat: общий набор контролов формы и диалог комнаты по нему —User-Visible: yesc54a7770refactor: типизировать ключи заливки вместо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(branchissue/594-form-kit-room, родитель073c45b0) - Дерево: рабочая копия совпадает с этим SHA на момент вывода вердикта (
git rev-parse HEADсверен непосредственно перед публикацией) - Диапазон:
origin/dev...HEAD=073c45b0..c54a7770
Материал раунда
- Ветка:
issue/594-form-kit-room, коммитc54a7770d924— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
59dc67066a10f550047f3d33e6da0fb2a8ed5976git log --all --format='%H %T' | grep 59dc67066a10 - Тело issue:
594c7771e678dae203db75069c75806da3eec9c1d9b155a3d7ba2236d338052a - Вердикт конвейера:
yellow· High 0