diff --git a/docs/reviews/SPEC-REVIEW-132-r1.md b/docs/reviews/SPEC-REVIEW-132-r1.md new file mode 100644 index 00000000..bfe42568 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-132-r1.md @@ -0,0 +1,326 @@ +# 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 раунда вопросов владельцу, две + редакции ТЗ) — на предмет того, что продуктовые вопросы были заданы + владельцу, а технические автор решил сам и пометил как предположения. + +## Как проверялось + +1. Прочитан весь тред 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`). +2. Прочитаны 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 и тем же + код-ревью — отдельная метка статуса ему на данном этапе не нужна. +3. Построчно сверены обязательные разделы ТЗ (`PROCESS.md` §7.1) — таблица + ниже. +4. Проверены **технические утверждения ТЗ о текущем (уже реализованном) + поведении** чтением реального кода на этой же ветке, а не поверено на + слово: + - §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» + корректно описывает это как новое поле, а не переименование + существующего. +5. Сверены type-specific light-правила §13 ТЗ (door/gate/passage прозрачны + при полу с обеих сторон, window всегда opaque, source внутри exterior + opening/window fail-dark) с `docs/LIGHT.md` («Deliberately opaque…», + «The classifier is an explicit `door | gate | passage` allowlist») — + ТЗ не придумывает новую световую семантику, а переиспользует + существующую type-specific политику один в один для нового host kind. +6. Сверены 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») — совпадает, это не + новое правило, а прямое следствие уже принятого канона. +7. Сверена толщина/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») — + совпадает дословно. +8. Сверена терминология с `docs/USER-GUIDE.ru.md`: «Стены» (§8, «Выберите + Стены»), «Проём» (§9, подменю «Окно / Дверь / Открытый проём / Ворота»), + host kind «Перегородка» (уже используется в §8 таблице инструментов и в + контекстной панели, см. заголовок скриншота «Выбранная перегородка»); + ТЗ не вводит новых, не согласованных с гайдом слов. +9. Проверено `docs/CONFIG-COMPATIBILITY.md` — раздел «Open-passage opening + type (#157)» уже фиксирует, что `passage` запрещает `contact/lock/invert/ + flip_h/flip_v` даже при `null`/`false`; ТЗ §4 п.1 и §15 корректно этого не + меняют и не противоречат зарегистрированной схеме совместимости. +10. Проверено `docs/TOUCH-SUPPORT.md` на предмет обязательного заявления + `Touch editor: supported / best effort / not exposed` для новой editor + feature — в тексте ТЗ такого явного маркера нет (см. находку Low-2). +11. Проверены трейлеры коммитов `b9bf210`/`2aaabc4` (`Issue: #132`, + `User-Visible: no` — верно для документации ТЗ) и двусторонняя ссылка + issue ↔ ТЗ в `docs/specs/README.md:91`. +12. Не запускал автотесты и не собирал бандл — на этапе `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 этого ТЗ. + +## Чего не проверял + +- Не проверял, что предложенный `ResolvedOpeningHost` resolver (§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), не выдавая догадку за факт.