docs: review document for #520

Issue: #520
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-10 15:38:40 +00:00
parent b49721a0bd
commit 50fa7f9be2
+228
View File
@@ -0,0 +1,228 @@
# SPEC-REVIEW-520-r3
## Якоря
- 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/r2.
- sha256 текущего тела issue (`gh issue view 520 --repo Matysh/houseplan-card
--json body -q .body | sha256sum`):
`5eca6e230c290483bba634505217d17072f4b4a121c4aa2111b08a0fc6d007c3`.
Отличается от хеша r2 (`7f68e4cd99242f44e13e9929cd0a7aa9dc3cb6ae5f871878670
bade18a2436ac`) — тело менялось, как и ожидается после возврата на правки.
- Заход r3. Бюджет циклов §4 до этого ревью: **2/4** (израсходован r1 и r2,
оба жёлтые). Вердикт ниже — зелёный, цикла не образует, бюджет остаётся
**2/4**.
- Материал кода не менялся между r2 и r3: `git log --oneline ad2f9afd..
b49721a0` даёт ровно один коммит — публикацию документа r2
(`docs/reviews/SPEC-REVIEW-520-r2.md`, 203 добавленных строки, `git diff
--stat ad2f9afd..b49721a0 -- src/ demo/ scripts/ test/` пуст). Рабочая
копия сейчас на `b49721a0` (detached HEAD от
`origin/issue/520-first-frame`, подтверждено `git status`, `git rev-parse
HEAD`). Изменилось только тело issue на GitHub — SHA репозитория тут не
причём, доказательство ТЗ находится вне git.
## Скоуп ревью
Повторный заход ревью ТЗ (PROCESS.md §2.4, §2.9/§2.10) после жёлтого
вердикта r2. Предмет — **дельта тела issue между r2 и r3**: одна правка,
заявленная автором в комментарии `2026-09-10T15:33:29Z`
(«Исправление по ревью ТЗ r2 → заход r3»): AC4 расширен на
`firstStableRenderMs` изометрии, плюс отрицательный результат про
`cache.entries.cleanFloor` изометрии перенесён из документа r2 в текст
«Причины». Автор сам называет объём дельты: «одна строка AC4 и одна фраза
в «Причине»» — проверено построчно, а не принято на слово (см. «Как
проверялось»).
Дельта локальна: правка текста ТЗ, ни ребейза, ни смены контракта, ни
новой подсистемы — код продукта между r2 и r3 не менялся вовсе (см.
«Якоря»). Полный разбор всей задачи заново не требуется.
## Как проверялось
1. Получено текущее тело issue #520 (`gh issue view 520 --json body`) и
комментарий автора после r2 (единственный новый комментарий, метка
`S4-spec-review` возвращена).
2. Прочитан `docs/reviews/SPEC-REVIEW-520-r2.md` целиком — вердикт, одна
находка Medium-1, материал (SHA `ad2f9afd2bfe`, дерево `cc8e3bd73eeb`,
тело `c5e79b63b531…`) — и `docs/reviews/SPEC-REVIEW-520-r1.md` для
раздела «Унаследовано».
3. **Находка Medium-1 r2 сверена построчно с текущим текстом**, а не
принята на слово автора. r2 требовал: строка AC4 должна покрывать
`firstStableRenderMs` изометрии тем же относительным пределом, что и
`modelReadyMs`, — либо явно исключить её одной фразой. Текущая строка
AC4 (тело issue, строка 59):
> «`timing.firstStableRenderMs.median` профиля взаимодействия ≤ 3000
> (абсолютный потолок) **и обе метрики старта — `modelReadyMs` и
> `firstStableRenderMs` — внутри относительного лимита к базе
> `a44fbd37` на обоих профилях, взаимодействия и изометрии**»,
«чем краснеет»: «превышение любого из **четырёх** относительных
пределов или абсолютного потолка — job красный».
Это буквально включает `firstStableRenderMs` изометрии — предмет
находки Medium-1. Закрытие полное, не частичное.
4. **Пересчитаны все четыре относительных предела**, которые теперь
называет AC4, по формуле `demo/performance/evaluate.mjs:73-118`
(`relativeLimit = max(base*(1+ratio), base+allowance)`,
`effective = min(hardMaxMs, relativeLimit)`), чтобы убедиться, что
счёт «четырёх» верен и не сбит на единицу:
- `modelReadyMs` interaction: база 1382, `budgets-large-house-
interaction.json` (`maxRegressionRatio: 0.3, noiseAllowanceMs: 200,
hardMaxMs: 2500`) → `max(1796.6, 1582) = 1796.6`; кандидат
**1930 > 1796.6** — нарушен.
- `firstStableRenderMs` interaction: база 2792.7, тот же файл
(`ratio 0.3, allowance 250, hardMax 3000`) → `max(3630.5, 3042.7) =
3630.5`, `effective = min(3000, 3630.5) = 3000` — здесь относительный
предел поглощён абсолютным потолком, что и заявляет отдельная первая
половина AC4; двойного счёта нет.
- `modelReadyMs` isometric: база 2165.6, `budgets-large-house-
isometric.json` (`ratio 0.2, allowance 200, hardMax 3000`) →
`max(2598.72, 2365.6) = 2598.72`; кандидат **2626.3 > 2598.72** —
нарушен (установлено ещё в r1).
- `firstStableRenderMs` isometric: база 2227, тот же файл
(`ratio 0.2, allowance 250, hardMax 3500`) → `max(2672.4, 2477) =
2672.4`; кандидат **2678.2 > 2672.4** — нарушен (предмет находки
Medium-1 r2, теперь покрыт текстом AC4).
Итого ровно четыре относительных предела, все они действительно сейчас
нарушены кандидатом — формулировка AC4 точна, не расширяет и не сужает
набор относительно того, что показывает «Симптом».
5. Проверено согласование «Контракта» (строка 46: «изометрия … чинится
тем же изменением и проверяется теми же метриками старта (AC4)») с
новым текстом AC4 — формулировка «тем же способом» из r2 заменена на
«теми же метриками старта», что точнее отражает: AC4 теперь буквально
покрывает обе метрики обоих профилей, а не «тот же способ» в общем
виде. Расхождений между «Контрактом» и таблицей AC больше нет.
6. Проверена вторая часть дельты — фраза в «Причине» (строка 24): «В
бюджетах изометрии этот потолок не поднимался и уже равен 100 — там
правка не нужна». Перепроверено чтением файлов (не принято на слово):
`grep -n cleanFloor demo/performance/budgets-{isometric-smoke,large-
house-isometric,interaction-smoke,large-house-interaction}.json` —
оба файла изометрии дают `100`, оба файла взаимодействия — `180`.
Совпадает с отрицательным результатом, который сам r2 получил
самостоятельно (не то же самое, что просто скопировать вывод r2, —
числа сверены заново на этом SHA, хоть код и не менялся).
7. `git log --oneline ad2f9afd..b49721a0` и `git diff --stat` — единственный
новый коммит это публикация документа r2; продуктовый код (`src/**`,
`demo/**`, `scripts/**`, `test/**`) не менялся между r2 и r3 — гейты
кода этому раунду не относятся (см. «Чего не проверял»), как и в r1/r2.
8. Проверены state/labels issue (`OPEN`, `bug`, `P1`, `S4-spec-review`) —
соответствуют ожиданиям заголовка задания (заход r3, 2/4 циклов).
## Находки
Нет находок, переживших проверку. Единственная находка r2 (Medium-1)
закрыта полностью — см. «Закрытие раунда r2» и п.3–5 «Как проверялось».
Новых находок делом дельты не выявлено: правка локальна (одна строка AC4,
одна фраза «Причины»), обе проверены построчно и не вводят новых
противоречий с остальным текстом ТЗ.
## Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
| Medium-1 — амендированный AC4 не покрывал `firstStableRenderMs` изометрии (второе число того же предложения «Симптома», что и уже покрытый `modelReadyMs`) | AC4 переписан: «обе метрики старта — `modelReadyMs` и `firstStableRenderMs` — внутри относительного лимита… на обоих профилях»; «чем краснеет» — «любого из **четырёх** относительных пределов»; «Контракт» синхронизирован формулировкой «теми же метриками старта (AC4)» | Тело issue, AC4 (строка 59) и «Контракт» (строка 46). Численно перепроверено в п.4 «Как проверялось» — все четыре предела названы верно, счёт не сбит |
## Унаследовано из r2 (и, транзитивно, из r1)
Приняты без повторной проверки — дельта r2→r3 их не задевает, код между
раундами не менялся:
- **Причина регрессии измерена, не догадка**: три технических утверждения
о поведении `wrapped`-свойств Lit — r1 сверил дословно с исходником
`@lit/reactive-element` (`docs/reviews/SPEC-REVIEW-520-r1.md`, «Как
проверялось» п.4, материал SHA `27ea23c1ec25`, дерево `f80bcdd58b72`).
- **M1 (раздел «Риски») и M2 (AC3) из r1** — закрыты в r2 и сверены там
построчно (`docs/reviews/SPEC-REVIEW-520-r2.md`, «Как проверялось» п.3,
«Закрытие раунда r1»), текст этих разделов между r2 и r3 не менялся.
- **AC1** (source-scan тест по образцу `config-adoption-ownership.
test.mjs`) и **AC2** (юнит на `onBodyReplaced`) — доказуемы дёшево и
детерминированно, текст этих строк не менялся с r1 (сверено построчно
с текущим телом в этом раунде — строки идентичны цитатам r1/r2).
- **AC5** (`cache.entries.cleanFloor` ≤ 100 на двух interaction-файлах
бюджетов) — привязан к точным файлам, не менялся; числа перепроверены
заново в п.6 «Как проверялось» этого раунда (не просто унаследованы).
- **Откат** (revert двух строк `static properties` + потолка 180) и
**release-артефакты** (`User-Visible: no`, разблокирует
`v1.74.0-beta.1`) — просты, однозначны, текст не менялся с r1.
- **Продуктовая рамка** (администратор дома, поверхность — первый показ
большого дома) совпадает с `docs/SCOPE.md` J1 и фикстурой 60 комнат/200
устройств — r1 сверил, текст не менялся.
- **Выбор полного трека** — верен, не оспаривается ни разу за три раунда.
- **Ни одного вопроса владельцу не требуется** — весь материал технический
(см. обоснование r1: «продуктовых развилок в задаче нет»), подтверждено
в r1 и не оспорено в r2; делта r3 тоже чисто техническая (числа, счёт
относительных пределов) — вывод не меняется.
Документы-источники: `docs/reviews/SPEC-REVIEW-520-r1.md` (материал —
ветка `issue/520-first-frame` на `27ea23c1ec25`, дерево `f80bcdd58b72`),
`docs/reviews/SPEC-REVIEW-520-r2.md` (материал — та же ветка на
`ad2f9afd2bfe`, дерево `cc8e3bd73eeb`).
## Что проверено и корректно (этот раунд)
- Единственная правка фикс-комментария автора (`2026-09-10T15:33:29Z`)
подтверждена чтением текущего тела issue, а не принята по заявлению —
Medium-1 r2 закрыт полностью, не частично.
- Счёт «четырёх относительных пределов» в AC4 арифметически точен:
пересчитаны все четыре по опубликованной формуле и файлам бюджетов,
все четыре сейчас действительно нарушены кандидатом — AC4 не занижает
и не завышает то, что реально показывает «Симптом».
- «Контракт» (строка 46) и таблица AC4 больше не расходятся в формулировке
охвата изометрии — предыдущая асимметрия («тем же способом» против
буквально одной метрики) устранена.
- Фраза «Причины» про потолок `cache.entries.cleanFloor` изометрии
(100, правка не нужна) подтверждена чтением всех четырёх файлов
бюджетов заново на этом SHA, не просто перенесена из документа r2 на
слово.
- Код между r2 и r3 не менялся (`git log`, `git diff --stat` на
диапазоне `ad2f9afd..b49721a0`) — дешёвые гейты кода (`tsc`, `test`,
`build`) этому раунду не относятся по той же причине, что и в r1/r2:
предмет этапа — текст ТЗ, продуктовой правки в дереве ещё нет.
- Остальной текст ТЗ (унаследованное) не тронут дельтой — проверка
инвариантов и код-ревью инфраструктуры (мутант, тесты) остаются
предметом код-ревью после `S5-ready`.
## Чего не проверял
- **Гейты кода** (`tsc --noEmit`, `npm test`, `npm run build`,
`check-docs.mjs`) — не гонял: `src/**` не менялся ни разу за три
раунда, весь диапазон коммитов ветки — диагностика бенчмарка
(`test(perf):`). Предмет этапа spec — текст ТЗ.
- **`npm run invariants`** — не гонял: геометрия и её ссылки не тронуты
ни кодом, ни правкой ТЗ.
- **Реальный прогон `Full Performance`** — не запускал; числа «Симптома»
и пересчитанные относительные пределы взяты из текста issue и
файлов бюджетов в дереве, перепроверены арифметически по опубликованной
формуле, не переизмерены заново на живом рантайме.
- **Регистрацию мутанта `adoption-bodies-declared-reactive`** в
`scripts/mutation-gate.mjs` — предмет код-ревью, ещё не существует
(продуктовой правки AC1/AC2 в дереве ещё нет).
- **Полную сверку разделов §7.1, не тронутых дельтой** (Риски, откат,
release-артефакты, продуктовая рамка) — заново не перечитывал построчно,
только сверил, что их текст байт-в-байт совпадает с тем, что видели r1
и r2 (см. «Унаследовано»).
## Вердикт
Зелёный. High: 0, Medium: 0. ТЗ готово к разработке (`S5-ready`).
Единственная находка r2 закрыта полностью и точно; новых находок дельта
не вносит. Бюджет циклов §4 не расходуется (зелёный вердикт цикла не
образует) и остаётся 2/4.
---
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/520-first-frame`, коммит `b49721a0bd98` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `50d8affd923b0a5e133ee1e453bb9388a5f44c78`
```
git log --all --format='%H %T' | grep 50d8affd923b
```
- Тело issue: `60492a3aec5ff7fed54f7b32c040b7a118526c209609d736ee68e116eb8f0aa6`
- Вердикт конвейера: `green` · High 0