Files
houseplan-card/docs/reviews/SPEC-REVIEW-293-r1.md
T
2026-08-24 17:35:11 +03:00

15 KiB
Raw Blame History

SPEC-REVIEW-293-r1

  • Issue: https://github.com/Matysh/houseplan-card/issues/293
  • Этап: spec (PROCESS.md §2.4)
  • Заход: r1 · блокирующих циклов израсходовано 0 из 4
  • Ветка/SHA: issue/293-resize-pointer-noop @ c514b534 (комментарий автора называет этот SHA явно)
  • Артефакт под ревью: docs/specs/293-resize-pointer-noop.md (211 строк, добавлен впервые — единственный файл в git diff origin/dev...c514b534, продуктовый код не менялся)

Скоуп ревью

Первый заход — разбор полный (§2.10 не применяется, дельты предыдущего раунда нет). Проверялись: соответствие docs/SCOPE.md (Core user jobs), формальная полнота ТЗ по §7.1, однозначность и проверяемость AC1–AC10, отсутствие догадок, выданных за факт, согласованность с каноническим docs/RESIZE.md и docs/TOUCH-SUPPORT.md, терминологическое соответствие docs/USER-GUIDE.ru.md, фактическая точность утверждений о текущем коде и фикстуре.

Как проверялось

  1. gh issue view 293 --comments — тело issue + оба комментария (аналитика 2026-08-24, подготовка ТЗ 2026-08-24).
  2. git diff origin/dev...origin/issue/293-resize-pointer-noop --stat — подтверждён единственный изменённый файл, продуктового кода в диффе нет.
  3. Прочитан весь текст docs/specs/293-resize-pointer-noop.md (git show <SHA>:...).
  4. Прочитаны docs/SCOPE.md, AGENTS.md, PROCESS.md (§1–§9), docs/RESIZE.md (целиком), docs/TOUCH-SUPPORT.md (выборочно, safety-floor раздел), docs/USER-GUIDE.ru.md (раздел Resize + поиск терминов toast/Undo/preview).
  5. Численно проверено ключевое фактическое утверждение раздела 1 ТЗ: в test/fixtures/real-plan-second-floor.json room-a.poly[2] = [0.4,-0.208…]→ [0.4,1.267…], room-b.poly[2] — та же пара в обратном порядке. Общая стена с точным совпадением концов подтверждена, это не догадка.
  6. Прочитан код src/houseplan-card.ts вокруг _rszEdgeDown/_rszMove/_rszUp/ _rszApplyPreview (строки 8548–8735) — все символы, названные в ТЗ как текущее поведение, реальны и ведут себя так, как описано (projection на plan.n, заморозка последнего валидного preview при reject, _svgPoint через clientX/clientY + getBoundingClientRect).
  7. Прочитан существующий demo/smoke_room_resize.mjs целиком — его pointer() диспатчит dispatchEvent(new PointerEvent(...)) из контекста страницы, а enter()/setRooms() подставляют _tool и _serverCfg.spaces[…].rooms программно, минуя тулбар и серверную загрузку — ровно то ограничение стенда, которое issue называет ограничением собственного репро.
  8. Сверено с docs/specs/281-resize-zero-range.md и docs/specs/README.md — для установления действующей конвенции секций/индекса на этом треке.

Гейты typecheck/test/build не запускались — на этапе ревью ТЗ они не предмет проверки (нет изменённого продуктового кода); они предмет код-ревью (§2.7) после реализации.

Находки

Medium (в скоупе — жёлтый вердикт, чинится в этом же ТЗ)

M1. Отсутствуют два обязательных раздела §7.1 — «риски» и «откат».

  • Файл: docs/specs/293-resize-pointer-noop.md
  • PROCESS.md §7.1 перечисляет обязательные разделы ТЗ: «…план автотестов · риски · откат · release-артефакты». В документе нет ни одного явного или скрытого упоминания риска или отката — проверено grep -i "риск\|откат\| rollback\|revert" по всему файлу, совпадений ноль.
  • Сценарий отказа: DoR §2.5 требует «откат: как выключить или вернуть назад» и «риски перечислены» как обязательные пункты чек-листа перехода в «Готово к разработке». Без этого раздела задача формально не может уйти в DoR даже при зелёном ревью — ревьюер такого перехода не заметит, ведущему разработчику придётся дописывать по памяти позже, когда контекст уже потерян.
  • Это не абстрактная бюрократия для этой задачи: сюда стоит явно вписать хотя бы — «откат = revert коммита, флага/миграции нет, схема не меняется» и риск «дефект интеграционный, точная причина не установлена до реализации; вероятна повторная итерация по пункту 1» плюс риск для touch-режима (safety-floor уже описан в §8, но как риск-фактор не назван).
  • Фикс: дописать два коротких раздела (или один совмещённый) в ТЗ. Чисто редакторская правка, не меняет контракт.

Low (снимается с записью либо правится по желанию автора)

L1. docs/specs/293-resize-pointer-noop.md не добавлен в индекс docs/specs/README.md.

  • Каждый предыдущий P1-спек этой же подсистемы (#277, #280, #281) имеет строку в таблице ## P1 этого файла; #293 такой строки не получил (git diff origin/dev...HEAD -- docs/specs/README.md — пусто).
  • Не блокирует: README.md сам говорит, что канонический статус — в issue, а таблица — навигационная. Но конвенция для этого трека соблюдается всеми соседними спеками, и разрыв в ней имеет свойство накапливаться незаметно.
  • Можно чинить сейчас (одна строка) либо снять с записью «добавится при следующей правке README» — на решение автора.

Что проверено и признано корректным

  • Соответствие SCOPE.md. Дефект — в J6 («Keep the plan true… drag/resize»), Resize — часть закрытого core job, регрессия в нём безусловно в скоупе; никакого конфликта с out-of-scope списком нет.
  • Проблема не является догадкой. Раздел 1 фактически проверяем: топология фикстуры подтверждена численно (см. «Как проверялось», п.5), названные символы кода (_rszDrag, _rszMove, _rszApplyPreview, _rszPreview) существуют и ведут себя как описано. Утверждение «чистая математика исправна» соответствует реальной сигнатуре clampSafeResize/applySafeResize в src/resize.ts/docs/RESIZE.md.
  • Не-скоуп корректен и не расширяется. Явно исключены изменения eligibility (#292/#289/#290), partial-shared/diagonal/>2-комнатный resize, схема/storage, прямая правка геометрии фикстуры тестом — всё ровно то, что подсказывает «Не входит» issue.
  • Контракт не выдуман, а восстанавливает документированное. Раздел 3 (Pointer contract) и раздел 4 (Live overlay contract) переизлагают уже зафиксированный в docs/RESIZE.md контракт («immutable snapshot», «_serverCfg не меняется до pointerup», «один Undo», «Esc/pointercancel/ lostpointercapture — без записи») — это не новая продуктовая рамка, это починка нарушенного исполнения существующей.
  • Новая часть контракта (toast при непредсказуемом reject в разделе 2/4) не является придуманным UX. Она прямо запрошена вторым комментарием владельца («ТЗ отдельно требует отличать ошибку pointer-координат от молчаливого отката live-preview») и использует существующий механизм (_showToast, каталог toast.*, уже применяемый в resize.disabled.* по docs/RESIZE.md строка 25) — не новая абстракция.
  • AC проверяемы и способны провалиться. AC1/AC2/AC5/AC8 прямо требуют mutation-тест («мутант, перестающий обновлять d, обязан убивать смок») — это ровно дисциплина «тест умеет падать», которую требует процесс. AC не подставляют один и тот же результат под два источника: preview — из одного applySafeResize-результата, commit переиспользует тот же результат (раздел 4, согласуется с docs/RESIZE.md «Preview and commit»), правило «одно число — один источник» не нарушено.
  • Touch/безопасность. Раздел 8 согласован с docs/TOUCH-SUPPORT.md: запрет коммита от pinch/pan/pointercancel буквально повторяет safety-floor этого документа («saving unintended geometry merely because a pinch, pointer cancellation… »). Новых HA-действий или security boundary нет.
  • i18n. Раздел 9 корректно ставит добавление RU/EN ключей в условие («если добавляется runtime reason»), а не отдельным решением владельца — выбор конкретного текста тоста — техническая, не продуктовая деталь по критерию PROCESS §7.1 («именование… решает автор»).
  • Модель данных/миграция. Явно «не меняется» в метаданных документа — корректно для чистого pointer-pipeline фикса.
  • Release-артефакты. Раздел 10 корректно требует Issue:/User-Visible: yes и оба changelog в том же коммите, закрытие только через бету — соответствует AGENTS.md/PROCESS §2.8.

Чего не проверял (сознательно, вне пределов этапа spec)

  • Не проверял, действительно ли причина инертности — screen-to-SVG конвертация, а не что-то ещё. ТЗ сам явно оставляет это открытым («реализация обязана сначала зафиксировать точную причину… а затем исправить») — это корректная позиция для этапа ТЗ, не находка.
  • Не гонял typecheck/test/build. Продуктовый код не менялся (см. «Как проверялось» п.2); эти гейты относятся к код-ревью (§2.7), не к ревью ТЗ.
  • Не запускал существующие смоки (demo/smoke_room_resize.mjs и т.п.) — на этапе ТЗ смоков для новой фикстуры ещё не существует, а регресс по AC9 проверяется в код-ревью.
  • Отложенное наблюдение, не оформленное как находка: формулировка раздела 5 («отправляет browser pointer events по фактическим screen coordinates handle») не уточняет механизм диспатча. Проверка кода показала, что _svgPoint() читает только clientX/clientY + getBoundingClientRect() — эти свойства идентичны для доверенных (page.mouse.*, как в issue-репро) и синтетических (dispatchEvent(new PointerEvent()), как в текущем smoke_room_resize.mjs) событий, поэтому у меня нет доказательства, что разница механизма диспатча сама по себе объясняет расхождение issue-репро и текущего зелёного смока — выдвигать это как находку значило бы выдать догадку за факт. Указываю как риск для реализатора: если новый смок унаследует pointer()-хелпер буквально, стоит явно проверить, что он падает на dev (pre-fix) — то есть соблюсти дисциплину «тест умеет падать» именно на этой фикстуре, а не полагаться на то, что похожий хелпер уже где-то используется.

Итог

High: 0. Medium: 1 (M1, в скоупе — фиксируется автором тем же ТЗ без нового issue, #202). Low: 1 (L1, на решение автора). Продуктовый контракт технически обоснован, факты в разделе 1 проверены по коду и фикстуре, скоуп не размыт и не расширен, AC способны провалиться. Возврат — жёлтый вердикт из-за отсутствующих обязательных разделов §7.1.