mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
22 KiB
22 KiB
CODE-REVIEW-423-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/423
- Этап: код-ревью (PROCESS.md §2.7), заход r1, блокирующих циклов израсходовано 0 из 4
- Материал: ветка
issue/423-v170-polish, HEAD на момент вывода3576cc2ea7e5f85b4e8d91a45be4d85b7158528b - ТЗ:
docs/specs/423-v170-polish.md(SPEC-REVIEW-423-r1, зелёный) - Диапазон:
git diff origin/dev...HEAD(54 файла, включая генерированныеdist/**иcustom_components/houseplan/frontend/**)
Скоуп
Шесть подтверждённых аудитом v1.70.0 дефектов support pipeline:
- Repair families по
translation_keyвместоbroken_plan_*. - Filename из SHA-256-префикса вместо capability-token.
- Двухфазная quota-проверка (до store/executor и после
await). support_api: 1вместо строгого равенства версий релиза.- Lazy support-словари вне initial View graph.
backdrop_decodebenchmark под общий browser-error guard.
Пункт (е) issue (комментарий docs-screenshots.yml) явно исключён — принадлежит #422, файл в диффе не тронут (подтверждено: git diff по этому workflow пуст).
Как проверялось
Гейты
| Гейт | Статус | Как подтверждён |
|---|---|---|
npx tsc --noEmit / npm run typecheck |
зелёный | не перегонял: Validate 3576cc2e → success, job «Фронтенд: типы, юниты, мутанты, синхрон бандла» выполнился заново (не reuse) и прошёл — https://github.com/Matysh/houseplan-card/actions/runs/33668037515 |
npm test |
зелёный | тот же job на том же прогоне; численно 1749/1749 (1 skip) заявлено автором, не расходится с зелёным job |
npm run build + bundle:sync (сверка копий) |
зелёный | тот же job (синхрон бандла — часть его имени и шагов) |
node scripts/no-new-any.mjs --base origin/dev --head HEAD |
зелёный | перепрогнал сам: «Новых any нет» |
npm run bundle:budget |
зелёный | перепроверил вручную числа из dist/houseplan-assets.json: initialViewGzipBytes=289137 < baseline 291046 и < budget 300000; lazyEditorGzipBytes=154772; assertSupportBundleOwnership подключён в scripts/bundle-budget.mjs и вызывается из main-блока, вызываемого npm run bundle:budget, который стоит в validate.yml:460 и publish-prerelease.yml:86 |
node scripts/check-docs.mjs / job docs |
зелёный | Validate 3576cc2e, job «Предполётные проверки: документация, провенанс, процесс» → success (на предыдущем SHA 2038ab91 падал именно на DOCS: failure; коммит 3576cc2e принял 2 скриншота и починил) |
python -m pytest tests_backend -q (Linux HA) |
зелёный | не перегонял локально (нет homeassistant в этом окружении — тихий скип, доказательством не считается). Проверил по CI: job «Бэкенд: pytest в Home Assistant» на прогоне для 4a7de337 (test: harden support preflight verification) реально выполнился и завершился success в 18:32:22 — до дальнейших изменений backend-путей не было, поэтому для 3576cc2e job корректно reuse-нут (ключ gate-reuse.mjs включает src/+demo/golden/+package-lock и т.д., но backend-ключ отдельный и завязан на измененные backend-пути; после 4a7de337 backend-файлы не менялись) |
npm run golden:verify |
зелёный, не по диффу | Validate 3576cc2e показывает job golden как skipped (reuse). Проверил происхождение реального прогона: на прогоне 4a7de337 (33667562495) job «Golden-кадры против принятых эталонов» дошёл до конца матрицы — лог показывает passed для всех сценариев, включая support-desktop-empty-light-en, support-desktop-preview-dark-en, support-phone-validation-light-ru, support-phone-success-dark-en, support-relay-error-light-en, support-tablet-preview-dark-ru — и успешно сохранил cache-маркер reuse-golden-366e97... в 18:34:48, после чего run получил cancelled только из-за отмены следующим пушем (смоки/golden в этом run отменились позже реального успеха golden). Это не «слепое доверие reuse»: сам прогон матрицы состоялся и был зелёным на коде, идентичном текущему demo/golden/harness.mjs (правка card._haSupportApi = 1 в сценарии support вошла именно в этот коммит) |
performance_smoke |
зелёный, не по диффу | тот же прогон 4a7de337: job «Перф-смок: бюджет времени кадра» реально выполнился и завершился success (18:31:37–18:33:15), не отменён |
| Браузерные смоки (полная матрица, 3 шарда) | зелёный, ПО ДИФФУ | Validate 3576cc2e: все 3 шарда + «Смоки: все шарды зелёные» выполнились заново (не reuse) и прошли — это полный прогон demo/smoke_*.mjs, включая smoke_support_feedback.mjs, smoke_help_affordance.mjs, smoke_preflight_diagnostics.mjs |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
выполнил сам | «Прямое совпадение» (9): smoke_support_feedback, smoke_help_affordance, smoke_optimize_coordinate_canonicalization, smoke_tap_ctx, smoke_junction_limits, smoke_partition_openings, smoke_preflight_diagnostics, smoke_room_resize, smoke_zero_wall_migration_unblocked. Первые три из списка совпадают по существу (support-диалог, _haIntegrationVersion); остальные — совпадение по _showToast/this.host._config (не изменённая диффом логика, просто те же геттеры переиспользованы в новых строках _copySupportText/_supportErrorText). Все 9, как и вся остальная матрица, реально прогнаны в job "Смоки в браузере" на `3576cc2e" (см. строку выше) — отдельный точечный прогон не потребовался |
node demo/benchmark_backdrop_decode.mjs --guard-probe |
перепрогнал сам | EXC Error: houseplan backdrop guard probe → FAILED: 1 uncaught exception(s) inside the card → exit code 1. Тест умеет падать: подтверждено выполнением, не чтением |
node --test test/smoke-harness-contract.test.mjs (контракт AC9) |
проверено чтением | Мутационная проверка встроена в сам тест (снимает watchPage/reportPageErrors и требует красноты) — не потребовало отдельного прогона поверх зелёного npm test |
Инварианты модели (npm run invariants) |
не прогонял, не требуется | диапазон не трогает геометрию/layout/marker.space/open_spans/толщину стен |
python -m pytest tests_backend -q без HA |
не прогонял | заведомо тихо скипнул бы test_ha_*.py, доказательством не является (AGENTS.md) |
Ручное чтение и трассировка
- Прочитан полный диапазон
custom_components/houseplan/websocket_api.py,support_transport.py,const.py— построчно сверен с §"Контракт реализации" ТЗ. - Прочитан полный диапазон
src/houseplan-card.ts,src/houseplan-editor-runtime.ts,src/support-feedback.ts,src/i18n/support.ts. - Прочитаны все новые/изменённые backend- и frontend-тесты построчно, а не по именам.
- Проведена архивная проверка происхождения зелёных гейтов в GitHub Actions (
gh api .../jobs,gh cache list) для reuse-джобов backend/golden/performance — см. таблицу выше. Это не входит в обычный объём ревью, но потребовалось, потому что итоговый прогон3576cc2eсам по себе не содержит подтверждения backend/golden — я не принял reuse на слово.
Находки
Пусто. High: 0, Medium: 0, Low: 0.
Проверено и корректно (по AC)
- AC1.
_support_repairs()(websocket_api.py:251-267) агрегирует поtranslation_keyчерез_SUPPORT_REPAIR_FAMILY = ^[a-z][a-z0-9_]{0,63}$, пропускает foreign domain,Noneи невалидные ключи.test_support_repairs_aggregate_safe_translation_key_familiesконструирует ровно эти шесть случаев (дваbroken_plan, одно новое семейство, unsafe-ключ, missing-ключ, foreign domain) и проверяет черезjson.dumps, что raw id/placeholders/unsafe-ключ физически отсутствуют в сериализованном результате. Тест умеет падать: откат кstartswith("broken_plan_")не подхватитfuture_repair. Доказано автотестом. - AC2. Backend (
support_transport.py:58-60) и frontend (houseplan-editor-runtime.ts:9308-9310) оба берутsha256[:12]/sha256.slice(0,12)из одного и того же значенияpreview["sha256"], вычисленного один раз в_build_snapshotи не пересчитываемого отдельно нигде (проверено:attachment_sha256=preview.get("sha256")вws_support_submit,websocket_api.py:2333) — источник числа один, ветвления сравнения "видно дважды" нет.filename_tokenполностью удалён изsupport_transport.pyи всех вызывающих мест (grep по репозиторию — пусто). Backend-тест проверяет точное имяhouseplan-support-0123456789ab.jsonи отсутствие token-префикса. Доказано транспортным тестом + frontend smoke (downloadExactвsmoke_support_feedback.mjs). - AC3/AC4. Прочитан порядок в
ws_support_preview(websocket_api.py:2164-2233): preflight-проверка стоит доwrite_lock/store.async_load/executor_job; финальная проверка — послеawait, до удаления старого token; удаление старого token — синхронный участок безawaitмежду проверкой и записью. Три специально сконструированных backend-теста доказывают это исполнением, а не декларацией:test_support_preview_quota_rejects_before_store_load_or_executorмон키патчитasync_load/async_add_executor_jobтак, что их вызов кидаетAssertionError, и подтверждает, что при исчерпанной квоте они не вызываются вовсе;test_support_preview_failed_refresh_keeps_previous_tokenроняетbuild_support_packageи проверяет, что старый token не исчез;test_support_preview_final_quota_check_closes_executor_raceреальнымthreading.Barrierсинхронизирует два конкурентных WS-запроса внутри executor-колбэка и проверяет, что ровно один получаетsupport_rate_limited, а итоговый набор превью не превышает лимит. Все три теста по конструкции обязаны падать при откате к прежнему порядку (задача явно этого требует, и логика совпадает с планом отрицательной проверки ТЗ, пункт 3). Доказано backend-тестами. - AC5.
SUPPORT_API_VERSION = 1(const.py) добавлен вws_config_getкак top-levelsupport_api, включая ветку сfields-проекцией (websocket_api.py:1264, обе ветки видел в диффе и в тесте). Backend-тест проверяет оба пути (resp["result"]иprojected["result"]) и явно — что поле отсутствует внутриconfig. Frontend:_adoptConfigCapabilities(houseplan-card.ts:4279-4288) принимает толькоNumber.isSafeInteger, иначе сбрасывает вnull; вызывается на всех четырёх путях полученияconfig/get(полная загрузка, structural adopt, periodic reload, config-reload) — проверил каждый вызов построчно. Доказано backend-тестом + чтением всех вызовов на фронтенде. - AC6. Единый предикат
supportApiCompatible(support-feedback.ts:6-8,value === 1) используется во всех трёх местах —_buildSupportPreview,_submitSupport,_renderSupportDialog(grep подтвердил отсутствие остатков старого_haIntegrationVersion === CARD_VERSION). Unit-тестsupportApiCompatibleперебираетundefined, null, 0, 2, '1', 1.0, true, {}, NaN— все должны быть несовместимы, только1совместим.smoke_support_feedback.mjsдобавил явный сценарий mixed-release (_haIntegrationVersion='mixed-release', support_api=1→ форма видна) и повторно проверил old-backend fail-closed через_haSupportApi=null. Доказано unit-тестом + smoke. - AC7.
src/i18n/support/{en,ru,de,fr}.jsonсодержат по 42 ключа (тест явно фиксирует число),en.json/ru.json/etc. в основном словаре оставляют толькоsupport.title.i18n.test.mjsдобавил тест на равенство key-set и плейсхолдеров всех четырёх support-словарей;i18n-dead-keys.test.mjsтеперь сканирует объединённый словарь (core + support).bundle-assets.test.mjs— юнит-тест наassertSupportBundleOwnershipс мутационной проверкой (marker в initial graph → падает; initial gzip не улучшился → падает). Доказано i18n- и bundle-тестами. - AC8.
dist/houseplan-assets.json:initialViewGzipBytes=289137(< 291046 baseline, budget не менялся — 300000). Перепроверил числа лично, не только со слов автора.assertSupportBundleOwnership— новый вызов вbundle-budget.mjs, подключён в CI (validate.yml:460,publish-prerelease.yml:86). Доказано build-манифестом + подключённым тестом. - AC9. Лично прогнал
node demo/benchmark_backdrop_decode.mjs --guard-probe— код возврата 1, ожидаемое сообщение о необработанном исключении. Обычный режим (cases) не тронут кроме обёрткиwatchPage/reportPageErrors. Контрактный тестsmoke-harness-contract.test.mjsидёт дальше формального совпадения по имени: у него есть встроенная негативная проверка (снятьwatchPageили заменитьreportPageErrors()наfalse→ контракт обязан покраснеть) — то есть тест доказанно умеет падать, а не просто присутствует. Доказано исполнением + контрактным тестом. - AC10.
git diffпо.github/workflows/docs-screenshots.ymlпуст. Доказано diff'ом напрямую. - AC11. Typecheck/unit/build — см. таблицу гейтов. Runtime-схемы (
CONFIG_SCHEMA,LAYOUT_SCHEMA) в диффе не тронуты —support_apiне проходит через них, добавляется в ответ отдельно после_layout_metadata/config-объекта. Проверено чтением. - AC12.
docs/USER-GUIDE.md/.ru.mdпереформулированы на «доступна при совместимом API», без упоминания SHA-префикса как публичного обещания. Обаdocs/CHANGELOG.md/.ru.mdотредактированы в том же коммите (2038ab91,User-Visible: yes) — трейлеры и оба changelog провереныgit showнапрямую. Доказано чтением diff + code review.
Что не проверял (и почему)
- Полный ручной прогон
python -m pytest tests_backend -qна этой машине — здесь нетhomeassistant, тихий скип не был бы доказательством. Заменено проверкой происхождения реального Linux-CI-прогона на предыдущем SHA той же ветки (см. таблицу). npm run golden:verifyиperformance_smokeвручную — ТЗ прямо говорит, что видимый DOM/layout не меняется (только текстsupport.update_required, который не входит в golden-покрытие по разметке). Вместо повторного прогона проверил, что зелёный результат в reuse-цепочке реален (не «зелёная галка по недоразумению») — см. лог golden-job сpassedпо всемsupport-*сценариям.- 8 из 9 «слабых»/совпавших по
_showToast/_configсмоков (smoke_junction_limits,smoke_partition_openings,smoke_room_resize,smoke_zero_wall_migration_unblocked,smoke_tap_ctx,smoke_optimize_coordinate_canonicalization) и 18 позиций из «слабой связи» — отдельно не гонял: они совпали по названию геттера, который диапазон не меняет по смыслу (_showToast/this.host._configиспользованы как есть, без изменения контракта). Все они входят в полную матрицу, которая реально прогналась на точном SHA3576cc2e(3 шарда + сводный job зелёные), так что фактическое покрытие есть — просто не через отдельный целевой запуск. - Инварианты модели (
npm run invariants) — диапазон не касается геометрии рёбер, толщины стен,layout,marker.space,open_spans; не применимо. - Реальный HA
IssueEntry.translation_key— не проверял исходникиhomeassistant-coreнапрямую (модуль недоступен локально); опирался на (а) существующее использованиеtranslation_key=вcustom_components/houseplan/repairs.py:51того же пакета для созданияbroken_plan-репаров, (б) зелёный Linux backend CI на этом же коде. Оснований сомневаться нет, но это не то же самое, что чтение исходников HA.
Вердикт
Зелёный. Все 12 AC доказаны автотестами, которые проверены на способность падать (чтением конструкции или прогоном), либо прямым чтением кода с фиксацией «проверено чтением». Находок нет.
Материал раунда
- Ветка:
issue/423-v170-polish, коммит3576cc2ea7e5— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
856c13aff5985cd40bf743c00137911b055fd955git log --all --format='%H %T' | grep 856c13aff598 - ТЗ
docs/specs/423-v170-polish.md, блоб3a0e7b78a9f8a01f3f54e4a6182dba04badecfaegit log --all --find-object=3a0e7b78a9f8a01f3f54e4a6182dba04badecfae -- docs/specs/423-v170-polish.md