12 KiB
SPEC-REVIEW-304-r1
Issue: #304 «Редактор плана: режим «Толщина» скрывает часть осевых линий и узлов стен»
Этап: spec (лёгкий трек, метка small)
Заход: r1 · блокирующих циклов израсходовано 0 из 2 (лимит лёгкого трека — 2)
Ревьюер: Claude (сессия ревью ТЗ), артефакт — комментарий в issue + этот документ (по правилу §5 документ не обязателен, но публикуется для трассируемости).
Скоуп
ТЗ живёт в теле issue #304 (комментарий владельца от 2026-08-25 подтверждает small).
Задача: unify статический слой архитектурных осей/узлов Plan editor (plan-snap-overlay)
так, чтобы он не зависел от активного MarkupTool — сейчас полный слой рендерится
только для draw, а в остальных девяти инструментах его подменяет
hidden-wall-diagnostic-overlay, который по контракту показывает только скрытые
перекрывающиеся независимые источники (draft/partition), а не полную геометрию
комнат. AC1–AC7, план проверок, риски, откат и release-артефакты присутствуют.
Продуктовая рамка: J6 SCOPE.md — «Keep the plan true as the home evolves», конкретно «достоверная топология при поддержании плана». Owner-комментарий явно ссылается на J6. Соответствие подтверждаю: задача не расширяет и не меняет ни один Core user job, чинит расхождение представления внутри уже принятого поведения инструмента «Стены».
Как проверялось
Ревью ТЗ выполнялось не на веру автору, а с чтением текущего кода — задача описывает конкретный технический механизм бага, и его нужно было заземлить, а не принять как заявление:
docs/SCOPE.md,AGENTS.md,PROCESS.md§2.4/§2.10/§5/§7.1/§7.2/§12 — рамка процесса и лёгкого трека.- Тело issue #304 и оба комментария владельца (аналитика + передача на ревью).
docs/USER-GUIDE.ru.md:373-379— подтверждает, что текущая документация уже фиксирует осевые линии как поведение именно инструмента «Стены»; после фикса это предложение придётся расширить на все инструменты Plan editor, что ТЗ и заявляет в Release-артефактах.src/houseplan-card.ts:588—type MarkupTool = 'select' | 'draw' | 'column' | 'merge' | 'split' | 'resize' | 'opening' | 'boundary' | 'wallthick' | 'delroom'— ровно десять значений, как в AC2, не выдумка;1524-1526—get _markup(): boolean { return this._mode === 'plan'; }— статический слой физически не может просочиться в View/Device/Backdrop-editor, потому что все точки рендера overlay обёрнуты вthis._markup ? … : nothing; AC5 — не гипотеза, а прямое следствие текущей структуры кода;17565_renderWallBodies,17572-17573_renderHiddenWallDiagnosticOverlay(без условия на_tool, то есть рендерится во всех markup-инструментах),17578-17579${this._markup && this._tool === 'draw' ? … this._renderPlanSnapOverlay() … : nothing}— это буквальное подтверждение корневой причины из issue: полныйplan-snap-overlayгейтится наdraw, diagnostic-слой остаётся один во всех остальных.
src/plan-snap-overlay.ts:166-215buildHiddenWallDiagnosticGeometry—hidden = sources.filter(source => source.kind !== 'room' && sources.some(other => other !== source && positiveCollinearOverlap(...)))— буквально подтверждает контракт «показывает только скрытые перекрывающиеся независимые источники», room-only рёбра без партнёра по перекрытию никогда сюда не попадают. Это и есть причина «части осей нет в Толщина» — не домысел автора ТЗ, а прочитанный код;228buildPlanSnapGeometry— существующая каноническая геометрия, которую фикс предлагает переиспользовать для статического слоя во всех инструментах (contract p.3);
demo/smoke_plan_snap_overlay.mjs:236-239—result.otherPlanToolsHaveHiddenDiagnostic = !overlay() && !!diagnostic && …— буквально закрепляет!overlay()(полного слоя нет) для инструментаselectкак ожидаемое поведение. Это подтверждает заявление issue «smoke сейчас закрепляет это расхождение вместо проверки межинструментального инварианта» — не голословно, а строкой теста.demo/golden/matrix.mjs,demo/golden/harness.mjs— существуют, ссылка в скоупе ТЗ не на вымышленную инфраструктуру.docs/TOUCH-SUPPORT.md— grep по overlay/snap не дал контрактных ограничений на этот слой; заявление «touch-контракт не расширяется» не противоречит канону (слой был и остаётсяpointer-events: none, десктоп-first редактор не меняется).docs/WALL-THICKNESS.md,docs/CANVAS.md,docs/UX-MODES.md— grep по overlay/wallthick не нашёл предписаний, которым ТЗ противоречило бы.
Находки
Нет находок уровня High или Medium. Задача полностью укладывается в лёгкий трек:
сложность/риск, заявленные владельцем (3/10 и 3/10), подтверждаются техническим
разбором — правка изолирована в рендер-гейтинге одного файла плюс возможное
разделение геометрии в plan-snap-overlay.ts, без миграции, новых i18n-ключей,
нового UX-контракта (видимое поведение просто перестаёт зависеть от инструмента —
это и есть заявленный "после") и без touch-влияния.
Low, не блокирует, не требует правки:
- AC1 фиксирует магическое число «шесть узлов» для контрольного участка. Это не
домысел: число заземлено в приложенных к issue скриншотах (
2026-08-25_15-46-52.png— полный вид «Стены») и будет закреплено unit-фикстурой на этапе реализации (план проверок, п.1). Претензий к формулировке нет, отмечаю для полноты «чего не проверял».
Что проверено и корректно
- Обязательные разделы §7.1 (для лёгкого трека — §5: проблема, контракт, AC1…ACn с доказательством, откат) присутствуют, ТЗ фактически даёт больше, чем требует лёгкий трек (сценарий, эдж-кейсы, риски, release-артефакты).
- Каждый AC (1–7) однозначен, имеет способ доказательства (
unit/smoke/golden/ «ревью кода») и указывает конкретный наблюдаемый инвариант, а не реализацию. - Корневая причина бага, названная в issue, дословно подтверждается кодом — это не «догадка, выданная за решение»: технический механизм проверен построчно (см. выше).
- Контракт поведения (пп.1–7) полон: перечисляет все 10 инструментов, явно разделяет
статическую топологию и transient-состояния инструмента «Стены», фиксирует
presentation-only и
pointer-events: none, границы режимов (View/Device/Backdrop) и отсутствие промежуточного пустого кадра при переключении. - Скоуп/не-скоуп корректно исключают алгоритмы привязки/записи толщины и построения стен — фикс ограничен представлением, не моделью.
- Совместимость, миграция, i18n, touch, performance разобраны и обоснованы отсутствием влияния, а не пропущены молчанием.
- Риски (двойная отрисовка, порядок рендера, случайная утечка transient-маркеров, инвалидация кэша на pointermove) названы предметно и совпадают с зонами, которые я сам выявил бы при чтении кода рендер-конвейера — заявка не занижает риск.
- Откат тривиален и реалистичен (revert рендер-гейтинга, схема/данные не меняются).
- Owner-аналитика подтверждает соответствие job J6, дубликаты проверены и отклонены предметно (#232 — про другое: hover при рисовании).
Чего не проверял
- Не проверял на глаз итоговое число узлов/сегментов для контрольной fixture построчным
выполнением
buildPlanSnapGeometry/buildHiddenWallDiagnosticGeometry— это задача unit-теста на этапе реализации, а не спецификации; на этапе ТЗ важно было убедиться, что функции и семантика существуют и соответствуют описанному контракту, что сделано. - Не запускал
npm test/npm run build/typecheck— на этапе ревью ТЗ гейты кода не прогоняются (нечего собирать, код не менялся), это относится к этапу код-ревью (§2.7). - Не проверял golden/смоук-набор целиком — задача их ещё не создала (в скоупе только план их появления), это тоже предмет код-ревью.
- Не оценивал реальный экспорт
houseplan-space-convergence-test-...json, приложенный к issue (файл не запрашивался и не был нужен: код уже подтверждает механизм независимо от конкретного экспорта).
Вердикт
Зелёный. ТЗ проверяемо, однозначно, технически заземлено чтением кода, укладывается в лёгкий трек и закрывает продуктовую строку J6. High/Medium-находок нет.