Files
houseplan-card/docs/reviews/SPEC-REVIEW-53-r2.md
T
2026-09-06 23:37:16 +00:00

187 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-53-r2 — Экспорт пространства в PDF: чистый архитектурный план
- Issue: https://github.com/Matysh/houseplan-card/issues/53
- Этап: ТЗ на ревью (PROCESS.md §2.4), полный трек
- Заход: r2 · блокирующих циклов израсходовано 1 из 4 (зелёный вердикт этого
раунда бюджет не тратит, #227)
- Материал: `docs/specs/053-pdf-export.md` и `docs/SCOPE.md` на коммите
`299629d2d27231267c47afc583801fdcf553d687` (HEAD, `origin/issue/53-pdf-export`)
- Материал предыдущего раунда (r1): `docs/specs/053-pdf-export.md` на коммите
`cc93e93da9277a5e20c677e35ea1a0be0af57791` — так явно названо в шапке
`docs/reviews/SPEC-REVIEW-53-r1.md`. Подтверждено таймстампами:
`cc93e93d` создан 2026-09-07 02:09:38+03, документ ревью `ee72e393` —
2026-09-07 02:21:30+03, то есть после `cc93e93d` и до `299629d2` (02:28:56+03).
**Замечание к машинному блоку «Материал раунда» в документе r1.** Автогенерируемый
конвейером якорь в конце `SPEC-REVIEW-53-r1.md` называет коммит `48a17f5dc998`
(дерево `be8a4d9c9e36…`), а не `cc93e93d`, на котором реально проводился разбор
согласно шапке того же документа. `48a17f5d` — предыдущая ревизия ТЗ (без
уточнения владельца по внутренним/внешним размерам из `cc93e93d`), не
осиротевшая и не удалённая — SHA живой, поэтому это не случай «SHA не
резолвится» из §2.10. Расхождение между шапкой документа и его же машинным
якорем не блокирует этот раунд: шапка недвусмысленно называет материал, и
таймстампы подтверждают, что `cc93e93d` предшествовал самому ревью, а `48a17f5d`
— нет (правки по внутренним/внешним размерам в `48a17f5d` ещё не было, но
именно они разбирались в Medium-2/3 r1). Использовал `cc93e93d` как базу дельты.
Это наблюдение о механике конвейера, не находка по ТЗ — фиксирую на случай,
если генератор якоря путает коммит issue-тела с фактическим коммитом ревью.
## Скоуп ревью
Второй заход, разбор по дельте (PROCESS.md §2.10, issue #214): предмет —
`git diff cc93e93d..299629d2` (два файла: `docs/SCOPE.md`,
`docs/specs/053-pdf-export.md`). Дельта локальна — это не ребейз на ушедший
`dev`, контракт поведения не сменился, новая подсистема не задета, объём
дельты (2 файла, точечные абзацы) существенно меньше исходного ТЗ. Полный
разбор не требуется; проверяю дельту плюс всё, до чего она дотягивается:
AC5, AC6, §7.3 целиком, §11, §15, §16, §9 (i18n), §13 (план тестов) —
поскольку правки задевают ровно эти разделы.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| Medium-1 — `docs/SCOPE.md` не содержит заявленного исключения для #53 | Добавлен абзац в `docs/SCOPE.md`, раздел Out of scope, по образцу записи для #89: ссылка на #53, обе даты решений (2026-08-15, 2026-09-07), явное описание узкого read-only исключения. §11 ТЗ переписан с «уже записано, проверить формулировку» на «узкое read-only исключение #53, принятое 2026-08-15 и суженное 2026-09-07» | `docs/SCOPE.md:107-114` (коммит `299629d2`, строки перед «A general dashboard framework»); `docs/specs/053-pdf-export.md:316-317` |
| Medium-2 — коллизии противоречили гарантии «нельзя опускать» для непрямоугольных комнат | Абзац «Коллизии» переписан: для непрямоугольного контура число из-за коллизии больше не скрывается — сначала кегль 6 pt и сдвиг вдоль ребра, а при нехватке места — компактная метка `R<n>` с выносной линией и полное значение в нумерованном нижнем блоке «Внутренние размеры». Нумерация детерминирована (`room.id`, лексикографически минимальная вершина по часовой стрелке). AC5 переформулирован: «значения не исчезают из-за коллизий» | `docs/specs/053-pdf-export.md:156-168` (§7.3 «Коллизии»), AC5 строка 330, свидетель `pdf-room-edge-dropped` (§13, строка 351) теперь проверяет именно это |
| Medium-3 — AC6 проверял правило (порог 30 см), которого не было в контракте внешних размеров | В абзац «Внешние размеры планировки» добавлено явное предложение: порог 30 см для внутренних рёбер здесь не применяется, каждая физическая наружная грань — включая короче 30 см — получает число; при нехватке места оно выносится наружу на полку, а не скрывается. AC6 переформулирован без клаузы про засечку, вместо неё — «включая грань < 30 см… тесные значения не скрываются, а детерминированно выносятся» | `docs/specs/053-pdf-export.md:146-150` (§7.3 «Внешние размеры планировки»), AC6 строка 331 |
| Medium-4 — порог производительности ссылался на несуществующую конфигурацию (60 комнат) | §15 и риск в §16 переписаны на фактическое число: «одно текущее пространство `large-house`: 20 комнат» вместо «60 комнат» | `docs/specs/053-pdf-export.md:359-361` (§15), `:372` (§16, строка риска про 4+ числа на комнату) |
Все четыре Medium-находки r1 закрыты правкой текста, без обращения к
владельцу, как и предписывал вердикт r1. Ни одна не осталась «оставленной в
тексте ревью» — везде видна конкретная строка контракта, а не заявление
автора.
## Унаследовано из r1
Без повторной проверки в этом раунде принято (документ
`docs/reviews/SPEC-REVIEW-53-r1.md`, материал `cc93e93d`, разделы дельтой не
задеты):
- Сверка 10 из 12 фактических утверждений ТЗ с кодом (таблица r1: модель
`spaceModels`, резолвер севера, поля подложки, `decor[]`/`furniture-art-runtime`,
`geometryArea`, грань-в-грань линейка ресайза `_rszEdgeLabels`, образец
ленивого чанка `iso-scene-render`, потолок `houseplan-card.ts`, терминология
кнопки и порядок иконок, touch-контракт диалога) — дельта эти строки не
трогает, коду ничего не противоречит.
- Обязательные разделы §7.1 PROCESS.md присутствуют и однозначны — структура
документа не менялась, кроме уточнённых абзацев §7.3/§11/§15/§16.
- AC1-AC15 пронумерованы без пропусков и дублей, у каждого указан способ
доказательства из принятого словаря — дельта меняет только текст AC5/AC6,
не их номер и не наличие доказательства.
- Q1/Q2/Q3 §18 — принятые предположения с обоснованием, не эскалированы
владельцу, ревьюер согласен — дельта раздел 18 не трогает.
- AC12 (ручная проверка на Android) — прецедент из #473 признан достаточным
в r1, дельта AC12 не касается.
- «Один источник числа» для площади и внутренних размеров (`_rszEdgeLabels`/
`_cleanFloor` → `geometryArea` → `formatArea`) — подтверждено в r1 чтением
кода, дельта этот путь не меняет.
- Свидетели §13 покрывают по имени защитные сценарии таблицы §7.1/§7.3/§8 —
состав свидетелей не расширялся и не сокращался этой дельтой (те же шесть
имён, включая `pdf-room-edge-dropped` и `pdf-outer-face-inside`), изменилось
только описываемое ими поведение (см. таблицу закрытия выше).
## Находки
### Low-1 — предложение об i18n-потребителях в §9 не покрывает новый ключ на листе
§9 заканчивается фразой «Тест `i18n-dead-keys` требует потребителя у каждого
— все используются диалогом или подвалом». Это предложение не менялось в
`cc93e93d`→`299629d2` буквально, но список ключей перед ним — да: в него
дельтой добавлен `pdf.internal_dimensions` (заголовок нумерованного блока
«Внутренние размеры» из фикса Medium-2, §7.3). Этот ключ печатается на теле
листа — в свободном поле рядом с геометрией, а не в диалоге и не в подвале
(«Масштаб 1:N», линейка, легенда, дата, версия — это подвал по §7.5).
Итоговое предложение теперь неточно описывает собственный список: оно
по-прежнему верно для одиннадцати из двенадцати ключей, но не для
добавленного.
**Почему не Medium.** Место использования ключа не создаёт двусмысленности
для реализации — §7.3 однозначно описывает, что и где печатается; тест
`i18n-dead-keys` по смыслу (потребитель у каждого ключа существует) от этой
неточности не пострадает, он не проверяет *где именно* строка используется,
только что она используется хоть где-то. Дефект чисто редакционный.
**Воспроизведение:** `grep -n "internal_dimensions\|диалогом или подвалом" docs/specs/053-pdf-export.md` — строки 291 и 294 в одном разделе, второе неточно описывает первое.
**Решение ревьюера:** снимаю как Low с записью, не блокирует. Правится
тривиально при следующей правке текста (например, «диалогом, листом или
подвалом»); отдельного цикла ради одной фразы не открываю.
## Что проверено и корректно
- Все четыре Medium-находки r1 закрыты по существу, а не переформулированы
без изменения смысла — проверено построчным сравнением `git diff
cc93e93d..299629d2` (таблица закрытия выше), а не заявлением автора.
- Правка Medium-2 не создаёт новой внутренней коллизии: приоритет коллизий
(§7.3 «Коллизии» — внешние → площади → внутренние длинные → короткие) и
правило «дубликаты у прямоугольников опускаются первыми» остались
согласованы с обновлённым fallback для непрямоугольных контуров
(кегль 6 pt → сдвиг → метка `R<n>` в нумерованном блоке); ни один путь
коллизии больше не приводит к молчаливому исчезновению числа.
- Нумерация меток `R<n>` в §7.3 детерминирована явно («комнаты — по
`room.id`, рёбра — от лексикографически минимальной вершины по часовой
стрелке»), что не ломает детерминизм байт PDF, заявленный в §8.4 и
требуемый AC10 — источник порядка не зависит от времени исполнения.
- Правка Medium-3 не оставила противоречия между AC6 и текстом §7.3: клауза
«рёбра < 30 см — засечка без числа» убрана из AC6 полностью, а не
продублирована с оговоркой — единственный порог 30 см в документе теперь
относится только к внутренним рёбрам комнаты (проверено `grep -n "30 см"
/tmp/spec_r2.md`: три вхождения, все — внутренние размеры/коллизии; одно —
явное «здесь не применяется» для внешних граней).
- Правка Medium-4 арифметически соответствует фикстуре: `large-house.mjs`
строит `FLOOR_COUNT=3` пространства по `ROOMS_PER_FLOOR=20` — новое число
«20 комнат на текущее пространство» в §15/§16 совпадает с тем, что PDF
реально получит на входе (одно пространство за раз, §5).
- Запись в `docs/SCOPE.md` текстуально согласована с решениями владельца в
комментариях issue (2026-08-15 — исключение принято; 2026-09-07 — сужение
до текущего пространства, read-only, без второй геометрической модели) —
сверено дословно, а не по духу.
- Изменение `docs/specs/052-view-dimensions.md` (о неприменимости раздела
«Длины» к бумаге) не входит в дельту этого раунда (`cc93e93d..299629d2` его
не трогает) — это уже было внесено до r1 и разобрано им; лишний раз не
проверял (см. «Унаследовано»), но сверил, что дельта его не откатывает.
## Чего не проверял
- Код фичи по-прежнему не существует (`src/pdf/**` нет в дереве) — дешёвые
гейты (`tsc`, `test`, `build`), golden, смоки, мутационные свидетели не
гонялись и не могли гоняться: это ревью ТЗ, а не код-ревью. Автор привёл
`node scripts/check-docs.mjs --screenshots=warn` (зелёный) в хендоффе —
необязательный для docs-only правки шаг (screenshots касаются `src/**`,
дельта его не трогает), не перепроверял, поскольку он не входит в состав
обязательных гейтов ревью ТЗ.
- Не проверял вручную макет печатного листа с нумерованным блоком «R<n>» —
на этапе ТЗ такого артефакта не существует; в r1 это же было явно указано
как непроверяемое на этой стадии.
- Не переоценивал заново все десять фактических утверждений ТЗ, подтверждённых
в r1 (модель, резолверы, ленивый чанк, потолок ядра, терминология,
touch-контракт) — дельта их не задевает (см. «Унаследовано из r1»).
- Не проверял i18n-ключи `de`/`fr` построчно — переводы появятся вместе с
кодом; на этапе ТЗ фиксируется только список ключей и их обязательность,
что не изменилось дельтой (кроме добавления одного ключа, см. Low-1).
## Вердикт
Все четыре Medium-находки r1 закрыты в тексте ТЗ, ни одна не осталась
непокрытой и ни одна не переоткрылась дельтой (проверено построчно, не на
слово автора). Единственная новая находка этого раунда — Low, снята
решением ревьюера с записью, реализации не мешает. High-находок нет.
Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0
---
<!-- material-anchors: заполняется конвейером -->
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/53-pdf-export`, коммит `299629d2d272` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `f83621000ce142c143b4896b3c267dbb7ca1b552`
```
git log --all --format='%H %T' | grep f83621000ce1
```