mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 05:08:53 +00:00
@@ -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`
|
||||
Reference in New Issue
Block a user