Files
houseplan-card/docs/reviews/SPEC-REVIEW-309-r1.md
T
2026-08-25 18:02:01 +00:00

192 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-309-r1
Issue: https://github.com/Matysh/houseplan-card/issues/309 «Стыковочные узлы: визуальный лимит mitre и устранение паразитных парных патчей»
Этап: spec (обычный трек — сложность/риск владельца 6/10 и 7/10, `small` невозможен)
Заход: r1 · блокирующих циклов израсходовано 0 из 4
Ревьюер: Claude (сессия ревью ТЗ), артефакт: `docs/specs/309-junction-visual-limit.md` в ветке `issue/309-junction-visual-limit`, коммит `ff623cd4` (только этот файл; `git diff origin/dev...HEAD --stat` — 1 файл, 72 строки, никакого кода).
## Скоуп
ТЗ описывает три класса визуальных артефактов кладки в узлах стыка стен (шип, горб,
ступенька), обнаруженных владельцем на реальном экспорте после #302 (полный mitre).
Решения владельца зафиксированы 2026-08-25 в аналитике issue: визуальный порог среза
`1.5·max(h)` вместо санитарного `MITRE_LIMIT=4`, форма среза — плоская фаска
перпендикулярно биссектрисе, устранение паразитных mitre-патчей между не-соседними
по азимуту лучами узла. Продуктовая рамка — рендер кладки стен как таковой, то есть
J1 SCOPE.md («show the whole home... spatially», точность отображения) и J6
(«keep the plan true»): задача не расширяет и не меняет job, чинит форму существующей
геометрии, принятой в #302. Соответствие подтверждаю.
## Как проверялось
Не на веру автору — с чтением текущего кода `src/wall-thickness.ts`,
`src/physical-geometry.ts`, `demo/golden/matrix.mjs`, `docs/WALL-THICKNESS.md`,
`docs/specs/README.md`, `PROCESS.md` §2.4/§2.5/§7.1/§7.2, `AGENTS.md`, `docs/SCOPE.md`:
1. `src/wall-thickness.ts:80` — `MITRE_LIMIT = 4` существует и является ровно тем
санитарным пределом, о котором говорит §3.1 ТЗ.
2. `src/wall-thickness.ts:1082-1150` (`linearWallJoinPatches`) — построчно
подтверждает: (a) функция перебирает **все** пары лучей `i<j` без фильтра
соседства по азимуту, хотя лучи уже отсортированы по `atan2` (строка ~1125) —
это буквально подтверждает механизм «ступеньки», описанный автором во втором
комментарии issue (паразитный патч между не-соседними лучами); (b) строка 1144
— `limit = MITRE_LIMIT * Math.max(a.halfDepth, b.halfDepth)` — ровно та ветка,
на которую ссылается аналитика issue как источник «шипа»; контракт §3.2 ТЗ
правит именно эту ветку.
3. `src/wall-thickness.ts:2087` (веера `junctionNodeGeometry`) — тот же
`MITRE_LIMIT`-предел на ветке фанов, соответствует ссылке аналитики «:2087» и
контракту §3.4 ТЗ.
4. Проверил область действия `linearWallJoinPatches` по всем вызовам:
`src/physical-geometry.ts:242` (канонические draft/partition-тела),
`src/houseplan-card.ts:19961` (live-превью рисования),
`src/wall-thickness.ts:1211` внутри `drawWallPreviewD` (превью открытого
контура). Все три — один и тот же экспорт. Это прямо подтверждает заявление
§3.5 ТЗ «превью использует ту же парную логику» — не декларация, а следствие
того, что все три сайта вызывают одну функцию; менять код в одном месте нельзя.
5. `src/wall-thickness.ts:2166` (`junctionContractHoles`) и
`demo/smoke_junction_holes.mjs` — оба существуют, соответствуют ссылкам §3.5/AC5.
6. `demo/golden/matrix.mjs:577-621` — блок `#302: junction close-ups` содержит
ровно **15** сцен `junction-*` (посчитано `grep -c`) плюс отдельно
`junction-owner-repro-dark` (строка 620) = 16 сцен итого, не 16 крупноплановых
+ repro = 17, как можно прочитать в тексте ТЗ (см. Находки, Low).
7. `docs/specs/README.md` — «Обязательные release-артефакты номерного ТЗ»: явный
перечень (changelog RU+EN, затронутая документация, golden/screenshots и способ
review) обязателен, если задача меняет пользовательское поведение. Рендер стыков
— видимое поведение (форма кладки в Plan/View/kiosk/Static/hidden-Iso, общий
структурный кэш по `docs/WALL-THICKNESS.md` §3), значит раздел обязателен.
8. `PROCESS.md` §7.1 — обязательные разделы ТЗ: сценарий · что человек увидит ·
проблема · скоуп и не-скоуп · контракт · UX · модель данных и миграция · i18n ·
AC1…ACn · план автотестов · риски · откат · release-артефакты.
9. `git log -1 --format=%B ff623cd4` — трейлеры `Issue: #309` / `User-Visible: no`
на месте (документация, код не менялся).
## Находки
### Medium (в скоупе задачи — правится в этом же документе)
**Отсутствуют несколько обязательных разделов §7.1, включая оба продуктовых.**
Файл `docs/specs/309-junction-visual-limit.md` содержит: Проблема (§1), Решения
владельца (§2), Контракт (§3), Затрагиваемые поверхности (§4), AC (§5), План
тестов (§6), Откат (§7). Отсутствуют как отдельные разделы:
- **Сценарий** — какая персона, на какой поверхности, в какой момент встретит
изменение (PROCESS.md §7.1, первый из двух обязательных продуктовых разделов).
Проблема формулирует технический механизм (mitre, лучи, паразитные патчи), но
не называет персону/поверхность. Это не мелочь: рендер узлов — общий структурный
кэш (`docs/WALL-THICKNESS.md` §3: «Full and Static render paths reuse the same
structural cache»), поэтому фикс проявится не только в Plan editor (где владелец
заметил дефект), но и в View, kiosk, Static, hidden-Iso — у всех трёх персон
SCOPE.md, не только у Home admin. Это технический факт, не продуктовый вопрос —
автор может и должен написать его сам, без обращения к владельцу.
- **Что человек увидит до и после**, одной фразой без терминов реализации (второй
обязательный продуктовый раздел). Ближайшее к этому — техническое описание в §1
(шип/горб/ступенька), но это не заменяет требуемую формулировку «до: кладка узла
выступает зубцом/пикой/уступом за естественный контур; после: кладка узла
ограничена аккуратной фаской, без выступов».
- **Скоуп и не-скоуп** как явный раздел (сейчас скоуп восстанавливается только из
§4 «Затрагиваемые поверхности», не-скоуп не сформулирован вовсе — например, не
сказано explicitly, что #249-шеврон для near-orthogonal узлов (#279) и общая
политика `thickLength` (#271) не пересматриваются, кроме одной фразы в §3.4).
- **UX** — раздел отсутствует; для чисто рендер-геометрической задачи корректный
ответ короткий («интерактивных элементов нет, только форма кладки»), но раздел
должен присутствовать явно, а не подразумеваться.
- **Модель данных и миграция** — не упомянуто явно. Ответ тривиален («нет модели
данных, нет миграции — чистая рендер-геометрия из уже сохранённых `walls`/
`partitions`»), но DoR (§2.5) требует явного «нет», а не молчания.
- **i18n** — не упомянуто явно. Ответ тривиален («нет новых строк»), нужен явный
раздел с этим ответом (DoR §2.5).
- **Риски** — не назван явный раздел (частично риск виден из §3.3 — «решение
фиксируется при реализации измерением» — но общего перечня рисков нет: например,
риск того, что после запрета несоседних пар в узлах ≥3 лучей часть существующих
16 golden-сцен изменится непредсказуемо и потребует пересмотра больше, чем
ожидается).
- **Release-артефакты** — не выделены как раздел. §4 упоминает
`docs/WALL-THICKNESS.md §3`, но нет явного перечисления `docs/CHANGELOG.md` +
`docs/CHANGELOG.ru.md` (обязательны по `docs/specs/README.md`, поскольку меняется
пользовательское поведение — форма отображаемой кладки) и способа ревью golden
(`--reviewed`, кто и как принимает изменившиеся сцены).
Все перечисленные пункты решаются самим автором без вопроса владельцу — ни один
не требует продуктового решения, которого ещё нет (персона/поверхность вытекают
из общего рендер-пайплайна, i18n/миграция/UX ответы тривиальны «нет»,
release-артефакты — механическое перечисление). Без High-находок это жёлтый
вердикт: разделы дописываются в этом же файле, следующий заход проверяется по
дельте (§2.10).
### Low (не блокирует, отмечаю для полноты)
- §3.5 и §4 повторяют формулировку «Все 16 junction-сцен» из
`docs/WALL-THICKNESS.md` («Junction tooling»: «sixteen close-up golden scenes
(`junction-*`) plus the owner's repro scene»). Фактически в
`demo/golden/matrix.mjs:580-615` ровно **15** близких-планов `junction-*`
(посчитано `grep -c`), плюс `junction-owner-repro-dark` — итого 16 сцен, не
16+1=17. Формулировка ТЗ («Все 16 junction-сцен + junction-owner-repro-dark»)
наследует ту же неточность канонического документа. Не блокирует ни один AC —
AC6 («весь сет зелёный») не зависит от точного числа, golden-раннер прогоняет
фактический список сцен, а не названное количество. Рекомендация: при правке
раздела не привязываться к числу («весь текущий junction-сет»), либо явно
посчитать `grep -c "id: 'junction-" demo/golden/matrix.mjs` перед фиксацией
цифры — это тот же класс дефекта, о котором предупреждает `AGENTS.md`
(«Never copy test counts into documents by hand; they go stale»), просто
применённый не к тестам, а к golden-сценам. Снимаю без правки по решению
ревьюера: не влияет на проверяемость ни одного AC.
## Что проверено и корректно
- Обязательные технические разделы §7.1 (проблема, контракт поведения, AC1…7 с
доказательством, план автотестов, откат) присутствуют и полны.
- Корневые причины всех трёх артефактов не являются догадкой: автор подтвердил
их исполнением на реальном экспорте (dev @ dc68868) и указал точные строки кода
(`:80`, `:1144`, `:2087`), которые я перепроверил построчно — они существуют и
делают ровно то, что написано в issue и в ТЗ.
- Инвариант «превью = персист» (§3.5, п.3) — не заявление, а прямое следствие
того, что `linearWallJoinPatches` имеет единственный экспорт и три вызывающих
сайта (`physical-geometry.ts:242`, `houseplan-card.ts:19961`,
`wall-thickness.ts:1211`); правка одной функции автоматически меняет все три.
- Порог `1.5·max(h)` и граница «прямые/тупые углы не затрагиваются» (AC4)
математически корректны: для равных толщин под 90° вершина mitre лежит на
`h·√2 ≈ 1.41h`, что действительно `< 1.5h` — контракт не ломает обычные углы.
- Контракт §3.3 (запрет паразитных пар в узлах ≥3 лучей) технически реализуем на
существующей структуре: лучи в `linearWallJoinPatches` уже сортируются по
`atan2` перед перебором пар (строка ~1125), так что «соседние по азимуту»
проверяется без новой инфраструктуры — просто ограничением диапазона `j`.
- Мутанты AC7 (a)-(d) однозначно целятся в четыре разных пункта контракта
(порог, форма среза, фильтр соседства, направление фаски) и их легко отличить
друг от друга — не избыточны и не дублируют друг друга.
- «Без дыр» (#302) и «без фантомов» (#271) named как обязательные инварианты с
конкретными существующими инструментами (`junctionContractHoles`,
`smoke_junction_holes.mjs`, `thickLength`-лимиты) — все три существуют и делают
заявленное.
- Аналитика issue и передача в S4 (владелец) корректно ссылаются на #302/#271 как
на предшествующие задачи, не пересекаются с #303 (заявлено и не противоречит
прочитанным документам).
- Трейлеры коммита `ff623cd4` (`Issue: #309`, `User-Visible: no`) корректны для
чисто документационного коммита.
## Чего не проверял
- Не прогонял `npx tsc --noEmit` / `npm test` / `npm run build` — на этом этапе
нет изменений кода (`git diff origin/dev...HEAD` — один файл документации),
гейты кода относятся к этапу код-ревью (§2.7).
- Не запускал `npm run golden:capture`/`golden:verify` и не пересчитывал вручную
контур `junctionContractHoles` на реальном экспорте владельца — фикстура
`test/fixtures/309-junction-teeth.json` из плана тестов ещё не создана, это
предмет реализации и последующего код-ревью.
- Не проверял файл экспорта владельца (`houseplan-space-convergence-test-...json`)
построчно на точное число «13 комнат/24 перегородки» (AC5) — доверяю
подтверждённому исполнением заявлению автора аналитики; при код-ревью это
число будет видно по фикстуре теста.
- Не оценивал `docs/USER-GUIDE.ru.md` на предмет новой терминологии — задача не
вводит пользовательских терминов (чистая геометрия рендера), проверка
нерелевантна.
## Вердикт
Жёлтый. High-находок нет. Один Medium **в скоупе** задачи: отсутствуют
обязательные разделы ТЗ §7.1 (Сценарий, Что человек увидит, явный Скоуп/не-скоуп,
UX, Модель данных и миграция, i18n, Риски, Release-артефакты) — все решаются
автором самостоятельно, без вопроса владельцу, и дописываются в этом же файле.
Технический контракт (§3.1-3.5) и критерии приёмки (AC1-7) при этом полны,
однозначны, обоснованы прочтением кода и не содержат догадок, выданных за факт.