# SPEC-REVIEW-584-r1 **Issue:** #584 · **Этап:** ТЗ на ревью (S4-spec-review) · **Заход:** r1 · **Трек:** полный (аналитика от 2026-09-15 назвала критерий: сложность/риск >3, несколько модулей и поверхностей, меняется публичный контракт «см. в свойствах = видимый габарит»). **Вердикт: жёлтый** --- ## Скоуп проверки ТЗ живёт в теле issue #584 под заголовком `## ТЗ` (комментарий владельца от 2026-09-16T19:33:08Z объявляет переход в `S4-spec-review`). Материал ревью — текст issue на момент чтения (последний комментарий владельца, тот же, что поставил статус); отдельного файла в `docs/specs/` нет и не создаётся (архив закрыт с #517). Прочитано перед разбором: `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком (включая §2.4, §2.5, §2.7, §7.1, §12), тело issue #584 и все 6 комментариев (аналитика → вопросы Q1/Q2 → ответ владельца → полное ТЗ дизайнеру → присланный архив → приёмка пака дизайнером-исполнением). `docs/TOUCH-SUPPORT.md` и канонические документы подсистем (SUN/LIGHT/CANVAS/WALL-THICKNESS/UX-MODES/ CONFIG-COMPATIBILITY) просмотрены на предмет пересечения — задача их не затрагивает (чистая геометрия SVG-артворка мебели, не стены/не свет/не солнце). ## Как проверялось Поскольку задача целиком про фактическую геометрию, а не про рассказ, каждое фактическое утверждение ТЗ сверено с текущим состоянием репозитория (SHA `7c5fd32a`, `dev`), а не принято на слово: | Утверждение ТЗ | Проверка | Результат | |---|---|---| | 44 плановых SVG в `svg/plan`, 33 иконки в `svg/menu` | `ls assets/furniture/houseplan-0.3.0/svg/plan \| wc -l`, `svg/menu` | 44 и 33 — совпадает | | `furnitureRenderTransform` существует | `grep -r furnitureRenderTransform src/` | найден в `houseplan-card.ts`, `houseplan-editor-runtime.ts`, `furniture.ts` | | `parseSvgPath` — производственный парсер экспорта | `grep -r parseSvgPath src/` | единственное определение в `src/pdf/svg-path.ts` | | `kitchen_floor` — дизайнерский символ 60×60, `svg/plan/kitchen_floor.svg` | `pack.json` строки 580-600 | `width_cm: 60, depth_cm: 60, file: svg/plan/kitchen_floor.svg` — совпадает | | `dishwasher` — legacy-примитив 60×60, заполняющий unit box | `pack.json` (единственная запись — `svg/menu/dishwasher.svg`, без `width_cm`) + `src/furniture.ts:155` (`w: 60, h: 60, g: [box(), …]`) | подтверждено: `dishwasher` не входит в дизайнерский пак, это legacy-примитив из `RETAINED_IDS`, `box()` рисует полный `0..1×0..1` | | `plant` — единственный legacy-символ с полями 2% | `src/furniture.ts:226-231` (`e 0.5,0.5,0.22,0.22` + лучи-линии до `0.02`/`0.98`) | внешний bbox линий действительно `0.02…0.98` — 2% с каждой стороны, совпадает | | Генератор склеивает `path` простой конкатенацией `paths.join(' ')` | `scripts/generate-furniture-assets.mjs:97` | `return { d: paths.join(' '), … }` — подтверждён ровно описанный дефект | | `furniture-plan-art.generated.ts`, `furniture-plan-catalog.generated.ts` существуют | `ls src/*.generated.ts` | оба файла на месте | | `furniture:check` — существующий гейт | `package.json:47` | `"furniture:check": "node scripts/generate-furniture-assets.mjs --check"` | | `test/furniture-*.test.mjs` — свидетели AC1–AC5 | прочитаны `furniture-stroke-contract.test.mjs`, `furniture-transform-contract.test.mjs` | оба существуют, но проверяют другие контракты (#361/#376/#383 — экранный stroke-resolver, resize/rotate editor-контракт), а не bounds SVG-геометрии; конфликта с новыми AC нет, но новые unit-тесты AC1–AC4, скорее всего, лягут в отдельный новый файл — это техническая деталь, не продуктовая, решать разработчику | Все фактические утверждения ТЗ подтвердились чтением кода — ни одной догадки, выданной за факт, не найдено. Отдельно ценно, что владелец не просто принял присланный дизайнером пак на слово, а перепроверил его **тем же production-парсером**, которым будет проверяться AC1/AC2 (комментарий от 2026-09-16T19:33:08Z: 44 из 44, худшее отклонение `0.000000`) — то есть контракт уже был воспроизведён на реальных файлах ещё до передачи в разработку. ## §7.1 — обязательные разделы | Раздел §7.1 | Есть в ТЗ | Комментарий | |---|---|---| | Сценарий | ✅ «Пользовательский сценарий» | персона не названа явно (home admin), но контекст (свойства мебели, план) её однозначно определяет | | Что человек увидит до/после | ✅ | числа названы (14% по стороне, 30% по площади), формулировка «это исправление, а не регрессия» — по делу | | Проблема | ✅ | в теле issue выше `## ТЗ` («Что происходит») | | Скоуп и не-скоуп | ⚠️ распределено | явного заголовка нет; не-скоуп фактически задан AC7 («редактор, hit-area, привязка к стенам, схема конфига и i18n не меняются») — содержание есть, форма нет. Low, не блокирует | | Контракт поведения | ✅ «Контракт», пп. 1–8 | однозначен, проверен по коду (см. таблицу выше) | | UX | ⚠️ отсутствует явно | по существу — «нет нового UX-контракта» (задача не трогает интеракции), это следует из AC7, но отдельного раздела нет. Low, не блокирует | | Модель данных и миграция | ✅ «Совместимость и миграция» | миграции нет, обосновано | | i18n | ✅ | явное «новых ключей нет» | | AC1…ACn с доказательством | ✅ AC1–AC8 | у каждого назван способ (unit/golden/review/гейты) | | План автотестов | ⚠️ распределено | не оформлен отдельным разделом, но каждый AC называет тип теста и файлы перечислены в «Затронутые файлы» — по существу план есть | | Риски | ✅ «Риски» | 3 пункта, по делу (маскировка регрессии golden-пересъёмкой, склейка путей, рост ленивого чанка) | | Откат | ✅ | «возврат коммита» — корректно для непереносимой миграции | | Release-артефакты | ⚠️ частично | changelog RU+EN и golden названы (в «Затронутые файлы»), перф — отдельным разделом; **security явно не упомянут** (ни разу, даже «нет») | ## Находки ### Medium (в скоупе) — влияние на touch не названо **Файл:** тело issue #584, раздел `## ТЗ`. DoR §2.5 явно требует пункт «влияние на touch по `docs/TOUCH-SUPPORT.md` (View и киоск — блокирующие)» как обязательный, отдельно от i18n и производительности, которые в ТЗ присутствуют. В тексте ТЗ слова «touch», «тач», «киоск» не встречаются вообще — раздел не просто краток, он отсутствует. **Почему это не тривиальная придирка:** задача меняет физический видимый размер мебели **в View/kiosk тоже**, не только в редакторе (мебель рендерится и там, и там одним и тем же `furnitureRenderTransform`). AC7 отдельно фиксирует «hit-area… не меняются», что фактически закрывает главный touch-риск (позиция resize/rotate-хэндлов на touch не сдвигается), но это написано как граница скоупа редактора, а не как ответ на явно предусмотренный процессом пункт. Реальный риск, скорее всего, нулевой — но DoR требует **явного** «нет», а не выводимого из соседнего пункта. **Как закрыть:** одно предложение в ТЗ, например: «Touch: изменение непосредственно не затрагивает — View/kiosk остаются readonly-поверхностью, позиции hit-area и resize/rotate-хэндлов не пересчитываются (AC7); мебель в редакторе визуально крупнее/меньше на ту же величину, что и в View, новых touch-жестов не добавляется.» Правки кода не требует. **Серьёзность:** Medium, в скоупе — блокирует переход в DoR (§2.5 явно требует пункт), но правится добавлением одного абзаца в тот же issue, без нового цикла имплементации. Без High это жёлтый вердикт. ### Low (снимается с записью, не блокирует) 1. **Раздел «UX» и «скоуп/не-скоуп» не оформлены отдельными заголовками.** Содержание по факту присутствует: AC7 однозначно перечисляет, что НЕ меняется (редактор, hit-area, привязка к стенам, схема конфига, i18n), а отсутствие нового UX-контракта следует из того же AC7 и из того, что задача правит только артворк и генератор. Формального заголовка нет, но переспрашивать автора ради двух заголовков дороже, чем цена неясности — снимаю с записью. 2. **Security как release-артефакт не назван даже «нет».** Задача не затрагивает ни одну поверхность из OWASP-класса рисков (правятся SVG-пути мебели и генератор ассетов, без сетевых вызовов, парсинга пользовательского ввода на новом пути или новых прав) — риска не вижу, но пункт DoR формально пуст. Снимаю с записью, а не как Medium: в отличие от touch, здесь нет спорной поверхности (View/kiosk), которую процесс называет блокирующей явно. 3. **AC3 «допуск, пропорциональный масштабу» не даёт формулу.** Это разумно оставить на усмотрение разработчика (технический, не продуктовый вопрос — §7.1: «то, чего пользователь не наблюдает, агенты решают сами») при условии, что реализация зафиксирует конкретную формулу и она останется проверяемой (не «плавает» между прогонами). Ревьюер кода должен убедиться, что тест «умеет падать» на конкретной, а не абстрактной величине допуска — отмечаю на будущее, не как находку ТЗ. ## Что проверено и корректно - **AC1–AC8 однозначны и снабжены способом доказательства** (unit / unit-generator / golden / review / гейты) — ни один не пришлось домысливать. - **AC2 конкретен**: именно пара `kitchen_floor`/`dishwasher`, оба подтверждены в `pack.json`/`furniture.ts` как реальные `60×60` записи — тест будет проверять то, что реально описано в баг-репорте, а не абстракцию. - **AC4 и контракт п.6** точно описывают дефект склейки путей — сверено с реальным кодом генератора (`paths.join(' ')`), включая упомянутые `coffee_table_round`/`table_round`. - **Ни одной догадки, выданной за факт**: все количественные утверждения (44 символа, доля полей 2,7–3,9 у дизайнерских, 2% у `plant`, конкретные id и размеры) подтверждаются либо кодом, либо независимым измерением владельца тем же production-парсером, которым будет доказываться AC1. - **Продуктовые вопросы закрыты владельцем предметно** (Q1 — видимый габарит = осевая линия контура, обводка не считается; Q2 — без миграции, центрируется в прежнем физическом боксе), открытых продуктовых вопросов в тексте не осталось; технические решения (склейка путей, `plant`) вынесены в явный блок «Принятые предположения», как требует §7.1. - **Границы скоупа (AC7)** явно исключают редактор/hit-area/привязку к стенам/схему конфига/i18n — правка не расползается на соседние подсистемы. - **Откат и совместимость** описаны корректно для правки без миграции: `x/y`, `w/h`, угол, зеркалирование сохраняются, откат — реверт коммита. - **Риски названы предметно**, включая специфичный для этой задачи риск — маскировку чужой регрессии golden-пересъёмкой. ## Чего не проверял - Не проверял сам присланный дизайнером ZIP-архив (`fix-584-1`) — на этапе ревью ТЗ кода/ассетов ещё нет, они появятся в ветке реализации; их проверка (в том числе повторное измерение AC1/AC2 после факта) — предмет код-ревью. - Не запускал `furniture:check`, `npm test`, `golden:verify` и т.п. — на этом этапе нет ни одной строки кода/ассета, гейты нечего гонять; это предмет §8 на код-ревью, не ревью ТЗ. - Не проверял `docs/CONFIG-COMPATIBILITY.md` построчно на предмет дополнительных полей совместимости сверх того, что решает п.8 контракта — задача явно не трогает схему конфига (AC7), риск счёл пренебрежимым для ревью ТЗ. ## Итог ТЗ содержательно сильное: контракт точен, AC проверяемы и уже частично эмпирически подтверждены владельцем на реальном присланном паке тем же инструментом, которым будет доказываться AC1. Единственная находка, требующая правки текста, — отсутствующий явный пункт про touch/View/kiosk, обязательный по DoR §2.5. High-находок нет, поэтому вердикт жёлтый: одно предложение в ТЗ, без нового раунда имплементации. --- ## Материал раунда - **Issue:** https://github.com/Matysh/houseplan-card/issues/584 - **Материал ТЗ:** тело issue #584, раздел `## ТЗ`, редакция комментария от 2026-09-16T19:33:08Z (тот же комментарий ставит `S4-spec-review`). - **Репозиторий на момент ревью:** `dev` @ `7c5fd32a08f07177d55905d65990180a354dd0ec` (кода по задаче ещё нет — ревью ТЗ смотрит только текст issue и сверяет его утверждения с текущим `dev`). --- ## Материал раунда - Ветка: `dev`, коммит `7c5fd32a08f0` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `2b57427cbed60f2f3b46aaf1b2c9a84147a8ed88` ``` git log --all --format='%H %T' | grep 2b57427cbed6 ``` - Тело issue: `a4ec41f212d36f3ec2d313b534161c25a38e58c118ed2f7b97ab437de1593eb3` - Вердикт конвейера: `yellow` · High 0