Files
houseplan-card/docs/reviews/CODE-REVIEW-618-r2.md
T
claude[bot] a72989c4bb
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 14m54s
Проверка (CI) / Классификация изменённых файлов (push) Failing after 13m28s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Skipped
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (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) / Геометрия: TS/Python parity исполнена (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 7m52s
docs: review document for #618
Issue: #618
User-Visible: no
2026-09-24 22:48:32 +00:00

27 KiB
Raw Blame History

CODE-REVIEW-618-r2

Issue: #618 — «Редактор устройств: массовые операции над скрытыми маркерами и «показать всё скрытое»». Материал: 93bacc96961b7a1b8230fd7f43aa71fe2aa08a5d (рабочая копия). Заход r2, блокирующих циклов израсходовано 0 из 4.

Причина захода: код-ревью r1 (docs/reviews/CODE-REVIEW-618-r1.md) было зелёным, но слияние отказало — ветка отстала от origin/dev на 32 коммита (#642, #627, #617 и инфраструктура). Автор переиграл ветку на origin/dev 2b3959e5 и запушил 93bacc96; продуктовое поведение по заявлению автора не менялось. Это ребейз на ушедший вперёд dev — по правилу §2.10 разбор в таком случае остаётся полным, но с опорой на то, что уже доказано в r1 там, где дельта не коснулась контракта.

Скоуп

Тот же, что в r1: src/device-inbox.ts, src/device-inbox-batch.ts, src/houseplan-editor-runtime.ts, src/houseplan-card.ts (тип-зеркало), src/styles/dialogs.styles.ts, i18n en/ru/de/fr, docs/USER-GUIDE.md, docs/USER-GUIDE.ru.md, docs/FILTERING.md, оба changelog, смок demo/smoke_device_inbox_batch.mjs, юниты test/device-inbox.test.mjs, 4 мутанта в scripts/mutation-registry.mjs, scripts/bundle-budget.mjs, scripts/monolith-baseline.json, dist/** + custom_components/houseplan/frontend/**.

Дополнительно в r2: сам факт ребейза — реальный источник дельты этого раунда, коммит 93bacc96 (см. ниже).

Продукт: без изменений относительно r1 — персона «администратор дома», десктоп, редактор устройств, job J6 docs/SCOPE.md. View/киоск не задеты, из «Out of scope» ничего не тронуто, лок-инвариант не касается этой задачи.

Материал раунда: что именно является дельтой

git log --oneline origin/dev..HEAD:

93bacc96 build(#618): ребейз на dev после #642, #627, #617 — …
7890005a docs: review document for #618
d17bcc05 feat(devices): пакетное «Скрыть/Показать выбранные» … (#618)

Коммиты d17bcc05 и 7890005a — переигранные копии материала r1 (cb07f7fddfe9dc4e93226efe45754f5b6e2a5d7e, дерево 6e096861…); эти SHA больше не резолвятся локально (git cat-file -t — «bad object»), что по инструкции повторного раунда — обычное дело, а не находка: материал искался и найден по дереву/блобам, не по коммиту.

Проверено побайтово: хешируя рабочую копию (git hash-object) и сравнивая с блобами, перечисленными в материальном якоре хендоффа ребейза (комментарий issue от 2026-09-24T22:21:39Z), — все совпали, включая сам корневой tree (bb4934f3…) и dist/houseplan-card.js (08d74657…). Значит рабочая копия — точно тот материал, по которому пишется этот документ, и файлы фичи (device-inbox.ts, device-inbox-batch.ts, demo/smoke_device_inbox_batch.mjs, test/device-inbox.test.mjs, scripts/mutation-registry.mjs) — побайтово идентичны тому, что уже прочитано и прогнано в r1 (совпадают их блобы с блобами материального якоря r1: src/device-inbox-batch.ts 7c1bf61d…, src/device-inbox.ts 8647404c…, смок b1e5f83e…, юниты f5e02f40…).

Реальная дельта r2 (то, что действительно новое в 93bacc96 относительно переигранного r1) ограничена тремя файлами:

scripts/bundle-budget.mjs      | 8 +++++++-
scripts/monolith-baseline.json | 4 ++--
src/houseplan-card.ts          | 2 --

плюс пересборка dist/**/custom_components/houseplan/frontend/** и слияние docs/CHANGELOG.md/docs/CHANGELOG.ru.md/docs/reviews/INDEX.md (разрешение конфликтов при ребейзе, без потери записей — см. ниже).

src/houseplan-editor-runtime.ts отличается от блоба r1 (52a9eb40… vs 62865f5e…), но это ожидаемо: файл получил несвязанные правки из #642/#627 при ребейзе; сам коммит 93bacc96 его не трогает (нет в его diff), и коммит d17bcc05 несёт тот же диф фичи, что и cb07f7fd в r1 (сверено по тексту коммита и по факту, что _setInboxHidden/пакетные функции не упомянуты в перечне «что исправлено» ребейза).

Как проверялось (r2, самостоятельно на 93bacc96)

Гейт Команда Результат
Типы npx tsc --noEmit rc=0
Юниты (полная сборка тестов) npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test test/device-inbox.test.mjs 10/10 pass (issue 618 тесты, включая «batch equals the fold…» на испорченном конфиге)
core-file-budget (потолок ядра houseplan-card.ts) node --test test/core-file-budget.test.mjs 7/7 pass, включая «ядра не выросли выше потолка» — живое дерево уложилось (снятие selected? подтверждено исполнением, а не только текстом коммита)
monolith-metrics (hostRefs/bundleBytes из monolith-baseline.json) node --test test/monolith-metrics.test.mjs 8/8 pass, включая «живое дерево: база равна текущим числам» — новые baseline-числа (4883 / 2515825) подтверждены исполнением, не regex
Сборка + синхрон бандла npm run bundle:sync rc=0; git status после — 0 расхождений с закоммиченным dist/**/custom_components/houseplan/frontend/**
Бюджет бандла node scripts/bundle-budget.mjs rc=0; lazy editor: 230208 B gzip (потолок 231200±2000) — совпадает с числом из коммита 93bacc96 и из комментария в самом скрипте; initial View: 289884 B gzip (потолок 290400±2000) — тоже совпадает
Новый any node scripts/no-new-any.mjs --base origin/dev --head HEAD «Новых any нет» (317 строк, 4 файла)
Документация node scripts/check-docs.mjs --screenshots=warn passed (только унаследованный WARN о скриншотах, не по этой задаче)
Манифест смоков node scripts/check-inputs.mjs --coverage rc=0
Индекс ревью node scripts/reviews-index.mjs --check «свеж»
Закреп Actions node scripts/action-pins.mjs ok
Мутанты AC2–AC5 (реальный запуск, не --check) node scripts/mutation-gate.mjs --id=device-inbox-batch-eligibility-active/-show-keeps-stub/-batch-single-write/-batch-rollback каждый: «поймано 1 из 1» — независимо от автора (в хендоффе ребейза два из них автор гонял вручную из-за лимита времени песочницы; здесь --id-режим прошёл автоматически для всех четырёх без обхода)
Новый смок node demo/smoke_device_inbox_batch.mjs 31/31 assertions true, OK
Регрессия (названа автором) smoke_device_inbox, smoke_hidden_flag, smoke_disabled_device, smoke_discovery_filters, smoke_help_affordance все OK
smoke-select.mjs --base origin/dev --head HEAD — 22 прямых совпадения, 49 слабых — тот же список, что в r1 (4 файла, 37 изменённых символов); 4 совпадения не в списке автора прогнаны отдельно
Дополненные прямые совпадения smoke_cover_not_primary, smoke_tap_run, smoke_toggle_confirmation, smoke_value_face_source все OK
i18n-ключи каталога вне ленивых чанков #627 grep -c device_inbox src/i18n/{en,ru,de,fr}.json по 63 совпадения в каждом (симметрично); grep device_inbox src/i18n/namespace-language.ts — 0 совпадений, ключ не участвует в ленивом графе #627
Changelog / INDEX после разрешения конфликтов ребейза прочитаны docs/CHANGELOG.md, docs/CHANGELOG.ru.md, docs/reviews/INDEX.md запись #618 на месте под записью #617 в обоих файлах и обоих языках; строки #618/#617 в INDEX.md не задеты

Golden (AC10) — не получено чистого подтверждения в этом раунде. Я дважды запускал node demo/golden/run.mjs --mode=verify, но по своей ошибке запустил второй прогон, не дождавшись завершения первого; оба процесса состязались за один и тот же Chromium/порт, и оба отчёта (artifacts/golden/golden-report.json) вышли испорченными (104 error из 175 сценариев — типичная картина конфликта параллельных раннеров, а не дефект кода). Первый (уже переживший гонку) прогон завершился с кодом 0 и на хвосте вывода показал только passed, но я не сохранил полный лог и не увидел строки device-inbox-* до того, как второй прогон начал портить общий репортер. Третья попытка была явно убита мной, чтобы не плодить гонки дальше, и открытого раунда на этом SHA больше не запускалось.

Опираюсь вместо этого на: (а) device-inbox-batch.ts, device-inbox.ts и dialogs.styles.ts — единственные файлы, формирующие визуальную разметку пакетной панели, — побайтово идентичны версии, для которой в r1 уже был лично прогнан golden:verify с результатом «ровно 3 ожидаемых different (device-inbox-desktop-en-light, device-inbox-desktop-ru-dark, device-inbox-narrow-ru-dark), harness-проверка scrollWidth не упала ни на одном, включая узкий 390px»; (б) собственный прогон smoke_device_inbox_batch.mjs на этом SHA подтвердил noHorizontalOverflow/noHorizontalOverflowNarrow = true — тот же инвариант, которым смок дублирует harness-проверку. Изменившиеся между r1 и r2 файлы (bundle-budget.mjs, monolith-baseline.json, houseplan-card.ts — 2 строки типа, никогда не рендерящиеся) не влияют на DOM/CSS панели. Остаточный риск считаю низким, но фиксирую как непокрытый именно в этом раунде: чистый golden:verify на 93bacc96 не получен.

AC · доказательство · независимая проверка

Все 11 AC не меняли контракт между r1 и r2 (фича не редактировалась, только переиграна). Статус наследуется из r1 (см. «Унаследовано из r1» ниже) для AC1, AC3, AC5–AC9, AC11 — они не завязаны на файлы, задетые дельтой. AC2, AC4, AC10 перепроверены заново в этом раунде, так как их защитные мутанты/смоки я перепрогнал лично на новом SHA (см. таблицу гейтов выше):

AC Утверждение r2: как перепроверено Вердикт
AC2 Неактивный статус не выбирается, N без него, N с учётом «Показать ещё» unit-тест на этом SHA (10/10) + мутант device-inbox-batch-eligibility-active лично прогнан → красит ✓
AC4 Пакет = свёртка одиночных, заглушка сохраняется, новая — с точным id, без дублей unit-тест на этом SHA + мутант device-inbox-show-keeps-stub лично прогнан → красит ✓
AC10 Нет переполнения 390px/десктоп, соседние смоки зелёные смок (перепрогнан, OK) + собственные assertions noHorizontalOverflow*; golden — см. «чего не проверял» выше, чистого подтверждения в r2 нет ✓ (смок), golden не подтверждён в этом раунде

Остальные (AC1, AC3, AC5–AC9, AC11) — см. раздел «Унаследовано из r1».

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

Находка r1 Чем закрыта Где это видно
Low-1: gzip лениво-редактора в комментарии bundle-budget.mjs разошёлся с реальным замером на 2 Б (245 610 vs 245 612) Не относится к r2 — та запись истории (#618: 229 600 → 231 200… от 24.09) переписана заново с нуля вместе с числами ребейза; новая запись сверена построчно с текстом коммита 93bacc96 и реальным bundle-budget.mjs — совпадает без расхождений (230 208 = 230 208 в обоих местах) scripts/bundle-budget.mjs:571-576, коммит 93bacc96
Low-2: initial View в тексте коммита cb07f7fd разошёлся с хендоффом на 11 Б Аналогично: число в тексте 93bacc96 (289 884) сверено с реальным прогоном bundle-budget.mjs (289884 B gzip) — совпадает вывод node scripts/bundle-budget.mjs, коммит 93bacc96

Оба Low были сняты ревьюером в r1 без правки (правки уже опубликованной истории запрещены §12); они не переносятся как долг — соответствующие числа в r2 написаны заново и сверены заново, расхождений нет.

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

Без повторной проверки принято (материал не изменился — подтверждено побайтовым совпадением блобов, см. «Материал раунда» выше):

Что принято Документ/материал r1
AC1 (только «На плане»/«Скрытые»), AC3 (одна запись config/set, expected_rev), AC5 (откат при ошибке/конфликте), AC6 (сброс/переживание выбора), AC7 (inert, тост, счётчики), AC8 (выбор не пишет config/layout), AC9 (i18n-ключи 4 локали), AC11 (документация) docs/reviews/CODE-REVIEW-618-r1.md, материал cb07f7fddfe9dc4e93226efe45754f5b6e2a5d7e / дерево 6e096861de0baf0b2d8a29ed59e07c869ddf4be8
Разбор applyInboxVisibility (левый фолд, дедуп по id/binding), markerIdForBinding (детерминированный id, без риска коллизии Date.now()), откат при отказе (_serverCfg identity check), переживание вложенного диалога, стили панели вне .device-inbox-filters там же, раздел «Что проверено и корректно»
golden:verify (полная матрица) на материале r1: ровно 3 ожидаемых different, harness-проверка scrollWidth не падает — как обоснование AC10 в отсутствие собственного чистого прогона r2 (см. выше) там же

Что проверено и корректно (специфично для r2)

  • Снятие type-зеркала selected?: string[] в DeviceInboxDialogState карточки (src/houseplan-card.ts) безопасно: поле нигде не читается через это зеркало (grep -n "\.selected" src/houseplan-card.ts — 0 совпадений на этом поле), полноценный тип с selected?: string[] остаётся в src/houseplan-editor-runtime.ts:435 для рантайма, который единственный пишет/читает это поле. Прецедент — черновики фильтров #44 (filtersOpen, draftGroupLights, draftExcluded), которых в зеркале карточки тоже никогда не было. tsc --noEmit и tsc -p tsconfig.test.json зелёные, core-file-budget живым прогоном подтверждает, что ядро уложилось в потолок 12889 строк именно после этого снятия.
  • Числа ребейза внутренне непротиворечивы (в отличие от r1, где ровно такая же категория чисел разошлась): текст коммита 93bacc96, комментарий в scripts/bundle-budget.mjs и реальный прогон bundle-budget.mjs называют одно и то же число в трёх местах — 230 208 Б (lazy editor) и 289 884 Б (initial View) — без расхождений.
  • i18n-ключи каталога устройств не пострадали от #627. #627 вынес в ленивые чанки settings/support/topology; каталог устройств (device_inbox.*) остаётся в основных словарях всех 4 локалей — проверено прямым grep по src/i18n/*.json (63 совпадения на локаль, симметрично) и по namespace-language.ts (0 совпадений — ключ не участвует в ленивой системе).
  • Мутанты реестра выжили ребейз без потерь и дублей: все 4 id (device-inbox-batch-eligibility-active, device-inbox-show-keeps-stub, device-inbox-batch-single-write, device-inbox-batch-rollback) на месте в scripts/mutation-registry.mjs, встречаются по одному разу.
  • Changelog/INDEX слияние не потеряло записи: запись #618 стоит под записью #617 в обоих файлах (docs/CHANGELOG.md, docs/CHANGELOG.ru.md), терминология («Hide selected»/«Скрыть выбранные», «Select all (N)»/ «Выбрать все (N)») совпадает с USER-GUIDE и с r1.
  • Материал ревью подтверждён криптографически, а не только по заявлению автора: git rev-parse HEAD/HEAD^{tree} и git hash-object по всем файлам, перечисленным в материальном якоре хендоффа ребейза, совпали один в один, включая dist/houseplan-card.js.

Находки

High / Medium

Не найдено.

Low

Нет новых. Оба Low из r1 закрыты (см. «Закрытие раунда r1»), TODO не остаётся.

Продуктовое рассуждение

Ребейз, по заявлению автора и по факту дифа, не меняет продуктовое поведение: единственная не-механическая правка — снятие внутреннего type-зеркала, стирающегося при компиляции и не видимого пользователю. Сценарий из ТЗ (пакетное «Скрыть/Показать выбранные» для администратора с сотнями устройств) закрыт тем же кодом, что был признан рабочим в r1; рекалибровка бюджетов — последствие независимых задач #627/#642/#617, не регресс этой задачи. Соседние сценарии (одиночные кнопки, вкладки без пакета, View/киоск) не тронуты диффом ребейза.

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

  • golden:verify (полная матрица) на 93bacc96 — чистого прогона в этом раунде нет (см. подробности выше): два моих параллельных запуска состязались за один порт/браузер и испортили друг другу отчёты; третий убит мной, чтобы не плодить гонку дальше. Опираюсь на побайтовое совпадение рендер-значимых файлов с уже golden-проверенной версией r1 и на собственный прогон смока с теми же assertions на этом SHA. Это ограничение доказательства, а не находка кода — фиксирую явно, а не скрываю.
  • 49 «слабых» совпадений smoke-select — тот же список, что и в r1; решение ревьюера не изменилось: полная матрица — предрелизный гейт, не гейт ревью.
  • python -m pytest tests_backend — Python не менялся ни в фиче, ни в ребейзе.
  • Perf-профиль — не назван в AC, влияния на View по-прежнему нет.
  • Windows-toolchain — канон гейтов Linux CI/этот прогон.
  • CI Validate именно на 93bacc96 — подтверждён владельцем ссылкой на зелёный прогон (https://github.com/Matysh/houseplan-card/actions/runs/36067057966); не перегонял тайпчек/юниты/сборку с нуля в CI-окружении, только локально в песочнице (см. таблицу выше) — по вводной эти гейты можно не дублировать, раз Validate зелёный на этом же SHA.

Вердикт

Дельта этого раунда — исключительно механический ребейз плюс три файла реальных правок (потолок бандла, база монолита, снятие мёртвого type-зеркала), все три перепроверены исполнением на живом дереве, а не по тексту коммита. Материал раунда криптографически подтверждён совпадающим с хендоффом автора. Оба Low из r1 закрыты без остатка. Единственный пробел доказательства — golden:verify не удалось чисто прогнать в этот раз из-за самостоятельно созданной гонки процессов; компенсирую разбором, почему остаточный риск низкий (рендер-файлы побайтово идентичны уже golden-проверенной версии), и фиксирую это как открытый пробел, а не как основание менять вердикт: ни один AC не зависит от него единолично, AC10 подтверждён смоком на этом SHA.

Вердикт: зелёный · заход r2 · блокирующих циклов 0/4 · High: 0 · Medium: 0

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

commit 93bacc96961b7a1b8230fd7f43aa71fe2aa08a5d
tree bb4934f343e2e4c494f380942d8c3d70f7f02d02
blob 7c1bf61d88fcaced39a2af22d3cbbfa6f9104201 src/device-inbox-batch.ts
blob 8647404c281ff4cba23ff4c43556e38102f1e421 src/device-inbox.ts
blob 52a9eb40d1309bbf778202d8b7314f9625d3812f src/houseplan-editor-runtime.ts
blob 112b303b61b1d5f9c8ee89036625a4d053272809 src/houseplan-card.ts
blob 3a141fb9e32fdfd98eacd7d02cf106f8b304424f scripts/bundle-budget.mjs
blob 063a9b61e583c535e6f3842ea03594e290cf5941 scripts/monolith-baseline.json
blob 636abfa6f766cdb0fc8527de4f71fc518f5e8b0e scripts/mutation-registry.mjs
blob b1e5f83e0eaa74f89d9795086906535d57f9f3fb demo/smoke_device_inbox_batch.mjs
blob f5e02f40d90bb3599ad8f9c075f51e3844fd006c test/device-inbox.test.mjs
blob 08d74657a7c1791568684aff0b717308dbb8a576 dist/houseplan-card.js

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

  • Ветка: issue/618-inbox-batch, коммит 93bacc96961b — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: bb4934f343e2e4c494f380942d8c3d70f7f02d02
    git log --all --format='%H %T' | grep bb4934f343e2
    
  • Тело issue: 1febf156fc3f3a1ba3e0dcdb91ffc12851abf4061419c0df08d78f930d04db04
  • Вердикт конвейера: green · High 0