Files
houseplan-card/docs/reviews/SPEC-REVIEW-520-r3.md
T
2026-09-10 15:38:40 +00:00

229 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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