18 KiB
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). Критерий §5perf-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 и убитый
мутант). Находок нет.
Материал раунда
- Ветка:
issue/756-warm-dialog-pending-mode, коммит7b7d6582e69c— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
1f6e1300ddf8b902ddb741a6291e9cbf29e4b5dbgit log --all --format='%H %T' | grep 1f6e1300ddf8 - Тело issue:
ba4c5568a2f2b4280acbd5dda51e7bbfe8e2d797f4de12ef083b16502198e57a - Вердикт конвейера:
green· High 0 · маршрутfix