15 KiB
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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
8ee4c0d4d88af6ec6508633a3c5841b261554825git log --all --format='%H %T' | grep 8ee4c0d4d88a - Тело issue:
7da4b97ada3ff166c59d48c1ed58f958ee8dcda491e0c1e424b6e1b6937e3de1 - Вердикт конвейера:
green· High 0 · маршрутfix