mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
@@ -0,0 +1,186 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user