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

17 KiB
Raw Blame History

SPEC-REVIEW-359-r1

Issue: #359 — Предпросмотр мебели на плане перед размещением ТЗ: 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).