Files
houseplan-card/docs/reviews/CODE-REVIEW-103-r1.md
T
claude[bot] 5fb510290a
Validate / docs (push) Successful in 34s
Validate / provenance (push) Successful in 59s
Validate / changes (push) Successful in 52s
Validate / process-gate (push) Failing after 57s
Validate / hacs (push) Skipped
Validate / hassfest (push) Skipped
Validate / frontend (push) Skipped
Validate / smoke (push) Skipped
Validate / golden (push) Skipped
Validate / performance_smoke (push) Skipped
Validate / backend (push) Skipped
docs: review document for #103
Issue: #103
User-Visible: no
2026-08-19 00:34:45 +00:00

15 KiB
Raw Blame History

Код-ревью issue #103 · цикл r1/4

Toggle confirmation: показывать текущее и ожидаемое состояние.

  • Issue: https://github.com/Matysh/houseplan-card/issues/103
  • ТЗ: docs/specs/103-toggle-confirmation-state.md, принято зелёным docs/reviews/SPEC-REVIEW-103-r1.md
  • Диапазон: origin/dev..HEAD, 3 коммита (a449edc ТЗ, 6c7958c ревью ТЗ, 9327737/9327737...cf реализация)
  • Ревьюер: Claude, свежая сессия без контекста реализации

Скоуп

Диапазон изменил:

  • src/device-toggle.ts — новый pure formatToggleConfirmation() и тип ToggleConfirmationFormatter; resolveToggleIntent/резолвер не менялись;
  • src/houseplan-card.ts — _tapConfirm расширен до discriminated union {kind:'toggle', lines, initialIntent, deviceId, exec} / {kind:'run', text, exec}; новый _toggleConfirmationLines() и _toggleConfirmationStateText(); шаблон диалога рендерит lines для toggle и прежний <p> для run;
  • src/styles.ts — .tapconfirm-body/.tapconfirm-line (перенос длинных строк, нет горизонтального скролла);
  • src/i18n/en.json + src/i18n/ru.json — 15 новых ключей confirm.* в обоих файлах;
  • test/device-toggle.test.mjs — юнит-тесты форматтера;
  • demo/smoke_toggle_confirmation.mjs — новый браузерный смок;
  • docs/CHANGELOG.md / docs/CHANGELOG.ru.md, docs/USER-GUIDE(.ru).md, docs/TESTING.md, docs/specs/README.md — документация и changelog в том же коммите, что и поведение;
  • три копии бандла (dist/, custom_components/.../frontend/, demo/srv/assets/) и docs/images/screenshots.json (обновление sourceFingerprint/sourceSha256, imageSha256 каждого сценария не изменился — визуальный контент прежний).

Соответствует docs/SCOPE.md J3 («tap-to-toggle for safe domains») и не задевает lock invariant: secure-цели (lock/alarm_control_panel/guarded cover) резолвер отфильтровывает раньше формирования intent, toggleOperation для них null, confirmation не открывается — новая ветка кода это условие не меняет.

Как проверялось

Гейт Команда Результат
Typecheck npx tsc --noEmit green
Unit npm test 880/880 green
Build + сверка бандлов npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js && cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js green, три копии идентичны
Целевой browser smoke (AC1,3,6,7,8,9) node demo/smoke_toggle_confirmation.mjs green, все 9 подпроверок true
Регрессия смежных confirmation-путей node demo/smoke_cover_tap.mjs, node demo/smoke_virtual_light_toggle.mjs, node demo/smoke_tap_run.mjs, node demo/smoke_ha_controls.mjs, node demo/smoke_controls.mjs все green
Golden npm run golden:verify (свежий бандл) 100% passed, 0 failed/error — новых сценариев dialog нет, стили аддитивны и не задели существующие сцены
i18n-паритет node -e "..." сравнение ключей en.json/ru.json равны, расхождений нет
Process gate node scripts/process-gate.mjs «гейт пройден, предупреждений 0» (без --issues, офлайн-часть)
Backend не прогонялся custom_components/**/*.py в диффе нет — гейт не применим
Performance-профили не прогонялись не названы в AC, изменение — только текст/CSS диалога

Дисциплина «тест умеет падать» — проверено для прогнанных тестов:

  • временно нейтрализовал ветку isGroup && nextEffect==='turn-on' в formatToggleConfirmation → npm test дал not ok 159 (тест группы), после отката снова 880/880;
  • временно подменил closed → confirm.state_open в _toggleConfirmationLines → node demo/smoke_toggle_confirmation.mjs дал englishCurrentExpected: false и russianCurrentExpected: false, после отката снова все 9 true и бандлы пересобраны/сверены заново.

AC — разбор

AC Доказательство Вывод
1. current/expected lines для исполняемой цели unit toggle confirmation formats every next effect...; smoke englishCurrentExpected/russianCurrentExpected подтверждено
2. expected строго по nextEffect, не по домену unit-тест перебирает все ToggleNextEffect на одном intent с фиксированным state; чтение formatToggleConfirmation — ветвление только по nextEffect/isGroup, не по domain/semantics подтверждено
3. локализация power/cover/valve/virtual/group unit (partial/all-off group, virtual) + smoke englishGroupAndVirtualCopy, RU/EN рендер подтверждено
4. partial group не обещает изменение skipped unit describes executable group targets and skipped targets separately (denominator = только targets, skipped — отдельная строка); проверено на способность теста падать подтверждено
5. toggle → «решит HA» unit-тест nextEffect: 'toggle' → expected:by-ha подтверждено
6. no-op не открывает confirmation smoke noOperationDoesNotOpen (guarded cover, secure no-op) подтверждено
7. Confirm выполняет заново разрешённый intent smoke sameTargetUsesCurrentDirection (cover меняет state closed→open между открытием и подтверждением, выполняется close_cover, а не команда снимка) подтверждено
8. смена target set отменяет actuation, старый toast smoke changedTargetCancels (переброс bindingRef на другую cover-сущность → 0 service calls, toast.tap_target_changed) подтверждено
9. desktop/mobile, keyboard, screen-reader order smoke accessibleDomOrder (dialog.title === текст вопроса, data-line 0/1, 2 кнопки) + narrowDialogDoesNotScrollHorizontally на 390px; keyboard focus/Escape/scrim — проверено чтением, не исполнением: dismiss-on-scrim, @hp-close и структура footer/кнопок в диффе не изменены подтверждено (смок + чтение)
10. run/другие confirmations не регрессируют smoke smoke_tap_run.mjs green; чтение шаблона — ветка kind==='run' рендерит тот же <p>{text}</p> с тем же заголовком btn.run, что и раньше подтверждено

Что проверено и корректно

  • Инвариант lock/alarm/guarded-cover не тронут: секьюрные цели отфильтровываются резолвером #94 до формирования nextEffect/targets, confirmation для них не строится ни при каком новом коде.
  • formatToggleConfirmation — чистая функция, не знает про HA/DOM; вся i18n и HA-formatter-специфика инкапсулированы в _toggleConfirmationStateText/ _toggleConfirmationLines в houseplan-card.ts, как и требовало ТЗ §5 («houseplan-card.ts не выводит next state по domain самостоятельно»).
  • Direction всегда берётся из intent.nextEffect, не из currentState — единственный источник расчёта — резолвер #94; UI не дублирует его логику.
  • Denominator группы — количество фактических targets (byEntity без skipped), а не число сконфигурированных ссылок; skipped выводится отдельной строкой и не входит в «включено N из M».
  • Race-контракт #94 не нарушен: exec() заново находит currentDevice → пересчитывает intent → сравнивает через sameToggleOperationTargets с исходным снимком; при совпадении целей выполняется текущее направление/команда (не снимок на момент открытия).
  • run-подтверждение осталось на прежней простой форме (text + <p>), discriminated union не заставил его притворяться toggle — ТЗ §7 требование выполнено буквально.
  • i18n: все 15 новых ключей присутствуют в обоих словарях; в целом по файлам en.json/ru.json расхождений ключей нет.
  • Три копии бандла синхронны (cmp byte-for-byte), docs/images/screenshots.json обновил только fingerprint/sourceSha256 — imageSha256 каждого сценария не изменился, то есть визуальный результат существующих сцен не задет; отдельного golden-сценария для диалога не требовалось (ТЗ §11 — narrow smoke покрывает).
  • Оба changelog правлены в том же коммите, что и поведение (932773773f0b), трейлеры Issue: #103/User-Visible: yes|no на месте на всех трёх коммитах.

Находки

Ни одной High/Medium-находки. Три ранее отмеченные Low из ревью ТЗ (SPEC-REVIEW-103-r1.md) не имеют кодового следствия — направление в рантайме берётся из nextEffect, а не из нормативной таблицы ТЗ, поэтому неточность таблицы (closing в одной строке с open) не воспроизвелась в коде.

  • Low (снято, без правки). houseplan-card.ts, ветка if (actionDevice.marker?.tap_confirm) { const lines = this._toggleConfirmationLines(initial); if (!lines.length) return; ... } — защитный if (!lines.length) return недостижим при нынешних инвариантах резолвера: везде, где toggleOperation(intent) истинен (единственное условие, пропущенное раньше по коду), у intent уже гарантированно есть nextEffect и targets.length >= 1 (прослежено по всем веткам resolveToggleIntent/ resolveGroupEntities/virtual-light intent). Формально это тихий no-op вместо открытия диалога, если бы условие когда-нибудь стало достижимым, но сегодня это мёртвый код, а не наблюдаемый дефект. Правка не требуется; снимаю с записью здесь, как того требует §8/§12 процесса.

Чего не проверял

  • Backend (python -m pytest tests_backend) — не прогонял: диапазон не затрагивает ни одного файла custom_components/**/*.py.
  • Performance-профили — не прогонял: AC их не называет, изменение не трогает чувствительные к перфу пути (только текст диалога и аддитивный CSS).
  • Полный набор из 127 браузерных смоков — прогнал только целевой (smoke_toggle_confirmation) плюс пять смежных по confirmation/cover/virtual- light/run поверхности; остальные 121 не запускал — diff не задевает геометрию, wall-thickness, canvas, sun/light рендер и т.д., которые они покрывают.
  • Ручная проверка в реальном браузере вне demo-харнесса (реальный screen reader, реальная Tab-навигация) — не выполнялась; keyboard/Escape/scrim утверждение AC9 закрыто чтением кода (структура диалога не менялась), как и предусмотрено самим ТЗ («narrow browser smoke + code review»).
  • node scripts/check-docs.mjs --external — не перепрогонял; автор в хендоффе указал green, доверяю без повторного запуска (внешний, не входит в обязательный набор ревью).
  • process-gate.mjs --issues (сетевая проверка статуса issue через gh) — не прогонял с токеном; офлайн-часть (process-gate.mjs без флага) прошла чисто.

Вердикт

Зелёный. Все 10 AC доказаны — юнитами/смоками, которые я прогнал и убедился, что они умеют падать, либо явным «проверено чтением, не исполнением» там, где так предусмотрено самим ТЗ (клавиатура/screen-reader в AC9, идентичность run-ветки в AC10). High: 0, Medium: 0. Единственная Low-находка — недостижимая защитная ветка — снята без правки, с записью выше.

Вердикт: зелёный · цикл r1/4 · High: 0 · Medium: 0 → нет · Документ: docs/reviews/CODE-REVIEW-103-r1.md