18 KiB
CODE-REVIEW-629-r1
Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0
Материал
- Ветка
issue/629-smoke-facade, вершинаe5662b0b188119e276866354aa995601dc49a4aa(рабочая копия на нём; вершина переподписана после инцидентаworkflow_sync, дерево05e744354b43ff449d7d467ab5e6d33b7f298ba9не менялось). - Диапазон:
git log --oneline origin/dev..HEAD— два коммита:2ba5d78afeat(hooks):data-hp="mode-tab" data-modeна вкладках режимов (класс A)e5662b0btest(harness): фасадwindow.__hpTestи гейтno-new-private-writes(класс B/C)
- Blob-якоря хендоффа (
docs/reviewsне хранит, см. комментарий issue от 04:15) сверены посегментноgit hash-object— все 12 совпали побайтно с материалом хендоффа, несмотря на переподпись коммита. - Validate на этом SHA — success (run 35958753744):
typecheck,npm test,npm run buildсо сверкой копий бандла иnode scripts/check-docs.mjs(frontend-шаг) этим прогоном подтверждены; ниже они не перегонялись как отдельный гейт ревью, ноnpm testиnpm run buildбыли перезапущены попутно при верификации смоков/мутантов (см. таблицу) и дали тот же результат.
Скоуп
Инструментальная задача: гейт scripts/no-new-private-writes.mjs, тестовый фасад demo/helpers/hp-test.mjs, правка фикстуры demo/srv/demo.html/demo/serve.mjs, перевод трёх смоков (smoke_area_relocation, smoke_glow, smoke_grid_snap), новый demo/smoke_test_facade.mjs, один продуктовый атрибут data-hp="mode-tab" на вкладках режимов, документы (docs/TESTING.md, docs/STYLING-HOOKS.md, docs/data-hp-contract.json, PROCESS.md, AGENTS.md). Ни одна персона docs/SCOPE.md продукт напрямую не видит; выигрывает разработчик/ревьюер смоков и косвенно — надёжность регрессионного покрытия редакторов (следствие (а) из аудита). Задача правомерно инструментальна и не обязана закрывать конкретный Core user job — она обслуживает саму разработку.
Как проверялось
ТЗ (тело issue, раздел ## ТЗ) построчно сопоставлено с диффом; каждый AC перепроверен исполнением, а не по тексту хендоффа. Прогнано лично (среда — тот же контейнер, Chromium уже установлен, npm ci не требовался):
| Гейт | Команда | Результат |
|---|---|---|
| Гейт задачи, CLI | node scripts/no-new-private-writes.mjs --base origin/dev --head HEAD |
0; «556 добавленных строк в 5 файл(ах), новых записей нет» |
| Юниты гейта | node --test test/no-new-private-writes.test.mjs test/hp-test-facade.test.mjs |
16/16 pass |
| Мутант AC1 | mutation-gate.mjs --id=private-writes-ignores-update-expressions |
поймано 1/1 |
| Мутант AC2 | mutation-gate.mjs --id=private-writes-credits-any-field |
поймано 1/1 |
| Мутант AC3 | mutation-gate.mjs --id=private-writes-accepts-bare-marker |
поймано 1/1 |
| Мутант AC4 | mutation-gate.mjs --id=private-writes-skips-covered-calls |
поймано 1/1 |
| Сборка (для смоков/мутантов AC6) | npm run build && node scripts/bundle-sync.mjs |
0; git status после — чисто (бандл побайтно = закоммиченному) |
| Смок AC6 | node demo/smoke_test_facade.mjs |
27/27 OK |
| Мутант AC6 | mutation-gate.mjs --id=room-settings-click-does-not-open |
поймано 1/1 |
| Мутант AC6 | mutation-gate.mjs --id=hp-dialog-escape-does-not-close |
поймано 1/1 |
| Мутант AC6 | mutation-gate.mjs --id=config-updated-event-ignored |
поймано 1/1 |
| AC7 | node --test test/hp-test-facade.test.mjs |
включено выше, pass |
| AC8 | node --test test/data-hp-contract.test.mjs |
6/6 pass |
| AC9 | grep -rc __hpTest dist custom_components/houseplan/frontend |
0 везде |
| AC9 | npm run bundle:budget |
0 (предупреждение о запасе — старое, не от этой задачи) |
| AC9 | node scripts/unused-locals-gate.mjs |
«мёртвого кода нет», все числа равны базе |
| AC10 | node demo/smoke_area_relocation.mjs |
28/28 OK |
| AC10 | node demo/smoke_glow.mjs |
37/37 OK |
| AC10 | node demo/smoke_grid_snap.mjs |
35/35 OK |
| AC11 | node scripts/check-docs.mjs --screenshots=warn |
passed; WARN об отпечатке скриншотов (см. «чего не проверял») |
| AC5 проводка | node --test test/gate-small.test.mjs test/validate-workflow.test.mjs |
27/27 pass |
| AC5 проводка | node scripts/check-inputs.mjs --coverage |
0 |
| Регрессия харнесса (§10 ТЗ, выборка) | smoke_houseplan_panel, smoke_edge_cases, smoke_room_settings_form, smoke_config_reload_race |
все 4 OK |
| Связь дифф↔смоки | node scripts/smoke-select.mjs --base origin/dev --head HEAD |
«НЕОПРЕДЕЛЁННОСТЬ» — ожидалось самим ТЗ (§12), закрыто ручной выборкой выше |
| Полный набор юнитов | npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && npm test |
2939 pass, 1 skip, 0 fail |
Общий no-new-any (не должен был сломаться от рефакторинга parseExemption) |
входит в npm test |
pass |
Плюс чтение кода: scripts/no-new-private-writes.mjs целиком (парсер G1–G6, зачёт G3, movedLinesByFile/parseExemption reuse в no-new-any.mjs), demo/helpers/hp-test.mjs целиком, диффы demo/smoke_area_relocation.mjs/smoke_glow.mjs/smoke_grid_snap.mjs построчно, demo/srv/demo.html, src/houseplan-card.ts (продуктовая правка + путь _reloadConfigOnly/config-reload-authority.ts, подтверждающий, что удалённые ручные _regSignature=''; _maybeRebuildDevices() в переводах дублировали то, что теперь делает afterAdopt при приёме события — поведение не потеряно, читкой подтверждено «проверено чтением, не исполнением» для этого конкретного утверждения автора), scripts/mutation-registry.mjs (все 7 новых мутантов и их якоря сверены grep на актуальный текст src/*.ts/скрипта), .github/workflows/validate.yml, scripts/gate-small.mjs, scripts/check-inputs.mjs, AGENTS.md, PROCESS.md, docs/TESTING.md, docs/STYLING-HOOKS.md, docs/data-hp-contract.json.
Находки
Нет ни одной находки High/Medium/Low. Ниже — то, что специально проверялось на предмет находок и не подтвердилось:
- G1 может не поймать запись через
for (c._x of list). Цепочка целей визитора проверяетBinaryExpression-присваивание,++/--,delete, но не инициализаторForOfStatement/ForInStatement, когда он не объявление, а существующий lvalue. Технически обходной путь есть, но: (а) не требуется ни одним AC ТЗ (§5 G1 перечисляет ровно три формы записи), (б) для смока это неестественная конструкция — переписывать приватное поле на каждой итерации цикла как «обход гейта» никто разумно не станет, (в) остаётся зона правила ревью (R), которое ловит любую новую приватную запись человеком. Не поднимаю до Medium: ни один AC этого не обещает, и это не тот путь, которым реально пишут смоки. via: 'x'на настоящемha-dialog(F4) не исполнялся — честно указано автором в «чего не проверял», демо-фикстура не содержит настоящегоha-dialog. Не находка: ни один AC не требует прогона именно этой ветки на этом стенде (#505-фикстура вне скоупа задачи), путь читкой соответствует описанию F4._saveMarker()/_openRoomEdit()и т.п. private-методы больше не вызываются напрямую в трёх переведённых смоках — проверено, что публичный путь (клик,hp.input, клик по[data-hp="dialog-confirm"], ожидание отсоединения диалога) эквивалентен по итоговому состоянию: три смока дали идентичный список имён проверок и идентичный результат (все true) на пересобранном бандле.- Риск «настоящий путь вскроет дефект продукта» (§12 ТЗ) не реализовался: все три перевода прошли без
private-ok, без bug-issue, что и заявлено в хендоффе и подтверждено прогоном.
Что проверено и корректно
- Гейт
no-new-private-writes.mjsреализует G1–G6 точно по контракту ТЗ: разбор черезts.createSourceFile(..., ScriptKind.JS), приватный сегмент — ближайший к корню,this-корень исключён, деструктуризация (assignmentTargets) и вызовы G5 (coveredCallOf, с исключениемthis-корня) разобраны корректно; зачёт правки — по(kind, field, path), перенос переиспользуетmovedLinesByFileизno-new-any.mjsбез копирования (новый параметрdetailsнеобязателен и не ломает существующие вызовы — подтверждено полным прогоном юнитов). parseAnyOkтеперь тонкая обёртка над общимparseExemption; для маркераany-okповедение побитово то же (в имени маркера нет regex-метасимволов, которые требовали бы экранирования иначе, чем раньше) — старые тестыno-new-anyпрошли в общем прогоне.- Фасад
hp-test.mjs: каждая из 10 операций плюсsettled()реализована по таблице F ТЗ; F1 (structuredClone), F2 (семантика «сервер прислал», а не «локальная правка»), F3 (толькоREADS, ни одной записи — подтверждено юнитом и моей повторной проверкой мутанта), F4 (ошибка наha-dialog-ветке) — все на месте. - Фикстура:
CFG/LAYOUTсталиlet, добавленыSERVER_LISTENERS,__pushServerConfig/__pushServerLayout, конфликт поexpected_rev— воспроизводитwebsocket_api.pyсервера и активируется только после первого push, так что необновлённые 260 смоков поведения фикстуры не видят (риск из §12 ТЗ закрыт условиемPUSHED_CFG_REV). - Продукт:
data-hp="mode-tab" data-mode=${m}рендерится только подthis._canEdit(то есть вместе с самими вкладками), ни в одном CSS/JS селекторе стилей не участвует —grepподтвердил единственное вхождение атрибута вsrc/;User-Visible: noна обоих коммитах корректен, changelog не тронут, что и требуется. since: "1.78.0-beta.2"вdocs/data-hp-contract.json— на единицу впереди текущегоpackage.json(1.78.0-beta.1) и последнего тега (v1.78.0-beta.1), коллизии с уже выпущенной версией нет.- Все 7 мутантов на месте в
scripts/mutation-registry.mjs, в предписанных ТЗ местах (рядом сno-new-any-*и рядом сhp-dialog-ignores-flex-content), ихfind-якоря совпадают с текущим текстом файлов — провереноgrepи живым прогоном каждого черезmutation-gate.mjs --id=. - Проводка гейта (AC5) —
gate-small.mjs,validate.yml(падениеno-new-anyне прячетno-new-private-writes:status=1/exit "$status"),check-inputs.mjs(скрипт вfrontend.entries,demo/helpers/**вfrontend.roots,demo/helpers/hp-test.mjsвBROWSER_PROTOCOL, устаревшие записиNOT_AN_INPUTдля двухha-dialog-*хелперов сняты) — все прогнаны и зелёные. - Документы:
docs/TESTING.md(779 строк, лимит 800 не нарушен; правило №6 и полный раздел про фасад совпадают с F/G-таблицами ТЗ),PROCESS.md§2.7 (пункт R),AGENTS.md(абзац про смоки + две правки перечней гейтов) — всё на месте и совпадает по содержанию сdocs/reviews-неприкасаемым источником (STYLING-HOOKS §7.4,data-hp-contract.json). - Трейлеры
Issue: #629иUser-Visible: no— на обоих коммитах; приnoизменений changelog нет, что и ожидается. - «Одно число — один источник» (§8):
sinceверсии дляmode-tabберётся изpackage.jsonодин раз и повторяется в JSON и STYLING-HOOKS текстом (не второй независимый источник, тот же факт записан дважды намеренно как документация — расхождения нет,check-docs.mjsэто же и проверяет как перекрёстную ссылку). Численных «двойников» с риском рассинхронизации в диффе не нашёл.
Чего не проверял
- Golden не снимал и не сверял: атрибут не рисуется, задача явно не касается (#455, §10 ТЗ «Golden. Не должен меняться»).
performance_smokeи полную матрицу из 264 смоков (только диффом заданная и регрессионная выборка §10 ТЗ) — предрелизный гейт, не гейт ревью.python -m pytest tests_backend— правкиcustom_components/**/*.pyнет.- Инварианты модели
npm run invariants -- --config …— задача не трогает геометрию/ссылки на неё сверх правки текста фикстуры demo.html, которая уже покрытаtest/model-invariants.test.mjs(44/44 прогнаны лично). - 14 «случайных» смоков из хендоффа не перепрогонял — выбрал свою выборку из 4 харнесс-зависимых смоков (включая
smoke_config_reload_race, который прицельно бьёт по путиhouseplan_config_updated, изменённому в этой задаче) вместо повтора чужого списка. via: 'x'на настоящемha-dialog(F4) — код-путь не исполним на этом стенде, разобран чтением (см. «Находки»).docs:capture/docs:accept -- --identicalне запускал:check-docs.mjs --screenshots=warnподтвердил ровно ту WARN, что заявлена в хендоффе (отпечаток стал устаревшим из-за любой правкиsrc/, не специфично для этой задачи; обязателен только перед кандидатом беты).- Полный набор мутантов (
mutation-gate.mjs --check) — прогнал только структурную сверку (--check, 0 FAIL) и 7 «своих» мутантов адресно; сотни несвязанных мутантов проекта не перезапускал (не relevant к дельте).
Материал раунда
- Ветка:
issue/629-smoke-facade, коммитe5662b0b1881— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
5c7ec84a6816b887e17ab07b53db5c79988cc294git log --all --format='%H %T' | grep 5c7ec84a6816 - Тело issue:
00fd8d5cfe7f3778ae654cefa41d7042e6f514b9a75b2db5eaa26c253a73deb9 - Вердикт конвейера:
green· High 0