From 929044b93222686397d4e04a7ed8b2597c42719c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:12:10 +0000 Subject: [PATCH] docs: review document for #757 Issue: #757 User-Visible: no --- docs/reviews/CODE-REVIEW-757-r1.md | 181 +++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 docs/reviews/CODE-REVIEW-757-r1.md diff --git a/docs/reviews/CODE-REVIEW-757-r1.md b/docs/reviews/CODE-REVIEW-757-r1.md new file mode 100644 index 00000000..7fda82ad --- /dev/null +++ b/docs/reviews/CODE-REVIEW-757-r1.md @@ -0,0 +1,181 @@ +# CODE-REVIEW-757-r1 + +Issue: #757 · Этап: code · Трек: show · Заход: r1 · блокирующих циклов 0/2 +Материал: `3398b69d635e775979c31f6f9ad128913919a313` (одна коммита поверх `origin/dev` `7ce3f662`) + +## Скоуп + +Баг из #731: `_renderBody` на каждой отрисовке звал `_ensureEditorRuntime()` / +`_ensureOnboardingRuntime()`, если поверхность (диалог масштаба киоска, +мастер импорта этажей, диалог, воскрешённый тёплым ре-маунтом) просила +рантайм, а его не было. После не-терминальной сетевой ошибки загрузчик +возвращался в `idle`, и следующая же отрисовка — не действие пользователя — +снова читалась загрузчиком как намерение и запускала новый цикл загрузки с +тостом каждые ≈3,5 с, пока поверхность открыта. Это противоречит +контракту #353 («повтор только по следующему явному намерению, фоновых +повторов нет»). + +Правка (класс A, `src/editor-runtime-loader.ts` + 2 строки `src/houseplan-card.ts`): +`EditorRuntimeLoader.ensure(intent)` различает `'explicit'` (по умолчанию, +действие пользователя или открывающий метод) и `'reconcile'` (сверочный +вызов из отрисовки). Сверка запускает первый цикл, который нужен +поверхности, но после не-терминальной ошибки возвращает `false` без +загрузки, пока явный вызов не начнёт новый цикл. Терминальная ошибка +(несовпадение отпечатка), `ready` и полёт (`_inFlight`) не меняются. +Обслуживает J6 (карточка остаётся верна состоянию рантайма без ложной +активности) и общий принцип «View/редактор не делают ничего без действия +пользователя» (UX-MODES). + +Критерии §5 (трек show, маршрут `fix`): сложность низкая (один класс, +булев флаг состояния + параметр), одна поверхность (`EditorRuntimeLoader` +и два места его сверочного вызова в одном методе), миграции конфига нет, +нового UX-контракта нет — исправляет уже описанный контракт #353, перфа и +touch не затронуты, ожидаемое поведение зафиксировано документально. +Задача проходит критерии трека show. + +## Как проверялось + +Прочитан весь дифф построчно: `src/editor-runtime-loader.ts`, +`src/houseplan-card.ts` (2 изменённые строки), `test/editor-runtime-loader.test.mjs`, +`demo/smoke_lazy_editor_chunk.mjs`, `scripts/mutation-registry.mjs`, +`docs/testing-notes/mutation-browser-guards.md`, оба CHANGELOG. + +Прослежены все вызовы `_ensureEditorRuntime()` / `_ensureOnboardingRuntime()` +(строки 745, 9440, 9660, 9666, 10120, 10131, 10139 `src/houseplan-card.ts`) — +подтверждено, что все они остаются явными (клик по вкладке, открывающие +методы диалогов, `_reviveMoonStatus` на тёплом ре-маунте — событие +«ровно один раз», не отрисовка) и ни один не маскирует сверку под намерение. +Только 2 места в `_renderBody` (онбординг и редактор) стали `ensure('reconcile')` — +ровно то, что заявлено в ТЗ. + +Разобрана логика `ensure()`: `ready`→true, `failed`→false, полёт→join, +`reconcile && _awaitingIntent`→false без загрузки, иначе обнуление флага и +старт цикла. `_awaitingIntent` выставляется в `!sawMismatch` после каждого +цикла — т.е. терминальная ошибка (`failed`) не трогает флаг сверки, нетерминальная +взводит его. Это in корректно реализует AC1/AC2. + +**Гейты, которые прогнал сам:** + +| Гейт | Результат | +|---|---| +| `npm run build` (`tsc --noEmit` + `rollup`) | зелёный, 22.7 с — подтверждает typecheck и сборку на этом SHA независимо от Validate | +| `npm run bundle:sync` → `node demo/smoke_lazy_editor_chunk.mjs` (назван в AC1–AC3) | зелёный, все 22 проверки `true`, включая новый раздел (а)/(б)/(в) | +| **Мутация вручную**: применил патч `render-reconcile-restarts-editor-runtime-cycle` из реестра (`ensure('reconcile')` → `ensure()` на строке редактора), пересобрал, перегнал смок | смок покраснел ровно там, где и обещано: `kioskDialogFailureIsOneCycle` (4 вместо 2), `kioskDialogFailureIsOneNotice` (3 вместо 1), `warmReviveFailureIsOneCycle` (3 вместо 1), `warmReviveFailureIsOneNotice` (2 вместо 1) — остальные 18 проверок, включая линию мастера импорта (не задета этим мутантом), остались зелёными | +| Патч отменён, `npm run bundle:clean` | рабочая копия восстановлена до закоммиченного бандла, `git status` чист | + +Это прямое исполнительное доказательство: тест умеет падать, и падает +именно от той дыры, которую описывает AC1 (цикл без намерения). + +**Гейты, подтверждённые Validate на этом SHA (не перегонял повторно, см. ссылку в промпте):** +`npx tsc --noEmit`, `npm test` (включая новые 14 тестов +`test/editor-runtime-loader.test.mjs` и `test/core-file-budget.test.mjs`, +не нарушенный потолок монолита), `npm run build` со сверкой бандла. + +**`smoke-select.mjs --base origin/dev --head HEAD`** (вывод приложен, +решение по каждой строке): + +- Прямое совпадение (16 файлов). `demo/smoke_lazy_editor_chunk.mjs` — прогнан + (см. выше, трижды: baseline, с мутантом, после очистки). Остальные 15 + (`smoke_danger_confirmation`, `smoke_houseplan_panel`, `smoke_plan_upload_limit`, + `smoke_post_write_adoption`, `smoke_version_recovery`, `smoke_backdrop_guard`, + `smoke_grid_scale_invariance`, `smoke_help_affordance`, `smoke_lazy_admin_locale`, + `smoke_warm_dialogs`, `smoke_discard_copy`, `smoke_nav_persist`, + `smoke_preloader_lifecycle`, `smoke_warm_owners`, `smoke_zigbee_topology_hover`) — + **не прогонял**: совпадение по символам `_editorRuntime`/`_ensureEditorRuntime`/ + `_onboardingRuntime`, но все они упражняют только явный путь (`ensure()` + по умолчанию или `_ensureEditorRuntime()` напрямую), а он в этом диффе не + изменился — проверено чтением (см. выше, список строк вызова). Отдельно + прочитан `smoke_version_recovery.mjs`: он не гоняет цикл загрузчика через + сеть, а дёргает `options.failed(...)` напрямую, минуя `ensure()`, — изменение + его не касается. `smoke_warm_dialogs.mjs` не инжектирует сетевых ошибок + рантайма вовсе. +- Слабая связь (31 файл, общий символ `_editorRuntime`) — не прогонял: ни + один не относится к состояниям загрузчика после сбоя, участвующего в этом + диффе; это обычные редакторские смоки, где рантайм грузится успешно с + первой попытки до появления диалога. +- `golden:verify` — не запускал: нет метки `ci:golden`, дифф не меняет + рендер плана/сцены (только состояние загрузчика и JS-условие вызова). +- `python -m pytest tests_backend` — не запускал: правок `custom_components/**/*.py` нет. +- `npm run invariants` — не запускал: геометрия и ссылки на неё не затронуты. +- `node scripts/mutation-gate.mjs --check` — не перегонял отдельно (входит в + `gate:small`, который автор прогнал: 273 с, зелёный); факт регистрации + мутанта и его смертность проверены вручную выше. +- performance-профили — не названы в AC, не запускал. + +## Находки + +Нет. Логика состояний (`ready`/`failed`/`_inFlight`/`reconcile`+`_awaitingIntent`) +разобрана построчно и подтверждена исполнением с ручной мутацией. Все вызовы +`ensure()` вне `_renderBody` остаются явными. CHANGELOG (RU и EN) в том же +коммите, текст совпадает с формулировкой из ТЗ. Трейлеры `Issue:` и +`User-Visible: yes` на месте. Единственное видимое пользователю число в +диффе — счётчики запросов/тостов из таблицы issue/комментария автора — они +не попадают в код или документацию пользователя (только в CHANGELOG, где нет +числа), так что вопрос «одно ли число — один источник» (§8) здесь не +применим. + +Единственная формальная не-находка (бухгалтерия, Low, трек show не открывает +цикл): зарегистрирован один мутант +(`render-reconcile-restarts-editor-runtime-cycle`), покрывающий только строку +вызова редактора; строка вызова онбординга (`_onboardingRuntimeLoader.ensure('reconcile')`) +отдельного мутанта в реестре не имеет, хотя и покрыта тем же смоком через +проверки `importWizardFailureIsOneCycle`/`importWizardFailureIsOneNotice` +(это я подтвердил чтением теста, не мутацией). Снимаю без правки: AC3 +называет единственный мутант, трек show не требует полноты реестра, и +фактическое покрытие смоком у онбординг-строки есть — просто без отдельной +записи в `MUTANT_DEFINITIONS`. + +## Что проверено и корректно + +- Контракт #353 (терминальная/нетерминальная ошибка, `_inFlight`, `ready`) + не нарушен — ветки `ready`/`failed`/`_inFlight` в `ensure()` не тронуты + этим диффом. +- Явные вызовы (`_ensureEditorRuntime`, `_ensureOnboardingRuntime`, все + открывающие методы, `_requestMode`, warm-revive-как-открытие) ведут себя + как раньше — прочитаны все их места вызова. +- `_renderBody`: оба сверочных вызова (онбординг и редактор) переведены на + `ensure('reconcile')`, правка в `src/houseplan-card.ts` строго 2 строки, + монолит не вырос (12 895 строк в `wc -l`, бюджет не задет — + `core-file-budget.test.mjs` в `npm test`, покрыт Validate). +- Исполнительно подтверждено мутацией: без правки смок красный ровно на тех + трёх сценариях, что и описаны в AC1 (киоск, воскрешённый диалог; мастер + импорта не затронут этим конкретным мутантом, так как патч меняет только + строку редактора — ожидаемо). +- CHANGELOG RU/EN добавлены в том же коммите, текст соответствует ТЗ + дословно. +- Юнит-тесты (`test/editor-runtime-loader.test.mjs`, 14/14, из них 2 новых + для #757) логически перепроверены: без правки (`intent` не различается) + ассерты на `cycles` после серии `ensure('reconcile')` не сошлись бы — + тест умеет падать. + +## Чего не проверял + +- Полную матрицу смоков (286 файлов) — не предрелизный гейт. +- 15 прямых и 31 слабое совпадение `smoke-select` вне `smoke_lazy_editor_chunk` — + обоснование по каждой группе выше, в разделе «Как проверялось». +- `golden:verify`, `pytest tests_backend`, `invariants`, performance — + не применимы к этому диффу (см. выше). +- Ручное HA-окружение/реальный Zigbee-разрыв сети — вне диапазона code-review, + достаточно смока с перехватом сети на уровне Chromium (тот же путь, которым + ловится прод-дефект в 404 на хешированный чанк). + +## Вердикт + +Зелёный. AC1–AC3 доказаны: AC1/AC2 — исполнением (`smoke_lazy_editor_chunk.mjs`, +включая ручную мутацию, доказывающую тест умеет падать), AC3 — чтением плюс +исполнением юнит-тестов и `npm run build`. Критерии трека show (§5) пройдены, +маршрут — `fix`. + +--- + + + +## Материал раунда + +- Ветка: `issue/757-runtime-loader-no-loop`, коммит `3398b69d635e` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `8ee4c0d4d88af6ec6508633a3c5841b261554825` + ``` + git log --all --format='%H %T' | grep 8ee4c0d4d88a + ``` +- Тело issue: `7da4b97ada3ff166c59d48c1ed58f958ee8dcda491e0c1e424b6e1b6937e3de1` +- Вердикт конвейера: `green` · High 0 · маршрут `fix`