Files
2026-10-01 12:30:45 +00:00

21 KiB
Raw Permalink Blame History

CODE-REVIEW-745-r2

Issue: #745 · Трек: show · Заход: r2 · блокирующих циклов 0/2 Материал: git log --oneline origin/dev..HEAD / git diff origin/dev...HEAD SHA материала: 7be2d456003f83148be113e37288ee915a075bb6 (сверено git rev-parse HEAD перед итогом — совпадает)

Причина нового захода — не находка r1 (там их не было), а ребейз: во время r1 dev продвинулся на 4 коммита, патч потерял совпадение patch-id с рецензированным (ca708d84 → 6cd29f9b, то же дерево 7be2d456 для коммитов публикации — комментарий к issue зафиксировал это явно: «Материал ревью ca708d84 и кандидат 7be2d456 дают разные patch-id»). Это «ребейз на ушедший вперёд dev» из правил — разбор ниже полный, а не по дельте.

Скоуп диффа

demo/smoke_space_card.mjs                     | 153 +++++++++++++++++++-
docs/CHANGELOG.md                             |   3 +
docs/CHANGELOG.ru.md                          |   3 +
docs/reviews/CODE-REVIEW-745-r1.md            | 193 ++++++++++++++++++++++++++
docs/reviews/INDEX.md                         |   3 +-
docs/testing-notes/mutation-browser-guards.md |   1 +
scripts/mutation-registry.mjs                 |  13 ++
src/space-render.ts                           |  12 +-
src/styles/plan.styles.ts                     |   9 +-
9 files changed, 382 insertions(+), 8 deletions(-)

Из девяти файлов продуктовая правка — ровно по ТЗ, одна поверхность: src/space-render.ts (534→540 вставляет keyed(space.id, repeat(shownRooms, (r, index) => r.id || index, (r) => {…})) вместо голого map(), импорты keyed/repeat добавлены строками 10-11). plan.styles.ts — только комментарий (строка 503+, дополняет заметку #742 словами про карточку пространства), сам переход .room { transition: 0.12s } не тронут. scripts/mutation-registry.mjs и docs/testing-notes/mutation-browser-guards.md — один новый мутант space-card-rooms-rendered-without-keys и его регистрация. CHANGELOG RU/EN — по одной строке, дословно текст из ТЗ. Остальные два файла (docs/reviews/CODE-REVIEW-745-r1.md, docs/reviews/INDEX.md) — публикация документа прошлого раунда, не продуктовый код; я их не редактирую и не оцениваю как находки, только как контекст прошлого раунда.

Я построчно сверил текущий git diff origin/dev...HEAD с тем, что описывает CODE-REVIEW-745-r1.md: по src/space-render.ts, src/styles/plan.styles.ts, scripts/mutation-registry.mjs, docs/CHANGELOG*.md дифф посимвольно совпадает с тем, что разбирал r1 (один символ различия — несущественная цифра в собственной таблице r1 по mutation-browser-guards.md, 5+- вместо фактических 1+, к нынешнему материалу не относится). Отличие ровно там, где его и следовало ждать после ребейза: контекст диффа пересчитан относительно нового dev, browser guards: 205/200 вместо 204/200 — потому что dev между раундами получил ещё один гард (#759), не потому что эта ветка что-то добавила сверх.

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

Рабочая копия уже стояла на материале (7be2d456). Validate зелёный на этом же SHA (run 36860677601) подтверждает дешёвые гейты — не перегонял tsc --noEmit/npm test/npm run build по этой причине. Но ребейз — не локальная дельта, поэтому браузерные гейты (смоки, мутант, smoke-select, gate:small) прогнал заново сам, не полагаясь на цифры r1.

Гейт Результат Примечание
npx tsc --noEmit, npm run build не перегонял отдельно Validate зелёный на 7be2d456; npm run bundle:sync ниже всё равно гоняет build как часть сборки бандла для смоков — тоже чисто
npm run bundle:sync ✅ нужно для честного запуска браузерных смоков (demo/srv/assets не обновляется автоматически)
node demo/smoke_space_card.mjs (раздел #745, AC1–AC2) ✅ зелёный прогнан лично; все 9 полей keyedRooms совпали с ожиданием дословно (см. ниже)
node scripts/mutation-gate.mjs --id=space-card-rooms-rendered-without-keys ✅ «поймано 1 из 1» независимое исполнение: гейт сам прогнал чистый смок (ok), применил патч мутанта во временном дереве, пересобрал и прогнал смок снова — покраснел по заявленной причине
node scripts/mutation-gate.mjs --check --changed=origin/dev..HEAD ✅ exit 0, browser guards: 205/200 (WARN, не fail, #699) рост с 204 (r1) до 205 — новый гард дал dev между раундами (#759), не эта ветка; missingReasons пуст
node scripts/smoke-select.mjs --base origin/dev --head HEAD выполнен то же решение по строкам, что в r1 (см. ниже)
npm run gate:small ✅ зелёный, 113 с, упало 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_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, делит plan.styles.ts) ✅ подтверждает, что правка не расшатала прецедент #742
npm run golden:verify не прогонялся нет метки ci:golden на issue (сверено gh issue view 745 --json labels); дефект транзитный (виден только в первом кадре перехода) — keyed/repeat не добавляют обёрточных DOM-узлов и не меняют порядок фигур относительно прежнего map() (проверено чтением: roomShapes используется один раз, svg того же вида для polygon/rect, data-hp/data-id/классы не изменились) — кадр в покое не меняется
python -m pytest tests_backend не прогонялся дифф не касается custom_components/**/*.py
npm run invariants не прогонялся дифф не меняет геометрию комнат или ссылки на неё — только идентичность DOM-узлов списка (ключи keyed/repeat), координаты/формы r.poly/r.x/y/w/h не тронуты
npm run bundle:clean ✅ рабочая копия приведена к закоммиченному виду после гейтов, git status --short пуст

smoke-select — решение по строкам (повторная проверка)

Изменено файлов src/**: 2 · символов проекта на изменённых строках: 2
Матрица: 286 смоков · порог «широкого» символа: больше 57 смоков

Прямое совпадение (1): demo/smoke_room_settings.mjs ← roomFillModeOf
Не учитывались как широкие: setConfig

То же решение, что в r1, и я его не унаследовал слепо — пересчитал: со смещением базы на новый dev матрица и совпадение не изменились. setConfig снова не расширяю — совпадение идёт из тестового файла (demo/smoke_space_card.mjs сам вызывает el.setConfig(...) многократно), а не из продуктового кода диффа.

Доказательство негатива (свидетель умеет падать)

Прогнал mutation-gate --id=space-card-rooms-rendered-without-keys сам, не доверяя отчёту прошлого раунда: гейт (1) прогнал чистый смок на правке — ok; (2) применил патч мутанта (внутренний repeat заменён на голый .map(), внешний keyed остался) во временном дереве, пересобрал бандл и прогнал тот же смок — красный по заявленной причине. Итог инструмента: «поймано 1 из 1». Это независимое исполнение на нынешнем SHA, а не повтор чужого вывода.

AC — разбор

AC Вердикт Как доказано
AC1 (путь A — смена space в setConfig того же элемента) ✅ выполнен Раздел смока, прогнан лично: фикстура держит ожидаемую форму (pathAFixtureHolds: true), после setConfig({ space: 'garden' }) и одного кадра нет CSSTransition на [data-hp="room"] (pathANoRoomTransition: []), ни один старый узел не остался подключён под чужим data-id (pathANoRoomNodeOutlivesTheSwitch: []), новый узел 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: список вырос (pathBRoomListGrew: true), ни один из прежних узлов не подменён (pathBNoRoomNodeSwapped: []), переходов не было (pathBQuietRooms: []). Настоящая смена custom_fill после этого по-прежнему идёт на том же узле (pathBRealFillChangeKeepsTheRoomNode: true) и анимируется (pathBRealFillChangeAnimates: true) — контроль против ложного фикса transition: none, подтверждён отдельно прогоном smoke_space_switch_transitions.mjs
AC3 (ничего не сломано, выпуск) ✅ выполнен gate:small зелёный лично; smoke_space_card, smoke_space_card_bg, smoke_space_card_decor_capability, smoke_moon_static зелёные лично; smoke-select учтён построчно; мутант пойман исполнением (не только --check); mutation-gate --check зелёный (205/200, WARN не fail); CHANGELOG RU/EN в том же коммите, текст дословно совпадает с ТЗ; golden не заказан и не нужен (резюме выше)

Единственное число, видимое пользователю дважды в этом диффе — DEFAULT_CUSTOM_FILL (#607d8b / 0.18, src/logic.ts:1301): диффом не менялось (src/logic.ts вне скоупа правки), источник один, смок читает фактическое значение через getComputedStyle, а не дублирует константу в продуктовом коде (дублирование — только в самом свидетеле, FINAL_FILL в smoke_space_card.mjs, это тестовая проверка, не источник истины).

Находки

Нет. High: 0. Medium: 0.

Что проверено и корректно

  • Диффы src/space-render.ts, src/styles/plan.styles.ts, scripts/mutation-registry.mjs, docs/CHANGELOG*.md на нынешнем материале посимвольно совпадают с тем, что уже разобрал r1 — ребейз сдвинул контекст и patch-id, но не содержание правки.
  • Форма правки — буквально та же, что #742 поставил в houseplan-card.ts: keyed(space.id, repeat(shownRooms, (r, index) => r.id || index, …)); импорты keyed/repeat добавлены там, где их не было (space-render.ts:10-11).
  • Пустой id ключуется числовым индексом (r.id || index) — число никогда не совпадает со строковым id по Map-семантике repeat.
  • .room { transition: 0.12s } не тронут; AC2-проверка настоящей смены заливки (и smoke_space_switch_transitions) подтверждает, что это не фикс через transition: none.
  • roomShapes используется один раз (space-render.ts:964 по старой нумерации), встроен в <svg> тем же способом, что и раньше — keyed/repeat не добавляют обёрточных DOM-узлов и не меняют порядок фигур.
  • Мутант space-card-rooms-rendered-without-keys в реестре синтаксически корректен (сборка мутанта проходит), guard пойман исполнением гейта, а не только текстовой проверкой --check, лично на этом SHA.
  • Трейлеры Issue: #745, User-Visible: yes на коммите 6cd29f9b; CHANGELOG RU/EN правлены в том же коммите, текст дословно совпадает с ТЗ (сверено построчно).
  • no-new-private-writes: свидетель пишет конфиг только через __hpTest.setServerConfig, отклонение от ТЗ названо и обосновано автором, подтверждено зелёным шагом gate:small на этом прогоне.
  • Не-скоуп, названный в ТЗ (маркеры devs.map, проёмы resolvedHosted.map), не тронут — верно, задача явно ограничивает поверхность одним списком комнат.
  • Рабочая копия после всех проверок чиста (npm run bundle:clean, git status --short пуст, HEAD = 7be2d456).

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

Находка r1 Чем закрыта Где это видно
— (r1 вынес зелёный вердикт, находок не было) Раунд r2 открыт не находкой, а ребейзом: dev продвинулся на 4 коммита во время r1, patch-id кандидата разошёлся с рецензированным (ca708d84 → текущий материал), конвейер потребовал новый заход по §2.10 Комментарий к issue между r1 и r2: «Материал ревью ca708d84 и кандидат 7be2d456 дают разные patch-id: соседние правки в dev изменили содержимое патча»

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

  • Факт воспроизведения бага на dev (зонд 8b7725ac, временный, в ветку не шёл) — принят из тела issue, как и в r1; сам зонд повторно не гонял, он не часть материала этого раунда.
  • Решение не заказывать golden:verify на паттерне, идентичном #742 (CODE-REVIEW-742-r1.md) — согласие с рассуждением r1 принято, но в этом раунде я его не просто унаследовал, а перепроверил чтением кода заново (см. таблицу гейтов выше): keyed/repeat не меняют состав/порядок DOM-узлов относительно map().
  • Остальное (AC1/AC2/AC3, мутант, smoke-select, gate:small, CHANGELOG) в этом раунде прогнано и перепроверено лично, не унаследовано — ребейз на ушедший вперёд dev требует полного разбора, а не доверия цифрам прошлого захода. Документ r1: docs/reviews/CODE-REVIEW-745-r1.md, SHA материала r1 — ca708d8443ae (не в текущем дереве — ребейз его осиротил).

Маршрут вердикта (§5)

Все критерии пройдены: сложность и риск низкие (копия уже проверенного на #742 паттерна, локальная правка одного списка), одна поверхность (src/space-render.ts), нет миграции конфига, нет нового UX-контракта (устраняется баг в рамках уже описанного поведения #525/#534/#742), нет влияния на производительность/touch-контракт (список рисуется тем же способом, без новых DOM-узлов), ожидаемое поведение зафиксировано тем же правилом и текстом 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 перепроверено чтением кода в этом раунде, не просто унаследовано.
  • Ручная проверка в браузере глазами — доказательство построено на getComputedStyle/getAnimations, тех же сигналах, которые увидел бы человек на первом кадре; отдельно не повторял.
  • Повторный прогон зонда бага на dev (8b7725ac) — принят из тела issue как контекст, не как материал этого раунда.
  • Пакетное/ночное ревью, release-review — вне объёма этого раунда (§8: объём гейтов соразмерен задаче).


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

  • Ветка: issue/745-space-card-room-keys, коммит 7be2d456003f — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 5ff5a59993d85725fbb729297ce1aec86f3ba266
    git log --all --format='%H %T' | grep 5ff5a59993d8
    
  • Тело issue: 8c17362760b68e61c8c6b00cda0e18656cacfb312c80e5828034f3cc79599971
  • Вердикт конвейера: green · High 0 · маршрут fix