Issue: #618 User-Visible: no
27 KiB
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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
bb4934f343e2e4c494f380942d8c3d70f7f02d02git log --all --format='%H %T' | grep bb4934f343e2 - Тело issue:
1febf156fc3f3a1ba3e0dcdb91ffc12851abf4061419c0df08d78f930d04db04 - Вердикт конвейера:
green· High 0