Files
houseplan-card/docs/reviews/SPEC-REVIEW-193-r1.md
T
2026-08-19 07:44:15 +00:00

267 lines
24 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-193-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/193
- **ТЗ под ревью:** [`docs/specs/193-passage-placement-preview.md`](https://github.com/Matysh/houseplan-card/blob/issue/193-passage-preview/docs/specs/193-passage-placement-preview.md)
(коммит `fc22d9a`), обычный трек — не `small`/`trivial`
- **Роль:** ревьюер ТЗ (не автор), этап `S4-spec-review`
- **Трек:** обычный, лимит циклов ревью ТЗ — 4 (§4 PROCESS.md)
- **Цикл:** r1/4
## Скоуп ревью
ТЗ #193: инструмент «Открытый проём» в Plan editor получает специальную
placement-preview геометрию только для `candidate.type === 'passage'` —
полупрозрачный сегмент будущего разреза стены и две поперечные засечки
границ, поверх уже существующих точки привязки и линеек. Сохранённый passage,
door/window/gate preview, конфиг, backend, i18n, миграция и touch-контракт не
меняются по тексту ТЗ.
Не в скоупе ревью: продуктовый код. Ветка `issue/193-passage-preview`
существует и содержит один коммит (`fc22d9a6c55b085dc273a70bdc8e2db22ec9fe19`,
`Issue: #193 · User-Visible: no`) — только спецификация, реализации нет.
Гейты (`typecheck`/`test`/`build`) не прогонялись: на этапе ревью ТЗ
продуктового кода не существует, прогон не относится к этому этапу
(PROCESS.md §2.4/§8).
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком (действующая
редакция, включая §1, §2.4, §2.5, §5, §7.1, §8, §10.2).
2. Прочитано тело issue #193 и оба комментария: аналитика владельца
(`Matysh`, `OWNER`, оценка 6/10 · 3/10 · 3/10 · P2 · `enhancement` ·
обычный трек, явное обоснование, почему `small`/`trivial` не подходят —
новый UX-контракт preview) и хендофф «ТЗ готово к ревью».
3. Построчно сверены все технические утверждения раздела «Подтверждённая
техническая база» (§4 ТЗ) с кодом на этой ветке:
- `_renderOpeningPlacementPreview()` — `src/houseplan-card.ts:17350`
(заявлено 17351, расхождение на строку из-за doc-комментария, не
дефект); вызывает `renderOpeningVisibleGeometry(visibleSpec)` без
ветвления по типу — подтверждает диагноз «инструмент слеп для passage».
- `renderOpeningVisibleGeometry()` (`src/render/opening-symbol.ts:51-55`):
`if (spec.type === 'passage') return svg\`\`;` — подтверждено дословно,
включая обоснование в комментарии кода («negative space», «no jamb, leaf,
arc, gate panel or standalone frame»).
- `OpeningPlacementCore` (`src/opening-placement.ts:33-48`) содержит ровно
заявленные поля: `x`, `y`, `angle`, `renderedLength`, `target`
(`OpeningPlacementTarget.physicalHalfWidth`, строка 17), `measure`,
`flipH`/`flipV`, `type` — ТЗ не выдумало ни одного поля.
- `openingPlacementTargets()` (строки 109-140): для совпадающих
`segmentKey` берёт `Math.max(previous.physicalHalfWidth, interval.half)`
— подтверждает точную формулировку ТЗ «для совпадающих room-owned копий
берётся максимальная реальная половина толщины».
- `_opMeasureView` — `src/houseplan-card.ts:11895` (заявлено 11896, тот же
класс погрешности) возвращает `OpMeasure`, используется на 15384 —
подтверждено, линейки действительно не требуют изменений.
- Порядок SVG (`src/houseplan-card.ts:15690-15733`): `_renderWallBodies()`
на строке 15723, `_renderOpeningPlacementPreview()` на 15727 — preview
реально рисуется после тел стен и будет виден поверх кладки без нового
слоя, как заявлено.
- `.opening-preview` (группа, `houseplan-card.ts:17366-17368`) уже несёт
`aria-hidden="true" pointer-events="none"`; `.opening-preview-dot`
(17371-17372) — то же. Оба факта подтверждены буквально.
- Дочерний `<circle>` точки использует `r=${this._gridPitch * 0.18}`
(17372) — подтверждает §16 п.3 ТЗ: выступ засечек, предложенный тем же
коэффициентом, действительно равен радиусу существующей preview-точки,
а не выдуман.
- Golden harness (`demo/golden/harness.mjs:450`) — allowlist
`['window', 'door', 'gate']`, требует `.op-leaf` (строка 474) —
подтверждает точный диагноз «harness валидирует только типы с
`.op-leaf` и не принимает passage».
- Golden matrix (`demo/golden/matrix.mjs:100-106`) — существует ровно один
сценарий `opening-placement-door-thick-wall-dark` (без light-варианта);
ТЗ корректно называет его существующим прецедентом и не завышает его
охват.
- CSS `.opening-preview { opacity: 0.5; }` (`src/styles.ts:1372-1375`) —
подтверждает реальность риска «opacity применяется дважды» (§13 ТЗ):
новый сегмент рисуется внутри той же группы, и наивная реализация с
`opacity: 0.35` на самом rect действительно даст `0.5 × 0.35 = 0.175`,
как явно предупреждает ТЗ. Риск назван точно, а не гипотетически.
- `demo/smoke_opening_preview.mjs:152` — `out.saveMatchesResolver` уже
существует под этим именем; AC2 ссылается на реальный, а не придуманный
идентификатор.
- `test/opening-symbol.test.mjs` существует (`ls test/`), AC3 ссылается на
реальный файл.
4. Прочитан `docs/USER-GUIDE.ru.md` (разделы 1 «Термины», 9 «Двери, окна,
открытые проёмы, ворота и замки»): терминология ТЗ («Открытый проём»,
«полупрозрачный символ», «толстая стена», ширина по умолчанию 90 см)
совпадает с существующим текстом (строки 428-465), включая уже описанное
поведение door/window/gate preview («Полупрозрачный символ показывает
точный будущий вид... виден поверх тела толстой стены», строки 437-438) —
ТЗ не изобретает интерфейсную лексику.
5. Прочитан `docs/WALL-THICKNESS.md` целиком — модель `half`/толщины,
growth ±½, masonry cuts согласуются с использованием
`target.physicalHalfWidth` в ТЗ; прямого противоречия канону нет.
6. Прочитан `docs/TOUCH-SUPPORT.md` целиком, включая «Documentation rule»
(строки 142-154): требование буквальной строки `Touch editor: …` для
«New editor feature specifications». В тексте ТЗ #193 такой строки нет
ни в одном разделе (проверено `grep -n touch` по файлу — единственное
упоминание, строка 123 Non-scope: «расширение touch-гарантий Plan editor»)
— см. находку Low-1.
7. Проверены прямые ссылки ТЗ на файлы демо/тестов —
`demo/smoke_opening_preview.mjs`, `demo/golden/matrix.mjs`,
`demo/golden/harness.mjs`, `test/opening-symbol.test.mjs` — все существуют
и содержат ровно те механизмы, на которые ссылается ТЗ (см. п.3 выше).
8. Проверено соответствие `docs/SCOPE.md`: задача — геометрия Plan editor
(J4 «zero to a working plan... room polygons» / J6 «keep the plan true as
the home evolves», обе персона home admin, поверхность — desktop Plan
editor), View/kiosk/backend не затрагиваются; попадает в допустимый скоуп,
не создаёт нового job'а. Родитель #157 (сам open passage) уже принят и
закрыт как реализованный J4/J6-функционал — #193 закрывает его собственный
UX-пробел, не расширяя скоуп.
9. Проверено, что issue не помечен `small`/`trivial`: аналитика владельца
явно называет причину (новый UX-контракт preview) — критерий §5 PROCESS.md
«нет нового UX-контракта» не выполняется, обычный трек обоснован корректно.
Гейты (`typecheck`/`test`/`build`, browser smoke, golden) не прогонялись —
продуктового кода нет, что и ожидается на этапе ревью ТЗ.
## Обязательные разделы (§7.1 PROCESS.md)
| Раздел | Есть | Комментарий |
|---|---|---|
| Сценарий (персона/поверхность/момент) | ✅ | §1: home admin, desktop Plan editor, наведение инструментом до клика |
| Что человек увидит до/после | ✅ | §2: одна фраза до/после, без терминов реализации |
| Проблема | ✅ | §3, подтверждена чтением кода (см. «Как проверялось» п.3) |
| Скоуп / не-скоуп | ✅ | §5/§6, скоуп ограничен геометрией+визуалом+тестами, не-скоуп явно исключает drag, символ passage, i18n, touch-расширение |
| Контракт поведения | ✅ | §7 (появление, hover/клик, после сохранения, остальные типы, события/доступность) |
| UX | ✅ | §8, таблица состояний, 7 строк |
| Модель данных и миграция | ✅ | §9, «не меняется», обоснованно для editor-only transient preview |
| i18n | ✅ | §10, «новых строк нет» |
| AC1…ACn с доказательством | ✅ | §11, 6 штук, у каждого назван способ доказательства (unit/smoke/golden/source contract) |
| План автотестов | ✅ | §12, unit/smoke/golden по отдельности, falsifiability явно потребована («на `origin/dev` до реализации обязаны краснеть») |
| Риски | ✅ | §13, 8 строк риск/мера, включая подтверждённый двойной-opacity риск |
| Откат | ✅ | §14, удаление ветки/helper/стилей/тестов, без миграции |
| Release-артефакты | ✅ | §15, оба changelog, RU/EN guide, TESTING.md, dist-синхронизация, golden candidates отложены на release runbook |
Присутствует также обязательный блок «Принято предположительно» (§16, 5
пунктов) — технические, не продуктовые решения, корректно отделены от
продуктовых вопросов, которых, по утверждению автора и аналитики, для этой
задачи нет. Проверка не нашла в §16 замаскированного продуктового вопроса:
все пять пунктов (расположение чистого helper'а, имена классов, коэффициент
выступа засечек, общая golden-fixture, поведение при нулевой толщине)
касаются исключительно того, чего пользователь не наблюдает или что уже
предрешено принципом «не выдумывать несуществующую толщину».
## Находки
### Low-1 — отсутствует обязательная декларация `Touch editor: …`
**Файл:** `docs/specs/193-passage-placement-preview.md`, весь документ (нет
подходящего раздела); ближайшее место — §6 Non-scope, строка 123.
`docs/TOUCH-SUPPORT.md`, раздел «Documentation rule» (строки 142-154):
«New editor feature specifications and code reviews must state one of:
`Touch editor: supported`; `Touch editor: best effort / intentionally
degraded`; `Touch editor: not exposed`.» ТЗ #193 добавляет новую видимую
geometry в Plan editor (`hover`-preview) — то есть однозначно подпадает под
«new editor feature specification». Полнотекстовый поиск `touch` по файлу
даёт единственное совпадение (строка 123 Non-scope: «расширение
touch-гарантий Plan editor»), которое по смыслу подразумевает ответ (не
расширяем существующий best-effort статус), но не содержит требуемой
буквальной строки. Тот же класс проверки в `SPEC-REVIEW-192-r1` (issue #192)
нашёл строку `**Touch editor:** picker поддерживается как в #57` и закрыл
вопрос без находки — здесь эквивалентной строки просто нет.
**Обоснование severity:** ответ не является продуктовой неопределённостью —
Plan editor по умолчанию desktop-first/best-effort (`docs/TOUCH-SUPPORT.md`,
таблица «Product contract»), preview — чисто presentation, hover-based
эффект, на устройстве без реального hover просто не активируется тем же
путём, что и сейчас для door/window/gate; отдельного решения владельца не
требуется. Дефект — отсутствие обязательной для канона декларации, не
дефект поведения. AC1…AC6 не зависят от этой строки.
**Решение ревьюера:** Low, не блокирует. Снимаю без возврата ТЗ на правку с
условием: автор добавляет в ТЗ (в §6 Non-scope или отдельной строкой рядом
с §7.5) явную декларацию `**Touch editor:** best effort / intentionally
degraded (наследует существующий статус Plan editor; preview — presentation-only
hover-эффект, не расширяет touch-гарантии)` до перевода issue в
`S5-ready` — это чисто редакционное дополнение, не требующее нового цикла
ревью ТЗ, и должно быть зафиксировано в хендоффе реализации или проверено
на код-ревью, если автор пропустит этот шаг.
## Что проверено и корректно
- **Соответствие `docs/SCOPE.md`:** J4/J6, персона home admin, desktop Plan
editor; View/kiosk/backend не затронуты; #193 закрывает собственный
UX-пробел уже принятого #157, не расширяя его скоуп.
- **Легитимность обычного трека:** аналитика владельца верно называет
причину, по которой `small`/`trivial` не подходят (новый UX-контракт
preview) — не самоощущение исполнителя, а проверяемый критерий §5
PROCESS.md.
- **Продуктовых вопросов владельцу нет и не додумано новых.** Единственный
потенциально продуктовый вопрос — объём видимого эффекта (сегмент + две
засечки, без нового цвета/настройки) — уже зафиксирован самим текстом
issue (владелец); ревью не нашло скрытой догадки, выданной за факт: каждое
утверждение §4 «Подтверждённая техническая база» проверено построчно по
реальному коду ветки (см. «Как проверялось» п.3) и подтвердилось без
исключений.
- **Технический диагноз проблемы не голословен** — `renderOpeningVisibleGeometry`
дословно возвращает пустой SVG для `passage`, `_renderOpeningPlacementPreview`
дословно не ветвится по типу; диагноз «слепой инструмент» доказан кодом.
- **Скоуп реально согласован с существующим pipeline:** `OpeningPlacementCore`
уже содержит все поля, на которые ссылается ТЗ (`x`, `y`, `angle`,
`renderedLength`, `target.physicalHalfWidth`, `measure`) — реализация не
потребует нового вычисления геометрии, только новую ветку рендера, как и
заявлено.
- **Риск двойного `opacity` реален, а не гипотетичен** — `.opening-preview`
действительно несёт CSS `opacity: 0.5` (`styles.ts:1372`); ТЗ верно
определило нужную меру (отдельная стилевая область + computed-value
проверка в AC6) до того, как эта ошибка попала бы в реализацию.
- **AC1–AC6 однозначны и проверяемы**, каждый снабжён допустимым по §2.5
PROCESS.md способом доказательства (unit/smoke/golden/source contract);
план автотестов (§12) явно требует falsifiability до реализации —
«unit геометрии и smoke DOM обязаны краснеть на `origin/dev`».
- **Другие типы openings защищены negative-контрактом** (AC4) и
существующим door-golden, который явно не меняется — предотвращает
случайную регрессию window/door/gate preview.
- **Существующие файлы и идентификаторы, на которые ссылается ТЗ, реальны**
— `demo/smoke_opening_preview.mjs` (`saveMatchesResolver`, строка 152),
`test/opening-symbol.test.mjs`, `demo/golden/harness.mjs` (allowlist
`['window','door','gate']`, строка 450), `demo/golden/matrix.mjs`
(`opening-placement-door-thick-wall-dark`, строка 100) — ни одна ссылка не
выдумана.
- **Откат тривиален** — preview-only ветка/helper/стили/тесты удаляются без
миграции данных; #157 продолжает работать без деградации.
- **Release-артефакты** ссылаются на существующие файлы (`docs/CHANGELOG.md`,
`docs/CHANGELOG.ru.md`, `docs/USER-GUIDE.ru.md`, `docs/TESTING.md`) и
корректно откладывают golden baseline acceptance на release runbook, а не
выдают его за часть implementation loop.
- **«Принято предположительно» (§16)** не маскирует продуктовый вопрос под
техническое решение — все пять пунктов однозначно технические.
## Чего не проверял
- Реализацию — её нет: ветка содержит один документный коммит
(`fc22d9a`, `docs/specs/193-passage-placement-preview.md`), продуктовый код
(`src/**`) не менялся — проверено `git show fc22d9a --stat`.
- Гейты `typecheck`/`test`/`build`/browser smoke/golden — не относятся к
этапу ревью ТЗ; предмет будущего код-ревью (PROCESS.md §2.4/§8).
- `python -m pytest tests_backend` — задача не затрагивает backend ни кодом,
ни ТЗ.
- Реальный визуальный результат (точные пиксельные пороги AC6, computed
`fill-opacity` в браузере) — CSS/рендер ещё не написаны; заявленные меры
(отдельная стилевая область, semantic pixel guard внутри `.wallbody-fill`)
зафиксированы в ТЗ как план, не как исполненный факт.
- Точность числовых оценок аналитики (ценность 6/10, сложность 3/10, P2) по
существу — поле владельца (PROCESS.md §2.2), уже принятое явным решением
до написания ТЗ.
- Возможность технического спора автор/ревьюер по деталям §16 — не
возникла: все пять пунктов приняты как разумные без необходимости
оспаривать.
## Вердикт
Зелёный. High: 0, Medium: 0, Low: 1 (отсутствует буквальная декларация
`Touch editor: …`, обязательная по `docs/TOUCH-SUPPORT.md` для новых
editor-фич; ответ однозначен — best effort, наследуется от существующего
статуса Plan editor — снимается без возврата ТЗ на правку, с условием
дописать строку до `S5-ready`). ТЗ подтверждено построчным чтением кода на
ветке задачи: каждое техническое утверждение §4 оказалось фактом, а не
догадкой; единственный потенциально продуктовый вопрос уже закрыт
владельцем в теле issue; AC1–AC6 однозначны, проверяемы и снабжены
допустимыми способами доказательства; риски названы точно, включая реально
существующий риск двойного `opacity`.
**Вердикт: зелёный · цикл r1/4 · High: 0 · Medium: 0 → нет · Документ:
docs/reviews/SPEC-REVIEW-193-r1.md**