Files
houseplan-card/docs/reviews/SPEC-REVIEW-484-r1.md
T
2026-09-07 17:42:28 +00:00

179 lines
15 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-484-r1
Issue: [#484](https://github.com/Matysh/houseplan-card/issues/484) — PDF: внешняя размерная цепь теряет размеры ступенчатого фасада
ТЗ: `docs/specs/484-pdf-exterior-dimension-chain.md` (ветка `issue/484-pdf-exterior-dimensions`, SHA на момент ревью — вершина ветки при отправке на ревью)
Этап: spec (PROCESS.md §2.4) · Заход r1 · блокирующих циклов израсходовано 0 из 4
## Скоуп
Полный трек (не `small`): задача не проходит порог сложности/риска ≤3 и
затрагивает контракт печатной геометрии PDF-экспорта. Аналитика владельца в
issue уже подтвердила это явно, отдельно проверять выбор трека не требовалось.
Продуктовая рамка (docs/SCOPE.md): PDF-экспорт — узкое разрешённое исключение
из «Out of scope → General CAD», зафиксированное для #53 и подтверждённое в
этой задаче как обслуживающее того же Home admin, который передаёт уже
поддерживаемую геометрию монтажнику/подрядчику. Задача не расширяет
исключение (не добавляет новых настроек, не вводит второй геометрический
движок) — соответствует ограничителю SCOPE.md.
## Как проверялось
Прочитаны в порядке инструкции: docs/SCOPE.md, AGENTS.md, PROCESS.md (§2.3–
§2.10, §3, §4, §7.1, §7.2), тело issue #484 и оба комментария владельца
(аналитика + ссылка на ТЗ), docs/USER-GUIDE.ru.md (раздел «Сохранение текущего
пространства в PDF»), docs/PDF-EXPORT.md (канонический документ подсистемы),
сам файл ТЗ `docs/specs/484-pdf-exterior-dimension-chain.md` целиком.
Технические утверждения ТЗ сверены построчно с действующим кодом на SHA
ветки, а не приняты на слово:
- `src/pdf/pdf-collision.ts` (`pdfSegmentTouchesGeometry`, строки 155–173):
подтверждено, что `allowStartBoundary` сейчас разрешает касание только в
точке `start` (`t≈0`), а `isSolid(midpoint)`/`isSolid(end)` и любое
пересечение с `hit.t * distance > ε` остаются коллизией без исключения для
собственного примыкающего ребра. Диагноз ТЗ («любое последующее
коллинеарное наложение и пересечение считаются коллизией») соответствует
коду буквально.
- `src/pdf/pdf-scene.ts` (строки 486–546, `externalSegments`,
`externalPlacementTouchesArchitecture`, цикл `dedupeOppositeDimensionEdges`
→ `groupCollinearDimensionEdges` → лейн-перебор 0…80 mm шагом 4): структура
ровно та, что описана в §3 и §6 ТЗ — `allowStartBoundary: true` выставлен
именно для двух extension lines (`{start: entry.a, end: entry.oa, ...}`),
как и заявлено.
- `src/pdf/pdf-dimensions.ts`: `dedupeOppositeDimensionEdges`,
`groupCollinearDimensionEdges`, `compareStableDimensionEdges` существуют и
реализуют ровно то поведение («один из пары равных, стабильный tie-break,
независимые лейны для разных линий фасада»), на которое ссылается ТЗ в §6.1
(«существующий stable source key»).
- `test/pdf-dimensions.test.mjs:103` уже проверяет, что ступенчатый контур
корректно порождает два параллельных верхних ребра на уровне
edge-вычисления — то есть регрессия действительно локализована на стадии
scene-level коллизий (`pdf-scene.ts`), а не в вычислении рёбер, что
соответствует диагнозу ТЗ. Ни в `test/pdf-scene.test.mjs`, ни в
`test/pdf-collision.test.mjs` нет существующего фикстура со ступенчатым
внешним контуром — заявленное отсутствие покрытия регрессии подтверждено.
- Perf-бюджет `<200 ms` для large-house сцены — существующий тест
`test/pdf-scene.test.mjs:641`, ссылка в ТЗ корректна, новый тест не нужен.
- `docs/PDF-EXPORT.md` и `docs/USER-GUIDE.ru.md` (раздел про PDF) сверены на
терминологию: «clean-floor geometry», «external dimensions follow the outer
physical outline», «equivalent opposite measurements are shown once» уже
задокументированы для #482 — ТЗ не изобретает новых терминов, а расширяет
существующую формулировку правилом для выступов (§13).
- `docs/specs/README.md:31` — обратная ссылка issue ↔ ТЗ на месте.
- Минимальный fixture-ring `(0,0)→(10,0)→(10,3)→(12,3)→(12,6)→(10,6)→(10,10)→
(0,10)` в ТЗ дословно совпадает с приведённым в теле issue.
Гейты кода не гонялись — на этапе spec-ревью нет кода для проверки (диапазон
изменений — только `docs/specs/**`, класс C). Это не «непрогнанный гейт», а
корректный объём для этапа: `typecheck`/`test`/`build`/`check-docs`/`golden`
здесь не применимы, а появятся предметом код-ревью после реализации.
## Проверка обязательных разделов ТЗ (PROCESS.md §7.1)
Все обязательные разделы присутствуют: сценарий (§1) · что человек увидит до/
после (§2) · проблема (§3) · цели/не-скоуп (§4/§5) · контракт поведения (§6) ·
геометрические инварианты (§7) · модель/миграция (§8) · UX/i18n/touch (§9) ·
производительность и безопасность (§10) · план реализации с планом тестов
(§11, п.3–5) · AC1…AC8 с указанием доказательства и отдельная таблица
защитных AC/мутантов (§12/§12.1) · release-артефакты (§13) · риски (§14) ·
откат (§15) · явный блок «принято предположительно» (§16).
Сценарий называет персону из SCOPE.md (Home admin), поверхность (View,
экспорт в PDF) и момент (передача плана монтажнику/подрядчику/страховщику) —
без этого раздел был бы техническим описанием, а не продуктовым.
## Проверка на «догадку, выданную за факт»
Каждое нетривиальное утверждение о поведении сверено с кодом или
существующей документацией (см. «Как проверялось») — расхождений не найдено.
Технические решения, не наблюдаемые пользователем (форма helper-функции,
формат intersection sweep, где именно живёт fixture — golden сценарий или
новый файл), явно вынесены в §16 «принято предположительно, можно менять на
ревью» с обоснованием, почему они не требуют продуктового ответа владельца.
Открытых продуктовых вопросов, ошибочно решённых втихую, не найдено.
## AC — проверка однозначности и доказуемости
Все восемь AC пронумерованы, формулируют проверяемое состояние (не процесс) и
у каждого указан способ доказательства (unit-файл или browser/golden +
код-ревью). Для двух защитных AC (AC3, AC4) отдельно указан обязательный
мутант с точным описанием того, что он обязан сломать — соответствует
формату «AC · чем доказан · чем краснеет», ожидаемому на код-ревью (§2.7).
AC1–AC6 сопоставлены с минимальным fixture, приведённым дословно из issue;
AC7 требует Linux golden с явной приёмкой; AC8 требует документацию и оба
changelog. Прямое сопоставление с чек-листом issue показывает полное
покрытие всех шести пунктов исходных критериев приёмки без потери ни одного.
## Находки
Не найдено ни одной High- или Medium-находки. ТЗ технически точно
описывает подтверждённую причину регрессии, узко и симметрично формулирует
контракт исключения (допустимый начальный выход vs запрет повторного входа),
явно перечисляет что не входит в задачу, и не смешивает техническую догадку с
продуктовым решением.
Low, снятые без правки:
- §2 («до и после») содержит лёгкий технический оттенок («без диагоналей и
линий сквозь посторонние стены») в разделе, который должен быть свободен от
терминов реализации. Формулировка тем не менее описывает то, что увидит
человек на листе (отсутствие лишних линий), а не внутренний механизм —
снимаю без правки.
- Раздел «план автотестов» не оформлен отдельным заголовком, а распределён
между §11 (шаги 3–5) и §12/§12.1 (таблицы AC и мутантов). Содержание
полностью покрывает требование §7.1, отдельная секция ничего бы не добавила
— снимаю без правки.
## Что проверено и корректно
- Диагноз причины регрессии в `pdf-collision.ts`/`pdf-scene.ts` — построчно
подтверждён по текущему коду.
- Контракт §6.2 (допустимый начальный префикс / запрет повторного входа)
внутренне непротиворечив, включая крайние случаи (общая вершина двух граней,
касание в точке перехода, invalid/неоднозначная классификация → fail
closed).
- Совместимость с #482 (дедупликация, независимые лейны, stable tie-break,
правило одного H/V для прямоугольника, отсутствие диагоналей) заявлена и
подтверждена ссылками на реально существующий код и тесты.
- Модель/миграция, i18n, touch, производительность, откат — корректно
оценены как «без изменений», решения обоснованы.
- Traceability issue ↔ ТЗ ↔ `docs/specs/README.md` — на месте в обе стороны.
- Продуктовых вопросов, требующих владельца, не осталось; технические решения
явно помечены как «принято предположительно».
## Чего не проверял
- Не запускал никакие гейты (`typecheck`/`test`/`build`/`golden`/`smoke`) —
на этапе spec-ревью нет диапазона кода для их применения (диапазон
изменений — только новый файл `docs/specs/484-...md`, класс C).
- Не проверял, как будущая реализация фактически справится со всеми
перечисленными в матрице случаями (обычный выход, начальное коллинеарное
наложение, проход через инцидентное тело, повторный вход, посторонняя
стена) — это предмет код-ревью с мутантами `pdf-own-exit-prefix-disabled` и
`pdf-post-exit-collision-ignored`, а не текущего этапа.
- Не оценивал возможные пиксельные различия golden-эталона `pdf-export-polish-
light` или нового сценария — они появятся вместе с реализацией и требуют
полного Linux-артефакта.
## Вердикт
Зелёный. ТЗ выполнимо, проверяемо, не содержит догадок, выданных за факт,
верно ограничивает скоуп и совместимо с ранее принятыми решениями по PDF-
экспорту.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/484-pdf-exterior-dimensions`, коммит `d53e4f70d33e` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `a17262e2ded74767c8b8f6648c2fee1c15705205`
```
git log --all --format='%H %T' | grep a17262e2ded7
```
- ТЗ `docs/specs/484-pdf-exterior-dimension-chain.md`, блоб `84fb3a65e309f90c2c8c149b6f7bb4f6bf876f4d`
```
git log --all --find-object=84fb3a65e309f90c2c8c149b6f7bb4f6bf876f4d -- docs/specs/484-pdf-exterior-dimension-chain.md
```