Files
houseplan-card/docs/reviews/SPEC-REVIEW-359-r1.md
T
2026-08-28 21:08:22 +00:00

197 lines
17 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-359-r1
Issue: [#359 — Предпросмотр мебели на плане перед размещением](https://github.com/Matysh/houseplan-card/issues/359)
ТЗ: [docs/specs/359-furniture-placement-preview.md](https://github.com/Matysh/houseplan-card/blob/issue/359-furniture-placement-preview/docs/specs/359-furniture-placement-preview.md)
Материал: коммит `392ef22c` "docs: specify furniture placement preview" (единственный
файл, `docs/specs/359-furniture-placement-preview.md`, 197 строк; трейлеры
`Issue: #359` / `User-Visible: no` корректны — код не менялся).
Заход: r1 (первый раунд, раздела «Унаследовано»/«Закрытие раунда» нет).
## Скоуп
Полный трек (owner-аналитика: `small` не выполнен — «нарушен критерий "нет
нового UX-контракта"»), поэтому спек живёт файлом в `docs/specs/`, а не в теле
issue. Диапазон разбора — весь документ ТЗ плюс тело issue #359 и единственный
комментарий владельца (оценка/взятие в работу).
## Как проверялось
Ревью документа без исполнения кода:
1. `docs/SCOPE.md` — задача закрывает J4/J6 (снижение ошибок первичного
размещения мебели, «Keep the plan true» / «zero to plan GUI onboarding»);
не задевает лок-инвариант, View/kiosk, ничего из «Out of scope».
2. `AGENTS.md`, `PROCESS.md` §2.4/§7.1/§12 — обязательные разделы, формат AC,
класс изменения (C — только `docs/**`, коммит и так закрыт DoD спека).
3. Тело issue #359 и комментарий-аналитика — сверка скоупа, персоны,
поверхности (Background editor, touch best-effort), P2/сложность 4/10.
4. `docs/TOUCH-SUPPORT.md`, `docs/CANVAS.md` §9.4, `docs/USER-GUIDE.ru.md`
(раздел «Мебель», строки 1303–1311) — сверка терминологии и заявленного
поведения магнита/`Shift`/touch с каноном.
5. Чтение текущего кода без исполнения: `src/furniture.ts` (`snapFurnitureToWall`,
`furnitureCorners`, `furnitureResize`), `src/houseplan-editor-runtime.ts`
(`_furnPlace`, `_furnMoveUpdate`, `_decorShapeDown`, ветка `t === 'furniture'`
в `_decorPointerDown` ~L4090), `src/pointer-modality.ts` — проверка
технической реализуемости single-resolver и hover-condition claims.
6. `demo/golden/matrix.mjs`, `demo/golden/README.md` — сверка заявления «golden
не требуется» с существующей практикой (`hoverRoom`/`hoverDevice`,
`junction-draft-end-node-dark`).
7. `demo/smoke_furniture.mjs`, `test/furniture.test.mjs` — существующее покрытие,
на которое опирается план автотестов.
Гейты `typecheck`/`test`/`build` не гонялись: диапазон — один документ в
`docs/specs/`, продуктовый код не тронут. `check-docs.mjs`/golden/smoke не
применимы к этапу спек-ревью — они станут предметом код-ревью, когда появится
реализация.
## Находки
### Medium (в скоупе) — контрактный пункт 9 (invalid/unknown symbol) не имеет доказательства
**Файл:** `docs/specs/359-furniture-placement-preview.md`, «Контракт поведения»
п.9 (строка 83) и «Критерии приёмки» (114–141).
**Воспроизведение по тексту:** п.9 контракта формулирует два независимых
требования: «изменение Width/Depth… пересчитывает preview» и «невалидное/
неизвестное изображение не создаёт preview и не ломает редактор». AC3
доказывает только первую половину («живые размеры»). Ни один из AC1–AC8, ни
«План автотестов» (145–155) не называет проверку для случая, когда у
`_furnPalette.symbol` нет соответствующего `FurnitureGraphic`/`FurnitureSymbol`
(например, `furnitureGraphic(id)` возвращает `null` — путь уже существует в
`src/furniture.ts:362`). Риски (161–174) упоминают это как «Fail dark», но риск
— не доказательство, а декларация намерения; без названного AC/теста это
утверждение контракта останется непроверенным после реализации.
**Почему это Medium, а не High:** не блокирует реализуемость — поведение
однозначно описано («не создаёт preview и не ломает редактор»), только не
привязано к способу проверки, как того требует §7.1 DoR («каждый AC… с
указанием доказательства»).
**Как чинится в скоупе:** добавить AC9 (или расширить AC3/AC7) с явным
доказательством — unit-тест резолвера на неизвестный `symbol` либо smoke-шаг,
проверяющий отсутствие `.furniture-placement-preview` и отсутствие исключения.
### Medium (в скоупе) — «golden не требуется» слабо обосновано на фоне уже существующей практики
**Файл:** `docs/specs/359-furniture-placement-preview.md`, «Release-артефакты»
(182–190), обоснование в конце «Плана автотестов» (157–159).
**Воспроизведение:** обоснование — «smoke проверяет реальный SVG path,
computed style и позиционную геометрию, а принятие нового изображения добавило
бы дорогой платформенный шум к transient editor-only состоянию». Но
`demo/golden/matrix.mjs` уже содержит именно такие детерминированные transient
и hover-состояния без «платформенного шума»: `hoverRoom`/`hoverDevice`
(`decor-over-opaque-hover-light:452`, `hover-over-glow-dark:627`,
`hover-nested-room-dark:629`) и черновик рисования линии
(`junction-draft-end-node-dark:668`) — оба задаются программно через состояние
карточки, а не реальным движением указателя, то есть детерминированы точно так
же, как предлагает избежать этот спек. Ценность фичи — именно пиксель-точное
совпадение preview с будущим объектом (контракт п.2, UX: «ghost находится в
decor composition layer поверх сохранённого decor»); AC7 доказывает это только
через DOM/computed-style-ассерты (path `d`, `opacity`, `aria-hidden`,
`pointer-events`), которые не ловят дефекты z-order/композитинга (например,
ghost отрисован позади другого decor-объекта, либо неверный порядок слоёв
относительно сохранённой мебели) — то, что видно только на растровом
сравнении.
**Почему это Medium, а не High:** не блокирует реализацию и не касается
продуктового решения — способ проверки визуального результата чисто
технический и не требует вопроса владельцу.
**Как чинится в скоупе:** либо добавить один детерминированный golden-сценарий
для preview-состояния тем же программным способом, что и
`junction-draft-end-node-dark`, либо заменить обоснование в «Release-
артефактах» на техническую причину, специфичную для этого случая (а не общее
«transient = шум», которое опровергается двумя уже существующими сценариями в
том же файле).
Low-находок нет.
## Что проверено и корректно
- **Обязательные разделы §7.1 присутствуют полностью**: сценарий, «что человек
увидит до/после», проблема, скоуп/не-скоуп, контракт поведения, UX, модель
данных и миграция, i18n, AC1–AC8 с доказательством, план автотестов, риски,
откат, release-артефакты — плюс обе продуктовые вставки из AGENTS.md (персона/
поверхность/момент в «Сценарии»; факт без терминов реализации в «Что человек
увидит»).
- **Персона и поверхность корректны и привязаны к J4/J6** из `docs/SCOPE.md`;
задача не задевает лок-инвариант, View/kiosk, ничего из «Out of scope».
- **Touch-контракт (п.10, «Не-скоуп») дословно совпадает с
`docs/TOUCH-SUPPORT.md`**: «best effort», допустимое отсутствие hover-preview
на coarse pointer, запрет «сохранения непреднамеренной геометрии из-за pinch/
cancel/второго касания» — прямое соответствие разделу «Safety floor that
still applies to touch editors».
- **Магнит к стене и роль `Shift` (контракт п.3) технически точны и совпадают
с каноном и текущим кодом**: `docs/CANVAS.md` §9.4 («bypassing the furniture
wall magnet while the ordinary decor/room/grid magnet remains active») и
`docs/USER-GUIDE.ru.md:1305-1311` слово в слово подтверждают заявленное
поведение; в `src/houseplan-editor-runtime.ts:4653` и `:4689`
(`_furnPlace`/`_furnMoveUpdate`) уже сегодня `ev.shiftKey ? null :
snapFurnitureToWall(...)` — ровно та развилка, которую описывает спек.
- **Единый resolver (контракт п.5) технически реализуем без притягивания
сущностей.** `_furnPlace` (L4642-4675) уже сегодня — короткая
последовательность чистых вызовов (`_decorSnap` → `snapFurnitureToWall` →
clamp → сборка `DecorShape`); выделение общей чистой функции, которую
вызовут и превью, и `pointerdown`, не требует новой архитектуры.
`snapFurnitureToWall` в `src/furniture.ts:418` уже чистая и уже покрыта
сигнатурой, которую предполагает AC2.
- **Условие видимости preview (контракт п.1, «последний fine/hover-capable
mouse pointer») опирается на существующий механизм**, а не на новый: в
проекте уже есть `src/pointer-modality.ts` и задокументированное в
`docs/TOUCH-SUPPORT.md` правило «Hover is instance-local and follows the
latest real pointer input… enabled only after a mouse event when the browser
also reports fine, hover-capable hardware» — спек его переиспользует, а не
изобретает.
Симметрично п.10 («touch/pen… очищают возможный mouse-preview») — прямая
калька с «Touch and pen input immediately clear transient room and device
hover» из того же документа.
- **Терминология UX-раздела корректна**: «decor composition layer» совпадает с
комментарием в `src/houseplan-card.ts:11207` («Decor is one composition
layer above every floor treatment»); класс decor-слоя (`.decorlayer`) уже
существует (`src/houseplan-card.ts:8140`).
- **Значение opacity `0.55` не произвольно** — то же число уже используется как
устоявшийся визуальный язык проекта для «неокончательного/фонового» состояния
(`src/styles/chrome.styles.ts:151` `.tab.dragging`, `src/styles/plan.styles.ts:572,1112`,
анимации в `src/styles/base.styles.ts`), то есть не гадание, а согласованный
выбор.
- **i18n корректен**: новых текстовых строк нет, новых ключей не требуется —
документ прав, что раздел можно закрыть без i18n-плана.
- **Модель данных/миграция корректны**: `DecorShape` не меняется, preview —
чисто runtime state; откат тривиален и не требует обратной миграции.
- **Открытых продуктовых вопросов не найдено.** Отдельно проверены места,
которые могли скрывать угаданное продуктовое решение под видом факта:
выбор непрозрачности (обоснован существующим стилем, не изобретён),
отсутствие дополнительной рамки/анимации (дословно повторяет «Предлагаемое
поведение» из тела issue, не добавляет нового решения), поведение на
гибридных touch+mouse устройствах (уже решено существующим
`pointer-modality.ts`, не новый вопрос). Раздел «Принято предположительно»
содержит только технические допущения (имя state, модуль резолвера, способ
коалесцирования, состав source-contract тестов) — ровно то, что §7.1 отдаёт
автору, а не владельцу.
- **Не-скоуп чётко ограничивает риск расползания**: multi-stamp, размерные
плашки, collision detection, миграция схемы, move/resize/rotate уже
сохранённой мебели — исключены явно и обоснованно, никаких «попутных»
претензий.
## Чего не проверял
- Реализуемость AC1–AC8 в реальном браузере (Playwright) — код ещё не
написан, это предмет код-ревью на S7.
- `npm run typecheck` / `npm test` / `npm run build` / `npm run bundle:sync` /
`npm run bundle:budget` / `node demo/smoke_furniture.mjs` — не применимы,
диапазон изменения не касается `src/**`, `demo/**`, `test/**`.
- `node scripts/check-docs.mjs` — не применим, `src/**` не менялся, отпечаток
скриншотов не мог устареть от этого коммита.
- Точная формулировка будущих i18n/changelog записей (`docs/FURNITURE.md`,
`docs/USER-GUIDE.ru.md`, `docs/CHANGELOG*.md`) — они появятся вместе с
реализацией и будут предметом код-ревью, а не спек-ревью.
- Производительность render-цикла на реальном железе — риск назван и выглядит
правдоподобно (один SVG path, без пересчёта модели), но это утверждение,
которое подтвердит только код-ревью/смок, не документ.
## Вердикт
Жёлтый: 0 High, 2 Medium, оба в скоупе задачи #359 и чинятся правкой ТЗ в этом
же issue (без отдельного issue, решение владельца 2026-08-19, #202).