Files
houseplan-card/docs/reviews/CODE-REVIEW-520-r1.md
T
2026-09-10 16:27:39 +00:00

18 KiB
Raw Blame History

CODE-REVIEW-520-r1

Issue: #520 · Этап: code · Заход: r1 · Материал: a625871538c2cf58c39efd2b0e52be912f2e04e4 Вердикт: красный · High: 1 · Medium: 0

Скоуп

ТЗ (тело issue #520, зафиксировано зелёным на SPEC-REVIEW r3) требует убрать лишнюю эпоху конфигурации на старте, которую внёс #500: _serverCfg и _layout перестают быть объявленными реактивными свойствами Lit, реактивность сохраняется через _adoption → onBodyReplaced → requestUpdate. Диапазон коммитов: 37ef970d, 3d439805, 27ea23c1 (диагностика бенчмарка), e181b08f (сама правка + тесты + мутанты + бюджеты), a6258715 (второй мутант). Диф продуктового кода: 19 строк в src/houseplan-card.ts — ровно контракт §1 ТЗ, без сюрпризов.

Как проверялось

Прочитан диф всех файлов диапазона (git diff origin/dev...HEAD), тело issue #520 и вся история комментариев (S2-анализ → ТЗ r1–r3 → хендофф на код-ревью). Дешёвые гейты подтверждены зелёным Validate на этом SHA (https://github.com/Matysh/houseplan-card/actions/runs/34499852060), но этот прогон — обычный пуш в ветку задачи (heavy=false в classify-changes.mjs), поэтому performance_smoke, golden и smoke в нём skipped, не success — они не подтверждают ничего сверх typecheck/юнитов/сборки/мутантов по диффу. Полные бенчмарки производительности (performance.yml) на этом SHA не запускались вовсе: последний прогон был на 27ea23c1 (диагностика, до правки) и он красный, как и ожидалось для дорегрессионного кода.

AC4/AC5 этой задачи — это именно то, что дорогой Full Performance гейт проверяет, а автор прямо пометил их «ждёт прогона» (пост-мерж CI). Это ровно случай из инструкции ревьюера: «любой гейт, который требуют AC задачи» Validate не покрывает и я отвечаю за него сам. В песочнице обнаружился установленный Chromium (~/.cache/ms-playwright уже заполнен), поэтому я прогнал полное сравнение сам, а не поверил записи «ждёт прогона».

Прогон Full Performance профиля interaction (ядро AC3–AC5)

Воспроизвёл процедуру performance.yml: git worktree add /tmp/hpc-baseline a44fbd37 (база, зафиксированная в ТЗ), npm ci && npm run build && node scripts/bundle-sync.mjs в обоих деревьях, затем npm run benchmark:large-house-interaction -- --target-root=<base|.> --samples=7 --warmups=1 и npm run benchmark:compare -- --budgets=demo/performance/budgets-large-house-interaction.json.

База a44fbd37 (7 образцов): 18 циклов обновления, 3 сборки модели, 3 эпохи конфигурации, epochs=[0->1@_saveConfig<_syncNewDevices ; 1->2@_saveConfig<_seedHiddenDevices ; 2->3@willUpdate<performUpdate], firstStableRenderMs медиана ≈2814–2828 — совпадает с числами, которые автор привёл в issue для этой базы (2792.7, диапазон те же порядки).

Кандидат a6258715 (7 образцов, HEAD этого ревью, правка применена, дист пересобран и сверен построчно — _serverCfg/_layout подтверждённо отсутствуют в nv.properties= минифицированного бандла, только геттеры/ сеттеры):

updates=19 models=4 cfgEpoch=4
epochs=[0->1@willUpdate<performUpdate ← ТА ЖЕ лишняя, первая
        1->2@_saveConfig<_syncNewDevices
        2->3@_saveConfig<_seedHiddenDevices
        3->4@willUpdate<performUpdate]
firstStableRenderMs медиана ≈3320–3496 (7/7 образцов)

Это дословно та же трасса, которую issue цитирует для ДОРЕГРЕССИОННОГО (добуквенно багованного) кандидата на 27ea23c1, до правки. Правка не изменила ни число эпох, ни число сборок модели, ни порядок событий.

npm run benchmark:compare на официальных бюджетах (demo/performance/budgets-large-house-interaction.json) на этих семи образцах даёт:

❌ timing.modelReadyMs.median        1912.3   лимит 1622.01   база 1247.7
❌ timing.firstStableRenderMs.median 3334     лимит 3000      база 2814.1
❌ longTask.maxSingleMs              1729     лимит 1492.4    база 1148
❌ cache.entries.cleanFloor          120      лимит 100       —
Performance budget failed: timing.modelReadyMs.median,
  timing.firstStableRenderMs.median, longTask.maxSingleMs,
  cache.entries.cleanFloor

Дополнительно проверен профиль изометрии (1 образец, для скорости): updates=19 models=4 cfgEpoch=4, та же лишняя эпоха первой, modelReadyMs 2617–2824 против относительного лимита 2598.72 (тот же расчёт, что в ТЗ) — тоже красный.

Что это означает по AC

AC Требование Измерено на a6258715 Статус
AC1 тела не объявлены в static properties подтверждено чтением бандла и тестом config-adoption-ownership.test.mjs ✅ выполнено
AC2 замена тела будит onBodyReplaced мутант adoption-notifies-no-host-on-config-replacement пойман 1/1 ✅ выполнено
AC3 (запись) 3 эпохи и 3 сборки на старте, как до #500 4 эпохи, 4 сборки — не изменилось относительно бага ❌ не выполнено
AC4 firstStableRenderMs ≤3000 и обе метрики старта внутри относительного лимита на обоих профилях firstStableRenderMs 3334 > 3000 (абсолютный потолок нарушен), modelReadyMs 1912.3 > 1622.01 (относительный лимит нарушен); то же на изометрии ❌ не выполнено
AC5 cache.entries.cleanFloor ≤100 120 (лимит уже возвращён на 100 правкой — job красный ровно как описывает сам AC5 «чем краснеет») ❌ не выполнено

AC1 и AC2 доказывают только синтаксический контракт («свойство не объявлено» и «вызов есть»), но не поведенческий эффект, ради которого вся задача затевалась. Диагностика, которую сам автор оставил в дереве (bootDiag), и есть инструмент, который это ловит — но её вывод никто не прочитал на кандидате после правки: PR-комментарий явно фиксирует AC3–AC5 как «ждёт прогона», а сам прогон (Full Performance) на этом SHA не выполнялся.

Находка 1 (High) — правка не устраняет регрессию, которую должна была устранить

Убрав _serverCfg/_layout из static properties, эпоха конфигурации на старте большого дома не вернулась к 3 (как требует AC3) — она осталась равна 4, с той же лишней эпохой на первом willUpdate, что и до правки. firstStableRenderMs не вернулся в бюджет (3320–3496 мс против потолка 3000 и базы ≈2814–2828), cache.entries.cleanFloor остаётся 120 против восстановленного потолка 100. Проверено дважды (7 образцов на профиле взаимодействия + отдельно на изометрии), числа воспроизводимы и совпадают между прогонами.

Судя по диагностике (willUpdate бампает эпоху, когда changed.has('_serverCfg') истинно, а this._cfgEpochPreservedConfig не совпадает с текущим _serverCfg), первичная загрузка конфигурации идёт через adoptStructuralResponses → MutableConfigAdoption.setConfig, которая уведомляет onBodyReplaced напрямую, минуя _setAreaLifecycleConfig (тот единственный путь, что заранее выставляет _cfgEpochPreservedConfig в то же значение и гасит бамп). Это не Lit-специфичный механизм wrapped, который диагностировало ТЗ, — а исходное состояние _cfgEpochPreservedConfig, которое остаётся рассинхронизировано с первым реальным приходом конфига независимо от того, объявлено ли свойство в Lit. Это наблюдение, не диагноз «что чинить» — корень требует отдельного расследования автора, не входит в мой мандат.

Это блокирующая находка: ядро задачи (перф-регрессия #520) остаётся неисправленным, при том что диф прошёл оба узких мутанта и весь остальной набор дешёвых гейтов. Без повторного изменения продуктового кода (не только диагностики бенчмарка) AC3/AC4/AC5 не могут стать зелёными.

Что проверено и корректно

  • Диф src/houseplan-card.ts (19 строк) — ровно контракт §1 ТЗ: две строки объявления убраны, добавлен комментарий-ловушка над static properties (§5 ТЗ). Постороннего кода нет.
  • AC1: test/config-adoption-ownership.test.mjs — прочитан, тест умеет падать (мутант adoption-bodies-declared-reactive, прогнан локально: «поймано 1 из 1»).
  • AC2: мутант adoption-notifies-no-host-on-config-replacement, прогнан локально: «поймано 1 из 1».
  • npx tsc --noEmit — чисто.
  • npm test — 2509 pass / 0 fail / 1 skipped (2510 тестов), совпадает с заявлением автора.
  • npm run build — чисто, бандл собирается.
  • node scripts/mutation-gate.mjs --check — реестр свидетелей цел.
  • Бюджеты budgets-interaction-smoke.json и budgets-large-house-interaction.json: cleanFloor возвращён на 100 в обоих файлах, как того требует AC5 (сама цифра в бюджете корректна — красный результат означает, что КОД не укладывается в правильно выставленный бюджет, а не что бюджет неверен). budgets-large-house-isometric.json не трогался — там потолок уже был 100, запись в ТЗ (r2→r3) подтверждена.
  • Гигиена ветки: .gitignore (node_modules без слэша) и удаление симлинка — проверено, в git diff --stat node_modules не фигурирует.
  • demo/performance/card-contract.mjs, test/performance-contract.test.mjs — добавленные поля (_buildModel, _cfgEpoch, _adoptAuthoritative) реальны в проде, тест это перепроверяет по компилированному бандлу.
  • Трейлеры коммитов — Issue: #520 присутствует на всех коммитах диапазона. User-Visible: no — корректно: контракт поведения снаружи не меняется (просто ещё не действует так, как задумано).

Чего не проверял

  • HA-харнесс (tests_backend/test_ha_*.py) — правка не касается custom_components/**, diff не затрагивает бэкенд.
  • Golden-кадры — diff не меняет рендер/геометрию/стили, только реактивность двух приватных полей и бюджеты в JSON; рендер-контракт не затронут.
  • Инварианты модели (npm run invariants) — diff не трогает рёбра комнат, layout, marker.space, open_spans; геометрия не переименована, только владение реактивностью. Не запускал.
  • Полный набор смоков — node scripts/smoke-select.mjs не запускал явно (инструмент недоступен без указания base/head в этой сессии review script), но раз ядро задачи уже красное на перф-гейте, останавливаться на находке High и возвращать в задачу — по процессу верное действие; добивать остальные тяжёлые гейты до починки бессмысленно.
  • process-gate.mjs на диапазоне — принял на слово прогон автора (Validate success на этом SHA включает эквивалентный шаг в preflight).
  • Прогонял всё внутри своей рабочей копии/временного git worktree (/tmp/hpc-baseline, удалён после использования) — репозиторий ревью не тронут, git status чист.

Гейты: что прогнал сам и почему

Гейт Прогнан Результат
npx tsc --noEmit да (дёшево, доп. проверка) чисто
npm test да 2509/2510 pass, 1 skip
npm run build + bundle-sync да чисто, дист пересобран для обоих деревьев
node scripts/mutation-gate.mjs --check + 2 мутанта задачи да реестр цел, оба мутанта пойманы 1/1
Full Performance interaction (7 образцов, база a44fbd37 vs кандидат) да, вручную (Chromium уже установлен в песочнице) budget failed — см. находку
Full Performance isometric (1 образец, для скорости) да тот же паттерн лишней эпохи, modelReadyMs за относительным лимитом
golden, HA pytest, полный smoke-набор, invariants нет вне диффа/уже нерелевантно после High-находки

Вердикт

Красный. Находка High блокирует: механизм задачи (лишняя эпоха конфигурации на старте большого дома) не устранён, измерено официальным инструментом проекта (benchmark:large-house-interaction + benchmark:compare) на зафиксированном материале ревью a6258715, воспроизводимо на 7 образцах и повторно на изометрии. AC1/AC2 (синтаксический контракт, проверен тестами и мутантами) выполнены; AC3/AC4/AC5 (поведенческий/перф эффект, ради которого затевалась вся задача) — нет. Возврат автору на доработку продуктового кода (не диагностики бенчмарка), с новым измерением на исправленном кандидате до следующего захода.


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

  • Ветка: issue/520-first-frame, коммит a625871538c2 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: fbd3b83fa6559f4470124d1b45e4272b4eadcdec
    git log --all --format='%H %T' | grep fbd3b83fa655
    
  • Тело issue: 60492a3aec5ff7fed54f7b32c040b7a118526c209609d736ee68e116eb8f0aa6
  • Вердикт конвейера: red · High 1