Files
houseplan-card/docs/reviews/SPEC-REVIEW-152-r2.md
T
2026-09-04 06:53:57 +00:00

22 KiB
Raw Blame History

Ревью ТЗ — issue #152, заход r2

  • Issue: https://github.com/Matysh/houseplan-card/issues/152
  • ТЗ: docs/specs/152-room-click-fit.md, ветка issue/152-room-click-fit, коммит f672de67 (родитель ревьюируемой правки — f29c4c5b)
  • Этап: spec (PROCESS.md §2.4)
  • Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0

Материал раунда

Вердикт r1 (комментарий issue) называет коммит 0fd4e66, документ docs/reviews/SPEC-REVIEW-152-r1.md в заголовке — коммит 7f8751b4dce8…. Ни один из двух SHA не резолвится в текущей истории (git cat-file -t на оба — fatal). Per PROCESS.md §2.10 это «обычное дело», а не находка сама по себе: ветку между раундами перебазируют/сквошат. Материал восстановлен по содержимому, а не по SHA: docs/reviews/SPEC-REVIEW-152-r1.md цитирует ТЗ построчно (§8 130–151, §9 153–163, §11 172–187, пример 2000×1000→800×800, формулировки «существующая accessible-подпись», «существующий double-click/tap fit-all»), и коммит f29c4c5b («docs: specify room click fit», Aug 15, начало истории файла) даёт файл, в котором все эти цитаты находятся на совпадающих строках дословно. Это и есть материал r1: он же единственный родитель следующего коммита f672de67, поэтому дельта раунда — git diff f29c4c5b..f672de67 -- docs/specs/152-room-click-fit.md.

Два SHA, названных r1 в разных местах, не резолвятся оба — вероятно, r1 работал на ветке, где рабочий коммит амендился/ребейзился уже после того, как были сняты оба значения, и они не сверялись друг с другом перед публикацией (§7.2 требует сверки на момент вывода). Отмечаю это как процессное наблюдение уровня Low применительно к прошлому раунду; на выводы текущего раунда это не влияет, поскольку материал восстановлен по содержимому надёжно.

Скоуп ревью

Дельта — не локальная правка: git diff --stat даёт 487 добавленных / 252 удалённых строк на файле, который в исходной редакции насчитывал 270 строк, то есть фактически полная переработка документа (изменился даже заголовок: «Issue #152 — …» → «ТЗ #152 — …», добавлен раздел «Подтверждённое текущее состояние», переписаны структура и формулировки всех разделов). Per PROCESS.md §2.10 («разбор остаётся полным, если … объём дельты сопоставим с исходной задачей») разбор проведён полностью, а не только по находкам r1.

Прочитаны: docs/SCOPE.md (Core user jobs, персоны, lock invariant), AGENTS.md/PROCESS.md (§2.4, §2.5, §2.7, §2.10, §3, §4, §7.1, §7.2), тело issue #152 и все три комментария, docs/reviews/SPEC-REVIEW-152-r1.md, текущий docs/specs/152-room-click-fit.md целиком, docs/SCOPE.md (персоны и accessibility-рамка), исходный код (src/houseplan-card.ts, src/viewport-transition.ts) для проверки каждого утверждения раздела «Подтверждённое текущее состояние» и математики геометрического контракта, а также текущий статус issue #28, #82, #182, #183 через gh issue view.

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

ТЗ активно опирается на раздел «Подтверждённое текущее состояние» — каждое его утверждение сверено с origin/dev заново, а не принято на веру (код с момента r1 успел измениться: #82 реализован):

  • #82 реализован. gh issue view 82 → state: CLOSED, stateReason: COMPLETED. В коде есть CameraTransitionController (src/viewport-transition.ts:119), CameraTransitionReason = 'button' | 'wheel' | 'fit' | 'home' | 'double-tap' (:10), инстанс в houseplan-card.ts:1241, CAMERA_FIT_MS = 220 (:438). Утверждение ТЗ «единый camera transition, реализован» — факт, не догадка.
  • Room hover сменил механизм со времён r1. r1 цитировал mouseenter/mouseleave на SVG-форме; в текущем коде этих обработчиков нет вовсе (grep — 0 совпадений), room floor теперь вешает @pointerenter / @pointerleave (houseplan-card.ts:11700-11731, функция enterRoom пишет _hoverRoom). Формулировка ТЗ «browser target … определяют тот же room hit, что текущий hover» верна для текущего кода, а не устарела — обновление корректно отражает факт, что #82 заодно перевёл hover на pointer-события.
  • Kiosk-only double-tap-reset подтверждён на актуальном коде. _stagePointerUp (:6970-6982) — весь блок _lastTap/_resetZoom внутри if (this._kiosk); вне kiosk на stage нет ни одного обработчика double-tap. Совпадает с утверждением ТЗ п.4 «Подтверждённого текущего состояния» и с находкой r1 Medium-2.
  • .roomlabel не клавиатурно-доступна. _renderRoomLabel (:12756-12805) не содержит role/tabindex/aria-label/keydown. Совпадает с утверждением ТЗ п.2 и с находкой r1 Medium-1 — в новой редакции claim сужен до .roomlabel, а не до «плана в целом» (важно, см. ниже).
  • Пустое имя не рендерит подпись в View. _renderRoomLabel:12761: if (!r.name && !this._markup) return nothing; — подтверждает claim § «Клавиатура и доступность» про edge case пустого имени.
  • LS_ZOOM хранит только zoom. _saveZoom (:6735-6746) пишет this._zoomBySpace (объект {spaceId: zoom}), без центра камеры — подтверждает п.5 «Подтверждённого текущего состояния».
  • ZOOM_MIN/ZOOM_MAX не изменились (8 и 1/3, houseplan-card.ts:6448-6449, space-geometry.ts:245).
  • Архитектурная возможность AC15 (room-reason не пишет zoom-only state) подтверждена чтением, не только заявлена. _settleCameraTransition (:1277-1284) сегодня безусловно вызывает _saveZoom() для любого reason, но state.reason уже прокинут через CameraTransitionState (viewport-transition.ts:21) в оба хука (frame/settled) — то есть ветвление по reason === 'room' не требует переделки контроллера, только добавления условия в существующий callback. ТЗ не выдаёт эту часть за уже решённую (это AC15 к разработке), но математика того, что она реализуема без второго controller-а, проверена.
  • Интерактивные владельцы существуют как классы/элементы, на которые ТЗ ссылается: .dev, .oplock, .op-hit, .roomlabel уже участвуют в других exclusion-списках (:6794, :6815), data-hp="opening" (:13055).
  • Инструменты, упомянутые в плане тестов и release-артефактах, существуют: scripts/mutation-gate.mjs, scripts/smoke-select.mjs, demo/smoke_smooth_zoom.mjs, npm run bundle:budget, golden:verify, golden:accept, docs:accept, invariants — все есть в package.json/дереве. demo/smoke_smooth_zoom.mjs уже содержит паттерн опроса промежуточных кадров через requestAnimationFrame в page.evaluate (settle(), строки 18-24) — ровно то, что требует AC10; проверяемость не гипотетична.
  • #28 закрыт NOT_PLANNED (gh issue view 28) — подтверждает и обновлённую формулировку ТЗ («#28 закрыта как not planned»), и закрытие Low-1 из r1.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
High-1 — ни один из AC1–AC14 не указывал способ доказательства Раздел «Acceptance criteria и доказательства» переписан: 16 AC, у каждого инлайн-аннотация метода(ов) через unit/smoke/golden/performance/«ревью кода» и исполнителя (Codex либо Codex/Claude) docs/specs/152-room-click-fit.md:300-416, AC1…AC16
High-2 — критерий issue «hit targets синхронны во время перехода» не стал отдельным AC Добавлен отдельный AC10 с явной проверкой промежуточных кадров tween (не только финального), способ доказательства smoke, названа мутация :369-375
Medium-1 (#182) — §9 выдавал .roomlabel за «существующую accessible-подпись» Раздел «Подтверждённое текущее состояние» п.2 прямо констатирует отсутствие role/tabindex/aria-label/keydown; раздел «Клавиатура и доступность» явно назван «узким исключением из non-scope accessibility плана», а не расширением существующего. Claim сужен до .roomlabel, что закрывает и уточнение из закрытия #182 (про бейдж осиротевшего проёма — см. «Унаследовано» ниже) :34-35, :227-244
Medium-2 (#183) — §3/§7 трактовали double-tap fit-all на фоне как поведение всего View П.4 «Подтверждённого текущего состояния» и раздел «Double-tap без задержки single tap» ограничивают double-tap-reset kiosk’ом явно; non-scope добавляет отдельный пункт «добавление fit-all double-tap в обычный non-kiosk View»; выбран архитектурный путь, не требующий выноса 350-мс порога из kiosk-ветки (room tap принимается сразу, без окна ожидания) :39-40, :174-184, :67
Low-1 — §10 называл #28 живой задачей Раздел «Связанные задачи» указывает «#28 (карточка комнаты закрыта как not planned)» :6-7
Low-2 — не названа персона из docs/SCOPE.md «Сценарий»: «Житель либо home admin …» :11
Low-3 — нет отдельного заголовка «Модель данных и миграция» Отдельный раздел «Данные, миграция, compatibility и i18n» :253-266
Low-4 — «единственный resolver» технически не один механизм (View hover vs. editor pointInRoom) ТЗ больше не заявляет объединение с editor-resolver: click-ownership описан только для View/kiosk через тот же SVG pointer-target, что и hover (data-hp="room" + @pointerenter); редакторы в non-scope и не затрагиваются :146-154, «Скоуп»: «работает только в основном View, включая kiosk»

Унаследовано из r1

Наследуется без повторной проверки на этом заходе (дельта их не задевает, подтверждено чтением текущей редакции ТЗ и кода выше, где это пересекалось):

  • Геометрическая арифметика примера 2000×1000 → 800×800 (проверена вручную в r1; в r2 формула и пример перенесены дословно, :137-138, дополнительно ре-проверены построчно в этом заходе как часть полного разбора).
  • Конфликт с #28 и решение в пользу primary-click за fit-to-room — продуктовое решение не пересматривалось, только формулировка статуса #28 (см. таблицу выше).
  • Non-scope не создаёт скрытого расширения задачи — подтверждено заново в этом заходе, поскольку раздел был существенно переписан (не наследование, а повторная проверка из-за нелокальности дельты).
  • Источник: docs/reviews/SPEC-REVIEW-152-r1.md, материал — коммит f29c4c5b (см. «Материал раунда» выше).

Находки

Блокирующих (High) и Medium-находок в этом заходе нет.

Low-note (не блокирует, к сведению). Закрывающий комментарий #182 (2026-08-23) уточняет, что на origin/dev уже существует один клавиатурно/ARIA-доступный элемент — бейдж осиротевшего проёма (role="button" tabindex="0" aria-label", houseplan-card.ts:18806), то есть план сегодня не абсолютно лишён доступной разметки. Текущая редакция ТЗ этой ошибки не повторяет: она нигде не утверждает «в плане нет ни одного доступного элемента» — claim сужен до .roomlabel конкретно, что фактически верно независимо от бейджа проёма. Считаю снятым без правки текста.

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

  • Обязательные разделы §7.1 присутствуют: сценарий + «что человек увидит до/после» первыми, скоуп/не-скоуп, геометрический и pointer/touch контракт, camera/intent/persistence, клавиатура и доступность, данные/миграция/i18n, AC1–AC16 с доказательством и исполнителем, план автотестов, риски, откат, release-артефакты — все на месте и в этом порядке.
  • Блок «Принятые предположения» (8 пунктов) использован по назначению: каждый пункт либо прямая цитата уже принятого владельцем решения из тела issue (10%-база, LS_ZOOM session-only, отсутствие невидимого tab-stop, статус #28), либо честно техническое решение реализации (single-tap без 350 мс окна, переиспользование CAMERA_FIT_MS, scope wall body по room provenance, SVG target как authority без нового resolver) — открытых продуктовых вопросов, замаскированных под предположения, не найдено.
  • DoR-требования (§2.5), проверяемые на этапе ТЗ: i18n-ключи названы (room.fit_action, en+ru), миграция/compatibility решены явно («не меняются», сверено с docs/CONFIG-COMPATIBILITY.md по неизменности схемы), производительность/бюджеты названы отдельным разделом, touch — блокирующая поверхность по docs/TOUCH-SUPPORT.md, откат описан («revert frontend/tests/docs/bundle», без обратной миграции), открытых продуктовых вопросов нет.
  • Все факты раздела «Подтверждённое текущее состояние» и математика геометрического контракта — перепроверены по актуальному origin/dev (список — в «Как проверялось»), расхождений с кодом не найдено.
  • Non-scope согласован с non-scope тела issue и не создаёт скрытого расширения: отдельно исключены Labs-флаг/новый config, floor-wide tab-stop grid, второй animator, вращение изометрии, general non-kiosk double-tap.
  • Персона (docs/SCOPE.md) названа явно («Житель либо home admin»), задача закрывает пространственную навигацию View (не отдельный Core user job, а улучшение J1 «show the whole home» applied к одной комнате) — в рамках guard rail SCOPE.md, не excess-функциональность.

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

  • Дешёвые гейты (npx tsc --noEmit, npm test, npm run build) не запускал: дельта раунда — исключительно docs/specs/152-room-click-fit.md (git show --stat f672de67 — один файл), кода это ревью не касается, а предыдущий прогон этих гейтов не может отражать документную правку. На этапе «ТЗ на ревью» кода к тестированию ещё нет (то же ограничение отмечал r1). node scripts/check-docs.mjs не запускал по той же причине: диап не трогает src/**.
  • Golden/browser smoke/performance/invariants — не запускал: реализации нет, §12 ТЗ корректно откладывает их до этапа «В разработке»/пре-релиза.
  • Backend/custom_components/houseplan/**/*.py — задача их не касается по заявленному и подтверждённому non-scope (модель/backend не меняются).
  • Полную ARIA-authoring-экспертизу «один action target на подпись» — вопрос код-ревью на этапе реализации, не ТЗ.
  • Обоснованность продуктового решения «primary click = fit-to-room» как такового — оно принято владельцем в теле issue и не пересматривается ни в этом, ни в прошлом заходе.

Вывод

Оба High из r1 закрыты предметно (полная таблица доказательств AC1–AC16 + восстановленный AC10 про синхронность hit targets на промежуточных кадрах), оба вынесенных Medium (#182, #183) отражены в тексте точнее, чем было — и подтверждены их же закрывающими комментариями от 2026-08-23 как полностью влитые в #152. Полный разбор, оправданный нелокальностью дельты (документ переписан почти полностью), не нашёл новых High/Medium: геометрический контракт, pointer/touch ownership, camera/intent lifecycle, accessibility и release-артефакты проверены по актуальному коду (включая то, что успело поменяться из-за реализации #82) и корректны. ТЗ готово к переводу в «Готово к разработке».


Материал раунда

  • Ветка: issue/152-room-click-fit, коммит f672de674fcf — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 862dfc7f9622c8432d7dc288446dc360a4900ea2
    git log --all --format='%H %T' | grep 862dfc7f9622
    
  • ТЗ docs/specs/152-room-click-fit.md, блоб fb036d2bb9cc522555c55e15000d7d693e204f8b
    git log --all --find-object=fb036d2bb9cc522555c55e15000d7d693e204f8b -- docs/specs/152-room-click-fit.md