Files
houseplan-card/docs/reviews/CODE-REVIEW-594-r3.md
claude[bot] ad20e23cc4
Проверка (CI) / Классификация изменённых файлов (push) Successful in 36s
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 47s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Failing after 1m1s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 45s
docs: review document for #594
Issue: #594
User-Visible: no
2026-09-19 08:35:45 +00:00

26 KiB
Raw Permalink Blame History

CODE-REVIEW-594-r3

Issue: #594 (шаг 1 эпика #591) · Ветка issue/594-form-kit-room · Заход: r3 · блокирующих циклов израсходовано 1 из 4 (зелёный r2 бюджет не потратил, #227)

Материал: 2fd166f955d21f33171720e2e12136d57a104a0d (рабочая копия уже на нём, HEAD detached). git diff origin/dev...HEAD — 67 файлов, ветка issue/594-form-kit-room, шесть коммитов на текущей вершине origin/dev (e249e3db): 4344ede1 (feat), a9dade3e (refactor: типизация ключей заливки), b53cca01 (review-документ r1), 44c3f0ab (test: M2/M3 из r1), 74388fb3 (test: приёмка четырёх эталонов), 2fd166f9 (review-документ r2).

Почему разбор полный, а не по дельте

Между зелёным вердиктом r2 (материал fd33a0dce3b9…, дерево 8fefb135dda0…) и этим запуском произошло два административных события, не код-правки:

  1. Слияние r2 дважды не удалось по вине инструментов, не задачи — test/release-gate.test.mjs спал на вершине-приёмке эталонов (#595) и merge-candidate.mjs падал по ENOBUFS на диффе с тремя копиями бандла (#596). Оба разобраны и починены отдельными инфраструктурными issue.
  2. Ветка дважды ребейзнута на ушедший вперёд dev (сначала на слияние #595, затем на слияние #596), и материал переписал SHA пять раз подряд (fd33a0dc→…→2fd166f9).

По PROCESS.md §2.10 ребейз на ушедший вперёд dev — другой код, разбор обязан быть полным, а не по трём находкам r1/закрытию r2. Поэтому я прочитал весь git diff origin/dev...HEAD заново своими руками, перепрогнал защитные тесты и мутанты лично, и отдельно проверил самое рискованное место — не подменил ли двойной ребейз доказательство AC7 (см. ниже).

Проверено, что содержимое не изменилось, а не только переписаны SHA. Файловый список диффа origin/dev...HEAD идентичен файловому списку, которым оперировал r2 (66 файлов + сам документ CODE-REVIEW-594-r2.md, добавленный после зелёного вердикта — ожидаемо). Хеш-суффиксы контент-адресованных чанков бандла (houseplan-card-BMVLvcks.js и т. д.) и initialViewGzipBytes: 291872 — те же числа и те же имена файлов, что фигурировали в диффе кандидата ДО первой (неудавшейся) попытки слияния — контент-адресованное имя не могло совпасть, если бы исходники хоть на байт разошлись. Это не заменяет чтение диффа (оно сделано полностью ниже), но подтверждает: два ребейза не задели ни строки продукта.

Скоуп

Классы: 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/{form-kit,i18n,i18n-dead-keys,styles-split}.test.mjs; C — docs/CHANGELOG*.md, docs/USER-GUIDE*.md; D — dist/**, custom_components/houseplan/frontend/**, demo/golden/baselines/**. Всё укладывается в скоуп ТЗ (раздел «Скоуп / не-скоуп»); src/houseplan-card.ts и src/houseplan-editor-runtime.ts теряют только тонкую обёртку _renderRoomSource (прочитан полный дифф — только удаление, оба хелпера _roomSrcCandidates/_roomSrcLabel, которыми пользуется новый модуль, остались на месте в ядре).

Все шесть коммитов несут Issue: #594; User-Visible: yes только на 4344ede1, и в нём же правки docs/CHANGELOG.md, docs/CHANGELOG.ru.md, docs/USER-GUIDE.md, docs/USER-GUIDE.ru.md (проверено git show --stat — 4 файла, 32 вставки). Коммит с эталонами (74388fb3) несёт Release: v1.76.0-beta.5 и Baseline-Reviewed: …/runs/35407468491.

ТЗ прошло ревью зелёным на r3 (SPEC-REVIEW-594-r3.md, High 0, Medium 0).

Как проверялось

Гейт Результат Как
Validate на этом SHA success (https://github.com/Matysh/houseplan-card/actions/runs/35431398726, headSha сверен) подтверждён; но джобы Golden/Смоки: все шарды/Перф-смок в НЁМ skipped (переиспользованы) — см. отдельное расследование ниже, я не принял это на веру
npx tsc --noEmit, npm run build, npm run bundle:sync git status после — чисто, дерево dist/custom_components/houseplan/frontend байт-в-байт то же прогнал сам
npm test (весь набор) 2781 tests, 2780 pass, 0 fail, 1 skipped прогнал сам
node --test test/form-kit.test.mjs test/i18n.test.mjs test/i18n-dead-keys.test.mjs test/styles-split.test.mjs test/editor-dialog-modules.test.mjs test/bundle-assets.test.mjs test/single-source-numbers.test.mjs 79/79 прогнал сам
node scripts/no-new-any.mjs --base origin/dev --head HEAD «новых any нет» (518 строк в 3 файлах) прогнал сам
npm run bundle:budget initial View 291872 Б / потолок 292400±2000, запас 9194 Б прогнал сам, число совпадает с хендоффом и r1/r2
node scripts/mutation-gate.mjs --id=form-kit-writes-to-a-neighbour-key «поймано 1 из 1» прогнал сам
node scripts/mutation-gate.mjs --id=form-kit-segment-drops-radio-semantics «поймано 1 из 1» прогнал сам
node demo/smoke_room_settings.mjs, 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 прогнал сам (AC1–AC6, AC9)
node scripts/smoke-select.mjs --base origin/dev --head HEAD 19 прямых совпадений, 31 слабая связь прогнал сам
node demo/smoke_render_parity.mjs, smoke_backup_transfer.mjs, smoke_binding_ui.mjs, smoke_general_settings.mjs, smoke_size_angle_parity.mjs, smoke_space_scale_defaults.mjs все OK прогнал сам — прямое совпадение по cardStyles плюс пять смоков r2 нашёл вручную по общим CSS-классам (.dispsection/.gsrow/.colorrow/.dropbtn/.droppanel); перепрогнал независимо, регрессий нет
node scripts/check-docs.mjs --screenshots=strict «Documentation checks passed» прогнал сам
npm run gate:small упало: 0 прогнал сам
npm run golden:verify не прогонял в песочнице — Chromium здесь 152.0.7977.0, индекс эталонов пришпилен к 151.0.7922.34 (тот же барьер среды, что у автора); прогон дал бы шум, а не доказательство вместо этого — расследование по реальному прогону CI, см. ниже
python -m pytest tests_backend не гонял диф не касается custom_components/**/*.py
npm run invariants не гонял диф не трогает геометрию/layout/marker.space/open_spans/записи толщины
перф-профили сверх bundle:budget не гонял диф не в src/iso-*/src/live-*/src/render-*/houseplan-render-lifecycle.ts/рендер-путях houseplan-card.ts (только удаление тонкой обёртки); AC8 про размер, не про время

Отдельное расследование: AC7 после ребейза — реальный прогон или молчаливое доверие?

Validate на материале 2fd166f9 показывает джобы Golden-кадры против принятых эталонов, Перф-смок, Смоки: все шарды как skipped — переиспользование по контент-ключу (job «Переиспользование: это дерево уже проверено», #208/#573). Это ровно тот случай, где нельзя просто прочитать «success» и пойти дальше: reuse доказывает совпадение ключа, а не то, что где-то golden действительно прошёл, а не тоже был переиспользован из чего-то более раннего и слабого.

Проверил цепочку руками (gh run view --job … --log):

  • Reuse-job печатает для golden: source_run=35426189502, source_sha=fd33a0dce3b9d7ffdcf37068dc5afc17dccbe39d — это ровно материал зелёного r2.
  • Прогон 35426189502 (push, заголовок «test: принять четыре эталона, которые изменили #594 и #588», headSha=fd33a0dc) — не reuse, а реальное исполнение: Golden-кадры против принятых эталонов там success (не skipped), как и Перф-смок. Этот прогон получил heavy=true не через full=true/PR, а потому что fd33a0dc сам несёт трейлер Release: v1.76.0-beta.5 — условие «heavy» из AGENTS.md сработало на обычном push.
  • Итог: между материалом r3 (2fd166f9) и последним реально исполненным зелёным golden-прогоном (fd33a0dc) дистанция — один ребейз-нейтральный докс-коммит (сам r2, 2fd166f9) плюс перебазирование на dev без единого изменения в продуктовых файлах или demo/golden/**. Ключ golden в scripts/check-inputs.mjs явным корнем включает demo/golden/** (то есть и сами PNG, и индекс), поэтому совпадение ключа — не совпадение по недосмотру, а доказательство, что кадры и рендер идентичны.

AC7 остаётся доказанным настоящим исполнением (35426189502, зелёный), а не цепочкой из одних reuse-меток без реального прогона внутри неё. Отдельно прочитал git diff по demo/golden/baselines/baselines-index.json — сменились хеши ровно четырёх сцен (room-temperature-dialog-{desktop-en,mobile-ru} — AC7; device-icon-state-table-{light,dark} — долг #588, признанный чужим ещё в r1 контрольным прогоном на чистом origin/dev), остальные хеши в индексе не менялись.

Закрытие раунда r2

r2 (материал fd33a0dce3b9…) дал зелёный вердикт, High 0, Medium 0, находок не было — закрывать нечего. Раунд r1 (жёлтый, три Medium) был закрыт уже внутри r2 (таблица в CODE-REVIEW-594-r2.md); я не переоткрываю её, но пересчитал результат независимо в этом раунде по тем же местам:

Находка r1 Где сейчас в дереве 2fd166f9 Проверено в r3
M1 — эталоны не приняты demo/golden/baselines/{room-temperature-dialog-desktop-en,room-temperature-dialog-mobile-ru}.png + индекс, коммит 74388fb3 реальный зелёный CI-прогон 35426189502 на этом содержимом (см. выше), плюс личное чтение диффа индекса
M2 — отпечаток документации устарел docs/images/screenshots.json, коммит 44c3f0ab node scripts/check-docs.mjs --screenshots=strict — зелёный, прогнал сам
M3 — AC1 уже заявленного areaWritesOnlyItsOwnKey/fillWritesOnlyItsOwnKey в demo/smoke_room_settings.mjs, коммит 44c3f0ab прочитал код проверок (строки 203–225) и прогнал смок сам — оба факта true

Унаследовано из r2

Как и r2 унаследовал из r1 (полный повторный разбор после ребейза — не принятие на слово), я не переоткрываю только продуктовые решения ТЗ, которые не относятся к предмету код-ревью:

  • Разбивка полей по четырём группам, их названия, решение не менять ширину диалога и не трогать «Сохранить только при изменениях» — приняты на этапе спецификации (SPEC-REVIEW-594-r3.md, зелёный вердикт). Код-ревью проверяет соответствие реализации этим решениям (сделано по каждому AC ниже), а не сами решения.

Всё остальное — К1–К7, AC1–AC11, гейты, мутанты, i18n, changelog — перепроверено в этом раунде своими руками (см. таблицы выше), а не принято по документу r2.

AC/К-таблица (перепроверено чтением и исполнением)

# Результат Как подтверждено в r3
К1 все 8 ключей черновика (_nameSel, _areaSel, _roomFill/_roomCustomFill, _roomTempMin/_roomTempMax, _roomTempSrc/_roomHumSrc, _roomNameScale/_roomLabelScale) читают/пишут те же поля хоста прочитан весь room-settings-dialog.ts (222 строки)
К2 canSaveNew/`?disabled=${!nameSel.trim()
К3 hp-color-opacity, событие hp-color-opacity-change, атомарная запись {c,a} — не подменены чтение + smoke_color_picker_consumers.mjs зелёный
К4 все 4 заголовка групп используют this._help('room.group_*.help'|'room.sizes_section.help') чтение + smoke_help_affordance.mjs, everyGroupHasHelp зелёные
К5 form-kit.ts не содержит ссылок на _room*; параметризован FormCardOptions/SegmentedOptions<T>/ColorRowOptions прочитан весь form-kit.ts (140 строк)
К6 src/summary-panel-* в диффе отсутствуют git diff --name-only — подтверждено; оба смока панели зелёные без правок
К7 segmented() рендерит role="radiogroup" + <input type="radio">; мутация на checkbox красит form-kit.test.mjs прочитано + поймано 1 из 1
AC1 8/8: name/nameScale/humSrc (смок), area/fill (смок), color/thresholds (AC3/AC4 отдельно), tempSrc — мутантом (перепутывание с humSrc ловится) прочитан код проверок + прогнаны demo/smoke_room_settings.mjs и оба мутанта
AC2 условия «Сохранить» не менялись; запись каждого поля — снимком черновика смок зелёный, К2 подтверждает диф
AC3 hp-color-opacity на месте, нативных пикеров 0 смок без правок зелёный
AC4 пороги температуры не изменились смок без правок зелёный
AC5 сегмент — радиогруппа, цель ≥44px, фокус берётся смок + form-kit.test.mjs (min-height 44px в CSS, role="radiogroup" в разметке)
AC6 4 заголовка с «?», абзацев не осталось, мёртвых ключей нет смок + i18n-dead-keys.test.mjs (9/9 в общем прогоне), i18n во всех 4 локалях
AC7 ровно 2 объявленные сцены изменились и приняты, панель и прочее — прежние реальный зелёный CI-прогон 35426189502 + чтение диффа индекса (см. расследование выше)
AC8 размер назван, лист — в ленивом графе bundle:budget (291872/292400, запас 9194) + grep form-kit вне cardStyles/houseplan-card.ts; styles-split.test.mjs подтверждает пятёрку каскада нетронутой
AC9 панель не тронута git diff --name-only + оба смока панели зелёные
AC10 полный набор зелёный npm test (2780/2780), gate:small (0 упало), Validate success на этом SHA
AC11 генератор с параметрами панели воспроизводит правила панели дословно form-kit.test.mjs + отдельно сверил все 5 фрагментов теста построчным grep по живому src/summary-panel-editor-style.ts — совпадают, тест не сравнивает генератор сам с собой

Защитная таблица «чем краснеет» (§2.7):

AC/К Чем доказан Чем краснеет (мутация → результат)
AC2 (сосед не пишется) demo/smoke_room_settings.mjs form-kit-writes-to-a-neighbour-key (temp↔hum) — поймано 1 из 1, прогнал лично
AC5/К7 (радиосемантика сегмента) test/form-kit.test.mjs form-kit-segment-drops-radio-semantics (radio→checkbox) — поймано 1 из 1, прогнал лично
AC11 (дословность фикстуры) test/form-kit.test.mjs тест сверяет фикстуру с живым файлом панели — расхождение красит оба ассерта; сам факт совпадения фрагментов с summary-panel-editor-style.ts проверен построчным grep, не единственным прогоном теста

Классы риска §2.6

async — не применимо. Данные/права — запись в те же ключи черновика (К1), прав не касается. Геометрия — не применимо, диф не трогает layout/ marker.space/open_spans/толщины (инварианты не запускал обоснованно). Визуал — главный риск, закрыт реальным CI-прогоном golden (см. выше), диф ограничен ровно двумя объявленными сценами. Объём/perf — AC8 подтверждён числом лично; новых проходов по данным нет, лист CSS — статическая строка. Host/input — клавиатура и цель нажатия ≥44px подтверждены в браузерном смоке лично.

Одно число — один источник. Единственная пара «то же число дважды на экране» в диффе — Math.round(this.host._roomNameScale * 100)}% рядом со слайдером (масштаб имени/подписи): и позиция слайдера, и подпись % читают одно и то же поле хоста в одном выражении, третьего значения нет. Структура идентична уже существовавшему коду (диф это подтверждает — только сдвиг отступа), новой развилки чисел диф не вносит. test/single-source-numbers.test.mjs прогнан лично (в составе общего прогона, 3/3).

Находки

Нет. High 0, Medium 0. Полный независимый разбор диффа, всех AC/К, обоих мутантов, гейтов typecheck/build/test/bundle/docs/budget и AC7 (через проверку цепочки реального CI-исполнения, а не одной метки «reused») не выявил дефектов ни в самом коде, ни в различиях между материалом r2 и r3.

Чего не проверял

  • npm run golden:verify лично в этой песочнице — Chromium 152.0.7977.0 не совпадает с индексом эталонов (151.0.7922.34); вместо личного прогона — прослежена цепочка reuse до реального зелёного исполнения на материале r2 (см. расследование выше), это не слабее личного прогона с правильным Chromium, только доказательство другим путём.
  • python -m pytest tests_backend — диф не касается custom_components/**/*.py.
  • npm run invariants — диф не трогает геометрию/layout/marker.space/ open_spans/записи толщины.
  • Perf-профили сверх bundle:budget — диф не в путях, чувствительных к времени кадра (только markup/CSS одного диалога, лист CSS статический).
  • Полный HA-харнесс (WSL/fcntl) — недоступен в среде ревью, не требуется этим диффом.
  • 14 из 19 «прямых совпадений» smoke-select.mjs (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) и 31 «слабая связь» по _curSpaceCfg — не прогонял; спот-проверил обоснование r2 напрямую: диф футера/обработчиков диалога (_saveRoom, _pendingSplit, _wallFaceBatch, _roomDialogCancel, _keepClosedAsPartitions) — байт-в-байт тот же текст, что на origin/dev (только отступ), символы совпали случайно как общее состояние хоста комнаты, а не как то, что этот диф меняет.

Вердикт

Зелёный. High: 0, Medium: 0. Полный независимый разбор после двух административных ребейзов (не код-правок) новых дефектов не выявил; AC7 после ребейза отдельно прослежен до реального CI-исполнения, а не принят по метке reuse на слово.


Материал раунда

  • SHA материала: 2fd166f955d21f33171720e2e12136d57a104a0d (branch issue/594-form-kit-room)
  • Дерево: рабочая копия совпадает с этим SHA (git rev-parse HEAD сверен непосредственно перед выводом вердикта)
  • Диапазон: origin/dev...HEAD = e249e3db..2fd166f9, 67 файлов, 6 коммитов

Материал раунда

  • Ветка: issue/594-form-kit-room, коммит 2fd166f955d2 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: c5f210fde209a16849ff396b85aa3e8492448064
    git log --all --format='%H %T' | grep c5f210fde209
    
  • Тело issue: 594c7771e678dae203db75069c75806da3eec9c1d9b155a3d7ba2236d338052a
  • Вердикт конвейера: green · High 0