22 KiB
Ревью ТЗ — 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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
862dfc7f9622c8432d7dc288446dc360a4900ea2git log --all --format='%H %T' | grep 862dfc7f9622 - ТЗ
docs/specs/152-room-click-fit.md, блобfb036d2bb9cc522555c55e15000d7d693e204f8bgit log --all --find-object=fb036d2bb9cc522555c55e15000d7d693e204f8b -- docs/specs/152-room-click-fit.md