Files
houseplan-card/docs/reviews/SPEC-REVIEW-199-r1.md
T
2026-08-22 13:07:27 +00:00

18 KiB
Raw Blame History

SPEC-REVIEW-199-r1 — Optimize geometry preflight

  • Issue: https://github.com/Matysh/houseplan-card/issues/199
  • ТЗ: docs/specs/199-optimize-geometry-preflight.md, коммит 0ac63b6 на ветке issue/199-optimize-geometry-preflight (родитель c397b78 → 4dbdb44 → dev@0d91c1e на момент ревью).
  • Трек: обычный (issue не помечен small), файл ТЗ обязателен и создан.
  • Заход: r1 (первый цикл этого этапа для #199).
  • Вердикт: см. итог ниже.

Скоуп ревью

Разобрано ТЗ целиком: сценарий, продуктовые решения владельца (Q1–Q3 в комментариях issue), контракт preflight (§7), UX-контракт (§8), backend/ atomicity (§9), производительность (§10), AC1–AC14 (§11), план тестов (§12), риски (§13), touch (§14), release-артефакты и rollback (§15), блок принятых технических предположений (§16).

Ревью строго по PROCESS.md §2.4: ищу, где ТЗ невыполнимо или непроверяемо, а не соглашаюсь с автором. Устных пояснений автора не было — только issue, комментарии и файл ТЗ.

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

  1. Прочитаны docs/SCOPE.md, AGENTS.md, PROCESS.md целиком.
  2. Прочитано тело issue #199 и все 6 комментариев (аналитика, продуктовые вопросы Q1–Q3, решения владельца, хендофф автора ТЗ).
  3. Прочитан файл ТЗ целиком (419 строк).
  4. Каждая техническая ссылка ТЗ на существующий код проверена по origin/dev (не по слухам автора):
    • все перечисленные символы (_openAlignDialog, optimizePlans, wallBodiesGeometry, floorFootprintGeometry, _runAlignToGrid, _renderAlignDialog, contentFingerprint, resolveOpenCuts, wallIntervals, partitionOpeningHasCompositeRoomWall, physicalBodyParts, spaceModels, NORM_W, GRID_STEP_N, GRID_PITCH, gs.align_none, gs.align_done) найдены git grep в заявленных файлах;
    • прочитан код _openAlignDialog / _runAlignToGrid / _renderAlignDialog (src/houseplan-card.ts:14903-15985) — подтверждает описанный в ТЗ текущий контракт диалога (changed:false → gs.align_none без Apply, changed:true → отчёт + Apply);
    • прочитаны оба реальных вызова wallBodiesGeometry() в продуктовом коде: iso-путь (_isoSource, строка ~5206) бросает Error при null и наличии стен/тел — это ровно тот "structural failure", который ТЗ требует ловить; light-barrier-путь (~15652) деградирует мягко (fallback на сырой контур). Плюс _wallUnionGeometry (~11259) — путь, которым реально рисуется масонри в 2D-виде: при null/пустом union он тоже молча откатывается на paperRoomShapes, то есть именно так пользователь видел "исчезнувшую кладку" в #197;
    • прочитан wallBodiesUnionPath() и floorFootprintGeometry() в src/wall-thickness.ts — подтверждает различение ТЗ §7.3 между null (structural failure) и «успешный пустой результат» (united && !d → не ошибка, просто нет масонри);
    • подтверждён large-house fixture (demo/fixtures/large-house.mjs): FLOOR_COUNT=3, ROOMS_PER_FLOOR=20 → 60 комнат, OPENING_COUNT=100, PARTITION_COUNT=60, COLUMN_COUNT=40 — числа в §10 ТЗ совпадают буквально;
    • проверены мутационные ID в scripts/mutation-gate.mjs — формат (kebab-case, уже есть прецеденты вроде union-failure-kills-space, union-failure-silent) совпадает с предложенными в ТЗ ID.
  5. Прочитан docs/CANVAS.md §9.5 («Оптимизировать планы») — контракт диалога, optimizePlans, one-deep snapshot, «отчёт — верхняя граница» совпадают с тем, как ТЗ их описывает.
  6. Прочитан docs/TOUCH-SUPPORT.md — safety floor («no data corruption or silent loss of saved plan data») дословно то основание, на которое ссылается ТЗ §1/§14.
  7. Прочитан docs/USER-GUIDE.ru.md (разделы про Optimize/толщину стен) — используемая в ТЗ терминология («Общие настройки → Оптимизировать планы», «предпросмотр», «отмена») не изобретена, а взята оттуда.
  8. Сверены два ближайших прецедента той же подсистемы для проверки единообразия: docs/specs/223-optimize-coordinate-canonicalization.md и реализация #224 (git show 4a798e3 --stat) — чтобы понять, что в этом диалоге принято фиксировать в ТЗ дословно (RU+EN строки диалога) и что действительно исторически остаётся вне DoD (английский USER-GUIDE.md в #223/#224 не трогался — значит расхождение с ним не считается дефектом и в ТЗ #199 тоже).
  9. Проверен формат записи docs/specs/README.md (двухколоночная таблица issue↔ТЗ, без колонки «Статус ТЗ») — соответствует актуальному состоянию PROCESS.md §7.3 п.1.

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

Находки

Medium (в скоупе — чинится в этом же ТЗ)

M1. Английский текст сообщения об отказе и подсказки не зафиксирован дословно — только «эквивалент», что делает AC6 непроверяемым для EN-локали.

  • Файл: docs/specs/199-optimize-geometry-preflight.md, §8.1.
  • Текущий текст:
    - RU: «Не удалось безопасно проверить геометрию следующих пространств:
      {spaces}{more}.»
    - RU hint: «Планы не изменены. Обновите House Plan и повторите. Если ошибка
      останется, приложите экспорт пространства к отчёту об ошибке.»
    - EN: equivalent plain-language text without implementation terms.
    
    Для двух главных предложений диалога EN-строка не приведена вообще — только директива «эквивалентный текст». При этом в том же §8.1 для меньших подстрок (fallback-имя Space N, суффикс and N more) EN дан дословно — несогласованность внутри одного раздела.
  • Почему это находка, а не техническая деталь: это пользовательский текст нового диалогового состояния, то есть ровно то, что PROCESS.md §7.1 требует фиксировать в ТЗ, а не отдавать на изобретение при реализации («Владельцу задаются только продуктовые вопросы» не означает, что английскую копию придумывает реализатор без утверждённого текста — второй язык тоже наблюдаем пользователем).
  • Прецедент того же диалога: docs/specs/223-optimize-coordinate-canonicalization.md §7 добавляет новую строку в тот же Align-диалог и даёт пару целиком:
    - RU: «обновлено пространств: {c}; устранён шум координат: {p}»;
    - EN: «spaces updated: {c}; noisy coordinate values removed: {p}».
    
    #199 — прямое продолжение той же серии задач (#197/#198/#223/#224/#229) над тем же диалогом; стандарт "RU+EN дословно" в ней уже установлен и не должен тихо ослабляться для более заметного по важности сообщения (блокирующий отказ, а не информационная строка).
  • Как воспроизвести проверку: открыть §8.1 ТЗ, попытаться написать unit-тест на AC6 («i18n/UI unit») для английской локали — не из чего писать assertion на конкретную строку, только на «что-то не техническое».
  • Фикс: дописать в §8.1 дословный EN-эквивалент RU-сообщения и RU-hint (по образцу уже данных в том же разделе пар "Space N"/"and N more").
  • Серьёзность: Medium, в скоупе задачи. High-находок нет, поэтому цикл жёлтый, правка — в этом же ТЗ, без нового issue (#202).

Low (снимается без действия, для протокола)

  • Хендофф-комментарий автора ТЗ в issue содержит артефакт шаблона: «Коммит: $sha.» — переменная не подставлена. Это дефект комментария, не ТЗ; на содержание, полноту и проверяемость документа docs/specs/199-*.md не влияет. Не блокирует, отмечаю для протокола; ревьюер не правит issue-комментарии.

Что проверено и корректно

  • Продуктовая рамка (§1–§2). Персона (домашний администратор), поверхность («Общие настройки → Оптимизировать планы») и момент названы; предложение «до/после» дано без терминов реализации. Явно назван J6 из SCOPE.md и safety floor TOUCH-SUPPORT.md — оба подтверждены чтением исходников этих документов, ссылка не декоративна.
  • Проблема подтверждена по коду, а не заявлена. Пять шагов §3 (preview → candidate → Apply → backend без polyclip-проверки) построчно совпадают с реальным _openAlignDialog/_runAlignToGrid в src/houseplan-card.ts; в частности, реальный fallback _wallUnionGeometry → paperRoomShapes при null/пустом union — это ровно механизм "молча исчезнувшая кладка" из #197, на который ссылается ТЗ.
  • Различение null (structural failure) и «успешный пустой результат» (§7.3) — не абстракция, а точное описание существующего поведения wallBodiesUnionPath()/floorFootprintGeometry(): первое — жёсткая ошибка (iso-путь её бросает исключением), второе — легитимный «пустая комната без кладки» результат, который renderer рисует как обычные комнаты. AC2/AC3 формулируют именно эту границу, и она проверяема.
  • Продуктовые решения владельца (Q1–Q3) перенесены в контракт без искажения: whole-plan block при любом failure (§7.1, AC4), блокировка уже деградированного пространства без исключения «не стало хуже» (§4 п.2, AC5), сообщение с именами пространств без технических деталей и без кнопки Apply (§8.1, AC6) — всё совпадает с текстом решений владельца в issue.
  • Non-scope (§6) явно исключает ремонт найденной геометрии, частичный Apply, смену geometry engine, миграцию схемы, изменение времени жизни Undo — снимает риск расползания скоупа, который прямо запрещён PROCESS.md §12.
  • Performance-бюджет (§10) — не оценка на глаз. Числа fixture (3 пространства/60 комнат/100 проёмов/60 partitions/40 columns) и базовое измерение (median 155.9 ms / p95 162.56 ms / max 163.67 ms) сверены с demo/fixtures/large-house.mjs и являются реальным замером автора, а не придуманным ориентиром; относительный бюджет (20%+15ms поверх прямого builder) корректно отделяет overhead preflight-обвязки от уже дорогого polyclip-прохода.
  • Backend/atomicity (§9) не добавляет новый параметр endpoint и не требует второй реализации polyclip на Python — согласуется с Non-scope и с описанным в CANVAS.md §9.5 контрактом одной атомарной транзакции.
  • AC1–AC14 — каждый пронумерован, однозначен и указывает способ доказательства (unit/smoke/golden/mutation/benchmark/code review), что требуется §2.5 DoR.
  • Блок §16 «Принятые технические предположения» корректно отделяет ненаблюдаемые пользователем решения (имена модулей, fingerprint-механизм, отсутствие preflight_passed в backend) от продуктового контракта и явно помечен как «ревьюер может оспорить» — соответствует PROCESS.md §7.1.
  • Формальная структура ТЗ покрывает все обязательные разделы §7.1 (сценарий · что видит человек · проблема · скоуп/не-скоуп · контракт · UX · данные/миграция · i18n · AC с доказательством · план тестов · риски · откат · release-артефакты); запись docs/specs/README.md обновлена в формате, актуальном после чистки колонки «Статус ТЗ».
  • Rollback (§15) — простой revert реализационного коммита, персистентные данные не меняются новым preflight, что верно логически (preflight ничего не пишет и не канонизирует сверх уже полученного Optimize-кандидата, §7.3).

Чего не проверял

  • Не проверялся сам будущий код реализации — его ещё нет, ревью только ТЗ.
  • Не запускались typecheck/test/build — на этапе ревью ТЗ продукт не меняется, гейты неприменимы.
  • Не оценивалась реальная производительность на CI-машине (замер в ТЗ — замер автора на его Windows-чекауте); согласно ТЗ §10 это будущая забота code-review при нестабильности абсолютного бюджета в CI.
  • Не проверялись мутационные тесты scripts/mutation-gate.mjs предметно (их ещё нет для #199) — сверен только формат ID с существующими записями.
  • Не исследовался docs/CONFIG-COMPATIBILITY.md построчно — ТЗ прямо заявляет «без изменений и миграции», и этот пункт не оспаривается: он логически следует из того, что preflight — чистая read-only проверка существующего candidate, ничего не пишущая в schema.

Итог

Единственная находка — Medium, в скоупе задачи, чинится правкой текста ТЗ (добавить дословный EN-текст в §8.1), без нового issue (#202). High-находок нет. Технические предположения корректны и проверены по коду origin/dev предметно, а не приняты на слово автора.

Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 1 → в задаче