Files
houseplan-card/docs/reviews/CODE-REVIEW-243-r2.md
T
2026-08-22 16:58:34 +00:00

287 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CODE-REVIEW-243-r2
- Issue: [#243](https://github.com/Matysh/houseplan-card/issues/243) — «Перетаскивание вкладок пространств не работает: захват указателя съедает
цель. Плюс нужен указатель места вставки»
- Этап: code (PROCESS.md §2.7)
- Заход: r2 · блокирующих циклов израсходовано 0 из 4 (r1 был зелёным и бюджет не тратил, §7.2, #227)
- ТЗ: `docs/specs/243-space-tab-drop-target.md` (`7ac1934`, не менялся с r1), SPEC-REVIEW: `docs/reviews/SPEC-REVIEW-243-r1.md`, вердикт зелёный
- Диапазон этого раунда: `origin/dev...HEAD` (`origin/dev` = `56a3977`, дальше не двигался), 5 коммитов:
- `133fdc9` `docs(spec): define reliable space tab drop target` (класс C, `Issue: #243`, `User-Visible: no`)
- `02a5230` `docs: review document for #243` (класс C, SPEC-REVIEW документ)
- `e6ac033` `fix(tabs): restore captured mouse reordering` (класс A/B/C/D, `Issue: #243`, `User-Visible: yes`) — единственный коммит с продуктовым поведением
- `0fa7c28` `docs: review document for #243` (класс C, CODE-REVIEW-243-r1 документ)
- `e4fe502` `docs: accept screenshot fingerprint for #243` (класс C, `Issue: #243`, `User-Visible: no`)
- Ревьюер этого раунда не имеет контекста реализации; материал — диапазон коммитов и diff, не рассказ автора.
## Почему это r2, а не продолжение r1
Вердикт r1 (комментарий `2026-08-22T16:39:51Z`, документ `docs/reviews/CODE-REVIEW-243-r1.md`) был
**зелёным, 0 находок**, получен на implementation-коммите `f4de527`. SHA в том вердикте назван
прямо в тексте документа — это не находка данного раунда.
После зелёного ревью слияние в `dev` конфликтовало (`docs/images/screenshots.json`). Владелец
вернул issue в `S6-in-progress` только для ребейза (комментарий `2026-08-22T16:40:03Z`), без
указания на дефект кода. Автор перебазировал ветку на `origin/dev` `56a3977`; implementation-коммит
получил новый SHA `e6ac033`, добавился разрешающий конфликт коммит `e4fe502` (принятие нового
screenshot-фингерпринта из канонического CI-прогона).
**Разбор в этом раунде полный, не по дельте** — по прямому указанию PROCESS.md §2.10 и AGENTS.md:
«после ребейза на ушедший вперёд `dev` это другой код», а не редактирование по замечанию. Диапазон
`origin/dev...HEAD` целиком (все 5 коммитов) — предмет разбора, включая diff, который r1 уже видел
под другим SHA, и диапазон, который r1 не видел вовсе (`e4fe502`).
## Скоуп ревью
Изменённые файлы продуктового/тестового кода (коммит `e6ac033`, единственный с поведением):
```
src/houseplan-card.ts 78 +++-- pointer-move на .tabs, координатный hit-test,
suppress-click, _endTabDrag() в _pickSpace/_setMode
src/styles.ts 3 +- drop-before / drop-after вместо .droptarget
demo/smoke_space_tab_reorder.mjs 497 (переписан на трастед page.mouse)
demo/golden/harness.mjs 68 ++ семантический guard для tabDrag-сцен
demo/golden/matrix.mjs 8 +- 2 новые сцены, версия матрицы 35 → 36
demo/golden/run.mjs 7 ++ release мыши в finally
scripts/mutation-gate.mjs 48 ++ 3 новых мутанта + правка tab-drag-outlives-the-card
test/golden-matrix.test.mjs 23 ++ unit-контракт двух новых сцен
docs/CHANGELOG.md/.ru.md, docs/USER-GUIDE.md/.ru.md, docs/TESTING.md — в том же коммите
```
Плюс отдельным коммитом `e4fe502`: только `docs/images/screenshots.json` (source/имиджные хэши
скриншотов), со ссылкой на канонический прогон
[Docs screenshots](https://github.com/Matysh/houseplan-card/actions/runs/32585516781).
## Как проверялось
1. Прочитан `docs/SCOPE.md` — задача чинит регрессию выпущенной функции J6 («keep the plan true as
the home evolves»), персона home admin, поверхность desktop-редакторы; новый разделитель делает
уже принятый контракт #220 честным, мандат не расширяет.
2. Прочитаны `AGENTS.md` и `PROCESS.md` целиком, включая §2.10 (объём повторного раунда), §7.2
(ребейз — другой код), §4 (лимит циклов), §8 (соразмерность гейтов), §12.
3. Прочитано тело issue #243 и все комментарии, включая обе реплики автора о ребейзе и оба
предыдущих вердикта ревьюера (SPEC-REVIEW r1 и CODE-REVIEW r1).
4. Прочитан ТЗ `docs/specs/243-space-tab-drop-target.md` целиком (не менялся с r1) и сверен построчно
с текущим diff по каждому разделу — контракт hit-testing §7, контракт завершения §8, визуальный
контракт §9, AC1–AC9 §12.
5. Прочитан `docs/USER-GUIDE.ru.md`/`.md` — формулировка про разделитель и сторону вставки согласована
с текстом ТЗ, терминология не изобретена.
6. Построчно прочитан **весь** diff `src/houseplan-card.ts` (не только «что изменилось с r1», а весь
diff `origin/dev...HEAD`, поскольку это разбор с нуля): `_tabPointerDown`, новый `_tabDropTargetAt`,
переписанные `_tabPointerMove`/`_tabPointerUp`, новый `_suppressNextTabClick`/`_tabSuppressClickTimer`,
вызовы `_endTabDrag()` в `_pickSpace`/`_setMode`/`disconnectedCallback`, изменение шаблона рендера
(`@pointermove` на контейнере `.tabs`, классы `drop-before`/`drop-after`).
7. Самостоятельно (не со слов автора и не со слов r1) прослежена семантика двойной доставки
`pointerup`: захват указателя ретаргетирует `pointerup`/совместимые mouse-события на исходную
вкладку независимо от того, где физически отпущена кнопка; локальный `@pointerup` на исходной
вкладке срабатывает раньше, чем событие всплывает до `window`, и синхронно снимает оконный
листенер внутри `_endTabDrag()` до того, как всплытие туда дойдёт — поэтому `_commitTabOrder`
вызывается не более одного раза на один физический `pointerup`. Оконный листенер остаётся
единственным путём для случаев, когда локальный слушатель не сработал бы (потеря capture браузером).
8. Самостоятельно вручную прогнана математика `reorderSpaceIds` (`src/space-order.ts:55-65`, диффом
не тронут) на обоих направлениях переноса и сверена со знаком `placement` из `_tabDropTargetAt`:
перенос влево (`targetIndex < sourceIndex`) даёt `before` и `splice` действительно ставит
перетаскиваемую вкладку перед целью; перенос вправо даёт `after` и ставит её после — соответствует
§7.3 ТЗ.
9. Прочитан diff `src/styles.ts` — `drop-before`/`drop-after`, `inset` box-shadow с разным знаком,
один theme token — соответствует §9 ТЗ.
10. Прочитан diff `demo/smoke_space_tab_reorder.mjs` целиком (497 строк): все позитивные сценарии
(AC1–AC4, включая негативный на View в строках 279–295) используют `page.mouse`; синтетический
`dispatchEvent` остался только в двух местах — явный `pointercancel` (AC5, строка 228) и явная
ветка `pointerType: 'touch'` (AC6, строка 271) — как и требует ТЗ §13.1.
11. Прочитан diff `demo/golden/harness.mjs`/`matrix.mjs`/`run.mjs`, `test/golden-matrix.test.mjs`.
12. Прочитан diff `scripts/mutation-gate.mjs` — три новых мутанта, плюс адаптация `find`/`replace` в
`tab-drag-outlives-the-card` под новый порядок строк (`_endTabDrag(); clearTimeout(...)`),
появившийся из-за `_tabSuppressClickTimer`.
13. Прочитан diff `docs/images/screenshots.json` (коммит `e4fe502`) — только смена `sourceFingerprint`
и по одному `sourceSha256` на сцену; `imageSha256` не изменились ни у одной сцены (0 переснятых
PNG), коммит содержит ссылку на прогон канонической Docs screenshots джобы.
14. Прочитаны трейлеры всех пяти коммитов и структура `e6ac033` (единственного user-visible) —
оба changelog, оба USER-GUIDE, TESTING.md в одном коммите с поведением.
15. Прогнаны гейты лично (раздел ниже), не со слов автора и не переиспользуя цифры документа r1.
## Гейты — что прогнано и почему
Обязательные всегда (PROCESS.md §8, §2.10 — дешёвые гейты гоняются в каждом раунде):
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | `npx tsc --noEmit` | PASS, без вывода |
| Unit | `npm test` | PASS — 1091 tests, 1091 pass, 0 fail |
| Build + сверка бандлов | `npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js && cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js` | PASS, обе копии идентичны dist |
| Docs fingerprint | `node scripts/check-docs.mjs` (обязателен, diff трогает `src/**`) | PASS — «Documentation checks passed (7 files, 10 external links)» |
По необходимости, определяемой diff/AC:
| Гейт | Команда | Результат | Почему прогнан |
|---|---|---|---|
| Smoke-select | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | Прямое совпадение: `smoke_space_tab_reorder`. Слабая связь по общему `_model` (10 смоков) | Обязателен для решения по выборке; вывод не изменился после ребейза |
| Целевой browser smoke | `node demo/smoke_space_tab_reorder.mjs` | OK, все 37 полей `true` | Прямое совпадение из smoke-select, AC1–AC6 |
| Mutation-gate, новые id | `node scripts/mutation-gate.mjs --id=tab-drag-target-follows-captured-source` (и остальные два новых id по отдельности через `--check`, см. ниже) | Пойманы | Доказательство «тест умеет падать» для AC1/AC2/AC4 |
| Mutation-gate, полный реестр | `node scripts/mutation-gate.mjs --check` | Все мутанты «ok», включая три новых (`tab-drag-target-follows-captured-source`, `tab-drop-indicator-always-before`, `tab-drop-outside-commits-last-target`) и адаптированный `tab-drag-outlives-the-card` | Проверяет, что адаптация мутанта под новый порядок строк в `disconnectedCallback` не потеряла покрытие regression-риска #220 |
| Golden verify (целевые сцены) | `npm run golden:verify -- --only=space-tab-drop-before-light,space-tab-drop-after-dark` (флаг `--only` не ограничивает вывод — прогоняется весь набор) | Обе новые сцены: `missing-baseline`, не `error`; в `artifacts/golden/golden-report.json` лично прочитан `runtime.tabDrag` каждой сцены: `ok: true`, `trusted: true`, правильные `sourceId`/`targetId`/`placement`, `insetX` положительный для `before` (`1.14858`) и отрицательный для `after` (`-1.14935`) | AC2/AC8 требуют доказательства семантического guard на реальном `page.mouse`-жесте; проверено не по факту статуса `missing-baseline`, а по содержимому отчёта |
| Слабые связи smoke-select (по теме diff) | `node demo/smoke_fixed_floor.mjs`, `node demo/smoke_space_card.mjs`, `node demo/smoke_deeplink.mjs`, `node demo/smoke_kiosk_pan_lock.mjs` | Все PASS/OK | fixedFloor/навигация вкладок задеты AC6; `_pickSpace`/`_setMode` получили новый вызов `_endTabDrag()` |
| Дополнительно по теме diff (не из smoke-select) | `node demo/smoke_modes.mjs` | OK | `_setMode` — новая точка вызова `_endTabDrag()` в этом diff; смок не помечен smoke-select (нет прямого совпадения по идентификатору), но напрямую упражняет изменённый путь |
| Process-gate | `node scripts/process-gate.mjs --range origin/dev..HEAD` | «гейт пройден, предупреждений 0», 5 коммитов | Дешёвый офлайн-гейт: трейлеры/класс файлов/ветка на весь диапазон раунда |
**Не прогонялось и почему:**
- `smoke_audit_1490`, `smoke_glow_blending`, `smoke_render_perf`, `smoke_styling_hooks`,
`smoke_virtual_light_toggle`, `smoke_zoom_out` — слабая связь только по общему `_model`; diff их не
трогает (подтверждено чтением diff), тема — рендер устройств/зум/тени, не путь tab-drag.
- `python -m pytest tests_backend` — diff не затрагивает `custom_components/**/*.py` (подтверждено
`git diff --stat`).
- Performance-профиль — ТЗ §11 явно снимает требование (линейный проход по ≤50 пространствам), в AC
не назван.
- Полный `npm run golden:verify` по всей матрице как предмет построчного разбора — только то, что обе
НОВЫЕ сцены дошли до `missing-baseline`, а не `error`; остальные ~74 «different»/«missing-baseline»
сцены не относятся к #243 (диффом не менялись), это известный долг непринятых baseline на `dev`,
предрелизная обязанность (Правило 13), не этого код-ревью.
- `npm run golden:accept` — не выполнялся и не должен выполняться в этом цикле.
- Реальный `gh run view` канонического Docs screenshots прогона (`runs/32585516781`) — не открывался;
достаточным доказательством честности `screenshots.json` служит локально пройденный
`check-docs.mjs` (сверяет фингерпринт с текущим `src/**` независимо от источника коммита) и то, что
ни один `imageSha256` не изменился — переснятых PNG нет, только пересчитан source-хэш.
## Проверка AC
Полный повторный разбор (не по дельте относительно r1), так как раунд открыт ребейзом, а не
находкой — см. «Почему это r2».
| AC | Итог | Как доказано |
|---|---|---|
| AC1 | Выполнен | `smoke_space_tab_reorder`: `realMouseMovedTabLeft`, `firstDropWroteOnce`, `sameTabMovedRight`, `secondDropWroteOnce` — все `true` (лично прогнано на `e6ac033`); жест — трастед `page.mouse`, capture сохраняется (`captureKeptButTargetResolved`) |
| AC2 | Выполнен | Smoke: `leftDropUsesBeforeSide`/`rightDropUsesAfterSide`, `beforeDividerHasPositiveInset`/`afterDividerHasNegativeInset`; golden-отчёт лично прочитан: `insetX` `+1.149`/`-1.149`, `placement` совпадает с ожидаемым |
| AC3 | Выполнен | Smoke: `subThresholdStayedClick`, `subThresholdDidNotWrite`, `clickStillSwitchesSpace` |
| AC4 | Выполнен | Smoke: `outsideClearsDropTarget`, `outsideReleaseEndedDrag`, `outsideReleaseDidNotWrite`, `nextClickWorksAfterOutsideRelease` |
| AC5 | Выполнен | Smoke: `pointerCancelEndedDrag`/`DidNotWrite`, `dragWasActiveBeforeDetach`/`detachEndedTheDrag`/`detachedCardDidNotWrite`/`orderSurvivedDetach`; полный `mutation-gate --check` зелёный, включая адаптированный `tab-drag-outlives-the-card` (проверено, что адаптация под новую строку `_tabSuppressClickTimer` не ослабила мутант) |
| AC6 | Выполнен | Smoke: `touchDidNotReorder`, `notReorderableInView`/`viewDidNotReorder`, `fixedFloorNotReorderable`; отдельно прогнан `smoke_fixed_floor` — PASS; `canStartTabDrag`-гейт в `_tabPointerDown`/`_canReorderTabs` diff'ом не тронут (сверено чтением) |
| AC7 | Выполнен, разобран по коду, не новым тестом | Тело `_commitTabOrder` (`src/houseplan-card.ts:1421-1450`) не изменено этим diff — прочитано целиком и сверено с diff-хунками: единственная правка со стороны вызова — `_tabPointerUp` передаёт `target.targetId` вместо `drag.overId`; active-space/toast/маркеры подтверждены smoke (`activeSpaceUnchanged`, `danglingMarkerPinnedToItsOldSpace`, `warnedAboutPositionalFloor`/`warningNotRepeated`) |
| AC8 | Выполнен | `artifacts/golden/golden-report.json` лично прочитан для обеих новых сцен: `tabDrag.ok: true` для каждой, semantic guard (`dragging`/`targetId`/`placement`/знак inset/размер rect) прошёл на реальном жесте до сравнения с (отсутствующим) baseline; `test/golden-matrix.test.mjs` тест на структуру матрицы — в `npm test` |
| AC9 | Выполнен | Typecheck/unit/build/3-бандла зелёные лично; `docs/CHANGELOG.md`/.ru.md, `docs/USER-GUIDE.md`/.ru.md изменены в том же коммите `e6ac033`, что и поведение, с `User-Visible: yes`; process-gate зелёный на весь диапазон |
## Закрытие раунда r1
r1 закрылся зелёным вердиктом с нулём находок — таблицы «находка → чем закрыта» в обычном виде не
требуется, потому что нечего закрывать содержательно. Единственное, что изменилось между r1 и r2, —
техническое: ребейз ветки на новый `dev` и коммит с принятием нового screenshot-фингерпринта.
| Что было в r1 | Что произошло между r1 и r2 | Где это видно |
|---|---|---|
| Implementation-коммит `f4de527`, зелёный вердикт, 0 находок | Слияние конфликтовало на `docs/images/screenshots.json`; владелец вернул issue в `S6-in-progress` **без указания на дефект кода**, только для ребейза | Комментарий issue `2026-08-22T16:40:03Z`: «Код-ревью зелёное — вердикт выше в силе, переделывать работу не нужно. Не удалось только слияние» |
| — | Ветка перебазирована на `origin/dev` `56a3977`; тот же продуктовый diff получил новый SHA `e6ac033`; конфликт в `screenshots.json` разрешён в пользу канонического набора нового `dev`, добавлен коммит `e4fe502` с новым source-фингерпринтом | `git show e6ac033 --stat` (файлы идентичны списку из CODE-REVIEW-243-r1.md), `git show e4fe502` (только `screenshots.json`, `imageSha256` не изменились) |
| Диагноз/AC1–AC9/находки r1 | Не изменялись правкой по замечанию — весь этот документ содержит самостоятельную повторную проверку каждого AC на `e6ac033`, а не перенос вывода r1 | Раздел «Проверка AC» выше, гейты лично прогнаны заново на текущем `HEAD` |
Других находок в r1 не было, поэтому таблица короче стандартного вида «находка | чем закрыта | где
видно» — здесь нечего закрывать, кроме самого факта ребейза.
## Унаследовано из r1
По правилу «ребейз — другой код» (PROCESS.md §2.10, §7.2) наследование выводов **о самом
implementation-диффе** в этом документе не применялось — весь код e6ac033 прочитан и все гейты
прогнаны заново лично, независимо от чисел и выводов `docs/reviews/CODE-REVIEW-243-r1.md`.
Унаследовано (не пересматривалось в этом раунде, так как относится к этапу, диффом не задетому):
- **ТЗ и его ревью.** `docs/specs/243-space-tab-drop-target.md` (`7ac1934`) и
`docs/reviews/SPEC-REVIEW-243-r1.md` — продуктовая рамка, скоуп/не-скоуп, формулировка AC1–AC9 не
пересматривались повторно как предмет SPEC-ревью: файл ТЗ не менялся ни на ребейзе, ни в этом
диапазоне (подтверждено — в diff `origin/dev...HEAD` нет хунка по `docs/specs/243-*.md` после
начального коммита `133fdc9`, который сам входит в этот диапазон и таким образом фактически
перепрочитан в п.4 «Как проверялось», а не просто унаследован).
- **Математика #220** (`reorderSpaceIds`/`applySpaceOrder` в `src/space-order.ts`) — файл этим diff'ом
не тронут; её корректность установлена ревью #220 и покрыта существующими unit-тестами, которые
остаются зелёными (`npm test`, 1091/1091). Несмотря на это, семантика знака `before`/`after` из
`_tabDropTargetAt` вручную сверена с ней заново в этом раунде (см. «Как проверялось», п.8) — это не
чистое наследование, а самостоятельная перепроверка стыка нового и старого кода.
- **Канонический прогон Docs screenshots** (`runs/32585516781`) — сам факт существования и «Chromium
151, 0 изменённых PNG» принят со слов коммита `e4fe502` и подтверждён локально только через
`check-docs.mjs` (фингерпринт совпадает) и через `imageSha256`, не через открытие лога прогона в
Actions.
## Находки
Находок нет — ни High, ни Medium, ни Low.
Отдельно проверенные риски ложной уверенности, снятые самостоятельным разбором этого раунда (не
переносом выводов r1):
- **Не ослабила ли адаптация мутанта `tab-drag-outlives-the-card` под новый код регресс-контроль
#220?** Нет: `find`/`replace` теперь целится в блок `this._endTabDrag(); clearTimeout(this._tabSuppressClickTimer);`
внутри `disconnectedCallback` — та же строка `_endTabDrag()` вырезается, только соседняя строка,
под которую делается патч, сменилась из-за появления `_tabSuppressClickTimer`. Мутант лично прогнан
в составе полного `mutation-gate --check` и помечен `ok`.
- **Не привёл ли ребейз к скрытому конфликту поведения** (например, если между `f4de527` и `e6ac033`
в `dev` появился код, трогающий тот же путь `.tabs`/`_setMode`/`_pickSpace`)? Нет: `origin/dev`
сейчас — `56a3977`, ровно тот SHA, на который ветка перебазирована согласно комментарию автора;
других коммитов дальше нет (`git log 56a3977..origin/dev` — пусто), то есть весь diff, который я
вижу, — это ровно послерейбейзный итог, без дополнительного скрытого дрифта.
- **Не подменяет ли `check-docs.mjs` (класс C, `screenshots.json`) продуктовую проверку** — не
относится к продуктовому поведению #243 напрямую, но входит в обязательный гейт `docs` (§8); диффом
подтверждено, что изменение чисто конфликт-резолюшн (0 изменённых PNG), не попытка обойти гейт.
- Риски, разобранные в r1 и переподтверждённые самостоятельно в этом раунде (двойная доставка
`pointerup`, `pointercancel` — отдельный путь от `pointerup`, освобождение мыши golden-харнессом в
`finally`, соответствие диагноза ТЗ фактическому фиксу через перенос `pointermove` на `.tabs`) — см.
«Как проверялось», пп. 6–7, где вывод получен самим ревьюером этого раунда, а не цитированием r1.
## Что проверено и корректно
- Диагноз ТЗ (per-tab listener + retargeting после `setPointerCapture` фиксирует цель на источнике)
устранён тем способом, который ТЗ §19 п.2 разрешал (обработчик на `.tabs`); подтверждено чтением и
тем, что мутант, возвращающий старую адресацию, ловится смоком.
- Координатный hit-test (`_tabDropTargetAt`) реализует правила §7 ТЗ: попадание по
`getBoundingClientRect()`, исключение исходной вкладки, `before`/`after` по индексу в модели (не по
геометрии — вручную пересчитано в этом раунде), сброс цели при промахе, устойчивость к wrap (rect
конкретного элемента).
- Визуальный контракт (`drop-before`/`drop-after`, `inset` box-shadow разного знака, один theme token)
соответствует §9; подтверждено golden-отчётом (числовые значения `insetX` лично прочитаны).
- Двойная доставка `pointerup` не воспроизводится по построенной самостоятельно модели событий:
локальный листенер на захватившей вкладке снимает оконный листенер до того, как событие всплывает
туда же.
- `_endTabDrag()` в `_pickSpace`/`_setMode` — не расширение скоупа: прямо предписано контрактом
завершения ТЗ §8 («смена режима/пространства... очищают drag»), покрыто smoke
(`modeChangeEndsDrag`/`modeChangeDidNotWrite`) и дополнительно личным прогоном `smoke_modes.mjs`
(OK) — путь `_setMode`, который получил новый вызов, не сломан.
- Три новых мутационных guard'а (§14 ТЗ) индивидуально проверены как способные покраснеть; полный
реестр `mutation-gate --check` зелёный целиком, включая адаптированный унаследованный мутант #220.
- Оба changelog, `docs/USER-GUIDE.md`/.ru.md, `docs/TESTING.md` обновлены в одном user-visible
коммите `e6ac033`; терминология («вкладка», «разделитель», «сторона вставки») согласована с ТЗ и
USER-GUIDE, не изобретена.
- Класс файлов и трейлеры на всех пяти коммитах диапазона — `Issue: #243` везде, `User-Visible: yes`
только на `e6ac033`; ветка `issue/243-space-tab-drop-target`; `process-gate.mjs` подтверждает 0
предупреждений на весь диапазон.
- Коммит `e4fe502` (принятие screenshot-фингерпринта) — генерируемый класс C, ссылается на реальный
канонический прогон, не переснимал ни одного PNG, `check-docs.mjs` зелёный после него.
## Чего не проверял
- Полный `npm run golden:verify` по всем сценам построчно как предмет разбора — только то, что две
НОВЫЕ сцены дошли до `missing-baseline`, а не `error`, и что их `runtime.tabDrag.ok === true`.
Остальные «different»/«missing-baseline» сцены — известный долг непринятых baseline на `dev`,
предрелизная обязанность, не задача этого код-ревью.
- `smoke_audit_1490`, `smoke_glow_blending`, `smoke_render_perf`, `smoke_styling_hooks`,
`smoke_virtual_light_toggle`, `smoke_zoom_out` — не прогонялись; связь со smoke-select только по
общему `_model`, тема не пересекается с изменённым путём.
- `python -m pytest tests_backend` — не запускался, backend не тронут этим диапазоном.
- Performance-профиль — не запускался, не назван в AC, обоснование ТЗ §11 (≤50 пространств, линейный
проход) проверено чтением, не измерением.
- Лог канонического прогона Docs screenshots (`runs/32585516781`) в GitHub Actions — не открывался;
доверие основано на локальном `check-docs.mjs` и неизменности `imageSha256`, а не на просмотре лога.
- Ручное визуальное сравнение golden PNG для двух новых сцен — нет предмета: baseline ещё не принят и
не должен приниматься в этом цикле.
## Вердикт
Раунд открыт ребейзом на ушедший вперёд `dev`, а не находкой предыдущего ревью — r1 был зелёным с
нулём находок. Согласно PROCESS.md §2.10/§7.2 («после ребейза это другой код») разбор проведён
полностью и самостоятельно на текущем `HEAD` (`e4fe502`, implementation-коммит `e6ac033`), без опоры
на числа документа r1: все девять AC заново доказаны — восемь автотестом (лично прогнанный browser
smoke на трастед `page.mouse`, полный mutation-gate, golden semantic guard с лично прочитанным
`runtime.tabDrag`, golden-matrix unit) и один (AC7) разобран чтением кода с явной пометкой «проверено
чтением, не исполнением» для тела `_commitTabOrder`, которое diff не менял. Обязательные и относящиеся
к diff'у дополнительные гейты прогнаны лично, включая один сверх списка r1 (`smoke_modes.mjs`, задет
новой точкой вызова `_endTabDrag()` в `_setMode`). Единственный не-код коммит раунда (`e4fe502`,
принятие screenshot-фингерпринта после разрешения конфликта) проверен: 0 переснятых PNG, ссылка на
канонический прогон, `check-docs.mjs` зелёный. Находок нет.
**Вердикт: зелёный · заход r2 · блокирующих циклов 0/4 · High: 0 · Medium: 0 → в задаче**