Files
houseplan-card/docs/reviews/SPEC-REVIEW-141-r1.md
T
2026-08-14 12:11:08 +00:00

270 lines
24 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-141-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/141
- **ТЗ под ревью:** `docs/specs/141-wall-junctions.md` (коммит `59c66dd`,
ветка `issue/141-wall-joints`)
- **Роль:** ревьюер ТЗ (не автор), этап `S4-spec-review`
- **Трек:** обычный (не `small`/`trivial`) — сложность/риск 7/10, две+
поверхности (перегородки/drafts и рисование комнатных стен), влияние на
golden, свет и производительность; лёгкий/короткий трек владелец и автор
корректно не применили.
- **Цикл:** r1/4
## Скоуп ревью
Проверялось соответствие ТЗ:
- `docs/SCOPE.md` — попадание в Core user jobs, отсутствие расширения скоупа;
- `PROCESS.md` §2.4, §2.5 (DoR), §7.1 (обязательные разделы), §3/§12 (запреты);
- `AGENTS.md` — классы файлов, ветка, трейлеры коммита ТЗ;
- каноническим документам: `docs/WALL-THICKNESS.md` (модель толщины,
mitre/bevel, union тел), `docs/LIGHT.md` (барьеры/`_lightBarriers`),
`docs/SUN.md` (occluders), `docs/ISOMETRIC.md` (`wallBodiesGeometry` +
extra physical bodies), `docs/CANVAS.md` (координаты/масштаб),
`docs/TOUCH-SUPPORT.md` (best-effort editors), `docs/CONFIG-COMPATIBILITY.md`
(миграция — в задаче её нет, но проверено, что это верно);
- `docs/USER-GUIDE.ru.md` — терминология «Контур комнаты» / «Перегородка»;
- фактическому коду (`src/wall-thickness.ts`, `src/physical-geometry.ts`,
`src/houseplan-card.ts`) — на предмет того, что технический диагноз ТЗ не
является непроверенной догадкой, выданной за факт.
## Как проверялось
1. Прочитан весь тред issue #141: исходное описание с уточнением владельца
(«проявляется и на прямых, и на непрямых углах»), полная аналитика
(ценность 8/10, сложность/риск 7/10, P2, тип `bug`, полный трек) с Q1–Q3 и
явными defaults, решение владельца «принимаю все defaults», финальный
комментарий автора со ссылкой на коммит ТЗ.
2. Сверены обязательные разделы ТЗ (§7.1 PROCESS.md) построчно — таблица ниже.
3. Прочитан код и подтверждены построчно все технические утверждения §3 ТЗ:
- `drawWallPreviewD()` (`src/wall-thickness.ts:490`) действительно строит
`outset−inset` только для `closed`-контура (:496); открытый путь —
независимый quad на каждый сегмент (:512-529). Диагноз ТЗ п.1 точен.
- `partitionBody()` (`src/physical-geometry.ts:41`) строит прямоугольник с
плоскими торцами без топологии узла; `physicalBodies()` (:89) просто
собирает такие прямоугольники в список. Диагноз ТЗ п.2 точен.
- `MITRE_LIMIT = 4` (`src/wall-thickness.ts:28`) и его использование в трёх
местах (:669, :1429, :1805) существуют — ссылка ТЗ §7.2.3/AC3 не
изобретена.
- Изометрия (`src/houseplan-card.ts:4606-4611`) уже вызывает
`wallBodiesGeometry(..., extras)` с `extras = physicalBodies(...)` —
значит full/static/iso уже объединяют независимые тела через
`wallBodiesUnionPath`/`wallBodiesGeometry` (тот же приём подтверждён в
`src/space-render.ts:338` для статической карточки). Ровно то, что ТЗ
утверждает в §3.5 и §8.2 («full/static/isometric render уже объединяют…»).
- `_lightBarriers()` (`src/houseplan-card.ts:13556`) вызывает
`wallBodiesGeometry()` **без** `extras` (:13623) и затем отдельно кладёт
сырые ребра каждого `physical`-тела в occluders (:13637:
`for (const body of physical) occluders.push(...polygonSegments(body));`).
Солнце (`src/houseplan-card.ts:12705-12716`) вызывает
`directionalOccluders(physical, ...)` напрямую на тех же сырых
прямоугольниках. Это подтверждает диагноз ТЗ §3.5/§8.2: свет и солнце
сегодня действительно обходят raw-прямоугольники отдельно от joined-пути
рендера, а не голословное утверждение.
- `floorMinusBodies()` вызывается с тем же `_physicalBodiesR()` (сырые
тела) и для чистой площади (:7366, :7388), и для проверки source-inside
(:11655) — совпадает с AC8.
4. Прочитан `docs/WALL-THICKNESS.md` целиком: подтверждён существующий
контракт mitre/bevel для комнатных стен (§3) и отдельный раздел «9.
Independent partitions, drafts and columns» — независимые тела уже
юнионятся с комнатными только после вырезов проёмов и участвуют в
clean-floor/Glow/sun occlusion. ТЗ §7.3.2, §9 и AC6/AC10 корректно
продолжают именно эту, а не новую, модель.
5. Прочитан `docs/LIGHT.md` и `docs/SUN.md`: подтверждено, что `_lightBarriers`
держит один общий барьерный набор на пространство и кэшируется по
геометрическому fingerprint, а не по `_cfgEpoch` — ТЗ §8.3.1-2 корректно
продолжает существующий кэш-контракт, не изобретая новый механизм
инвалидации.
6. Прочитан `docs/ISOMETRIC.md`: `src/iso-walls.ts` явно описан как
потребитель «канонического `wallBodiesGeometry()` MultiPolygon after
openings and extra physical bodies have been resolved» — буквально
подтверждает ТЗ §8.2 («full/static/isometric не создают разные join
алгоритмы»), т.е. это не домысел автора, а прямая цитата канона.
7. Прочитан `docs/TOUCH-SUPPORT.md`: политика требует, чтобы «новые
спецификации editor-фич и код-ревью» явно указывали одно из трёх значений
`Touch editor: supported / best effort / not exposed`. ТЗ §10 описывает
ровно best-effort-поведение словами, но не использует эту точную метку —
см. Low-1.
8. Прочитан `docs/USER-GUIDE.ru.md` (раздел «Создание комнаты», таблица
«Инструменты плана», раздел «Виртуальные стены»): термины «Контур комнаты»
и «Перегородка» в ТЗ (§4.1, §5.3 и др.) совпадают с интерфейсным словарём
ровно там, где употребляются полностью; там же нашёл существующую строку
«Углы | Соседние тела стен соединяются…» (:347) — актуальное описание,
которое release-артефакты ТЗ (§15) обязывают дополнить для partitions —
корректно учтено.
9. Прочитан `src/types.ts:45-72` — `RoomDraftCfg`, `PartitionCfg`,
`space.room_drafts`, `space.partitions` существуют как реальные поля
схемы, а не придуманы для ТЗ; AC4/AC5 про кросс-типовые соединения
(partition↔partition, draft↔partition, partition/draft↔room wall)
технически имеют смысл на этой модели.
10. Проверена запись `docs/specs/README.md:85` — строка на #141 добавлена в
том же коммите; ссылка issue ↔ ТЗ двусторонняя (ТЗ ссылается на issue в
заголовке, issue-комментарий ссылается на файл ТЗ и коммит).
11. Проверены трейлеры и class-принадлежность: `git diff --stat
origin/dev...HEAD` показывает только `docs/specs/141-wall-junctions.md` и
`docs/specs/README.md` (класс C, ни одного файла класса A); коммит
`59c66dd` несёт `Issue: #141` и `User-Visible: no` — корректно для
документа ТЗ, который сам не меняет поведение.
## Обязательные разделы (§7.1 PROCESS.md)
| Раздел | Есть | Комментарий |
|---|---|---|
| Сценарий (персона/поверхность/момент) | ✅ | §1 — домашний администратор, desktop Plan editor, момент второго клика |
| Что человек увидит до/после | ✅ | §2, «До:»/«После:» — см. Low-2 |
| Проблема (с подтверждённой причиной) | ✅ | §3, шесть пунктов, все проверены по коду (см. выше) |
| Скоуп / не-скоуп | ✅ | §5 / §6, явные границы (без snap #137, без нового cap, без #138) |
| Контракт поведения | ✅ | §7 (геометрия) + §8 (архитектура) |
| Модель данных и миграция | ✅ | §9 — явное «schema не меняется», «читается исправленно, без записи» |
| UX, i18n, accessibility, touch | ✅ | §10 — см. Low-1 |
| AC1…ACn с доказательством | ✅ | §12, 13 штук, каждый с типом доказательства и наблюдаемым результатом |
| План автотестов | ✅ | §13, по подпунктам на unit/smoke/golden/performance |
| Риски | ✅ | §16, таблица с вероятностью/ущербом/снижением |
| Откат | ✅ | §17 |
| Release-артефакты | ✅ | §15, конкретный список файлов документации и обоих changelog |
Все обязательные разделы присутствуют и содержательны. Дополнительно есть
раздел «Решения владельца» (§4, явно фиксирует принятые Q1–Q3), архитектурный
контракт (§8), план реализации (§14, помечен как техническая свобода) и явный
блок «принятые технические предположения» (§18) — соответствует духу
PROCESS.md §7.1 об отделении продуктового решения от технического.
## Находки
Находок уровня **High** и **Medium** нет — новых issue не требуется.
### Low-1 — нет явной метки touch-контракта по `docs/TOUCH-SUPPORT.md`
**Файл:** `docs/specs/141-wall-junctions.md:252-261` (§10)
`docs/TOUCH-SUPPORT.md` требует: «New editor feature specifications and code
reviews must state one of: `Touch editor: supported`; `Touch editor: best
effort / intentionally degraded`; `Touch editor: not exposed`.» §10 ТЗ по
существу описывает best-effort-контракт словами («Plan editor остаётся
desktop-first… новый hover parity не обещается») и корректно ссылается на
safety floor (сохранённая geometry тапнутого сегмента должна совпадать), но не
использует ни одну из трёх канонических формулировок буквально.
**Решение ревьюера:** Low, не блокирует. При следующей правке ТЗ или в
хендоффе код-ревью достаточно добавить одну строку `Touch editor: best effort
/ intentionally degraded` — по содержанию это именно то, что §10 уже
описывает.
### Low-2 — «что человек увидит» длиннее одной фразы
**Файл:** `docs/specs/141-wall-junctions.md:28-37` (§2)
PROCESS.md §7.1 требует «одной фразой, без терминов реализации». Раздел
написан двумя короткими абзацами («До:» / «После:»), по одному предложению
каждый; по существу требование выполнено (без терминов реализации, конкретно
и однозначно), но формально это не «одна фраза». Тот же класс находки уже
фиксировался как Low и не блокировал приёмку в `SPEC-REVIEW-137-r1`.
**Решение ревьюера:** Low, не блокирует. Косметическая правка на усмотрение
автора.
### Low-3 — доказательство AC10 («code review») до кода не проверяемо, но это ожидаемо для code-review-этапа
**Файл:** `docs/specs/141-wall-junctions.md:315-321` (AC10, AC11)
AC10 и AC11 указывают доказательство «`unit` + code review» / «`performance` +
code review» — это не входит буквально в список §2.5 PROCESS.md
(`unit`/`backend`/`smoke`/`golden`/«ревью кода»), но «code review» — это то же
самое «ревью кода» иначе сформулированное, и AC11 корректно называет
`performance`, обязывая код-ревьюера прогнать performance-профиль по правилу
«гейты соразмерны AC» (`PROCESS.md` §8). Не дефект по существу, тот же класс
находки не блокировал `SPEC-REVIEW-137-r1` (Low-3 там).
**Решение ревьюера:** Low, не блокирует. Оставить как есть.
## Что проверено и корректно
- **Соответствие `docs/SCOPE.md`:** задача закрывает **J4** («от нуля до
плана без внешнего редактора» — администратор должен доверять форме стены в
момент рисования, не после замыкания) и **J6** («план остаётся правдивым по
мере развития» — независимые перегородки не деградируют в кривую геометрию
навсегда). Обе строки Closed — это исправление дефекта внутри принятой
функциональности, не расширение продукта.
- **Легитимность полного трека:** сложность/риск 7/10, минимум две
поверхности (перегородки и рисование стен комнаты), влияние на golden, свет
и производительность — критерии `small`/`trivial` (§5/§5.1 PROCESS.md) не
выполняются ни по одному пункту; полный трек выбран верно.
- **Продуктовые вопросы закрыты по процессу:** Q1 (какие соединения входят),
Q2 (нормализуется ли rubber-band до клика) и Q3 (что делать со свободными
торцами) заданы одним пакетным комментарием, каждый с предлагаемым default,
issue корректно ушёл в `blocked`+`S3-spec` до ответа и вышел из `blocked`
сразу после решения владельца. Ни одна догадка не выдана за факт без
пометки — §18 отдельно и явно перечисляет технически свободные решения и
прямо фиксирует «нет открытых продуктовых вопросов» (§18.10).
- **Технический диагноз не голословен.** Все шесть пунктов §3 (независимый
путь `drawWallPreviewD`, плоские торцы `partitionBody`, отсутствие топологии
узла в `physicalBodies`, отдельный путь mitre для закрытого контура,
частичное объединение в full/static/iso против необъединённых barriers у
Glow/sun, неподтверждённость отдельного дефекта свободного торца по
скриншоту) построчно проверены по исходному коду и совпадают с ним — см.
«Как проверялось» п.3.
- **Не-скоуп (§6) корректно отсекает соседние соблазны:** snap-tolerance/#137,
автоматическое дробление persisted-геометрии, превращение partition в
границу комнаты/HA-зону, новый persisted junction-тип и миграция, новый
cap/join selector, #138 (замыкание контура по углам существующей комнаты) —
все явно исключены с указанием, почему это не эта задача.
- **Регрессионные гарантии сформулированы явно:** AC10 поимённо защищает
проёмы комнатных стен, virtual-T, nested/partial стены, экстерьер #123 и
колонны — то есть ровно те механизмы, которые уже используют
`MITRE_LIMIT`/union и могли бы негласно пострадать от новой топологии узлов.
- **Производительность:** §11/AC11 корректно ссылаются на существующую
60-partition фикстуру `large-house-v1` и явно запрещают full-house boolean
union на каждый pointermove/HA-tick (§8.3.3) — согласуется с
`docs/LIGHT.md` («barriers keyed by geometry fingerprint, never `_cfgEpoch`»)
и не вводит новый бюджет без решения процесса.
- **Модель данных и миграция (§9):** корректно заявлено «schema не меняется»,
«читается исправленно без записи», без прямой/обратной миграции — сверено с
`docs/CONFIG-COMPATIBILITY.md`: задача не создаёт нового
compatibility-случая, потому что не меняет persisted-представление.
- **Release-артефакты (§15)** перечисляют конкретные существующие документы
(`WALL-THICKNESS.md`, `LIGHT.md`, `SUN.md`, `ISOMETRIC.md`, `ARCHITECTURE.md`,
`TESTING.md`, `STATUS.md`, `USER-GUIDE.ru.md`) и оба changelog в одном
implementation-коммите — соответствует §7.1/правилу 11 PROCESS.md. Golden
корректно ограничен принятием только через `npm run golden:accept --
--reviewed` по полному Linux-артефакту (§13.3), perf/golden/browser-suite
верно отнесены к пре-релизному, а не implementation-гейту (§8, §11.4
PROCESS.md).
- **Откат (§17)** корректно опирается на отсутствие миграции данных: revert
implementation-коммита восстанавливает прежний (дефектный) визуал без
риска для сохранённых данных.
- **Трассируемость:** `docs/specs/README.md:85` обновлён тем же коммитом
(`59c66dd`); ветка `issue/141-wall-joints` и трейлеры (`Issue: #141`,
`User-Visible: no`) корректны для документа класса C, который сам не меняет
поведение. `git diff --stat origin/dev...HEAD` не содержит ни одного файла
класса A — продуктовый код не тронут до `S5-ready` (правило №1).
## Чего не проверял
- Не проверял реализуемость «одного чистого вычисляемого frame» (§8.1) как
конкретной структуры данных/API — по правилам ТЗ это явно свободное
техническое решение автора кода (§18.2), не предмет ревью ТЗ.
- Не запускал автотесты, `golden`, `performance` или browser-смоки — на этапе
`spec` это не требуется; существование фикстур (`large-house-v1`, реальные
поля схемы, конкретные функции и их поведение) проверено чтением кода, а не
исполнением.
- Не проверял, что boolean-decomposition конкретной реализации (mitre patch
для узла степени 3+, T-разрез сквозного сегмента) технически осуществима
средствами `polyclip-ts` в разумное время — это открытое для автора кода
техническое решение, отмеченное в ТЗ как свободно изменяемое (§18.3), и
фактическая проверка бюджета (AC11) относится к код-ревью и пре-релизному
гейту.
- Не проверял корректность конкретных числовых оценок аналитики (8/10 · 7/10 ·
P2) по существу — это поле владельца (PROCESS.md §2.2), уже принято явным
решением владельца до написания ТЗ.
- Не проверял связанные issue #123/#137/#138 по существу за пределами того,
что понадобилось для верификации ссылок ТЗ (регрессия #123, снэп #137,
разграничение с #138) — они не входят в предмет этого ревью.
## Вердикт
Зелёный. High: 0, Medium: 0. Три находки Low (отсутствие буквальной метки
touch-контракта по `docs/TOUCH-SUPPORT.md`; «что человек увидит» длиннее
одной фразы; типы доказательства AC10/AC11 вне буквального перечня §2.5) — ни
одна не блокирует приёмку, все либо правятся косметически при следующей
редакции, либо снимаются этой записью без нового цикла.