docs: review document for #520

Issue: #520
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-10 15:30:21 +00:00
parent ad2f9afd2b
commit b49721a0bd
+203
View File
@@ -0,0 +1,203 @@
# SPEC-REVIEW-520-r2
## Якоря
- Issue: [#520](https://github.com/Matysh/houseplan-card/issues/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`).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/520-first-frame`, коммит `ad2f9afd2bfe` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `cc8e3bd73eeb7274db2725b8b308e12dc59a36e4`
```
git log --all --format='%H %T' | grep cc8e3bd73eeb
```
- Тело issue: `c5e79b63b5312eb40c5243fba19bd003f9b72e11b83fed51c1b1522547cc349c`
- Вердикт конвейера: `yellow` · High 0