Files
houseplan-card/docs/reviews/CODE-REVIEW-586-r1.md
T
2026-09-16 19:23:02 +00:00

109 lines
14 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-586-r1
**Issue:** #586 — Validate: строгий режим свежести скриншотов документации не включается (сравнение с многострочным выводом CLI).
**Трек:** инфраструктурный (§1 PROCESS.md) — ни один файл класса A не тронут, ТЗ/S1–S6 не требуются.
**Материал:** `21923c344de012c8849083b68200a6a205a53649` (ветка `issue/586-screenshots-strict-mode`), два коммита от `303c0843` (dev на момент ветвления):
- `245b7f98` — фикс разбора вывода `classify-changes.mjs` в `validate.yml`;
- `21923c34` — пересъёмка и приёмка 4 кадров документации (снятый долг #479).
Заход r1, блокирующих циклов израсходовано 0 из 4.
## Скоуп диффа
```
.github/workflows/validate.yml | 10 ++++++----
docs/images/01-view-desktop.png | Bin
docs/images/02-view-touch.png | Bin
docs/images/08-room-card.png | Bin
docs/images/09-device-info.png | Bin
docs/images/screenshots.json | 40 ++++++++++++++++----------------
scripts/classify-changes.mjs | 29 +++++++++++++++++++++++++++-
scripts/mutation-registry.mjs | 15 +++++++++++++++
test/classify-changes.test.mjs | 30 +++++++++++++++++++++++++++++
```
Все файлы — класс B (гейты/инструменты) и C (документация/скриншоты). Продуктовый код (`src/**`, `custom_components/**`) не тронут — классификация «инфраструктурная» верна.
## Как проверялось
Читал по порядку: `docs/SCOPE.md`, `PROCESS.md` (§1–§10), тело issue #586 и хендофф-комментарий автора, сам диффа. Продуктового поведения нет, поэтому `docs/USER-GUIDE.ru.md` и канонические документы подсистем нерелевантны.
### Гейты — что прогнано и почему
| Гейт | Статус | Комментарий |
|---|---|---|
| Validate на `21923c34` | **не перезапускал** | Подтверждён вводной задачи как success: https://github.com/Matysh/houseplan-card/actions/runs/35139056409. Дешёвые гейты (`tsc`, `npm test`, `build`+сверка бандла) считаю закрытыми этим прогоном |
| `node --test --test-name-pattern="#586" test/classify-changes.test.mjs` | **прогнан**, зелёный | 2/2 pass — оба новых теста-свидетеля |
| Мутация вручную (снял защиту так же, как патч в `mutation-registry.mjs`, прогнал тест) | **прогнан** | тест `#586: режим гейта скриншотов…` покраснел: `expected 'strict', actual 'warn'`. Working tree восстановлен после проверки (`git status` чист) |
| `node scripts/mutation-gate.mjs --id=screenshot-freshness-never-strict` | **прогнан**, зелёный | `ok screenshot-freshness-never-strict: заявленный тест покраснел на мутанте · поймано 1 из 1` — независимое подтверждение официальным гейтом, не только ручной правкой |
| `node scripts/mutation-gate.mjs --check` | **прогнан**, зелёный | структурная проверка реестра, включая новый id — без FAIL |
| `node scripts/check-docs.mjs --external --screenshots=strict` | **прогнан**, зелёный | `Documentation checks passed (7 files, 12 external links)` — подтверждает, что отпечаток кадров действительно синхронизирован после второго коммита |
| `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | **прогнан** | 0 добавленных строк в `src/**/*.ts` — неприменимо, но чисто |
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | **прогнан** | «Исполняемого frontend-диффа нет» — смоки не выбираются, выбирать нечего |
| `node scripts/process-gate.mjs` | **прогнан**, зелёный | 2 коммита, диапазон `origin/dev..HEAD`, предупреждений 0 |
| `npx tsc --noEmit`, полный `npm test`, `npm run build`+cmp бандла | **не прогонял отдельно** | покрыты зелёным Validate на этом SHA (см. выше); diff не трогает `src/**`, риска для бандла/тайпчека нет |
| `npm run golden:verify` | **не прогонял** | diff не меняет рендер карточки — визуального продуктового результата нет, только скриншоты документации |
| `python -m pytest tests_backend` | **не прогонял** | `custom_components/**/*.py` не тронут |
| performance-профили | **не прогонял** | не названы в AC, чувствительные пути (`src/iso-*`, `src/live-*`, `src/render-*`) не тронуты |
## Находки
Блокирующих (High) находок нет. Medium в скоупе или вне скоупа — нет.
Low, снятая с записью: комментарий во `validate.yml` («#586: CLI отдаёт ОДИН ответ…») и в `classify-changes.mjs` дублируют друг друга почти дословно — не мешает читаемости и не влияет на корректность, менять не требую.
## AC — построчно
Приёмка из issue:
1. **«Свидетель краснеет на текущем коде: при `heavy=true` в выводе из двух строк выбранный режим обязан быть `strict`»** — доказано автотестом `test/classify-changes.test.mjs`. Таблица «чем краснеет»:
| AC | Чем доказан | Чем краснеет |
|---|---|---|
| AC1 (защитный: старая логика никогда не давала `strict`) | `node --test --test-name-pattern="#586" test/classify-changes.test.mjs` | Патч из `mutation-registry.mjs` id `screenshot-freshness-never-strict` (возвращает `strict` только для несуществующего события) — тест падает `expected 'strict', actual 'warn'`; дополнительно подтверждено `mutation-gate.mjs --id=screenshot-freshness-never-strict` → «поймано 1 из 1» |
Дополнительно тест `assert.notEqual(run(['--heavy'], candidate), 'heavy=true', …)` напрямую фиксирует регрессионный факт (вывод `--heavy` многострочный), из-за которого баг вообще возник — не дал ему тихо вернуться.
2. **«Кандидат с устаревшим отпечатком скриншотов краснеет в Validate»** — проверено чтением, не исполнением полного Validate: `validate.yml` теперь вызывает `node scripts/check-docs.mjs --external --screenshots=$mode`, где `$mode` считает `screenshotsGateMode()` (класс `strict` при трейлере `Release:`, PR, schedule, `workflow_dispatch full=true`). Поведение `check-docs.mjs --screenshots=strict` на устаревшем отпечатке (падение с ERROR) — уже установленный факт из #479 и подтверждён самим текстом issue («Локально… `check-docs.mjs --strict` падает с `ERROR`»); в этом диффе `check-docs.mjs` не менялся. Комбинация «правильное значение считается» (доказано тестом п.1) + «правильное значение используется» (доказано вторым тестом «preflight спрашивает режим одним значением…», который проверяет содержимое `validate.yml` через regex) закрывает AC2 без необходимости гонять весь Validate.
3. **«Обычный push остаётся в `warn`»** — доказано юнит-тестом: `screenshotsGateMode({ eventName: 'push', headMessage: 'fix: x…' })` → `'warn'`.
4. **«Заодно проверить остальные места, где вывод `classify-changes.mjs` разбирается сравнением строки целиком»** — проверено чтением: `grep` по всем `.github/workflows/*.yml` и `scripts/**` на использования `classify-changes.mjs` показывает единственный паттерн вида `[ "$var" = "…" ]` — тот, что был пофикшен. Остальные вызовы (строки 265, 305, 329, 339, 347 в `validate.yml`) пишут вывод в `$GITHUB_OUTPUT` построчно (`tee -a`/`>>`) — это штатный формат `ключ=значение`, а не сравнение целой строки, и он не задет багом.
## Что проверено и корректно
- Второй коммит (пересъёмка скриншотов) — не самостоятельная работа над гейтом «раз уж я здесь»: он прямо мотивирован тем, что включение strict-режима без актуального отпечатка тут же покрасило бы кандидата, а «долг» скопился именно потому, что защита не работала. Это укладывается в рамку задачи, а не расширяет её.
- Приёмка скриншотов — легитимным путём: `docs-accept.mjs` поддерживает `--expect-change`, и `declared` в `screenshots.json` (`view-desktop, view-touch, room-card, device-info`) совпадает с реально изменившимися 4 файлами из диффа. JSON не редактировался руками мимо инструмента.
- Автор задокументировал двойной прогон капчура (11/11 байт-в-байт совпадение) как довод против шума раннера, и визуально сверил 4 разошедшихся кадра (таблица differences ≤0.17%, maxΔ≤17) — качество доказательства выше типового для подобной правки.
- Трейлеры обоих коммитов: `Issue: #586`, `User-Visible: no` — корректно (изменение не меняет наблюдаемое поведение продукта; экран пользователя не задет, правится только CI и скриншоты документации).
- `mutation-registry.mjs`: новый id `screenshot-freshness-never-strict` синтаксически и структурно корректен (`--check` проходит), а guard (`--test-name-pattern="#586"`) точечно нацелен на добавленные тесты, не гоняя весь файл.
- Working copy после моих проверок чиста (`git status --porcelain` пуст) — я не оставил артефактов в репозитории.
## Чего не проверял
- Полный `npm test` / `npx tsc --noEmit` / `npm run build`+cmp — не гонял отдельно, положился на зелёный Validate этого точного SHA (см. таблицу гейтов).
- `golden:verify`, `pytest tests_backend`, performance-профили — не применимы к диффу (не трогает рендер/бэкенд/perf-пути).
- Реальный сценарий «Validate на кандидате со специально устаревшим отпечатком краснеет end-to-end» не воспроизводился запуском workflow — закрыт комбинацией юнит-теста + чтения кода (см. AC2 выше), это осознанное решение, а не пропуск.
## Материал раунда
- SHA материала: `21923c344de012c8849083b68200a6a205a53649`
- Дерево: см. `git show 21923c34^{tree}`
- Первый раунд — раздел «Унаследовано из r0» не требуется.
## Вердикт
Зелёный. AC1–AC4 доказаны (автотест + мутационный гейт + чтение кода), защитный AC имеет полноценную таблицу «чем краснеет» с подтверждением через `mutation-gate.mjs`, скоуп не расширен, трейлеры корректны, инфраструктурный трек применён правомерно.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/586-screenshots-strict-mode`, коммит `21923c344de0` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `ebc8db699ad297494bc533175728e18947afef32`
```
git log --all --format='%H %T' | grep ebc8db699ad2
```
- Тело issue: `18937affcac4a9658ede2b880f86cf8f3c1a1e78f4b87f6823d451e5ed313c42`
- Вердикт конвейера: `green` · High 0