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

24 KiB
Raw Blame History

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-находка снята запиской, не требует правки. Вердикт — зелёный.


Материал раунда

  • Ветка: 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