docs: review document for #152

Issue: #152
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-04 06:53:57 +00:00
parent f672de674f
commit 81026539d3
+230
View File
@@ -0,0 +1,230 @@
# Ревью ТЗ — 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) и корректны. ТЗ готово к переводу в «Готово к
разработке».
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `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
```