# CODE-REVIEW — issue #528, заход r1 **SHA материала:** `f37ccdcebc3f454cb03c8cd25f5081a45eb4e22f` (рабочая копия уже на нём) **Ветка:** `issue/528-marker-identity-witness` **Класс изменения:** B (`demo/smoke_space_switch_transitions.mjs`) — issue переиспользован, продуктовый код и реестр мутантов не тронуты (совпадает с заявленным «Ограниченный объём»). ## Скоуп Свидетель #525 (`demo/smoke_space_switch_transitions.mjs`) ловил мутант `device-markers-rendered-without-keys` через побочный признак: отсутствие бегущего перехода `box-shadow` на `.device-shell-frame` после переключения пространства. #524 убрал этот переход из стилей (перф-регрессия Firefox), признак стал тривиально истинным, и позиционное переиспользование DOM-узлов маркеров перестало ловиться — свидетеля ослепили не намеренно, а как побочный эффект соседней задачи. Задача #528 узкая: маркерная половина свидетеля должна судить по идентичности узла (`data-id` привязанного к узлу до/после переключения), а не по анимации. Дверная половина (`op-leaf`/`op-arc`) остаётся как есть — там переход часть продуктового контракта (#525) и её не просили трогать. ## Диф (весь, 16 строк) Один файл, `git diff origin/dev...HEAD --stat`: ``` demo/smoke_space_switch_transitions.mjs | 17 ++++++++++++++++- ``` Добавлено: 1. Перед первым переключением (`c._pickSpace(ids[0])`) собираются ссылки на все узлы `[data-hp="device"]` вместе с их `data-id` (`markersBefore`). 2. `markersDrawnInTheFirstSpace = markersBefore.length >= 3` — страховка от вырожденного сравнения на пустом множестве (сама тоже проверяется `checkAll`, дефолт `true`). 3. После второго `_pickSpace` вычисляется `reused` — узлы из `markersBefore`, которые остались подключены (`isConnected`) и сменили `data-id`; `noMarkerNodeReusedForAnotherDevice` и диагностический список `reusedMarkerNodes` (ожидаемо пустой, `checkAll(res, { ..., reusedMarkerNodes: [] })`). 4. Старая проверка `noMarkerTransitionOnSwitch` (по переходу оболочки) не удалена — она просто перестала быть единственной, что безопасно (AC не требовал её убирать). ## Как проверялось **Дешёвые гейты этого SHA уже зелёные** (Validate [34572622663](https://github.com/Matysh/houseplan-card/actions/runs/34572622663), success): `typecheck`/`test`/`build` не перегонял — diff не трогает `src/**`, `test/**` юнитов, конфиги сборки. `node scripts/check-docs.mjs` также не требуется: diff не задевает `src/**`, отпечаток скриншотов не устаревает. `node scripts/smoke-select.mjs --base 78bc76073ea2...(merge-base) --head HEAD` подтвердил ожидаемое: «Исполняемого frontend-диффа нет (`src/**/*.ts` не тронут)», browser-smoke по этому инструменту не выбираются — выбирать нечего, изменённый файл сам и есть свидетель. Главная проверка — не общий гейт, а прямое доказательство по AC, и оно уже есть на этом самом SHA в логах Validate, job «Мутанты по диффу (1/6)» (id `103177915852`): ``` 07:05:52 ok чистый прогон: node demo/smoke_space_switch_transitions.mjs 07:06:16 ok device-markers-rendered-without-keys: тест покраснел, как обязан ``` Это ровно то, что требуют AC2 и AC3: чистый (немутированный) прогон смока целиком зелёный, и на замутированном коде (`repeat(devs, (d) => d.id, ...)` → `devs.map(...)` в `src/houseplan-card.ts:11787`, реестр `scripts/mutation-gate.mjs:7325`) свидетель краснеет. Не заявление автора — факт из лога CI на материале ревью. Дополнительно прочтением (без исполнения): - `demo/serve.mjs:68-78` (`check`/`checkAll`) — сравнение через `JSON.stringify`, так что `reusedMarkerNodes: []` в `checkAll` действительно требует пустой массив, а не просто «поле определено». - `src/space-render.ts:607` — реальная разметка маркера несёт `data-hp="device" data-id="${d.id}"`, соответствует селектору смока. - `src/styles/devices.styles.ts:200-215` — `.device-shell-frame` сейчас имеет только `transition: border-color .15s, opacity .2s` (без `box-shadow`), подтверждает описанную в issue причину: старый переход правда убран #524, и старая проверка правда стала тривиальной. - Логика `reused` (строки 124-129): узел считается «переиспользованным чужим id», только если он ещё подключён к дереву (`isConnected`) и его `data-id` изменился относительно зафиксированного до переключения — именно позиционное переиспользование, а не обычное удаление узла или сохранение того же устройства между пространствами (когда `data-id` не меняется). Разобрано корректно. ## Проверено и корректно - AC1 — реализовано: сравнение по ссылкам на узлы, а не по CSS-переходу; подтверждена и защита от вырожденного случая (`markersDrawnInTheFirstSpace`). - AC2 — доказано логом CI на этом SHA (мутант красный), не голым заявлением. - AC3 — доказано тем же логом («чистый прогон» = OK целиком); дверная половина (`op-leaf`/`op-arc`, `noOpeningTransitionOnSwitch`, `theTwoDoorsDifferInState` и т. д.) не тронута, дифф это подтверждает. - Трейлеры: `Issue: #528`, `User-Visible: no` — верно, диф не меняет видимое поведение продукта, changelog не требуется. - Скоуп: ни продуктовый код, ни `scripts/mutation-gate.mjs` не изменены — соответствует заявленному в issue ограничению. ## Чего не проверял и почему - `npx tsc --noEmit`, `npm test`, `npm run build`, сверку бандла — не требуется: diff не трогает `src/**`/`test/**`/конфиги сборки, а Validate на этом же SHA уже зелёный. - `node scripts/check-docs.mjs` — diff не трогает `src/**`. - `npm run invariants` — diff не трогает геометрию/toplogy/marker.space. - `npm run golden:verify` — diff не может изменить видимый рендер (только тестовый файл). - `python -m pytest tests_backend` — Python не тронут. - Полный прогон `demo/smoke_*.mjs` вручную — не требуется отдельно: ровно тот смок, который меняется, уже прогнан CI на материале ревью дважды (чистый и замутированный), что сильнее локального прогона без мутанта. ## Вердикт Зелёный. AC1–AC3 выполнены и подтверждены логом CI на самом материале ревью, а не заявлением автора; изменение в границах заявленного скоупа, трейлеры верны. --- ## Материал раунда - Ветка: `issue/528-marker-identity-witness`, коммит `f37ccdcebc3f` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `3a16a24c59b27eac3280c63ce77f48ca13875750` ``` git log --all --format='%H %T' | grep 3a16a24c59b2 ``` - Тело issue: `f178b89645d3d0967d356c110b81f4262fd676aa9b7bf30203cb8ffb9b63190b` - Вердикт конвейера: `green` · High 0