Files
houseplan-card/docs/reviews/CODE-REVIEW-267-r2.md
2026-08-27 19:57:33 +00:00

17 KiB
Raw Permalink Blame History

CODE-REVIEW-267-r2

  • Issue: https://github.com/Matysh/houseplan-card/issues/267
  • Этап: код-ревью (PROCESS.md §2.7)
  • Заход: r2 · блокирующих циклов израсходовано 1 из 4
  • Материал: ветка issue/267-device-presentation-table, git rev-parse HEAD = 118062bb8cce6ffe7ab86233f10b9dfd00ac6f6d (совпадает с SHA, заявленным в хендоффе r2), база origin/dev@2294d46f
  • Предыдущий раунд: r1, вердикт красный, docs/reviews/CODE-REVIEW-267-r1.md, получен на 6efc315ba62e9b62ad5d95bbd0d2bdecb7d967ba
  • Вердикт: зелёный

Скоуп ревью

Второй заход. Разбор — по дельте: git diff 6efc315b..118062bb (исключая сам файл docs/reviews/CODE-REVIEW-267-r1.md, который автор закоммитил вместе с хендоффом и который не является предметом правки). Полный повторный прогон не требовался: дельта локальна (один коммит 118062bb, 8 нерендер-файлов вне docs/reviews), не задевает ребейз, не меняет контракт видимого поведения и не вводит новую подсистему — это ровно точечное закрытие трёх находок r1.

Файлы дельты (без сборочных артефактов и review-документа):

docs/DEVICE-PRESENTATION.md                       |  7 +--
docs/specs/267-device-presentation-decision-table.md | 10 ++--
scripts/mutation-gate.mjs                         | 60 +++++++++++++++++++++-
src/device-presentation-policy.ts                 |  7 +--
src/device-presentation.ts                        |  2 +-
test/device-presentation-policy.test.mjs          | 14 +++--
test/fixtures/device-presentation-decisions.mjs   |  7 +--

Плюс синхронные пересборки (dist/, custom_components/.../houseplan-card.js, docs/images/screenshots.json) — сверены штатными гейтами, не разбирались построчно.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
High-1 — L04/L05/L06 в fixture ссылались на общий mutant device-presentation-policy-lifecycle, чей патч физически задевает только ветку ha_disabled и не может сломать userHidden/orphaned Добавлены три новых, реально нацеленных мутанта: device-presentation-policy-user-hidden (патчит else if (input.userHidden && !input.designPreview)), device-presentation-policy-user-hidden-preview (патчит else if (input.userHidden) через false &&), device-presentation-policy-orphaned (патчит else if (input.bindingLifecycle === 'orphaned') тем же приёмом). Fixture переключена на них: L04→…-user-hidden, L05→…-user-hidden-preview, L06→…-orphaned scripts/mutation-gate.mjs (новые записи в MUTANTS, ветка после мутанта device-presentation-policy-lifecycle); test/fixtures/device-presentation-decisions.mjs:11-13; src/device-presentation-policy.ts:86-92 — текст find каждого патча посимвольно совпадает с соответствующей веткой production-кода. Проверено лично прогоном каждого --id= (см. таблицу гейтов) — все три «поймано 1 из 1», при снятом мутанте git status чист
Medium-1 (в скоупе) — unverified в BindingPresentationLifecycle был недостижим: devices.ts фильтрует его через continue до построения DevItem, но L06 «доказывал» его напрямую вызовом policy-функции, а не через resolveDevicePresentation() unverified убран из типа BindingPresentationLifecycle (src/device-presentation-policy.ts:27-28), из ветки резолвера (src/device-presentation.ts:587-588, тернарник больше не содержит unverified), из теста (снят отдельный assertDecision(unverified, …) прогон) и из строки L06/§6.1 ТЗ (переименована в «orphaned binding», без второго исхода). Я перепроверил читанием, что devices.ts действительно фильтрует unverified на всех путях построения DevItem: явные device/entity-маркеры (src/devices.ts:1193,1225, continue до конструирования) и авто-обнаруженные устройства (src/devices.ts:1108, if (bindingStatus.kind !== 'active') continue) — других мест, где bindingStatus: присваивается DevItem, в файле нет (grep -n "bindingStatus:" src/devices.ts → 3 совпадения, все проверены). Значит удаление ветки — доказуемо чистое удаление мёртвого кода, а не изменение поведения src/device-presentation-policy.ts:27-28,86-92; src/device-presentation.ts:587-588; src/devices.ts:1108,1193,1225; docs/DEVICE-PRESENTATION.md L06; docs/specs/267-…table.md §6.1 и интерфейс DevicePresentationPolicyInput
Low-1 — source.skipped_static_fast_path производился кодом, но не документирован ни в одной строке Добавлен ряд S14 в docs/DEVICE-PRESENTATION.md, §6.2 ТЗ, test/fixtures/… и тест (presentationRow для static-fast-path), плюс новый targeted mutant presentation-static-source-fast-path, патчащий именно EMPTY_SOURCES.decisionIds docs/DEVICE-PRESENTATION.md строка S14; test/device-presentation-policy.test.mjs:205-210; scripts/mutation-gate.mjs (мутант presentation-static-source-fast-path, патч find: "sourceKind: 'none', decisionIds: ['source.skipped_static_fast_path']," — текстуально совпадает с src/device-presentation.ts:178). Прогнан лично — «поймано 1 из 1»

По каждой строке проверка — не заявление автора: я сверил текст патча каждого нового мутанта посимвольно с текущим кодом (find — точная подстрока) и лично прогнал все четыре новых/изменённых --id= индивидуально, наблюдая результат поймано 1 из 1 и чистый git status после (мутация не осталась в рабочем дереве).

Унаследовано из r1

Без повторной проверки в этом раунде — дельта их не задевает:

  • AC1 (каноническая таблица покрывает все ряды) — docs/reviews/CODE-REVIEW-267-r1.md, проверено на 6efc315b. Дельта добавляет один новый ряд (S14, см. выше, перепроверен), остальные 43 не менялись.
  • AC2 (единственный pure policy owner) — там же. Дельта не трогает архитектуру вызовов, только состав веток внутри уже выделенного resolveDevicePresentationPolicy().
  • AC3 (source decisions названы) — там же, на 6efc315b.
  • AC6 (controller/target parity) и AC7 (surface parity) — там же; дельта не касается controller-ветки и preview-путей.
  • AC8 (refactor-only pixel/config contract), включая полный npm run golden:verify — 130/130 на Linux на 6efc315b. В этом раунде golden не перезапускался целиком: единственное изменение в рендер-пути (src/device-presentation.ts:587-588) — удаление ветки unverified, которая, как показано в таблице выше, доказуемо никогда не могла быть достигнута с реальным DevItem; следовательно результат любого golden-сценария тождественен прогону на 6efc315b. Три копии бандла пересобраны и идентичны по SHA-256 на 118062bb (см. таблицу гейтов) — байт-в-байт то же, что даёт git status после локальной пересборки (чисто), то есть коммит уже содержит именно этот бандл.
  • AC9 (fast path без лишних вызовов) — там же, чтением кода; дельта не трогает эту ветку.
  • AC10 (штатные гейты) — перепрогнаны заново на 118062bb в этом раунде (см. ниже), не наследуются.
  • Трейлеры Issue: #267, User-Visible: no — верны на всех коммитах ветки, включая новый 118062bb (проверено в этом раунде).

Как проверялось (гейты этого раунда)

Что Команда Результат
Типы npx tsc --noEmit зелёный
Юнит-тесты npm test 1379 тестов, 1378 passed, 0 failed, 1 skipped (совпадает с r1)
Сборка + сверка бандлов npm run build && npm run bundle:sync + sha256sum трёх копий все три идентичны: 01c5390f051b135f2c21e4e19bd5fa059abb9381d69fbda3eccffa677bf68a1a; git status после пересборки чист — коммит уже содержит этот бандл
Документация node scripts/check-docs.mjs Documentation checks passed (7 files, 10 external links)
Провенанс/статус issue node scripts/process-gate.mjs --base origin/dev --head HEAD --issues гейт пройден, предупреждений 0
Реестр мутантов, структурная сверка node scripts/mutation-gate.mjs --check зелёный, включая 4 новых/изменённых записи
Targeted mutation — реально применены и пойманы (предмет находки High-1) node scripts/mutation-gate.mjs --id=device-presentation-policy-user-hidden, --id=device-presentation-policy-user-hidden-preview, --id=device-presentation-policy-orphaned, --id=presentation-static-source-fast-path (каждый отдельно) все четыре «поймано 1 из 1»; git status после каждого — чисто
Выборка смоков по дельте раунда node scripts/smoke-select.mjs --base 6efc315b --head HEAD НЕОПРЕДЕЛЁННОСТЬ: 0 символов проекта на изменённых строках дельты — решение см. ниже
Выборка смоков по полному диффу issue node scripts/smoke-select.mjs --base origin/dev --head HEAD прямое совпадение (2): smoke_controls.mjs (← DevItem), smoke_wireless_controller_parity.mjs (← controllerAvailability) — то же, что в r1
Названные смоки node demo/smoke_controls.mjs, node demo/smoke_wireless_controller_parity.mjs, node demo/smoke_device_icon_design.mjs все три OK
Golden не перезапускался в этом раунде обоснование — см. «Унаследовано из r1», AC8
Инварианты геометрии/модели не прогонялись дельта не трогает геометрию/layout/толщину стен/marker.space/open_spans — неприменимо
Backend не прогонялся дельта не трогает custom_components/**/*.py

Решение по смокам: smoke-select на дельте раунда (6efc315b..HEAD) не находит доказанной связи — сама дельта не меняет исполняемых веток production-кода, кроме удаления мёртвой unverified-ветки (доказано выше) и одного текстового литерала (decisionIds фолбэк S14, уже покрыт новым production-тестом S14). Прогнал три смока, названные в r1 и в issue (smoke_controls, smoke_wireless_controller_parity, smoke_device_icon_design), для контроля регресса на итоговом 118062bb — все зелёные. Полный набор 192 смоков — предрелизный гейт, не гейт ревью; дельта не даёт оснований его расширять сверх того, что уже покрывал r1.

Находки

Нет. Все три находки r1 (High-1, Medium-1, Low-1) закрыты и верифицированы чтением production-кода и личным прогоном мутантов/тестов/смоков (таблица выше), новых находок в дельте не возникло.

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

  • Закрытие High-1: три новых мутанта нацелены на уникальные, реально различающиеся ветки resolveDevicePresentationPolicy(); патчи проверены посимвольно и прогнаны лично.
  • Закрытие Medium-1: unverified доказуемо недостижим на всех путях построения DevItem (три места присвоения bindingStatus: в devices.ts, все фильтруют unverified до конструирования) — удаление ветки корректно, а не «спрятанная» правка поведения.
  • Закрытие Low-1: S14 добавлен по всей цепочке document↔fixture↔production↔ mutation, включая рабочий production-тест (presentationRow с реальным resolveDevicePresentation(), не изолированный policy-вызов).
  • Контракт-тест device presentation decision document, fixture and mutation registry stay exact (test/device-presentation-policy.test.mjs:93-107) по-прежнему проходит на новом составе из 44 рядов (замена L06 на «orphaned binding», добавление S14) — сверка документ↔fixture↔registry не нарушена.
  • Трейлеры Issue: #267 / User-Visible: no на 118062bb корректны: видимое поведение не менялось (удалена недостижимая ветка, добавлена тестовая/документационная инфраструктура), CHANGELOG.md не тронут — это верно для User-Visible: no.
  • git rev-parse HEAD = 118062bb…, совпадает с материалом, заявленным в хендоффе r2.

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

  • Полный npm run golden:verify в этом раунде — не перезапускал; см. обоснование в разделе «Унаследовано из r1» (AC8): единственное изменение рендер-пути — доказуемо мёртвая ветка.
  • Полный набор demo/smoke_*.mjs (192 файла) — не запускал, только три названных плюс два, названных smoke-select по полному диффу issue; остальные 189 не связаны дельтой ни прямо, ни по «слабой связи» — не повод их гонять на этом раунде.
  • Инварианты геометрии/модели и backend-тесты — дельта их не задевает.
  • Ручное открытие демо-стенда в браузере — не делал; полагаюсь на унаследованный из r1 полный golden (130/130) и текущие смоки как эквивалентное подтверждение отсутствия визуального регресса.

Итог

Все находки r1 закрыты и верифицированы независимо (не по слову автора): патчи мутантов сверены построчно с production-кодом, все четыре новых/изменённых targeted-мутанта лично прогнаны и пойманы, недостижимость unverified доказана чтением всех точек конструирования DevItem. Штатные гейты (typecheck/test/build+сверка бандлов/check-docs/ process-gate/mutation-gate --check) зелёные на 118062bb. Новых находок нет. High: 0, Medium: 0.