19 KiB
CODE-REVIEW-745-r1
Issue: #745 · Трек: show · Заход: r1 · блокирующих циклов 0/2
Материал: git log --oneline origin/dev..HEAD / git diff origin/dev...HEAD
SHA материала: ca708d8443aee387895a0547d4a6c52fb95e816f (сверено git rev-parse HEAD перед итогом — совпадает)
Один коммит на ветке issue/745-space-card-room-keys поверх dev (7ce3f662, #742 уже влит).
Скоуп диффа
demo/smoke_space_card.mjs | 153 +++++++++++++++++++++++++-
docs/CHANGELOG.md | 3 +
docs/CHANGELOG.ru.md | 3 +
docs/testing-notes/mutation-browser-guards.md | 5 +-
scripts/mutation-registry.mjs | 13 +++
src/space-render.ts | 12 +-
src/styles/plan.styles.ts | 9 +-
7 files changed, 189 insertions(+), 9 deletions(-)
Ровно поверхность из ТЗ: src/space-render.ts. Правка — список фигур комнат
карточки пространства (space-render.ts:534–570) стал
keyed(space.id, repeat(shownRooms, (r, index) => r.id || index, (r) => {…}))
вместо голого map(): буквально та же форма, что #742 поставил в
houseplan-card.ts:10952 для основной карточки (src/houseplan-card.ts:10950-10952
сверено построчно). Переход .room { transition: 0.12s }
(plan.styles.ts) не тронут — только дополнен комментарий про карточку
пространства. Остальные файлы: свидетель (новый раздел существующего смока,
а не новый файл — как и объявлено «принято предположительно»), один мутант и
его запись в mutation-browser-guards.md, CHANGELOG RU/EN.
Как проверялось
Рабочая копия уже стояла на материале (ca708d84). Дешёвые гейты (tsc --noEmit, npm test, npm run build) подтверждены зелёным Validate на
этом SHA (run 36858825861) —
не перегонял; смоки/golden/мутанты на этом SHA Validate не гонял вовсе
(ветка задачи, пуш не PR/кандидат — политика validate.yml: «настоящую
приёмку там делает код-ревью»), поэтому все браузерные проверки ниже я
прогнал сам.
| Гейт | Результат | Примечание |
|---|---|---|
npx tsc --noEmit, npm run build |
не перегонял отдельно | Validate зелёный на ca708d84; npm run bundle:sync ниже всё равно прогоняет build как часть сборки бандла для смоков — тоже чисто |
npm run bundle:sync (сборка + копия в demo/srv/assets) |
✅ | нужно для честного запуска браузерных смоков — demo/srv/assets не обновляется автоматически |
npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs |
✅ | тестовая сборка для test-build/ |
npm run gate:small |
✅ зелёный, 126 с, упало 0 | сборка+typecheck, no-new-any, no-new-private-writes, smoke-select, npm test (юниты), bundle-policy --verify, bundle:budget, lint:unused — все шаги ok |
demo/smoke_space_card.mjs (новый раздел, AC1–AC2) |
✅ зелёный на правке | прогнан лично на честно собранном бандле; все 9 полей keyedRooms совпали с ожидаемыми значениями дословно (см. ниже) |
Мутант space-card-rooms-rendered-without-keys — node scripts/mutation-gate.mjs --id=space-card-rooms-rendered-without-keys |
✅ «заявленный тест покраснел на мутанте» | гейт сам применяет патч во временном дереве, собирает и гоняет гард — не доверие авторскому отчёту, а независимое исполнение |
node scripts/mutation-gate.mjs --check --changed=origin/dev..HEAD |
✅ exit 0, browser guards: 204/200 (WARN, не fail — ориентир, не лимит, #699) |
новый id размечен в mutation-browser-guards.md, missingReasons пуст |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
выполнен | см. решение по строкам ниже |
demo/smoke_space_card_bg.mjs (AC3) |
✅ | |
demo/smoke_space_card_decor_capability.mjs (AC3) |
✅ | |
demo/smoke_moon_static.mjs (AC3) |
✅ | |
demo/smoke_room_settings.mjs (прямое совпадение smoke-select) |
✅ | |
demo/smoke_space_switch_transitions.mjs (companion-свидетель #742, не назван в AC, но делит plan.styles.ts) |
✅ | подтверждает, что правка не расшатала прецедент #742 |
npm run golden:verify |
не прогонялся | метки ci:golden на issue нет; тот же вывод, что и на #742 (CODE-REVIEW-742-r1.md: «golden не заказывается») — дефект чисто транзитный (видим только в первом кадре перехода), кадр в покое (стили/разметка/порядок узлов в DOM) не меняется: keyed/repeat не добавляют обёрточных узлов и сохраняют тот же порядок, что и прежний map() |
python -m pytest tests_backend |
не прогонялся | дифф не касается custom_components/**/*.py |
npm run invariants |
не прогонялся | дифф не меняет геометрию комнат и ссылки на неё — только идентичность DOM-узлов списка |
smoke-select — решение по строкам
Изменено файлов src/**: 2 · символов проекта на изменённых строках: 2
Матрица: 286 смоков · порог «широкого» символа: больше 57 смоков
Прямое совпадение (1): demo/smoke_room_settings.mjs ← roomFillModeOf
Не учитывались как широкие: setConfig
- Прямое совпадение (1) —
smoke_room_settings.mjsпрогнан, зелёный. setConfigкак «широкий» — не прогонял отдельно. Совпадение идёт не из продуктового кода диффа (тамsetConfigне менялся), а из тестового файла: новый раздел смока сам многократно вызываетel.setConfig(...)на карточке пространства.setConfig— общий метод HA-карточек, матрица отметила бы десятки несвязанных смоков; решение осознанное, не ищу скрытую связь там, где символ пришёл из теста, а не из правки.
Доказательство негатива (свидетель умеет падать)
Не подменял файл руками — использовал официальный путь: node scripts/mutation-gate.mjs --id=space-card-rooms-rendered-without-keys. Гейт
сам: (1) прогнал чистый смок на правке — ok; (2) применил патч мутанта
(внутренний repeat заменён на голый map(), оставив внешний keyed) во
временном дереве, пересобрал бандл и прогнал тот же смок — `ok
space-card-rooms-rendered-without-keys: заявленный тест покраснел на
мутанте». Итог: «поймано 1 из 1». Это ровно AC2 (путь B) — инструмент
подтверждает, что без внутреннего ключа тест красит правильную причину, а
не падает случайно.
Отдельно глазами проверил баланс скобок патча реестра: исходная строка
держит два открытых ( (keyed(, repeat() и один {; патч меняет
repeat( на .map(, то есть тоже даёт два ( и один { — закрывающая
})); на space-render.ts:570 остаётся синтаксически верной и для
мутанта, и для оригинала (структурная проверка mutation-gate --check
подтвердила это же: «якорь найден 1 раз», сборка мутанта не падает
компиляцией).
AC — разбор
| AC | Вердикт | Как доказано |
|---|---|---|
AC1 (путь A — смена space в setConfig того же элемента) |
✅ выполнен | Раздел смока: фикстура держит ожидаемую форму (pathAFixtureHolds: true — r1 без заливки/polygon, garden/g1 с заливкой/polygon), после setConfig({ space: 'garden' }) и одного кадра: нет CSSTransition на [data-hp="room"] (pathANoRoomTransition: []), ни один старый узел не остался подключён под чужим data-id (pathANoRoomNodeOutlivesTheSwitch: [] — внешний keyed(space.id,…) отбрасывает всё поддерево целиком при смене space), новый узел g1 рождается сразу с итоговым rgb(96, 125, 139) / 0.18 (pathANewRoomBornInItsFill, совпадает с DEFAULT_CUSTOM_FILL в src/logic.ts:1301) |
| AC2 (путь B — событие конфигурации меняет состав комнат; плюс настоящий переход не сломан) | ✅ выполнен, красит мутант space-card-rooms-rendered-without-keys |
Вставка комнаты первой в f1.rooms через __hpTest.setServerConfig (путь, которым правка с другого устройства доходит до карточки — то же событие, на которое подписан onConfigChange → _load() в space-card.ts): список вырос (pathBRoomListGrew: true), ни один из прежних 4 узлов не подменён (pathBNoRoomNodeSwapped: [] — внутренний ключ repeat по r.id), переходов не было (pathBQuietRooms: []). Настоящая смена custom_fill после этого по-прежнему идёт на том же узле (pathBRealFillChangeKeepsTheRoomNode: true) и анимируется (pathBRealFillChangeAnimates: true) — это тот самый контроль «ложный фикс transition:none не прошёл бы», подтверждён отдельно прогоном smoke_space_switch_transitions.mjs (companion #742) |
| AC3 (ничего не сломано, выпуск) | ✅ выполнен | gate:small зелёный; smoke_space_card, smoke_space_card_bg, smoke_space_card_decor_capability, smoke_moon_static зелёные лично; smoke-select учтён построчно; мутант в реестре рядом с #742, пойман своим guard’ом (проверено исполнением, не только --check); mutation-gate --check зелёный (204/200 — предупреждение по #699, не отказ); CHANGELOG RU/EN в том же коммите, текст дословно совпадает с ТЗ; golden не заказан и не нужен (см. таблицу выше) |
Единственное число, которое дифф делает видимым пользователю дважды —
DEFAULT_CUSTOM_FILL (#607d8b / 0.18, src/logic.ts:1301): оно не
менялось этим диффом, у него один источник (logic.ts), и смок читает его
же значение через getComputedStyle, а не дублирует константу независимо
(дублирование есть только в самом свидетеле — FINAL_FILL в
smoke_space_card.mjs, это сверено вручную при разборе AC1, расхождения
нет).
Находки
Нет. High: 0. Medium: 0.
Что проверено и корректно
- Форма правки — буквально та же, что #742 поставил в
houseplan-card.ts:keyed(space.id, repeat(shownRooms, (r, index) => r.id || index, …)); импортыkeyed/repeatдобавлены туда, где их не было (space-render.ts:10-11). - Идентификаторы фикстуры в новом разделе смока (
f1,garden,r1–r4,g1) — не придуманы: это демонстрационные данные проекта, используемые уже десятками других смоков (smoke_iso_floor_switch.mjs,smoke_merge_split.mjs,smoke_infinite_canvas.mjsи др.) и реальные SVG-подложкиdemo/srv/assets/f1.svg,garden.svg. - Пустой
idключуется числовым индексом (r.id || index) — тот же приём, что в #742, поMap-семантикеrepeatчисло никогда не совпадёт со строковымid. .room { transition: 0.12s }не тронут; AC2-проверка настоящей смены заливки подтверждает, что это не фикс черезtransition: none.roomShapesиспользуется ровно один раз (space-render.ts:964), встроен в<svg>тем же способом${roomShapes}, что и раньше —keyed/repeatне добавляют обёрточных DOM-узлов, порядок фигур не меняется.shownRoomsвынесен в переменную и используется один раз — не тронул остальные независимые фильтры поspace.rooms(например,glowBaseShapesниже по файлу).- Мутант реестра синтаксически корректен для обеих версий (считал баланс
скобок вручную, см. «Доказательство негатива»), guard пойман исполнением,
а не только текстовой проверкой
--check. - Трейлеры
Issue: #745,User-Visible: yesна коммите; CHANGELOG RU/EN правлены в том же коммите, тексты дословно совпадают с текстом ТЗ. no-new-private-writes: свидетель пишет конфиг только через существующий__hpTest.setServerConfig, как и заявлено отклонением от ТЗ (п. «Путь B свидетель проверяет через__hpTest.setServerConfig, а не записью в_snap.config») — подтверждено прогоном соответствующего шагаgate:small.- Рабочая копия репозитория после всех проверок чиста (
npm run bundle:clean,git status --shortпуст,HEAD=ca708d84).
Маршрут вердикта (§5)
Все критерии пройдены: сложность и риск низкие (копия уже проверенного на
#742 паттерна), одна поверхность (src/space-render.ts), нет миграции
конфига, нет нового UX-контракта (устраняется баг в рамках уже описанного
поведения), нет влияния на производительность/touch-контракт, ожидаемое
поведение зафиксировано тем же правилом #525/#534/#742 и текстом в CHANGELOG.
route: fix.
Чего не проверял
- Полную сверку бандла из трёх независимых копий — не требовалась: Validate
зелёный на этом SHA подтверждает
build; локальная пересборка (bundle:syncдля смоков +--idмутанта) прошла ещё дважды без расхождений. - 285 «слабых»/неотобранных смоков вне прямого совпадения
smoke-select— единственная прямая связь (smoke_room_settings) прогнана;setConfigкак источник совпадения разобран и сознательно не расширен (см. выше). golden:verify,pytest tests_backend,npm run invariants, performance-профили — не требуются по диффу/AC (нет правки Python, нет правки геометрии комнат, меткиci:goldenнет, перф не затронут). Решение по golden совпадает с прецедентом #742 на идентичном паттерне правки.- Ручная проверка в браузере (глазами) — доказательство построено на
getComputedStyle/getAnimations, то есть на тех же сигналах, которые увидел бы человек на первом кадре; отдельно её не повторял. - Пакетное/ночное ревью, release-review — вне объёма этого раунда (§8: объём гейтов соразмерен задаче).
Материал раунда
- Ветка:
issue/745-space-card-room-keys, коммитca708d8443ae— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
799d5ef74e6afa50d64480013db2799c42b848d0git log --all --format='%H %T' | grep 799d5ef74e6a - Тело issue:
8c17362760b68e61c8c6b00cda0e18656cacfb312c80e5828034f3cc79599971 - Вердикт конвейера:
green· High 0 · маршрутfix