Files
houseplan-card/docs/reviews/SPEC-REVIEW-669-r1.md
T
2026-09-27 06:28:44 +00:00

194 lines
17 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-669-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/669
- **Этап:** `S4-spec-review` (ревью ТЗ, PROCESS.md §2.4)
- **Трек:** полный (метки `bug`, `P1`, `S4-spec-review`; ни `small`, ни
`trivial` не выставлены — документ, а не только комментарий).
- **Материал:** тело issue #669, раздел `## ТЗ`, + 3 комментария: (1) «Взял»
автора с планом A/B-замера, (2) аналитика — таблица замеров, причина
регрессии, оценка, решение «вопросов владельцу нет», ссылка на новый #675,
(3) поправка к одной строке таблицы замеров.
- **Заход:** r1 · блокирующих циклов израсходовано 0 из 4
- **Роль:** ревьюер ТЗ (не автор)
## Скоуп ревью
Регрессия производительности в `cleanFloorForRoom` (`src/clean-floor.ts:47`):
каждый промах кэша чистого пола сразу вычитает **все** лестницы пространства
(`geometryAreaMinusStairs` → `geometryMinusStairs`, `src/stairs.ts:234-250`) —
хотя площадь читают только два потребителя (подсказка комнаты, PDF), а путь
пола (нужный пяти... по факту четырём местам рендера) вычитания не требует.
Задача переносит вычисление площади на момент первого чтения (лениво,
мемоизировано в том же кэше) и добавляет отбор лестниц по пересечению bbox
перед polyclip. Это разблокирует `v1.78.0-beta.5`, у которой красный
performance-smoke на точном SHA `09873251`.
**SCOPE-проверка (`docs/SCOPE.md`):** чисто внутренняя оптимизация без нового
видимого поведения — под J1 («показать дом сейчас», включая скорость первого
кадра) как выполнение уже принятого бюджета, а не новый job. Ничего из
«никогда не строить» не задевается.
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `docs/process/REVIEWER.md` целиком; по ссылкам
конспекта открыт PROCESS.md §2.3, §2.4, §2.5, §4, §7.1, §7.2 (§2.10 не
применялся — это r1).
2. Прочитано тело issue #669 целиком и все 3 комментария (`gh issue view 669
--json body,comments`).
3. Сверены обязательные разделы §7.1: Сценарий, Что человек увидит до/после,
Проблема, Скоуп/Не-скоуп, Контракт поведения, UX/модель данных и
миграция/i18n/touch/производительность, Критерии приёмки AC1–AC4 со
способом доказательства, План автотестов, Риски, Откат, Release-артефакты,
Затронутые файлы, «Принято предположительно» — все на месте, первые два
раздела продуктовые и без терминов реализации.
4. Прочитан текущий код `src/clean-floor.ts` и `src/stairs.ts` целиком, чтобы
проверить, что описание проблемы соответствует коду, а предложенный скоуп
реализуем без переписывания контракта:
- подтверждено: `path`/`geom` в `cleanFloorForRoom` вычитают только
физические тела (`floorMinusBodies`), лестницы в путь никогда не попадали
— вычитание лестниц целиком живёт в поле `area`. Значит лениво откладывать
нужно именно и только его; контракт «путь не меняется» (AC1, «Контракт
поведения») уже выполняется today и задача его не трогает — только
переносит момент вычисления `area`.
- подтверждено количество мест, читающих только `.path`: `grep -n
"_cleanFloor("` даёт 4 попадания (`src/houseplan-card.ts:9059, 9229,
10459, 11011`), а не 5, как сказано в разделе «Проблема». Пятое место
(`:11873`) читает весь объект ради `.area` (подсказка комнаты). Второй
потребитель площади — `resolveRoomArea` (`:10184`) → та же
`._cleanFloor(...).area`. Отдельно в `src/pdf/pdf-scene.ts:311` есть
independent fallback-вызов `geometryAreaMinusStairs` в обход
`cleanFloorForRoom` (когда `resolveRoomArea` не вернул число) — он не
проходит через ленивый кэш и в ТЗ не упомянут, но и не входит в
«Затронутые файлы»; поскольку это редкий fallback-путь, а не типовой рендер
кадра, на бюджет AC3 он не давит — оставляю без находки, только для
полноты цепочки.
- подтверждено, что `geometryMinusStairsSteps` (сводная панель) реализован
отдельно от `geometryMinusStairs` и не переиспользует его — фильтр по
bbox внутри `geometryMinusStairs` (место, названное в «Принято
предположительно») не заденет сводную панель автоматически, как и
требует «Не-скоуп».
- сверены оба файла бюджетов `demo/performance/budgets-*-smoke.json`:
`spaceSwitchMs` — обязательная (`hardMaxMs`) метрика обоих профилей, а не
информационная; лимиты (`modelReadyMs` 3000/2500, `firstStableRenderMs`
3500/3000, `spaceSwitchMs` 1800/1500) совпадают с числами из таблицы
наблюдений в issue.
5. Сверены данные аналитики (таблица A/B) с самой формулировкой AC3 — см.
находку M1 ниже.
6. Проверена логика ролбэка/release-артефактов на согласованность с политикой
`docs/STATUS.md` (STABLE-тело исключает баги, которые «introduced and fixed
strictly inside the beta line»): `v1.78.0` ещё не имеет стабильного релиза
(`git tag -l 'v1.78.0*'` — только беты 1–4), значит «changelog — нет»
обоснованно, регрессия #663 нигде не выходила стабильным релизом.
## Находки
### Medium (в скоупе) — AC3 не гарантирует заявленную цель issue
**Что не так.** «Ожидаемое поведение» (преамбула issue) и AC3 обещают: «После
исправления полный Validate точного release-candidate SHA проходит с первого
воспроизводимого запуска» / «Полный Validate кандидата слияния и кандидата
беты: `performance_smoke` обоих профилей … проходит `modelReadyMs` и
`firstStableRenderMs` с первого прогона при неизменных бюджетах». Формулировка
называет только эти две метрики, но `performance_smoke` — единый job с
несколькими обязательными (`hardMaxMs`) метриками в том же прогоне, включая
`spaceSwitchMs`. По собственным замерам аналитики в этом же issue
`spaceSwitchMs` **уже проваливается** на изометрическом профиле точного SHA
`09873251` (1821.2 мс и 1813.5 мс против лимита 1800 мс — два из трёх
зафиксированных провальных прогонов), и признаётся «не относящейся к
лестницам» — не-скоуп, вынесено в отдельный **#675**. Ни ТЗ, ни преамбула не
говорят, что делать с этим фактом: если #675 не сольётся раньше или вместе с
кандидатом beta.5, тот же самый exact-SHA `performance_smoke` может остаться
красным на `spaceSwitchMs` даже после идеального выполнения AC1–AC4 этой
задачи — то есть заявленная цель issue («кандидат стабильно проходит
действующие performance-бюджеты… полный Validate… проходит с первого
воспроизводимого запуска») **не гарантируется** тем, что реально проверяет
AC3.
**Почему это находка, а не придирка к тексту.** Разработчик и последующий
код-ревьюер будут закрывать именно AC3 как написано — двумя названными
метриками. Формально «AC3 выполнен» при этом возможен ровно в момент, когда
release по-прежнему заблокирован тем же job'ом на другой метрике; никто не
обязан заметить разрыв между «AC выполнен» и «issue решает свою же
преамбулу», а этот разрыв прямо запрещён REVIEWER.md («жёлтый вердикт
допустим и при выполненных AC, если изменение не решает заявленный
сценарий»).
**Чем закрывается в скоупе задачи (без нового инженерного объёма).**
Переформулировать AC3 и/или преамбулу «Ожидаемое поведение», выбрав одно из:
(a) явно ограничить обещание двумя названными метриками и добавить в
«Не-скоуп»/«Влияние» прямую фразу «для полностью зелёного `performance_smoke`
кандидата требуется также #675»; либо (b) если у автора/владельца есть
основания считать `spaceSwitchMs` эпизодически проходящим независимо от
данной задачи (шум, а не устойчивый провал) — явно это утверждать со ссылкой
на замер, а не оставлять предположение читателю. Технический характер этого
уточнения (не продуктовый вопрос) означает, что owner не привлекается —
решение принимает автор ТЗ, я его снимаю как открытый вопрос и фиксирую сюда.
### Low — фактическая неточность в «Проблема» (не блокирует)
Раздел «Проблема» утверждает: «пять мест рендера запрашивают только путь
пола». Чтением кода (`grep -n "_cleanFloor(" src/houseplan-card.ts`)
подтверждаю **четыре** таких места (`:9059, :9229, :10459, :11011`); пятый
вызов (`:11873`) читает `.area`, а не только `.path`, и относится к другой
категории потребителей (подсказка комнаты), уже перечисленной отдельно.
Утверждение не входит ни в один AC и не влияет на реализуемость — рекомендую
исправить число при следующей правке ТЗ, не считаю это блокером и снимаю сам.
## Что проверено и корректно
- Обязательные разделы §7.1 присутствуют, в правильном порядке; первые два —
продуктовые, без терминов реализации.
- AC1 и AC2 однозначны, имеют явную пару «доказательство / чем краснеет»,
тестовый план реализуем без изменения контракта (`geom`/`path` уже не
зависят от лестниц; ленивость нужна только для `area`).
- AC4 и контракт поведения («в пределах относительной погрешности 1e-9»)
корректно фиксируют неизменность видимого числа площади.
- «Не-скоуп» корректно ограждает `geometryMinusStairsSteps` (независимая
реализация — трогать `geometryMinusStairs` её не заденет) и 2.5D-рендер
лестниц, с явным условием возврата в ТЗ, если остаточных издержек окажется
достаточно, чтобы провалить AC3 на изометрии — хорошая гигиена против
молчаливого расширения скоупа.
- UX/модель данных/i18n/touch — «нет изменений», согласовано с кодом
(никаких новых полей конфигурации, никакого нового текста).
- Release-артефакты («changelog — нет») обоснованы политикой
`docs/STATUS.md` о STABLE-теле: `v1.78.0` ещё ни разу не выходил стабильным
релизом (только beta.1–4), регрессия #663 нигде не публиковалась стабильно.
- Риск «устаревшая площадь при копировании объекта» реалистично описан и
сейчас не имеет ни одного нарушителя (`grep` на spread/`Object.assign`
результата `_cleanFloor`/`cleanFloorForRoom` — пусто), т.е. риск корректно
назван, а не выдуман.
- «Принято предположительно» корректно помечает две технические развилки
(геттер vs `areaOf`, место фильтра bbox) как свободно изменяемые — не
требует ответа владельца, соответствует §7.1 (не продуктовый вопрос).
- Открытых продуктовых вопросов к владельцу нет — согласен с автором:
видимое поведение не меняется, различие в AC3 (находка Medium выше) —
техническая формулировка, а не продуктовый выбор.
## Чего не проверял
- Не запускал `npm test`/`typecheck`/`build` — на этапе spec кода ещё нет,
реализация не начата (ветка `issue/669-*` не создана).
- Не запускал perf-смок и не проверял поведение реального `polyclip-ts` на
250 лестницах — AC3 по определению доказывается на кандидате в код-ревью,
не на этапе ТЗ.
- Не проверял `#675` по существу (отдельная задача); использовал только факт
её существования и формулировку не-скоупа для находки Medium выше.
## Вердикт
Вердикт: жёлтый · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 1 → в задаче
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `dev`, коммит `c9cd6cbf1f5b` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `2c7a4c124868cf7499d7b5c725ee028d3810eb62`
```
git log --all --format='%H %T' | grep 2c7a4c124868
```
- Тело issue: `1500ed85bc2e116a29201cc0f77d438e9e6381195fffc70053acc9f250e1dff6`
- Вердикт конвейера: `yellow` · High 0