Files
houseplan-card/docs/reviews/SPEC-REVIEW-400-r1.md
T
2026-08-31 04:45:04 +00:00

14 KiB

SPEC-REVIEW #400 · заход r1

  • Issue: https://github.com/Matysh/houseplan-card/issues/400
  • Артефакт ТЗ: docs/specs/400-beta-polish.md
  • SHA материала: 60c211c82ad12940e68040de1455f13a1410683e (HEAD ветки issue/400-beta-polish, единственный коммит docs: specify the beta polish batch (#400))
  • Трек: полный (три несвязанные поверхности — рендер .dtframe, состав ленивых чанков, _alignCandidates); критерий small «одна поверхность» явно назван как нарушенный в шапке ТЗ. Заход r1, лимит цикла 4 (полный трек).

Скоуп

ТЗ объединяет три пункта из аудита беты v1.70.0-beta.1 (прецеденты #369, #385 подтверждают практику батчить несколько Low/Medium-находок аудита в одно ТЗ):

  1. M5 — осевые ручки трансформ-рамки декора (.dtframe) рисуются поверх угловых и на мелкой мебели (≲80 см) забирают часть хит-зоны угла.
  2. Low «б» — 38 ключей помощи en/ru лежат в initial-чанке, а не в ленивом чанке редактора.
  3. Low «г» — исключение перетаскиваемого маркера в _alignCandidates (режим devices) опирается на мёртвый this.host._drag?.id вместо _deviceDrag?.id.

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

Ревью ТЗ — не код-ревью: продуктовый код не менялся, гейты (typecheck, test, build) неприменимы на этом этапе и не запускались. Проверка велась чтением исходников на SHA выше и сверкой с ТЗ.

Проверено построчно:

  • src/houseplan-card.ts:8439 — hr = Math.max(view.w, view.h) * 0.018, общий для угловых (:8467, corners.map) и осевых (:8474, sides.map) ручек. sides.map рендерится после corners.map в одном шаблоне → на overlap-зоне хит достаётся последним отрисованным (осевым) кругам. Комментарий кода на месте (:8434-8438) подтверждает решение владельца от 2026-08-05 «уменьшить в 4 раза» — оно относится к видимой бусине kr = hr/4, не к хит-радиусу hr; ТЗ корректно исключает hr из скоупа.
  • src/houseplan-editor-runtime.ts:10943,10970 — метод называется _alignCandidates() (ТЗ ссылается на него верно); в ветке devices исключение действительно идёт по this.host._drag?.id (:10970). _drag (houseplan-card.ts:2547) и _deviceDrag (:2548, заполняется :6736) — разные поля; в режиме devices перетаскивание всегда через _deviceDrag, _drag там не присваивается — подтверждает «мёртвый источник».
  • demo/smoke_align_guides.mjs (ветка «редактор устройств», строки 30-47) — прочитан целиком. Сценарий 3 подтверждает диагноз ТЗ буквально: _layout[b] выставляется на ту же Y, что и цель выравнивания, _deviceDrag включается для b, и guides() >= 1 не различает «гид от a» и «гид от самого b», потому что кандидат b (не исключённый живым _drag=null) физически совпадает с целью. Смок не умеет упасть на этот дефект — ТЗ прав, что нужна правка теста (AC5), а не только кода.
  • src/logic.ts:1977 — alignGuides(pt, candidates, tol) существует и принимает список кандидатов, как описано.
  • dist/houseplan-assets/houseplan-card-*.js — grep -c '\.help\.' находит совпадение только в initial-чанке (houseplan-card-*.js), не в editor-*.js; de-*.js/fr-*.js существуют как отдельные lazy-чанки (src/i18n/de.ts, src/i18n/fr.ts помечены «lazy» в комментариях) — ровно та архитектура, которую описывает ТЗ («en/ru всегда в initial, лениво только de/fr»).
  • scripts/bundle-budget.mjs:34 — INITIAL_VIEW_GZIP_BUDGET = 300_000 — совпадает с числом в ТЗ (примечание: AGENTS.md в другом месте называет устаревшее «256000 B» из #337 — расхождение самого AGENTS.md с текущим кодом, не дефект этого ТЗ).
  • Сверены все внешние ссылки: issue #383 (мебель resize, closed), #369/#385 (прецеденты батч-ТЗ), #352/#355 (архитектура ленивых словарей), #337 (ленивый editor-чанк), #74 (undo/redo устройств, откуда возникло состояние _deviceDrag) — все существуют и релевантны заявленному контексту.
  • docs/FURNITURE.md, docs/DECOR-EDITOR.md, docs/USER-GUIDE.ru.md (строка 1293) — описывают поведение угловых/средних ручек мебели в общих словах, ни один не фиксирует приоритет хита при пересечении кругов. Правка AC1 не противоречит ни одному канону, новый контракт не заменяет и не ломает задокументированное поведение.
  • docs/TOUCH-SUPPORT.md — редактор подложки (декор) отнесён к «best-effort»-поверхностям; правка — только хит-приоритет уже существующей десктоп-ручки, нового touch-контракта не создаёт.
  • scripts/mutation-gate.mjs — существующие id мутантов оформлены как kebab-case-описание дефекта; два новых id из ТЗ (furniture-edge-handles- steal-the-corner, align-guides-exclude-dead-source) следуют тому же стилю.

Находки

Ни одной находки High или Medium. Две Low, обе сняты ревьюером с записью (не требуют возврата автору):

  • Low. Раздел «Release-артефакты» не проговаривает пункт docs/specs/README.md «затронутая пользовательская документация» явно (в отличие от i18n/модели данных, где ТЗ пишет «не применимо»). По факту docs/USER-GUIDE.ru.md:1293 уже описывает угловой/осевой resize мебели в терминах, которые AC1 не меняет (контракт «углы двигаются плавно и по умолчанию сохраняют пропорции» остаётся верным, меняется только надёжность попадания) — обновление не требуется по существу. Снято: добавление пустой строки «USER-GUIDE не меняется» ничего не изменило бы в приёмке.
  • Low. ТЗ не проговаривает влияние на touch явно (пункт DoR §2.5), хотя явно проговаривает i18n и модель данных. Поверхность — редактор подложки, который весь целиком best-effort на touch (docs/TOUCH-SUPPORT.md), а правка — приоритет хита у уже существующей десктоп-ручки без изменения touch-контракта. Снято: добавлять строку «touch: без изменений» формально корректно, но не несёт информации сверх уже действующей политики.

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

  • Оба обязательных продуктовых раздела на месте: персона/поверхность/момент (Сценарий) и «что человек увидит» одной фразой на каждый из трёх пунктов — включая честное «видимой разницы нет» для пунктов (2) и (3).
  • Трек выбран верно и обоснован явным нарушенным критерием («одна поверхность»), а не общей отговоркой — соответствует правилу AGENTS.md после #338.
  • Все три технических контракта проверяемы и не оставляют скрытых допущений, выданных за факт: формулировка M5 из исходного аудита («ресайз углом недоступен») explicitly исправлена на подтверждённую замером и явно более слабую («зона урезана вдвое»), с указанием, что именно осталось непроверено (elementFromPoint через shadow root) и почему выбран другой способ доказательства (синтетический pointerdown вместо elementFromPoint).
  • Технические решения, не видимые пользователю (два исхода по help-чанку, порядок отрисовки vs скрытие осевых ручек, имена тестовых файлов и мутантов), явно поданы как варианты с обоснованным дефолтом и оставлены реализации — ровно то, что требует §7.1, без скрытых догадок, выданных за решение.
  • AC1…AC6 пронумерованы, у каждого указан способ доказательства (smoke/unit/gate-команда/код-ревью), и план автотестов покрывает AC1, AC2, AC4, AC5 конкретными тестовыми сценариями; AC3 и AC6 — gate-командами (grep+замер бюджета, npm run bundle:budget), что допустимо по DoR §2.5.
  • Мутанты в плане нацелены именно на регрессии, которые новый тест должен ловить (возврат старого порядка отрисовки → красный AC1; возврат _drag?.id → красные AC4/AC5) — дисциплина «тест умеет падать» заложена в сам план, а не декларирована.
  • Скоуп/не-скоуп корректно исключает смежные, но чужие задачи: hr (владелец, 2026-08-05), механику ресайза мебели (#383), тексты помощи (#86), архитектуру ленивой поставки словарей (#352–#355) — предотвращает расползание батча.
  • Откат прост и не трогает пользовательские данные (два блока шаблона, состав чанка/строка документации, одно поле условия).
  • Ссылки issue ↔ ТЗ на месте в обе стороны (шапка ТЗ → issue; в issue есть единственный комментарий S2-анализа, который ведёт к тому же батчу).

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

  • Не запускал typecheck/test/build/bundle:budget — на этапе ревью ТЗ продуктовый код не менялся, гейты неприменимы; они станут предметом код-ревью.
  • Не проверял браузером фактическое перекрытие ручек в px (14 px, порог «≈80 см») — принято как измерение автора в реальном DOM, зафиксированное в комментарии S2-анализа; для этапа спек-ревью это описание проблемы, а не критерий приёмки (критерий приёмки — бинарный AC1/AC2, он проверяем независимо от точной пороговой цифры).
  • Не проверял точное текущее значение запаса бюджета (15 945 Б) и суточный расход (6,1 КБ) — это мотивационный фон проблемы (2), не предмет AC; AC3/AC6 сформулированы как проверяемые независимо от этих чисел.
  • Не оценивал техническую реализуемость исхода (1) для пункта (2) (вынос help-ключей без второго сетевого запроса) — ТЗ прямо оставляет это реализации с честным запасным исходом (2), что и требуется от технического решения на этом этапе.

Вердикт

Зелёный. ТЗ полное по §7.1, критерии проверяемы, трек обоснован, ссылки и факты в тексте подтверждены чтением кода на SHA 60c211c8. Обе Low-находки сняты ревьюером с записью выше — доработки не требуют.