Files
houseplan-card/docs/reviews/CODE-REVIEW-512-r1.md
T
claude[bot] 9b65d4bcf7
Проверка (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
docs: review document for #512
Issue: #512
User-Visible: no
2026-09-09 16:15:24 +00:00

95 lines
24 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-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