From 5e1b5082154a43839457748eabf4ce477e0619a4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:30:38 +0000 Subject: [PATCH] docs: review document for #745 Issue: #745 User-Visible: no --- docs/reviews/CODE-REVIEW-745-r2.md | 238 +++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 docs/reviews/CODE-REVIEW-745-r2.md diff --git a/docs/reviews/CODE-REVIEW-745-r2.md b/docs/reviews/CODE-REVIEW-745-r2.md new file mode 100644 index 00000000..0b17e8f6 --- /dev/null +++ b/docs/reviews/CODE-REVIEW-745-r2.md @@ -0,0 +1,238 @@ +# 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](https://github.com/Matysh/houseplan-card/actions/runs/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` по старой + нумерации), встроен в `` тем же способом, что и раньше — + `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`