Files
houseplan-card/docs/reviews/SPEC-REVIEW-520-r2.md
T
2026-09-10 15:30:21 +00:00

17 KiB

SPEC-REVIEW-520-r2

Якоря

  • Issue: #520 «Первый устойчивый кадр большого дома вырос до 3.36 с при потолке 3.0 с — регрессия после #509/#500», метка S4-spec-review, состояние OPEN (подтверждено gh issue view 520 --json labels,state, 2026-09-10).
  • ТЗ: тело issue #520, раздел ## ТЗ, продолжает жить в теле issue (#517: архив docs/specs/ закрыт для новых ТЗ) — то же место, что и в r1.
  • sha256 текущего тела issue (gh issue view 520 --repo Matysh/houseplan-card --json body -q .body | sha256sum): 7f68e4cd99242f44e13e9929cd0a7aa9dc3cb6ae5f871878670bade18a2436ac. Отличается от хеша r1 (5477d052e0e87182f51a9b3b05d37fce0a7ada3d9dc66c10f92 7f64564ccc6e4) — тело менялось, что и ожидается после возврата на правки.
  • Заход r2. Бюджет циклов §4 до этого ревью: 1/4 (израсходован r1, жёлтый вердикт). Вердикт ниже — снова жёлтый, поэтому расходует цикл: после этого ревью бюджет 2/4.
  • Материал кода не менялся: git log --oneline -5 на момент этого ревью — тот же диапазон, что видел r1 (ad2f9afd — публикация документа r1, 27ea23c1/3d439805/37ef970d — диагностика бенчмарка, test(perf):, без единой строки в src/**). Изменилось только тело issue.

Скоуп ревью

Повторный заход ревью ТЗ (PROCESS.md §2.4, §2.10) после жёлтого вердикта r1. Предмет — дельта тела issue между r1 и r2: три правки, заявленные автором в комментарии 2026-09-10T15:23:01Z («M1 — раздел «Риски»», «M2 — AC3», «M3 — изометрия»), плюс всё, до чего эта дельта дотягивается (AC4, симметрия профилей). Остальной текст ТЗ (продуктовая рамка, AC1/AC2/AC5, откат, release-артефакты, «принято предположительно») дельту не задевает — наследуется из r1 без повторной проверки (см. раздел «Унаследовано»).

Дельта локальна: правка текста ТЗ, ни ребейза, ни смены контракта, ни новой подсистемы — полный разбор всей задачи заново не требуется.

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

  1. Получено текущее тело issue #520 (gh issue view 520 --json body) и три новых комментария после r1 (фикс-комментарий автора, метка S4-spec-review возвращена).
  2. Прочитан docs/reviews/SPEC-REVIEW-520-r1.md целиком — вердикт, три находки Medium, материал (SHA 27ea23c1ec25, дерево f80bcdd58b72, тело 5477d052e0e8…).
  3. Каждая находка r1 сверена построчно с текущим текстом, а не принята на слово автора:
    • M1 (риски) — раздел ### Риски в теле issue теперь существует, 4 пункта. Проверил сам утверждение «потребителей elementProperties в дереве нет»: grep -rn "elementProperties" src test scripts demo --exclude-dir=srv — 0 совпадений, подтверждает пункт 1 раздела.
    • M2 (AC3) — строка AC3 в таблице теперь помечена (не гейт, запись), колонка «чем краснеет» заменена на «— · критерий поглощён AC1+AC2+AC4»; AC3 больше не выдаёт себя за независимо доказуемый гейт.
    • M3 (изометрия) — AC4 расширен до «modelReadyMs обоих профилей — взаимодействия и изометрии — внутри относительного лимита»; в «Контракте» добавлено «изометрия чинится тем же изменением и проверяется тем же способом (AC4)».
  4. Симметрию M3 перепроверил количественно, а не по формулировке. Пересчитал по формуле demo/performance/evaluate.mjs:73-118 (effective = min(hardMaxMs, max(base*(1+ratio), base+allowance))) не только modelReadyMs (как это сделал r1), но и второе число из того же предложения «Симптома» — firstStableRenderMs профиля изометрии. Прочитаны бюджеты demo/performance/budgets-large-house-isometric.json:6-7: firstStableRenderMs: maxRegressionRatio: 0.2, noiseAllowanceMs: 250, hardMaxMs: 3500. См. находку Medium-1 ниже — результат разошёлся с тем, что покрывает амендированный AC4.
  5. Проверено, что потолок cache.entries.cleanFloor в budgets-large-house-isometric.json и budgets-isometric-smoke.json уже равен 100 (не поднимался в 914e8402, который трогал только два файла interaction-профиля) — значит для AC5 симметрии с изометрией не требуется, вопрос из хвоста M3 («и его budgets-файлов, если они регрессировали похожим образом по cleanFloor») закрыт отрицательным результатом: там регресса нет.
  6. git log --oneline -5 и git status — рабочая копия на ad2f9afd, той же, что видел r1; продуктовый код (src/**) не менялся между раундами, гейты кода этому раунду не относятся (см. «Чего не проверял»).

Находки

Medium-1 — амендированный AC4 не покрывает второе число из того же предложения «Симптома»: firstStableRenderMs изометрии

Файл: тело issue #520, AC4 (строка 59 полученного тела) против «Симптома» (строка 3) и «Контракта» (строка 46).

«Симптом» приводит для профиля изометрии два числа одним предложением: «Профиль изометрии — то же: modelReadyMs 2165.6 → 2626.3 при относительном потолке 2598.72, firstStableRenderMs 2227 → 2678.2». Правка M3 (закрытие находки r1) добавила в AC4 только первое из них — modelReadyMs «обоих профилей». Второе число того же предложения контракт не упоминает вовсе.

Пересчитано по той же формуле, которую сам автор и r1 применили к modelReadyMs (demo/performance/evaluate.mjs:107-118, budgets-large-house-isometric.json:7: maxRegressionRatio: 0.2, noiseAllowanceMs: 250, hardMaxMs: 3500):

relativeLimit = max(2227 * 1.2, 2227 + 250) = max(2672.4, 2477) = 2672.4
effective     = min(hardMaxMs=3500, relativeLimit=2672.4) = 2672.4

Кандидат 2678.2 > 2672.4 — превышение на 5.8 мс (≈0.22 %). Тот же класс «мягкого» (не абсолютного) нарушения, что и modelReadyMs изометрии (2626.3 против 2598.72, превышение ≈1.05 %), который M3 признал достаточным основанием для правки AC4. По одинаковому стандарту оба числа одного предложения должны попасть в контракт одинаково — либо оба, либо ни одного с явным объяснением почему.

Это не формальность: относительные пределы (в отличие от абсолютного firstStableRenderMs ≤ 3000 профиля interaction) не гейтуют обычный Validate, они видны только при ручном чтении «полного сравнения» — единственный документ, который скажет будущему код-ревьюеру, что именно сверять в этом логе, — это текст AC4. Формулировка «Контракта» («изометрия … проверяется тем же способом (AC4)», строка 46) сейчас утверждает больше, чем буквально требует таблица AC4: в контракте — «тем же способом» для изометрии в целом, а в AC4 — только одна из двух её метрик.

Чем чинится: одной правкой AC4 — добавить firstStableRenderMs изометрии в тот же относительный чек, либо явно исключить его одной фразой (например, «в пределах шума 250 мс, отдельно не гейтуем») там же, где ТЗ уже объясняет статус AC3. Не блокирует контракт — правка одна и дешёвая.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
Medium-1 — отсутствует раздел «Риски» (§7.1) Добавлен раздел ### Риски, 4 пункта; названный ревьюером риск (elementProperties) снят с указанием способа проверки Тело issue, раздел ### Риски; проверено grep -rn "elementProperties" src test scripts demo --exclude-dir=srv → 0 совпадений
Medium-2 — AC3 не имеет дешёвого способа доказательства AC3 явно понижен до записи: помечен (не гейт, запись), «чем краснеет» заменено на «поглощён AC1+AC2+AC4» — больше не выдаёт себя за независимый гейт Тело issue, строка AC3 таблицы AC
Medium-3 — регрессия изометрии не отражена в контракте/AC AC4 расширен на modelReadyMs обоих профилей; в «Контракте» явно сказано, что изометрия чинится тем же изменением Тело issue, AC4 (строка 59) и «Контракт» (строка 46). Частично — см. новую находку Medium-1 этого раунда: второе число того же предложения «Симптома» (firstStableRenderMs изометрии) осталось непокрытым

Унаследовано из r1

Приняты без повторной проверки — дельта их не задевает:

  • Причина регрессии измерена, не догадка: три технических утверждения о поведении wrapped-свойств Lit (createProperty, форсированная запись в changedProperties на первом апдейте, работа requestUpdate с необъявленным именем) — r1 сверил дословно с исходником @lit/reactive-element (docs/reviews/SPEC-REVIEW-520-r1.md, раздел «Как проверялось» п.4, материал SHA 27ea23c1ec25, дерево f80bcdd58b72).
  • AC1 (source-scan тест по образцу существующего config-adoption-ownership.test.mjs) и AC2 (юнит на вызов onBodyReplaced) — доказуемы дёшево и детерминированно, текст этих строк между r1 и r2 не менялся.
  • AC5 (cache.entries.cleanFloor ≤ 100 на двух interaction-файлах бюджетов) — привязан к точным файлам, числа сверены построчно; не менялся.
  • Откат (revert двух строк static properties + потолка 180) и release-артефакты (User-Visible: no, разблокирует v1.74.0-beta.1) — просты, однозначны, не менялись.
  • Продуктовая рамка (администратор дома, поверхность — первый показ большого дома) совпадает с docs/SCOPE.md J1 и фикстурой 60 комнат/200 устройств — r1 сверил, текст не менялся.
  • Выбор полного трека — верен, не оспаривается.
  • Ни одного вопроса владельцу не требуется — весь материал технический, подтверждено и в r1, и в этом раунде.

Документ-источник: docs/reviews/SPEC-REVIEW-520-r1.md, материал — ветка issue/520-first-frame на 27ea23c1ec25, дерево f80bcdd58b72.

Что проверено и корректно (этот раунд)

  • Все три пункта фикс-комментария автора (2026-09-10T15:23:01Z) подтверждены чтением текущего тела issue, а не приняты по заявлению — M1 и M2 закрыты полностью, M3 закрыт частично (см. Medium-1 выше).
  • Потолки cache.entries.cleanFloor в бюджетах изометрии (budgets-large- house-isometric.json, budgets-isometric-smoke.json) не поднимались в 914e8402 и уже равны 100 — симметричная правка AC5 для изометрии не требуется, это установлено чтением файлов, а не предположением.
  • Код между раундами не менялся (git log, git status на ad2f9afd) — дешёвые гейты (tsc, test, build) этому раунду не относятся по той же причине, что и в r1: предмет этапа — текст ТЗ, продуктовой правки в дереве ещё нет.

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

  • Гейты кода (tsc --noEmit, npm test, npm run build, check-docs.mjs) — не гонял: src/** не менялся ни в r1, ни между r1 и r2, весь диапазон коммитов — диагностика бенчмарка (test(perf):).
  • npm run invariants — не гонял: геометрия и её ссылки не тронуты.
  • Реальный прогон Full Performance — не запускал; числа Симптома (включая пересчитанный firstStableRenderMs изометрии) взяты из текста issue и перепроверены только арифметически по опубликованной формуле и файлам бюджетов, не переизмерены заново на живом рантайме.
  • Регистрацию мутанта adoption-bodies-declared-reactive в scripts/mutation-gate.mjs — предмет код-ревью, ещё не существует.

Вердикт

Жёлтый. High: 0, Medium: 1 (в скоупе задачи — чинится автором в тексте ТЗ, без нового issue). Возврат в «ТЗ в работе» (S3-spec).


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

  • Ветка: issue/520-first-frame, коммит ad2f9afd2bfe — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: cc8e3bd73eeb7274db2725b8b308e12dc59a36e4
    git log --all --format='%H %T' | grep cc8e3bd73eeb
    
  • Тело issue: c5e79b63b5312eb40c5243fba19bd003f9b72e11b83fed51c1b1522547cc349c
  • Вердикт конвейера: yellow · High 0