# CODE-REVIEW — issue #441 · заход r2 Ветка `issue/441-vacuum-route-draft`, HEAD на ревью `75478905` (проверено `git rev-parse HEAD` непосредственно перед выводом, §2.7). ## Почему это заход r2, а не r1 Заголовок задачи, переданный этой сессии, утверждает «заход r1 · блокирующих циклов 0 из 2». Это не подтверждается материалом: - в комментариях issue #441 уже есть терминальный вердикт код-ревью: `Вердикт: жёлтый · заход r1 · блокирующих циклов 1/2 · High: 0 · Medium: 1 → в задаче` (комментарий `claude`, `IC_kwDOTOcLQM8AAAABSa2lLw`, 19:35:18 UTC); - следом (19:35:28 UTC) владелец подтвердил: «Автоматическое ревью не отработало… Статусная метка не менялась» — то есть вердикт реальный, но шаг конвейера, переставляющий метку после вердикта (§10.4), не выполнился, и задача осталась висеть в `S7-code-review`, откуда её забрала эта сессия повторно; - полный документ того раунда уже закоммичен в ветку самим конвейером (`75478905 docs: review document for #441`, автор `claude[bot]`) — это штатный механизм публикации (§10.4: «кладёт документ в `docs/reviews/` ветки задачи»), не постороннее вмешательство; - SHA того раунда назван в документе — `fa41d298` — и он не осиротел: `git diff fa41d298..HEAD` резолвится и виден ниже. Таким образом это второй заход ревью, разбор — по дельте (§2.10), а не заново. Файл `docs/reviews/CODE-REVIEW-441-r1.md` уже существует, второй документ с тем же номером затёр бы его, поэтому этот — `r2`. Присвоенный этой сессии заголовок «r1 · 0/2», видимо, следствие того же сбоя конвейера, который не переставил метку: трекер раундов не увидел уже опубликованный r1. Само по себе это не код-находка issue #441 и не блокирует вердикт, но чтобы бюджет §4 не потерялся — ниже он посчитан по факту (см. «Вердикт»), а не по переданному заголовку. ## Материал предыдущего раунда - Документ: `docs/reviews/CODE-REVIEW-441-r1.md` (в этой же ветке, коммит `75478905`). - SHA раунда: `fa41d298` (`fix: keep vacuum routes pending until a floor is chosen`). - Вердикт r1: жёлтый, High 0, Medium 1 в скоупе — красный `check-docs.mjs` на `fa41d298` (устаревший отпечаток скриншотов документации). ## Дельта r1 → r2 ``` git diff fa41d298..HEAD --stat docs/images/screenshots.json | 25 ++-- docs/reviews/CODE-REVIEW-441-r1.md | 246 +++++++++++++ ``` Два файла, оба класса C (документация/артефакт ревью). Продуктовый код (`src/**`), тесты и смоки, тронутые в `fa41d298`, дельтой r1→r2 не задеты — `git diff fa41d298..HEAD -- src test demo scripts docs/CHANGELOG.md docs/CHANGELOG.ru.md` пуст. Это делает дельту локальной по §2.10: полный повторный разбор AC не требуется, только проверка закрытия находки r1 плюс дешёвые гейты. `docs/reviews/CODE-REVIEW-441-r1.md` — служебный артефакт публикации предыдущего раунда, не правка автора; содержательного разбора не требует. ## Закрытие раунда r1 | Находка r1 | Чем закрыта | Где это видно | |---|---|---| | Medium: `node scripts/check-docs.mjs` красный на `fa41d298` — отпечаток скриншотов документации устарел после правки `src/editors/vacuum-maps-section.ts` | Коммит `0ea9c0f5` `docs: refresh canonical screenshots for #441` обновил `sourceFingerprint`/`sourceSha256` в `docs/images/screenshots.json` на все 10 сценариев; `imageSha256` каждого сценария не изменился (диалог, который правит #441, ни в одном сценарии не рендерится — пиксели те же), манифест получил `acceptance.lastWriteWasFingerprintOnly: true` — след легитимной приёмки `scripts/docs-accept.mjs` (fingerprint-only ветка `acceptedDocsManifest`, docs-accept.mjs:107), а не самодельной правки JSON | `git diff fa41d298..HEAD -- docs/images/screenshots.json`; перепрогнан `node scripts/check-docs.mjs` на HEAD `75478905` → `Documentation checks passed (7 files, 10 external links)` (было `ERROR screenshot source fingerprint is stale`) | Находка закрыта корректно и через штатный инструмент приёмки (`docs-accept.mjs`, а не ручная правка `screenshots.json` мимо проверки совпадения пикселей) — именно та дисциплина, которую требует §8 («скриншоты снимаются только… принимаются локально `docs:accept`»). ## Унаследовано из r1 Без повторной проверки принято (документ `docs/reviews/CODE-REVIEW-441-r1.md`, материал — SHA `fa41d298`, тот же SHA подтверждён этой сессией как резолвящийся предок HEAD): - **AC1** (черновик до выбора этажа) — доказательство `beginVacuumRouteDraft` (`src/vacuum-route-edit.ts:34`) и `test/vacuum-routes.test.mjs:279` — код не менялся дельтой; для собственного спокойствия эта сессия всё же **независимо повторила** мутацию r1 (не обязательна по §2.10, сделана как дешёвая проверка): временно заменила `space: routes.length ? '' : dockSpace` на `space: dockSpace`, пересобрала `test-build` (`npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs`) и прогнала `node --test test/vacuum-routes.test.mjs` → `not ok 25 - новый маршрут живёт в черновике до выбора этажа (#441 AC1)`, `# fail 1`; откат мутации вернул `# pass 32 / # fail 0`. Рабочее дерево после отката чисто (`git status --short` пуст). AC1 подтверждён на текущем SHA, а не только унаследован по документу. - **AC2** (только валидная атомарная запись) — `commitVacuumRouteDraft` (`src/vacuum-route-edit.ts:39-56`), `test/vacuum-routes.test.mjs:287`, смоки `noEmptySpaceEverSubmitted`/`rejectedPayloadWasValid`/ `retryKeepsExactIdentity` — код не менялся, наследуется по документу r1. - **AC3** (отмена/отказ не оставляют фантом) — `persistRoutes`/`confirmDraft` (`src/editors/vacuum-maps-section.ts`), смоки `cancelLeavesAcceptedRoutes`/`rejectionRestoresAcceptedMarker`/ `rejectionKeepsEditorUsable` — код не менялся, наследуется по документу r1. - Наблюдение r1 про побочное усиление `retarget`/`drop` (переход на общий `persistRoutes`/`rollbackOptimistic`) и про отсутствие новых ключей i18n — без изменений, код не тронут дельтой. - Оценка `docs/SCOPE.md` (J4/J6) — без изменений, продуктовый скоуп не менялся. ## Гейты — что прогнал в этом раунде и с каким результатом Дельта r1→r2 не трогает `src/**`/`test/**`, поэтому по §2.10 достаточно дешёвых гейтов; они прогнаны заново на HEAD `75478905`, а не унаследованы: | Гейт | Команда | Результат | |---|---|---| | Typecheck | `npx tsc --noEmit` | чисто, без вывода | | Тесты | `npm test` | `# tests 1857 · # pass 1856 · # fail 0 · # skipped 1` — совпадает с заявленным в r1 | | Сборка + синхронизация бандла | `npm run build`, затем `git status --short` | `dist` пересобран, working tree чист после сборки — `dist/houseplan-card.js` и `custom_components/houseplan/frontend/houseplan-card.js` совпадают в коммите (`cmp` → без вывода, идентичны) | | Документация | `node scripts/check-docs.mjs` | **зелёный**: `Documentation checks passed (7 files, 10 external links)` — находка r1 закрыта (см. выше) | | Мутация AC1 (доп. проверка этого раунда) | временный откат `src/vacuum-route-edit.ts:34` → `space: dockSpace`, `npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test test/vacuum-routes.test.mjs` | `not ok 25` на мутанте, `# pass 32 / # fail 0` после отката — дисциплина «тест умеет падать» подтверждена независимо | | Смок AC1–AC3 | `node demo/smoke_vacuum_route_draft.mjs` (после `npm run bundle:sync`, иначе смок падает на отсутствующем `demo/srv/assets/houseplan-card.js` — сгенерированный, не коммитится, #255) | все 14 проверок `true`, `OK` | Не прогонялись повторно (дельта их не задевает, обоснование см. в документе r1, наследуется): `smoke-select.mjs` по полному диффу, `golden:verify`, `pytest tests_backend`, `npm run invariants`, performance-профили, остальные точечные смоки (`smoke_vacuum_firstuse`, `smoke_vacuum_multifloor`, `smoke_save_race`, `smoke_config_writer`) — их результат на `fa41d298` уже зафиксирован в r1 и код, который они покрывают, не менялся. Трейлеры дельты: `0ea9c0f5` — `Issue: #441`, `User-Visible: no` (верно: обновление служебного отпечатка, а не пользовательского поведения, changelog не требуется); `75478905` — `Issue: #441`, `User-Visible: no` (служебный артефакт публикации ревью, класс C). ## Находки Нет. Единственная находка r1 (Medium, в скоупе) закрыта корректно и через штатный инструмент. Новых находок дельта r1→r2 не вносит — она состоит из точечного обновления отпечатка документации и служебного коммита самого конвейера. ## Что проверено и корректно (без отдельной находки) - `docs/images/screenshots.json`: у всех 10 сценариев `imageSha256` не изменился при обновлении `sourceFingerprint` — подтверждает заявление r1, что правка не задевает ни один заскриншоченный экран (диалог второго маршрута пылесоса нигде не снимается для документации). - Приёмка прошла через `scripts/docs-accept.mjs` (ветка `lastWriteWasFingerprintOnly`), а не через ручную правку JSON — соответствует требованию §8 «коммит скриншотов делает человек» и «приёмка отказывает, если кандидат снят не с этого дерева»: сам скрипт это проверяет (`verifyDocsCandidate`), а не декларация автора. - `docs/reviews/CODE-REVIEW-441-r1.md` корректно оформлен как документ своего раунда и не требует правок задним числом (документы ревью не переписываются, только дополняются новым заходом). ## Чего не проверял - Полный повторный разбор AC1–AC3 по коду и всем смокам — не требуется по §2.10, т.к. дельта не трогает `src/**`/`test/**`/`demo/**`; AC1 дополнительно перепроверен мутацией (см. «Гейты»), AC2/AC3 унаследованы. - `npm run golden:verify`, `pytest tests_backend`, `npm run invariants`, performance-профили — не прогонял; обоснование то же, что в r1 (View, backend `.py`, геометрия/ссылки на неё не затронуты ни `fa41d298`, ни дельтой r1→r2). - `node scripts/smoke-select.mjs --base origin/dev --head HEAD` по полному диффу — не перегонял: набор символов на изменённых строках не изменился с r1 (дельта не трогает код), решение по каждому пункту из r1 остаётся в силе. - Реальность прогона CI-джобы `Docs screenshots` для коммита `0ea9c0f5` (не видна из репозитория) — доверие основано на структурном доказательстве самого `scripts/docs-accept.mjs` (совпадение `imageSha256` с уже закоммиченными файлами и наличие `acceptance.lastWriteWasFingerprintOnly`), а не на слепом заявлении автора; независимый пересчёт `visualFingerprint(root)` на HEAD не делал отдельно от `check-docs.mjs`, который эту сверку и выполняет. ## Вердикт Зелёный. AC1–AC3 подтверждены (AC1 — независимо на HEAD этой сессией, AC2/AC3 — унаследованы из r1 по неизменному коду), единственная Medium-находка r1 закрыта штатным инструментом и подтверждена зелёным `check-docs.mjs` на HEAD `75478905`. Новых находок нет. Бюджет циклов (§4): r1 был жёлтым и потратил 1 цикл из 2 (лёгкий/короткий трек, лимит код-ревью — 2, §5.1). Этот заход (r2) зелёный и цикл не образует (#227) — израсходовано остаётся **1 из 2**, а не 0 из 2, как было в заголовке этой сессии; расхождение объяснено в разделе «Почему это заход r2». --- ## Материал раунда - Ветка: `issue/441-vacuum-route-draft`, коммит `75478905d5eb` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `6e2f7f4813e3b45927eb282b1e5fdd62b872d019` ``` git log --all --format='%H %T' | grep 6e2f7f4813e3 ```