docs: review document for #512
Проверка (CI) / Классификация изменённых файлов (push) Successful in 36s
Проверка (CI) / Мутанты по диффу (1/3): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/3): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/3): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 1m0s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 41s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 19s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 1m10s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 12m6s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 5m0s

Issue: #512
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-09 16:15:24 +00:00
parent ffe2ec51c0
commit 9b65d4bcf7
+94
View File
@@ -0,0 +1,94 @@
# CODE-REVIEW-512-r1
- **Issue:** #512 — «Golden: текст версии через seam вне кадров; `docs:accept --identical` по попиксельной идентичности»
- **Этап:** код-ревью (PROCESS.md §2.7)
- **Материал:** `git log --oneline origin/dev..HEAD` / `git diff origin/dev...HEAD` на SHA `ffe2ec51c0ca841931d4073f6d9d06aa1ddc4462` (рабочая копия стоит на нём же, ничего не подтягивалось и не переключалось).
- **Заход:** r1 · блокирующих циклов израсходовано 0 из 4.
- **Предыдущий этап:** ревью ТЗ прошло два захода (SPEC-REVIEW-512-r1 — красный, H1; SPEC-REVIEW-512-r2 — зелёный). Первая попытка запуска code review не состоялась вовсе — конфликт при ребейзе на `dev` вернул задачу в `S6` до чтения кода (комментарий issue 15:50); после ребейза (15:55) материал стал текущим `ffe2ec51`. Это первый фактический прогон код-ревью, поэтому разбор полный — разделов «Закрытие раунда» и «Унаследовано» не требуется (правило дельты применяется от r2).
## Скоуп ревью
71 файл в `git diff origin/dev...HEAD --stat`, из них по существу:
- **Класс A** (`src/**`): `src/card-version.ts` (новый файл, seam), точечные правки чтения версии в `src/houseplan-card.ts` и `src/houseplan-editor-runtime.ts` — по 5–7 строк на файл.
- **Класс B** (`scripts/**`, `test/**`, `demo/**`, `tsconfig.test.json`): `scripts/docs-accept.mjs` (+125 строк, новый режим `--identical`), `scripts/png-identical.mjs` (новый), `scripts/mutation-gate.mjs` (+22, два новых мутанта), `demo/golden/harness.mjs` (seam-override перед монтированием карточки), новые тесты.
- **Класс C** (`docs/**`, `AGENTS.md`, `PROCESS.md`): описания seam и `--identical` в `docs/TESTING.md`, `demo/golden/README.md`, `docs/DEVELOPMENT.md`, плюс упоминания в `AGENTS.md`/`PROCESS.md`, спека и оба ревью-документа спек-этапа, `docs/specs/README.md`.
- **Класс D** (`dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**`): пересборка бандла (хеши чанков сменились из-за правки исходников) и переприёмка 7 golden-эталонов с версией.
Остальное (docs/reviews/SPEC-REVIEW-512-r1.md, r2.md) — публикация предыдущего этапа, не предмет этого ревью.
Продукт не меняется для пользователя (`User-Visible: no` на всех коммитах, корректно — единственная видимая строка `gs.about_version` не меняет ни текст, ни поведение вне харнесов).
## Как проверялось
**Прочитан весь диф построчно** (`git diff origin/dev...HEAD` по каждому из файлов классов A/B/C, поимённо), не только сгенерированные части класса D.
**Соответствие спеке (`docs/specs/512-...md`) коду, построчно:**
- §4 «Seam версии»: `src/card-version.ts` — `displayVersion(fallback)` читает `globalThis.__HP_VERSION_OVERRIDE__`, при непустой строке возвращает её, иначе `fallback`; ровно то, что написано в ТЗ. Все перечисленные в ТЗ точки заменены на `displayVersion(CARD_VERSION)`: PDF `version:` (`houseplan-card.ts:10732`), `frontendVersion` (`:2131`), `card_version` в `support/preview` (`houseplan-editor-runtime.ts:8980`) и `export/create`/backup (`:9616`), `gs.about_version` (`:9198`), `cardVersion` в preflight-документе (`:9343`), `_preflightVersionsDiffer` (`:9371`). `hp_retry` (4 места) и `console.info` остались на литерале — как и требует §3 «не-скоуп». `release-contract.mjs` не тронут, продолжает читать оба литерала регэкспом.
- §5 «Golden-харнес»: `cardVersion` в `demo/golden/harness.mjs` теперь константа `'0.0.0-golden'` вместо чтения `package.json`; `window.__HP_VERSION_OVERRIDE__ = cardVersion` ставится в `page.evaluate` до `document.createElement('houseplan-card')`; `card._haIntegrationVersion = cardVersion` (было и раньше, теперь несёт ту же константу); `integrationVersion: '0.0.0-golden-backend'` в `matrix.mjs` не тронут — отношение frontend≠backend в version-mismatch сценариях сохранено.
- §6 «`docs:accept --identical`»: прочитан целиком новый код `scripts/docs-accept.mjs` (`identicalDocsManifest`, `identicalDecision`, `acceptIdentical`) и `scripts/png-identical.mjs` (`compareDecodedPixels`, `compareInPage`, `compareFramePairs`). Подтверждено по коду: `demo/docs/capture.mjs` не тронут (`git diff` по нему пуст) — это прямое закрытие H1 из ревью ТЗ r1; кадры на диске **всегда** восстанавливаются из бэкапа (`restoreFrames()` вызывается и на успешной, и на отказной, и на аварийной ветке, `finally` подчищает temp-каталог); при полном совпадении манифест берёт `sourceFingerprint`, `scenarios[*].sourceSha256` и `captureScriptSha256` из кандидата, а `imageSha256`/`chromium`/`oxipng`/`acceptance.*` — из закоммиченного, ровно как в AC3.
- §7 «Переприёмка golden»: коммит `ffe2ec51` несёt `Release: v1.74.0-beta.1` и `Baseline-Reviewed: .../runs/34366858855`, семь кадров (3 version-mismatch, 3 PDF-футера, support-preview) заменены на `0.0.0-golden`, остальные 162 сохранены как есть.
- §8 «Тесты и мутанты»: все перечисленные проверки на месте (source-text тест объединён с `test/card-version.test.mjs` вместо отдельного файла `test/houseplan-card-version-seam.test.mjs` — расхождение с именем файла в ТЗ, не с содержанием; не поднимаю как находку, это внутренняя деталь реализации, которую ТЗ прямо отдаёт автору).
- §11 «Затронутые файлы»: полностью совпадает со списком реально изменённых файлов, включая `docs/images/screenshots.json` (fingerprint-only) и `demo/golden/baselines/**`.
**Подлинность `Baseline-Reviewed`-ссылки (не принято на слово):**
`gh api repos/Matysh/houseplan-card/commits/605991ef1287ce3636a582b2902fadf857bf1525` — коммит существует на GitHub, его сообщение слово в слово совпадает с локальным `b7d8b9e6` (тем же диффом `src/card-version.ts` — сверено байт-в-байт через `gh api .../commits/605991ef... --jq '.files[]...'`). Локально этого SHA нет (`git cat-file -t` — «bad object»), потому что ветка была ребейзнута на `dev` **после** dispatch-прогона (issue-комментарий 15:55: «Материал теперь `ffe2ec51` (= `b7d8b9e6` код + `ffe2ec51` golden)… Содержимое коммитов не менялось»). Разница между `605991ef` и текущим деревом ограничена файлами класса D (`dist/**`, `custom_components/houseplan/frontend/**`) — то есть ребейз пересобрал бандл, не тронув источник, для которого прогонялся golden. Само `gh run view 34366858855` подтверждает: workflow `Проверка (CI)`, `event: workflow_dispatch`, job «Golden-кадры против принятых эталонов» — `failure` (это ожидаемо: dispatch и предназначен для получения артефакта расхождений, из которого затем принимается переприёмка), «Смоки: все шарды зелёные», «Перф-смок» — `success`; три job «Мутанты по диффу» — `cancelled` (не относится к golden-переприёмке). Ссылка не выдумана и указывает на реальный прогон по факту тому же коду.
**Собственные гейты (зелёного Validate на `ffe2ec51` не найдено, прогнал сам):**
- `npx tsc --noEmit` — чисто, без вывода.
- `npm test` — 2432 passed / 1 skipped / 0 failed (`tests 2433`, из них 1 subtest пропущен — не относится к этой задаче).
- `npm run build` — детерминированная пересборка, `git status --porcelain` после неё пуст (дифф с закоммиченным `dist/**` нулевой).
- `npm run bundle:sync` — синхронизация трёх копий бандла (`dist` → `custom_components/houseplan/frontend` → `demo/srv/assets`) прошла без изменений в рабочем дереве — три копии совпадают байт-в-байт с закоммиченными.
- `node scripts/check-docs.mjs` — «Documentation checks passed (7 files, 12 external links)»: `sourceFingerprint` в `docs/images/screenshots.json` совпадает с текущим `src/**`, `--identical`-переприёмка (`605991ef`/`b7d8b9e6`) не устарела.
- `node scripts/smoke-select.mjs --base origin/dev --head HEAD` — **НЕОПРЕДЕЛЁННОСТЬ**: изменено 3 файла `src/**`, символы `CARD_VERSION`/`displayVersion` не связаны доказуемо ни с одним из 236 смоков (эти символы не рендерят собственный DOM-контракт, который смоки называют по имени). Инструмент явно не решает «смоки не нужны» — решил по коду: диф трогает ровно 4 видимые поверхности (about/support-превью/бэкап-экспорт, версия в баннере version-recovery, PDF-футер), для каждой в `demo/` есть тематический смок. Прогнал все четыре: `demo/smoke_version_recovery.mjs`, `demo/smoke_pdf_export.mjs`, `demo/smoke_support_feedback.mjs`, `demo/smoke_backup_transfer.mjs` — все зелёные (`OK`). Полный набор 236 смоков не прогонял: диф не задевает геометрию/толщину/Zigbee/этот класс подсистем, где выборка по теме исторически подводила (#234); здесь выборка «по факту прочитанного кода», а не только по имени, и она узкая и предсказуемая — версия печатается в фиксированном, коротком списке мест, все они перечислены выше построчно.
- Мутанты AC5 — не гонял весь тяжёлый набор (первая попытка через неверный флаг `--only` по ошибке запустила часть общего реестра и упёрлась в отсутствующий `pytest` в бэкенд-мутанте, не относящемся к задаче — прервано, не в счёт). Верно: `node scripts/mutation-gate.mjs --id=version-seam-ignores-override` → «поймано 1 из 1»; `node scripts/mutation-gate.mjs --id=docs-identical-accepts-any-frame` → «поймано 1 из 1». Полный `--changed`-прогон (гейт CI «Мутанты по диффу») не гонял целиком — упёрся в таймаут при пробе (гейт объективно тяжёлый, это его штатное место в конвейере Validate, а не в ручном код-ревью); точечная проверка обоих мутантов AC5 равноценна для целей этого ревью.
- `npm run golden:verify` — диф меняет видимый рендер (текст версии в кадрах), гейт в категории «по необходимости» применим. Прогнал локально: 173 из 227 сценариев обработаны, **все 173 — `passed`**, включая все три `version-mismatch-*`, все три `pdf-export-*` и `support-desktop-preview-dark-en` — ровно те кадры, которые переприняты в §7. Прогон оборвался без сообщения об ошибке (процесс пропал из списка процессов среды между двумя последовательными проверками; похоже на исчерпание ресурсов рантайм-раннера, не связано с кодом) — не переигрывал: это тяжёлый пред-релизный гейт (PROCESS.md §8), а не гейт ревью, и вместе с уже проверенной подлинностью `Baseline-Reviewed`-прогона (полный матрикс, 227/227, на коде, идентичном текущему за вычетом класса D) и с зафиксированным здесь частичным локальным подтверждением (0 расхождений на всех переприятых кадрах) даёт достаточную уверенность без повторной тяжёлой попытки.
- `python -m pytest tests_backend` — не прогонял: диф не касается `custom_components/**/*.py`.
- Инварианты модели (`npm run invariants`) — не прогонял: диф не касается геометрии (рёбра, толщина, `layout`, `marker.space`, `open_spans`); ни один из изменённых файлов не входит в эти подсистемы.
## Находки
Нет находок High или Medium.
**Low, не блокирует** (снимаю с записью, не требую правки): текст в `docs/DEVELOPMENT.md`, добавленный этой задачей для `docs:accept -- --identical`, не повторяет предупреждение о зависимости от окружения съёмки (0 отличий воспроизводимо только на том же каноне рендеринга шрифтов, что и `docs:capture` — Linux/WSL). Ревью ТЗ r2 уже отметило это тем же способом («не блокирует, стоит упомянуть в будущей правке `docs/DEVELOPMENT.md`, уже в списке затронутых файлов») — правка этого файла произошла именно в этой задаче, повод был, автор им не воспользовался. Ничего не ломает: инструмент на другом окружении просто откажет с честным «--identical не даёт 0» и не спрячет проблему, а сценарий §1.1 не заявлен как AC. Не создаю отдельный issue (Low либо правится на месте, либо снимается запиской — здесь снимаю).
## Что проверено и корректно
- Обе точки закрытия H1 из ревью ТЗ (`demo/docs/capture.mjs` не правится; `captureScriptSha256` в манифесте берётся из кандидата при `--identical`) реализованы кодом один в один с решением ТЗ.
- Единственный источник отображаемой версии — `displayVersion()`; ни одна из шести перечисленных в ТЗ точек рендера/запроса не читает `CARD_VERSION` напрямую (проверено и построчным чтением диффа, и тестом `test/card-version.test.mjs`, который сверяет исходный текст обоих файлов регулярками — тест умеет падать: мутант `version-seam-ignores-override` красит его штатно, поймано «1 из 1»).
- `release-contract.mjs`, `hp_retry`-кэшбастинг и `console.info` намеренно не тронуты — совпадает с §3 «не-скоуп» ТЗ и не расходится с релизным контрактом.
- `docs:accept --identical`: кадры на диске гарантированно возвращаются к закоммиченным в любом исходе (успех/отказ/исключение) — прочитано по коду (`try/catch/finally` в `acceptIdentical`), закреплено тестами `test/docs-accept.test.mjs` (обе ветки — идентичные кадры и один отличающийся пиксель — гоняют реальный код через инъекцию съёмки/компаратора, а не мокают его целиком).
- Компаратор пикселей строгий и не имеет допуска (включая альфа-канал) — тест `test/png-identical.test.mjs` явно проверяет обнаружение единичного различия байта альфы; мутант `docs-identical-accepts-any-frame` пойман.
- Golden-харнес пинит версию до создания карточки единой константой, `integrationVersion` version-mismatch сценариев не тронут — отношение frontend≠backend, которое эти сценарии проверяют, не пострадало (подтверждено локальным прогоном `golden:verify`, все три `version-mismatch-*` — `passed`).
- Трейлеры: `Issue: #512` и `User-Visible: no` на каждом коммите; коммит с `demo/golden/baselines/**` несёт также `Release: v1.74.0-beta.1` и `Baseline-Reviewed:` — ссылка проверена как настоящая (см. выше), не выдумана. `User-Visible: no` корректно: единственное DOM-видимое место (`gs.about_version`) не меняет ни ключ, ни текст (`src/i18n/*.json` не в диффе), changelog не тронут — верно, что не тронут.
- Переприёмка golden сделана дисциплинированно: приняты только семь кадров, где реально текст версии; девять кадров с подпороговым дрейфом окружения в других сценариях (tray, resize handles, компас, junction) намеренно **не** приняты — не растащено «заодно», соответствует правилу «не приёмка ради зелёного CI».
- «Одно число — один источник»: отображаемая версия и раньше, и теперь имеет ровно один источник на страницу — `CARD_VERSION`, читаемый один раз в каждой точке рендера через одну и ту же функцию; seam ничего не дублирует и не считает по-новому, только подменяет литерал на условие. Дублирования не вносится.
## Чего не проверял
- Полный набор из 236 браузерных смоков — не прогонял целиком, обоснование выше (§ «Как проверялось»); прогнал 4 темово и содержательно связанных.
- Полный `--changed`-прогон мутантов по диффу (job «Мутанты по диффу» в Validate) — упёрся в таймаут при локальной пробе; это штатный тяжёлый гейт конвейера, не ручного ревью; оба именованных в AC5 мутанта проверены точечно и оба пойманы.
- Полный `golden:verify` не досмотрен до 227/227 — процесс пропал без диагностики на середине; 173/227 (все — pass, включая все переприятые кадры) плюс независимо проверенный подлинный `Baseline-Reviewed`-прогон на полном матриксе закрывают вопрос без повторной тяжёлой попытки.
- `python -m pytest tests_backend`, `npm run invariants` — не применимы к этому диффу (не тронуты `custom_components/**/*.py` и геометрия), не прогонял.
- Поведение `acceptIdentical` при `SIGKILL` между съёмкой и восстановлением бэкапа — то же самое, что уже разобрано и снято как не-находка в ревью ТЗ r2 (не новый класс риска, ничего не коммитится автоматически); заново не поднимаю.
- Не тестировал вручную в браузере (кроме смоков и golden-сравнения) — доступа к интерактивной сессии нет; visual-заключение опирается на golden/smokes.
## Вывод
Все шесть AC ТЗ выполнены и доказаны либо автотестом, способным упасть (unit на seam, unit на компаратор, интеграционный `docs-accept.test.mjs`, оба мутанта отдельно пойманы), либо реальным прогоном на текущем дереве (`check-docs`, частичный и полный-по-CI `golden:verify`), либо чтением кода с явной пометкой (`release-contract.mjs`, i18n-ключ). Находок High/Medium нет; единственная Low-находка снята запиской, не требует правки. Вердикт — зелёный.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/512-golden-version-seam-docs-identical`, коммит `ffe2ec51c0ca` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `dbe34717af925e03fb5bc6b52501bc640dbb0c2c`
```
git log --all --format='%H %T' | grep dbe34717af92
```
- ТЗ `docs/specs/512-golden-version-seam-and-docs-identical-accept.md`, блоб `0ff211f5598812b6d66f31cb136727b161b9c7ed`
```
git log --all --find-object=0ff211f5598812b6d66f31cb136727b161b9c7ed -- docs/specs/512-golden-version-seam-and-docs-identical-accept.md
```
- Вердикт конвейера: `green` · High 0