docs: review document for #757

Issue: #757
User-Visible: no
This commit is contained in:
claude[bot]
2026-10-01 12:12:15 +00:00
parent 4091d83af3
commit 929044b932
+181
View File
@@ -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`.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/757-runtime-loader-no-loop`, коммит `3398b69d635e` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `8ee4c0d4d88af6ec6508633a3c5841b261554825`
```
git log --all --format='%H %T' | grep 8ee4c0d4d88a
```
- Тело issue: `7da4b97ada3ff166c59d48c1ed58f958ee8dcda491e0c1e424b6e1b6937e3de1`
- Вердикт конвейера: `green` · High 0 · маршрут `fix`