30 KiB
SPEC-REVIEW-132-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/132
- ТЗ под ревью:
docs/specs/132-partition-openings.md(коммит2aaabc48d65b8886b72578907e9622379807d991, веткаissue/132-partition-openings-v2) - Связанный bug в том же scope: #185 (решением владельца исправляется в рамках #132)
- Роль: ревьюер ТЗ (не автор), этап
S4-spec-review - Трек: обычный (не
small/trivial) — сложность/риск 9/10, несколько поверхностей (placement, geometry, light, room-topology, HA state, i18n, golden); лёгкий трек корректно не применён, файл ТЗ вdocs/specs/создан, как требуется - Цикл: r1/4
Скоуп ревью
Проверялось соответствие ТЗ:
docs/SCOPE.md— попадание в Core user jobs (J4, с поддержкой J1/J2/J3), отсутствие расширения скоупа, lock-инвариант, правило «никогда не удалять файл по догадке» (задача файлов не касается — у openings нет attachments, §16 ТЗ это явно фиксирует);PROCESS.md§2.4, §2.5 (DoR), §7.1 (обязательные разделы), §12 (запреты);AGENTS.md— классы файлов, веткаissue/132-partition-openings-v2, трейлеры, связь issue ↔ ТЗ вdocs/specs/README.md;- канонические документы затронутых подсистем:
docs/LIGHT.md,docs/WALL-THICKNESS.md,docs/SUN.md,docs/CANVAS.md,docs/UX-MODES.md,docs/CONFIG-COMPATIBILITY.md,docs/TOUCH-SUPPORT.md— на предмет того, что технические утверждения ТЗ о «текущем поведении» не являются непроверенной догадкой, а описывают код и контракты, которые действительно существуют; docs/USER-GUIDE.ru.md— терминология («Стены», «Проём», «Перегородка», «Открытый проём») берётся оттуда, а не изобретается;- весь тред issue #132 (7 комментариев, 3 раунда вопросов владельцу, две редакции ТЗ) — на предмет того, что продуктовые вопросы были заданы владельцу, а технические автор решил сам и пометил как предположения.
Как проверялось
- Прочитан весь тред issue #132: исходный отчёт, решение владельца «проём
пропускает свет» (2026-08-13), аналитика S2 (сложность 8/10 → 9/10 после
переоценки от 2026-08-19 после релиза #173), вопросы Q1–Q3
(комментарий 2026-08-14) и их дефолты, принятые владельцем без изменений
(2026-08-15), первая редакция ТЗ (
issue/132-partition-openings, коммитda05728), актуализация аналитики после #173 (комментарий 2026-08-19, семь конкретных пунктов о том, что изменилось), вопросы Q4–Q5 и их принятие владельцем вместе с решением включить фикс #185 в этот же issue, финальная редакция ТЗ (issue/132-partition-openings-v2, коммит2aaabc4). - Прочитаны issue #173 (единый инструмент «Стены», слияние
room-outline/partition) и #157 (
passage— «Открытый проём») — оба закрыты и уже выпущены вv1.65.0-beta.2(см.git log— коммит54c5ca3 test: accept v1.65.0-beta.2 golden baselinesуже в истории до ветки задачи). Прочитан #185 (bug, замыкание контура ломается проёмом на участвующей стене) — открыт, без статусной меткиS*, что нормально: поAGENTS.md/PROCESS.md§9 issue без статуса вне процесса, а решение владельца прямо говорит, что #185 закрывается кодом #132 и тем же код-ревью — отдельная метка статуса ему на данном этапе не нужна. - Построчно сверены обязательные разделы ТЗ (
PROCESS.md§7.1) — таблица ниже. - Проверены технические утверждения ТЗ о текущем (уже реализованном)
поведении чтением реального кода на этой же ветке, а не поверено на
слово:
- §3 «current opening placement/index принимает только derived room
walls» — подтверждено:
openingWallIndex()(src/wall-thickness.ts:2059-2092) строитedgesтолько изfor (const room of rooms || [])→roomWallProfile(...);partitionsв этой функции не участвуют вовсе; - §3 «Physical union специально добавляет partitions после opening cuts»
— подтверждено:
_lightBarriers()(src/houseplan-card.ts:14157-14231) режетwallsпроёмами (openCuts/passages) и передаёт уже нарезанныеwallsвместе с сырым (без вычетов)physicalвwallBodiesGeometry(...);physicalBodySet()/physicalBodyParts()(src/physical-geometry.ts:108-173) строят тела partitions/drafts/columns и их junction-патчи независимо отopenings— ни один вызов не режет их проёмом; - §3 «#173 заменил отдельный инструмент "Перегородка" одной цепочкой
"Стены"; активные сегменты живут в
room_drafts, явное завершение превращает каждый сегмент вpartition» — подтверждено кодом (src/houseplan-card.ts:6657комментарий «Walls: every completed segment is crash-safe in room_drafts until an […] finish») и текстомdocs/USER-GUIDE.ru.md:282-315(«Выберите Стены… сегменты сохранятся обычными независимыми стенами»), а такжеdocs/CANVAS.mdразделами «Architectural connection overlay» и «Planar wall faces» (эти разделы явно добавлены после #173 и описывают именно ту архитектуру, на которую опирается ТЗ #132); - §11/#185 «room-face detection режется опенингом» — подтверждено:
buildPlanSnapGeometry()(src/plan-snap-overlay.ts:118-132) строитsourcesизroomEdges(...)и явно передаётcuts: roomCutsна каждый сегмент — то есть сегодня граф для замыкания комнаты режется по opening cuts, что и есть причина #185;docs/CANVAS.mdраздел «Planar wall faces» прямо говорит «any physical gap — including an opening cut — remains a gap» про действующее поведение; OpeningCfg(src/types.ts:169-181) сегодня не содержитhost— подтверждено; заявление ТЗ §7 «получает optional host discriminator» корректно описывает это как новое поле, а не переименование существующего.
- §3 «current opening placement/index принимает только derived room
walls» — подтверждено:
- Сверены type-specific light-правила §13 ТЗ (door/gate/passage прозрачны
при полу с обеих сторон, window всегда opaque, source внутри exterior
opening/window fail-dark) с
docs/LIGHT.md(«Deliberately opaque…», «The classifier is an explicitdoor | gate | passageallowlist») — ТЗ не придумывает новую световую семантику, а переиспользует существующую type-specific политику один в один для нового host kind. - Сверены sun-правила §14 («partition window не создаёт exterior wedge») с
docs/SUN.md(«For every opening of type "window" sitting on an EXTERIOR wall… windows on interior walls do not participate») — совпадает, это не новое правило, а прямое следствие уже принятого канона. - Сверена толщина/cut-геометрия §10 (1–100 см для partition, jamb returns,
composite cut только для collinear-покрывающих тел) с
docs/WALL-THICKNESS.md§9 («1–100 cm for draft and partition segments», «unioned with room-wall bodies only after door/window/gate cuts») — совпадает дословно. - Сверена терминология с
docs/USER-GUIDE.ru.md: «Стены» (§8, «Выберите Стены»), «Проём» (§9, подменю «Окно / Дверь / Открытый проём / Ворота»), host kind «Перегородка» (уже используется в §8 таблице инструментов и в контекстной панели, см. заголовок скриншота «Выбранная перегородка»); ТЗ не вводит новых, не согласованных с гайдом слов. - Проверено
docs/CONFIG-COMPATIBILITY.md— раздел «Open-passage opening type (#157)» уже фиксирует, чтоpassageзапрещаетcontact/lock/invert/ flip_h/flip_vдаже приnull/false; ТЗ §4 п.1 и §15 корректно этого не меняют и не противоречат зарегистрированной схеме совместимости. - Проверено
docs/TOUCH-SUPPORT.mdна предмет обязательного заявленияTouch editor: supported / best effort / not exposedдля новой editor feature — в тексте ТЗ такого явного маркера нет (см. находку Low-2). - Проверены трейлеры коммитов
b9bf210/2aaabc4(Issue: #132,User-Visible: no— верно для документации ТЗ) и двусторонняя ссылка issue ↔ ТЗ вdocs/specs/README.md:91. - Не запускал автотесты и не собирал бандл — на этапе
specэто не требуется; факты о существовании кода и функций проверены чтением файлов на диске, не исполнением.
Обязательные разделы (§7.1 PROCESS.md)
| Раздел | Есть | Комментарий |
|---|---|---|
| Сценарий (персона/поверхность/момент) | ✅ | §1 — администратор, инструменты «Стены»/«Проём», Plan editor (поверхность называется через инструменты, не текстом «Редактор плана», но однозначно определяется) |
| Что человек увидит до/после (без терминов реализации) | ⚠️ | §2 — см. Low-1: использует внутренний термин «host-сегмент» |
| Проблема и связь со scope | ✅ | §3, с подтверждённым построчно техническим диагнозом |
| Скоуп / не-скоуп | ✅ | §5 / §6, оба конкретны и проверяемы |
| Контракт поведения | ✅ | §7–17: модель данных, resolver, placement, толщина/cut, room topology (#185), floor/tunnel, light, sun, HA state/actions, move/edit/delete, orphan |
| UX | ✅ | §9 (placement), §16 (move/edit/delete) |
| i18n / accessibility | ⚠️ | §19 — строки описаны по смыслу, но не перечислены как конкретные ключи en+ru; см. Low-3 |
| Touch | ⚠️ | затронуто по существу (§9, §21, AC10), но нет обязательного по docs/TOUCH-SUPPORT.md явного маркера Touch editor: …; см. Low-2 |
| Модель данных и миграция | ✅ | §7, §18 — explicit host discriminator, backward compatibility, no schema migration |
| Критерии приёмки AC1…ACn с доказательством | ✅ | §20, 12 штук, у каждого назван тип доказательства (unit/backend/smoke/golden/reviewed golden) |
| План автотестов | ✅ | §21 — Unit/Backend/Browser smoke/Golden/Performance, конкретные сценарии |
| Риски | ✅ | §23, 9 пунктов с мерами (без явной ссылки на закрывающий AC — необязательно по §7.1, но снижает читаемость) |
| Откат | ✅ | §23, последний абзац |
| Release-артефакты | ✅ | §24 — оба changelog, USER-GUIDE.ru.md, шесть канонических документов, TESTING.md |
Все обязательные по PROCESS.md §7.1 разделы присутствуют и содержательны.
Дополнительно есть §25 «Принятые технические предположения», корректно
отделяющий свободно изменяемые технические решения от продуктовых решений
владельца.
Находки
Находок уровня High и Medium нет.
Low-1 — §2 использует термин реализации «host-сегмент»
Файл: docs/specs/132-partition-openings.md:22
Раздел «Что человек увидит до и после» обязан по PROCESS.md §7.1
формулироваться «одной фразой, без терминов реализации». Текущая
формулировка — «выбранный door/window/gate/passage вырезает её тело,
следует за конкретным host-сегментом и ведёт себя как тот же тип
проёма в обычной стене» — использует «host» и «host-сегмент», термины
модели данных этого же ТЗ (§7), которых нет ни в docs/USER-GUIDE.ru.md,
ни в обычной речи пользователя. Человек не думает про «host» — он видит,
что дверь/окно теперь можно поставить на перегородку и она держится на
своём месте при переносе стены.
Почему не блокирует: раздел присутствует, разбит на «до» и «после», и по существу корректен; страдает только буквальное соответствие «без терминов реализации». AC и контракт поведения (§7–17) не зависят от этой фразы.
Решение ревьюера: Low, не блокирует. Рекомендация — заменить «host-сегмент» на «эту перегородку» при следующей правке; можно также оставить как есть с записью здесь, так как продуктовый смысл раздела не искажён.
Low-2 — нет обязательного маркера Touch editor: … по docs/TOUCH-SUPPORT.md
Файл: docs/specs/132-partition-openings.md (раздел §9/§21, отсутствует
явное заявление)
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.» ТЗ #132 добавляет новую функциональность
Plan-редактора (placement/drag/delete проёма на перегородке) и по существу
описывает touch-поведение («Touch placement — best effort;
pointercancel/multi-touch не сохраняют draft», AC10, browser smoke «touch
cancel/pinch safety»), но нигде не даёт этой ровно сформулированной строки.
Почему не блокирует: содержательно контракт уже соответствует
best-effort политике редакторов (docs/TOUCH-SUPPORT.md: «Plan editor:
Best effort»), никакого расхождения с политикой нет — не хватает только
формальной декларативной строки, которую сам канон требует именно текстом.
View/kiosk (обязательная touch-поверхность) в этом ТЗ — чисто presentation
(рендер уже существующим пайплайном через общий resolver, AC7), интерактива
там не добавляется.
Решение ревьюера: Low, не блокирует. Рекомендация — добавить строку
Touch editor: best effort / intentionally degraded в §9 или §21 при
следующей правке.
Low-3 — i18n-раздел не перечисляет конкретные ключи en/ru
Файл: docs/specs/132-partition-openings.md:330-342
DoR-чеклист (PROCESS.md §2.5) требует на входе в «Готово к разработке»:
«i18n: ключи en + ru перечислены». §19 ТЗ описывает нужные строки по
смыслу («Стена или перегородка» в placement guidance/error», host kind
«Перегородка» и т.д.), но не называет литеральные идентификаторы ключей
(например opening_no_wall_or_partition, opening_host_kind_partition).
Почему не блокирует: выбор конкретных строковых констант — техническое решение (именование), прямо подпадающее под §25 «точная форма discriminator fields может меняться на ревью» по духу того же принципа: разработчик и ревьюер кода решают его сами, без продуктового смысла. Из текста однозначно понятно, какие строки нужны и где.
Решение ревьюера: Low, не блокирует. Рекомендация — при переводе issue
в S5-ready дописать в §19 (или в отдельном комментарии) точные ключи,
чтобы DoR-пункт был закрыт буквально, а не только по духу.
Что проверено и корректно
- Соответствие
docs/SCOPE.md. Функция закрывает J4 («от нуля до рабочего плана без внешнего SVG/YAML») и корректно поддерживает J1/J2/J3 через сохранение геометрии/contact/lock/actions идентичными room-wall contract — ни один пункт «Out of scope» не задет,passageне создаёт новую семантику (§6 явно это исключает), lock-инвариант не расширяется (§15: «Door/gate lock action остаётся единственной sanctioned opening surface», «Passage остаётся inert»). - Продуктовые вопросы владельцу заданы корректно и по существу. За три
раунда (Q1–Q3, затем Q4–Q5) все вопросы — это «что человек видит/делает»
(какие типы проёма разрешить, что видно при переносе/удалении
перегородки, нужен ли отдельный chooser при совпадении стен) или «сколько
видимых изменений входит в issue» (включать ли уже реализованный
passage, сворачивать ли фикс #185 в этот же issue) — ни одного чисто технического вопроса владельцу не передано; каждый вопрос шёл с предлагаемым default. Владелец принял все defaults без правок. - Технические решения по существу верны и отделены от продуктовых. Обширный раздел «Актуализация после #173/#157» (комментарий 2026-08-19) и итоговая ревизия ТЗ корректно диагностируют, что изменилось в кодовой базе после #173/#157/#185 и что это означает для #132 — проверено построчным чтением реального кода (см. «Как проверялось» п.4–7): расхождений между заявленным и действительным поведением не найдено. Раздел §25 явно маркирует свободно изменяемые технические предположения (форма host-поля, имя resolver'а) отдельно от settled-решений владельца (§4) — никакая догадка не выдана за факт без пометки.
- AC1–AC12 однозначны и у каждого указан тип доказательства из
допустимого по DoR перечня (unit/backend/smoke/golden/reviewed golden).
AC5 отдельно требует production-bundle smoke, который «краснеет на
origin/dev» — то есть автор заранее закладывает воспроизводимость регресса #185 тестом, который умеет падать до фикса; это ровно тот стандарт доказательства, который код-ревью потребует на следующем этапе (AGENTS.md, issue #143 про смок, не умеющий падать). - Не-скоуп (§6) корректно отсекает смежные соблазны: новый opening type, несколько host segments на один opening, конверсия старых room-wall openings в partition openings, новый способ завершения Walls chain, изменение light/window semantics, sun rays от внутреннего окна, полная touch parity редактора, свободное удаление host без confirmation — все типичные места, где скоуп мог бы незаметно расшириться.
- Миграция и совместимость (§18) корректны: старые данные не
мигрируют,
host— чисто additive optional-поле, поведение room-wall openings без host не меняется; согласуется сdocs/CONFIG-COMPATIBILITY.md(существующая запись проpassageне противоречит новым правилам). - Откат описан симметрично (§23, последний абзац): запрет на создание новых partition-host openings плюс явный запрет тихой авто-конвертации уже сохранённых host-объектов в room-wall openings.
- Release-артефакты (§24) называют реальные документы, шесть из семи
канонических файлов подсистемы (
ARCHITECTURE.md,CANVAS.md,UX-MODES.md,WALL-THICKNESS.md,LIGHT.md,SUN.md,CONFIG-COMPATIBILITY.md) плюсUSER-GUIDE.ru.md/TESTING.md/оба changelog — соответствует правилу «документация в том же коммите, что поведение». - Трассируемость:
docs/specs/README.md:91ссылается на ТЗ и на #185 одной строкой в обе стороны; трейлеры обоих коммитов ТЗ (Issue: #132,User-Visible: no) корректны для чисто документационного изменения. - Решение свернуть #185 в #132 — явное решение владельца
(комментарий 2026-08-18), а не самовольное расширение скоупа автором; в
AGENTS.md/PROCESS.mdнет запрета объединять связанный bug в feature issue по решению владельца, а отсутствие статусной меткиS*у #185 не создаёт противоречия, так как #185 явно не идёт по процессу отдельно — его код и доказательство целиком описаны AC5/AC6 этого ТЗ.
Чего не проверял
- Не проверял, что предложенный
ResolvedOpeningHostresolver (§8) реализуем без побочных эффектов на существующие_glowClipCache/OpeningWallIndexкеши — по §25 п.7 это свободно изменяемое техническое решение автора кода, предмет код-ревью, а не ревью ТЗ. - Не запускал автотесты, не собирал бандл и не гонял golden/смоки — на
этапе
specэто не требуется; существование упомянутых модулей и контрактов проверено чтением файлов на диске (см. «Как проверялось» п.4–7), не исполнением кода. - Не проверял, сколько именно golden-сцен с перегородками изменится визуально (владелец просил оценить это заранее в первом комментарии) — это в явном виде эксплуатируется через переоценку сложности 8→9/10 и через требование «reviewed Flat/Iso/Glow golden artifacts» (§24); точное число сцен — вопрос реализации и пре-релизного гейта, не ревью ТЗ.
- Не проверял реализуемость composite room-wall/partition cut в текущей
boolean-геометрии (
polyclip-ts) на предельных случаях (например, три и более совпадающих тела) — §23 называет это риском («Composite overlap режет nearby/crossing body») с мерой («collinear full-interval coverage + negative units»), достаточной для ТЗ; сама корректность реализации — предмет код-ревью на unit-тестах §21. - Не проверял статус issue #185 на предмет корректности процесса
еженедельной гигиены (issue без
S*-метки дольше некоторого срока) — это вне скоупа ревью ТЗ #132.
Вердикт
Зелёный. High: 0, Medium: 0. Три находки Low (жаргон в §2, отсутствие
обязательной по docs/TOUCH-SUPPORT.md строки Touch editor: …,
отсутствие конкретных i18n-ключей) — ни одна не блокирует переход в
«Готово к разработке»; все три — точечные текстовые дополнения, не
меняющие контракт поведения или AC. Технические утверждения ТЗ о текущем
состоянии кодовой базы (после #173/#157) проверены построчным чтением
реального кода на этой же ветке и подтверждены без расхождений. Продуктовые
вопросы за три раунда обсуждения заданы владельцу корректно (что видит/
делает человек, какой объём входит в issue) и закрыты явными решениями;
технические вопросы автор решил сам и промаркировал как предположения
(§25), не выдавая догадку за факт.