docs: review document for #745

Issue: #745
User-Visible: no
This commit is contained in:
claude[bot]
2026-10-01 12:30:45 +00:00
parent 88702eedc6
commit 5e1b508215
+238
View File
@@ -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` по старой
нумерации), встроен в `<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: объём гейтов соразмерен задаче).
---
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/745-space-card-room-keys`, коммит `7be2d456003f` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `5ff5a59993d85725fbb729297ce1aec86f3ba266`
```
git log --all --format='%H %T' | grep 5ff5a59993d8
```
- Тело issue: `8c17362760b68e61c8c6b00cda0e18656cacfb312c80e5828034f3cc79599971`
- Вердикт конвейера: `green` · High 0 · маршрут `fix`