Files
houseplan-card/docs/reviews/SPEC-REVIEW-132-r1.md
T
2026-08-18 21:33:56 +00:00

327 lines
30 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-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), не выдавая догадку за факт.