Files
houseplan-card/docs/reviews/CODE-REVIEW-441-r1.md
T
2026-09-03 19:46:13 +00:00

200 lines
16 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 — 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».
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/441-vacuum-route-draft`, коммит `75478905d5eb` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `6e2f7f4813e3b45927eb282b1e5fdd62b872d019`
```
git log --all --format='%H %T' | grep 6e2f7f4813e3
```