Files
2026-10-01 13:56:37 +00:00

18 KiB
Raw Permalink Blame History

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 и убитый мутант). Находок нет.


Материал раунда

  • Ветка: issue/756-warm-dialog-pending-mode, коммит 7b7d6582e69c — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 1f6e1300ddf8b902ddb741a6291e9cbf29e4b5db
    git log --all --format='%H %T' | grep 1f6e1300ddf8
    
  • Тело issue: ba4c5568a2f2b4280acbd5dda51e7bbfe8e2d797f4de12ef083b16502198e57a
  • Вердикт конвейера: green · High 0 · маршрут fix