# 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`, скоуп не расширен, трейлеры корректны, инфраструктурный трек применён правомерно. --- ## Материал раунда - Ветка: `issue/586-screenshots-strict-mode`, коммит `21923c344de0` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `ebc8db699ad297494bc533175728e18947afef32` ``` git log --all --format='%H %T' | grep ebc8db699ad2 ``` - Тело issue: `18937affcac4a9658ede2b880f86cf8f3c1a1e78f4b87f6823d451e5ed313c42` - Вердикт конвейера: `green` · High 0