mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
@@ -0,0 +1,200 @@
|
||||
# CODE-REVIEW-756-r1
|
||||
|
||||
Материал раунда: ветка `issue/756-warm-dialog-pending-mode` @ `7b7d6582e69cbf3f4b2b5398b063ad22056e5d21`
|
||||
поверх `dev` `7ce3f662`. Диапазон: `git log --oneline origin/dev..HEAD` — один коммит;
|
||||
`git diff origin/dev...HEAD` — 9 файлов, +230/−47. Трек: `track:show`, заход r1,
|
||||
блокирующих циклов 0/2.
|
||||
|
||||
## Скоуп
|
||||
|
||||
Баг тёплого ре-маунта: когда право записи (`hass`/`can_write`) приходит уже
|
||||
после вставки карточки в DOM, режим редактора откладывается в
|
||||
`_pendingNavMode` и позже входит через `_resumePendingNavMode → _setMode`.
|
||||
Этот путь не вызывал `_warmReviveDialog`, поэтому открытый диалог с
|
||||
черновиком молча терялся, а флаг `_warmRevivePending` оставался поднятым —
|
||||
следующий ре-маунт в цепочке A→B→C воскрешал чужой черновик предшественника.
|
||||
|
||||
Правка выносит общий хвост тёплой адопции (воскрешение черновика ровно один
|
||||
раз + удержание refit до оседания редакторской панели) в новый модуль
|
||||
`src/warm-mode-adoption.ts` (`finishWarmModeAdoption`, `resumeWarmMode`) и
|
||||
применяет его на обоих путях: немедленном (`_requestMode(..., adopt=true)`) и
|
||||
отложенном (`_requestMode(..., adopt='resume')` / прямой вызов
|
||||
`resumeWarmMode` из `_resumePendingNavMode`, когда рантайм уже есть).
|
||||
`docs/WARM-REMOUNT.md` §2 дополнен абзацем, описывающим именно этот механизм.
|
||||
Работа закрывает J6 (SCOPE.md: «keep the plan true», три редактора, drag/
|
||||
resize — сюда же относится устойчивость состояния редактора к техническому
|
||||
пересозданию карточки).
|
||||
|
||||
Один коммит, трейлеры на месте: `Issue: #756`, `User-Visible: yes`; оба
|
||||
CHANGELOG правлены в этом же коммите (RU и EN, формулировка совпадает с ТЗ
|
||||
issue дословно).
|
||||
|
||||
## Риск по изменённым участкам (#707)
|
||||
|
||||
- **perf** — `src/houseplan-card.ts:761` (удалённый) и
|
||||
`src/warm-mode-adoption.ts:35` (новый) токен `requestAnimationFrame`.
|
||||
Это не новая перф-поверхность: до правки существовал ровно один такой
|
||||
двойной-rAF (в немедленной ветке адопции, `_requestMode(..., adopt=true)`),
|
||||
и после правки он остался ровно тем же механизмом, перенесённым в
|
||||
`finishWarmModeAdoption` без изменения логики (построчно сверено —
|
||||
см. «Что проверено чтением» ниже) — старый токен не исчезает, а
|
||||
перемещается. Единственное фактическое расширение: та же процедура теперь
|
||||
используется ещё из `resumeWarmMode` (отложенный путь) — но это и есть
|
||||
заявленное поведение задачи, зафиксированное в
|
||||
`docs/WARM-REMOUNT.md` §2 (правка этого же диффа, абзац «Отложенный
|
||||
редактор — та же тёплая адопция, только позже...») и доказанное AC1
|
||||
(`watchView` проверяет вьюпорт покадрово после ре-маунта с поздним
|
||||
`hass`/`can_write` — именно это окно и использует новый rAF). Критерий
|
||||
§5 `perf-touch` пройден предметно, а не отпиской: поведение документировано
|
||||
и покрыто тестом, который я исполнил сам (см. гейты). `route: fix`,
|
||||
reclassify не требуется.
|
||||
|
||||
Прочие классы из §5 не нарушены: одна поверхность (`src/houseplan-card.ts` +
|
||||
новый выделенный модуль того же механизма — ровно то, что назвал владелец в
|
||||
оценке issue), миграции/compat-полей нет, нового UX-контракта нет (видимое
|
||||
поведение совпадает с уже описанным в WARM-REMOUNT.md), touch не затронут.
|
||||
Трек `show` подтверждён владельцем явно в оценке issue («трек: show
|
||||
(... перфа, touch и миграции нет)»).
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Дешёвые гейты уже зелёные на этом SHA (Validate,
|
||||
https://github.com/Matysh/houseplan-card/actions/runs/36858886158) — не
|
||||
перегонял отдельно, но они шли и локально как побочный эффект подготовки
|
||||
окружения для браузерных смоков (см. ниже) и тоже зелёные.
|
||||
|
||||
| Гейт | Статус | Примечание |
|
||||
|---|---|---|
|
||||
| `npx tsc --noEmit` | ✅ | без вывода |
|
||||
| `npm test` | ✅ | 3465 passed / 1 skipped / 0 failed |
|
||||
| `npm run build` + `bundle-sync` | ✅ | собрано, скопировано в `demo/srv/assets`; рабочее дерево возвращено `npm run bundle:clean` |
|
||||
| `node scripts/mutation-gate.mjs --check` | ✅ | `warm-pending-mode-leaves-revive-waiting` применяется без конфликта (`ok`); 204/200 browser guards — рост объяснён строкой в `docs/testing-notes/mutation-browser-guards.md` (сам диф) |
|
||||
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | ✅ выполнен | см. раздел ниже — решение по каждой строке |
|
||||
| `demo/smoke_warm_dialogs.mjs` (все разделы, включая H) | ✅ зелёный на HEAD | **и воспроизведено красным на `origin/dev`**: применил новый файл смока к коммиту `7ce3f662` (worktree), собрал — ровно 14 несовпадений, список совпадает с заявленным автором 1:1 (`hAfterInsert*`, `hNextTask*`, `hNonAdminCanWrite*`, `hChainCarriesOwnDraft`, `hSpaceSwitchReviveSettled`) |
|
||||
| Мутант `warm-pending-mode-leaves-revive-waiting` | ✅ проверен исполнением | вручную применил патч из реестра к `src/warm-mode-adoption.ts`, пересобрал, прогнал `smoke_warm_dialogs.mjs` — те же 14 падений, что и на dev. Тест умеет падать, мутант действительно убивается названным смоком |
|
||||
| `demo/smoke_warm_remount.mjs` | ✅ | AC3 |
|
||||
| `demo/smoke_warm_owners.mjs` | ✅ | AC3 |
|
||||
| `demo/smoke_nav_persist.mjs` (включая `pendingModeUsesTransitionAuthority`) | ✅ | AC3 — отдельно проверил ключ в выводе |
|
||||
| `demo/smoke_config_reload_race.mjs` | ✅ | AC3 |
|
||||
| `node --test test/core-file-budget.test.mjs` | ✅ | потолок 12896 не поднят; `wc -l src/houseplan-card.ts` = 12891 (у автора в комментарии 12892 — расхождение на одну строку, не влияет на вердикт потолка, не являюсь уверенным, что это не разница в подсчёте конечного перевода строки) |
|
||||
| `node --test test/smoke-select.test.mjs` | ✅ | валидирует, что новая запись `smoke-links.mjs` указывает на существующие смоки/символы |
|
||||
|
||||
## Решение по строкам `smoke-select`
|
||||
|
||||
Матрица: 286 смоков, порог «широкого» символа — 57. Вывод: 35 прямых
|
||||
совпадений, 54 слабых, 1 зарегистрированная связь, `_setMode`/`requestUpdate`
|
||||
исключены как слишком широкие.
|
||||
|
||||
- **Прогнал явно** (4 из 35 прямых + 1 зарегистрированная): `smoke_warm_dialogs`
|
||||
(прямое, названо в AC1/AC2), `smoke_nav_persist` (прямое, названо в AC3),
|
||||
`smoke_warm_owners` (прямое, названо в AC3), `smoke_warm_remount`
|
||||
(зарегистрированная связь — она явно указана как наблюдатель новых
|
||||
экспортов `finishWarmModeAdoption`/`resumeWarmMode`/`_holdWarmRefit`/
|
||||
`_releaseWarmRefit`), плюс `smoke_config_reload_race` (назван в AC3,
|
||||
хотя в выборке отсутствует — изменённые строки не задевают его символы
|
||||
напрямую, но ТЗ требует его явно).
|
||||
- **Не прогнал остальные 31 прямое и 54 слабых — решение разобрать чтением**:
|
||||
все они матчатся по `_mode`, `_view`, `_editorRuntime`, `_ensureEditorRuntime`,
|
||||
`_requestMode`, `_stageEl`, `_pendingRefitSize`, `_refitRaf`,
|
||||
`_lastValidStageSize`, `_warmRevivePending` — символам, которые
|
||||
`_requestMode` читает/пишет и до, и после рефакторинга. Построчно сверил
|
||||
обе ветки (`adopt === true` и `adopt === false`) со старой версией функции:
|
||||
поведение не изменилось — то же вычисление `request`, то же решение
|
||||
«можно ли войти» (`ready`/`current` эквивалентны старым двум последовательным
|
||||
проверкам), тот же `_adoptMode`/`_setMode`, тот же двойной rAF, перенесённый
|
||||
в `_releaseWarmRefit`/`finishWarmModeAdoption` без изменения порядка
|
||||
присваиваний, влияющего на результат. Единственная новая ветка — `adopt ===
|
||||
'resume'`, и единственные вызовы с этим значением — `src/houseplan-card.ts:4167`;
|
||||
второй новый путь (прямой вызов `resumeWarmMode`, когда рантайм уже есть) —
|
||||
`src/houseplan-card.ts:4169` (новая строка в `_resumePendingNavMode`). Ни
|
||||
один из 85 оставшихся смоков `_requestMode` с `adopt: 'resume'` не вызывает
|
||||
и `_resumePendingNavMode`/`resumeWarmMode`/`finishWarmModeAdoption`/
|
||||
`_holdWarmRefit`/`_releaseWarmRefit` не называет — их совпадение с диффом
|
||||
целиком объясняется тем, что они используют тот же режим/вьюпорт
|
||||
инфраструктуры в не затронутых ветках. Это «проверено чтением, не
|
||||
исполнением», а не пропуск.
|
||||
|
||||
## Находки
|
||||
|
||||
Нет. High: 0, Medium: 0, Low: 0.
|
||||
|
||||
## Что проверено чтением (построчно, без исполнения)
|
||||
|
||||
- `_requestMode` (houseplan-card.ts:734–761): ветки `adopt===true` и
|
||||
`adopt===false` после рефакторинга байт-в-байт эквивалентны версии до
|
||||
правки (см. разбор выше) — старое поведение не меняется ни для одного
|
||||
существующего вызова.
|
||||
- `_warmReviveDialog(settle)` (houseplan-card.ts:3468 и далее): параметр
|
||||
`settle=true` НЕ означает «съесть черновик безусловно» — он лишь отключает
|
||||
повторное взведение `_warmRevivePending` («подождать ещё»); дальше функция
|
||||
идёт по тем же условиям §3 (owner жив/TTL, совпадение `space`/`mode`) и либо
|
||||
открывает диалог, либо роняет его. Это соответствует тексту
|
||||
WARM-REMOUNT.md §2: «камера, уже ушедшая сама... остаётся обычному рефиту,
|
||||
а черновик проверяется по §3 п.4» — черновик не отбрасывается автоматически
|
||||
только из-за того, что камера разошлась с памяткой, он по-прежнему
|
||||
открывается, если совпали пространство и режим. Подтверждено и исполнением:
|
||||
раздел H покрывает как совпадающий (hAfterInsert/hNextTask/
|
||||
hNonAdminCanWrite — камера совпала, диалог открылся), так и несовпадающий
|
||||
случай (hSpaceSwitch — пространство разошлось, диалог съеден).
|
||||
- `resumeWarmMode` (warm-mode-adoption.ts:54–64): `kept` сравнивается с
|
||||
текущим `_view` ДО вызова `commit()` — то есть с видом, который памятка
|
||||
ещё не трогала, а не с видом после рефита `_setMode`. Это соответствует
|
||||
замыслу «камера, которую окно ожидания не трогало, возвращается
|
||||
бит-в-бит» — сравнение именно с состоянием непосредственно перед входом в
|
||||
редактор, а не после.
|
||||
- Единственный вызов с `adopt==='resume'` (`:4167`) срабатывает только когда
|
||||
`!this._editorRuntime` (рантайм ещё не загружен — ожидание в `_requestMode`
|
||||
само дождётся `_ensureEditorRuntime()`), иначе (`:4169`) вызывается
|
||||
`resumeWarmMode` напрямую с `_setMode` синхронным коммитом — ветвление
|
||||
корректно покрывает оба случая «рантайм ещё грузится»/«рантайм уже есть»,
|
||||
которые в противном случае гонялись бы по разным путям без реального
|
||||
различия в итоговом поведении.
|
||||
|
||||
## Что проверено исполнением
|
||||
|
||||
Раздел «Как проверялось» выше: typecheck, unit-тесты, сборка, все названные в
|
||||
AC3 браузерные смоки плюс полный `smoke_warm_dialogs.mjs` (включая новый
|
||||
раздел H), мутант применён и исполнен вручную, `smoke-select` запущен и его
|
||||
строки разобраны. Красный результат на `dev` воспроизведён самостоятельно
|
||||
(не только заявлен автором) — 14 из 14 заявленных проверок совпадают.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Полная матрица `smoke-select` (286 смоков) — не гейт ревью, предрелизная
|
||||
обязанность; 85 оставшихся (прямых + слабых) совпадений разобраны чтением,
|
||||
не исполнением (см. выше).
|
||||
- `golden:verify` — не запрашивался (нет метки `ci:golden`, диф не меняет
|
||||
геометрию/рендер плана, только тайминг входа в редактор и воскрешение
|
||||
диалога).
|
||||
- `python -m pytest tests_backend` — диф не трогает `custom_components/**/*.py`.
|
||||
- `npm run invariants` — диф не трогает геометрию модели или ссылки на неё.
|
||||
- performance-профили — не названы в AC.
|
||||
- Поведение при реальной Home Assistant (не демо-песочнице) и при настоящей
|
||||
задержке ответа сервера `can_write` — проверено только синтетическим
|
||||
`callWS`-перехватом смока (as designed by the author); это соответствует
|
||||
объёму track:show.
|
||||
- Конфликт с #757 (CHANGELOG, место в реестре мутантов, счётчики инвентаря
|
||||
гвардов), упомянутый автором в комментарии к задаче, — это вопрос
|
||||
последующего ребейза при слиянии, а не дефект текущего SHA; материал этого
|
||||
раунда не содержит #757.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Зелёный. route: fix (критерии §5 пройдены предметно, не отпиской — см.
|
||||
«Риск по изменённым участкам»). AC1–AC3 доказаны исполнением тестов, которые
|
||||
я сам прогнал и которые умеют падать (воспроизвёл красный `dev` и убитый
|
||||
мутант). Находок нет.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/756-warm-dialog-pending-mode`, коммит `7b7d6582e69c` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `1f6e1300ddf8b902ddb741a6291e9cbf29e4b5db`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 1f6e1300ddf8
|
||||
```
|
||||
- Тело issue: `ba4c5568a2f2b4280acbd5dda51e7bbfe8e2d797f4de12ef083b16502198e57a`
|
||||
- Вердикт конвейера: `green` · High 0 · маршрут `fix`
|
||||
Reference in New Issue
Block a user