docs: spec review for #141

Issue: #141
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-14 08:54:30 +00:00
parent 59c66ddfb9
commit 28581754a7
+269
View File
@@ -0,0 +1,269 @@
# 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) — ни
одна не блокирует приёмку, все либо правятся косметически при следующей
редакции, либо снимаются этой записью без нового цикла.