Files
houseplan-card/docs/reviews/SPEC-REVIEW-243-r1.md
T
2026-08-22 19:40:31 +03:00

189 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-243-r1
- Issue: [#243](https://github.com/Matysh/houseplan-card/issues/243) — «Перетаскивание вкладок пространств не работает: захват указателя съедает
цель. Плюс нужен указатель места вставки»
- Этап: spec (PROCESS.md §2.4)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4
- ТЗ: `docs/specs/243-space-tab-drop-target.md`, коммит `7ac1934ac5002f915c2e1f885c9142daaee645a8`
(`docs(spec): define reliable space tab drop target`, `Issue: #243`, `User-Visible: no`)
- Трек: обычный (не `small`) — подтверждено: задача вводит видимый UX-контракт
(вертикальный разделитель before/after), не подпадает под критерии §5.
- Ревьюер этого раунда контекста автора не имеет; ТЗ и issue разобраны как
состязательный материал.
## Скоуп ревью
Первый заход, предыдущего вердикта нет — раздел «Унаследовано из r0» не
применяется, разбор ниже полный по PROCESS.md §2.4 и §7.1.
## Как проверялось
1. Прочитан `docs/SCOPE.md` — задача чинит регрессию выпущенной функции J6
(«keep the plan true as the home evolves», порядок пространств/вкладок),
персона — home admin, поверхность — desktop editors (plan/devices/decor).
Новый UX-контракт (разделитель) не расширяет мандат: это правка уже
принятой функции #220, не новая возможность.
2. Прочитаны `AGENTS.md`, `PROCESS.md` целиком (классы изменений, §7.1
обязательные разделы, §5 критерии `small`, §4 лимит циклов, шаблон
вердикта §7.2).
3. Прочитано тело issue #243 и все три комментария (аналитика владельца,
занятие автором, сдача ТЗ). Открытых продуктовых вопросов автор не
поднимал — сверено, что действительно нечего было спрашивать (см. ниже).
4. Прочитан `docs/USER-GUIDE.ru.md` (§ вкладки/перетаскивание, строки
276–282) — терминология «вкладка», «перетаскивание», используемая ТЗ,
совпадает с зафиксированной там, новых терминов не изобретено.
5. Прочитан канонический документ затронутой функции —
`docs/specs/220-space-tab-reorder.md` (диагноз, контракт `reorderSpaceIds`,
продуктовые решения владельца, границы touch/View) — и
`docs/TOUCH-SUPPORT.md` (§ «Safety floor», формулировка `Touch editor: not
exposed` — ТЗ цитирует её дословно, не изобретает).
6. Диагноз ТЗ сверен построчно с актуальным `dev` (не с описанием в issue):
- `src/houseplan-card.ts:1302-1329` (`_tabPointerDown`) — `capturePointer`
вызывается, `_tabDrag.overId` инициализируется значением `id` источника;
- `src/houseplan-card.ts:1331-1340` (`_tabPointerMove`) — обработчик
навешен на каждую вкладку отдельно (`houseplan-card.ts:16426`,
`@pointermove=${(e) => this._tabPointerMove(e, s.id)}`) и получает
фиксированный `overId = s.id` того элемента, на который повешен;
- после `setPointerCapture` браузер ретаргетирует все `pointermove` на
захвативший элемент — значит, только обработчик, навешенный на
исходную вкладку, продолжает получать события, и передаёт в него
собственный `overId` (= `drag.id`). Логический вывод ТЗ «`overId`
остаётся равен `drag.id`» подтверждён чтением, не только заявлением
автора issue;
- `_tabPointerUp` (`:1342-1348`) → `_commitTabOrder(drag.id, drag.overId)`
→ при `drag.id === drag.overId` `reorderSpaceIds` (`src/space-order.ts:55-65`)
закономерно возвращает тот же массив — запись не идёт. Диагноз ТЗ §3
воспроизводится по коду, а не только по логу автора issue.
7. Проверена математика `reorderSpaceIds` на нескольких комбинациях
(перемещение к более ранней/более поздней вкладке, соседние позиции,
перемещение в начало/конец массива) — во всех прогнанных вручную случаях
результат совпадает с заявленным в ТЗ §4.3 правилом «`to < from` → before,
`to > from` → after»: сторона индикатора однозначно соответствует
итоговой позиции после `splice`. Расхождений не найдено.
8. Проверены факты, на которые ТЗ опирается как на данность: лимит
`MAX_SPACES = 50` (`custom_components/houseplan/validation.py:478`,
задокументирован в `docs/USER-GUIDE.ru.md:1520`) — используется в ТЗ §11
как обоснование «отдельный performance-профиль не нужен»; существующие
CSS-классы `.tab[data-reorderable]`, `.tab.dragging`, `.tab.droptarget`
(`src/styles.ts:1725-1728`) — ТЗ корректно описывает их как
«текущее, недостаточное» состояние (единственный `.droptarget` без
стороны).
9. Проверены команды гейтов из ТЗ §15 на существование: `node
scripts/mutation-gate.mjs --check` и `--id=<mutant>` реализованы
(`scripts/mutation-gate.mjs:1608,1620`); `node scripts/smoke-select.mjs
--base <base> --head <head>` — существующий скрипт. Три новых
mutation-id (`tab-drag-target-follows-captured-source`,
`tab-drop-indicator-always-before`, `tab-drop-outside-commits-last-target`)
не пересекаются с уже зарегистрированными (`tab-reorder-not-persisted`,
`tab-drag-survives-release-outside`, `tab-drag-outlives-the-card`,
`tab-reorder-eats-click`, `tab-reorder-ignores-pointer-type`).
10. Проверено наличие прецедента в golden-харнесе для утверждаемого способа
доказательства AC2/AC8: `demo/golden/harness.mjs` уже использует
`page.mouse`, то есть захват мыши в момент капчура — не новый для
инфраструктуры приём, а не голая гипотеза автора.
11. Проверены обязательные разделы §7.1: сценарий и персона (§1) · что
человек увидит до/после (§2) · диагноз/проблема (§3) · продуктовые
решения (§4) · скоуп и не-скоуп (§6) · контракт поведения и
hit-testing (§7–8) · UX/визуальный контракт (§9) · данные/i18n/a11y/touch
(§10) · перф/безопасность (§11) · AC1–AC9 с доказательством (§12) · план
автотестов (§13) · mutation guards (§14) · гейты (§15) · release-
артефакты (§16) · откат (§17) · риски (§18) · явный блок принятых
технических предположений (§19). Все присутствуют, ничего не
зачёркнуто «TBD».
12. Проверен трейлерный коммит: `Issue: #243`, `User-Visible: no` —
корректно для чисто документационного изменения; diff коммита
ограничен `docs/specs/243-space-tab-drop-target.md` и
`docs/specs/README.md` (класс C), продуктовый код не тронут — Rule #1
соблюдено.
## Находки
Находок нет — ни High, ни Medium, ни Low.
Отдельно проверенные потенциальные риски ложной уверенности, снятые
разбором:
- **Не является ли диагноз просто пересказом автора issue?** Нет: диагноз
сверен построчно с `dev` (см. п.6 выше) независимо от текста issue —
вывод «`overId` не меняется после capture» логически следует из
сочетания «per-tab listener + retargeting событий при `setPointerCapture`»,
а не принят на веру.
- **Не выдана ли догадка за решение?** Технические решения (расположение
обработчика на `.tabs`, форма transient-state, способ рисования линии,
вынос hit-test helper в `space-order.ts` или нет) явно помечены в §19
«принято предположительно, поменять свободно» — ревьюер вправе оспорить,
но это не блокер: ни одно из них не является пользовательским поведением.
Нормативные же требования (реальный `page.mouse`, сохранение capture,
точная сторона before/after, отмена drop вне вкладки, отсутствие drag во
View/touch/fixedFloor) явно исключены из категории предположений в конце
§19 и присутствуют как факт в основном тексте — обоснованно, они прямо
унаследованы от продуктовых решений #220 и явного ТЗ #243.
- **Нет ли скрытого расширения скоупа?** «Не входит» (§6) явно исключает
touch/pen drag, клавиатурную сортировку, смену модели порядка, схему
`config.spaces`, `expected_rev`, материализацию маркеров, `floor`-toast,
свайп/карусель и `fixedFloor`-карточку — совпадает с P0-границами #220,
которые #243 обязан не трогать.
- **Проверяем ли AC однозначно и с доказательством?** Да, AC1–AC9 (§12)
пронумерованы, у каждого указан способ доказательства (`browser smoke`,
`unit`, `golden`, `gates`), совпадающий с допустимыми видами из DoR §2.5.
- **Продуктовые вопросы владельцу нужны?** Нет открытых: сторона
разделителя (before/after), обязательность реального `page.mouse` и
сохранение capture — всё это уже зафиксировано либо в теле issue
(`AC2`, «мой вариант по умолчанию — первый»), либо в комментарии
аналитики владельца («действующие границы #220 сохраняются»). Технические
вопросы (где хранить transient-state, чем рисовать линию) ТЗ не выносит
владельцу и решает само — корректно по §7.1.
## Что проверено и корректно
- Соответствие `docs/SCOPE.md`: правка регрессии J6, персона/поверхность
верны, новый видимый элемент (разделитель) не создаёт новой функции —
только делает существующий контракт #220 рабочим и honest.
- Диагноз причины бага — подтверждён чтением кода независимо от заявления
issue (см. «Как проверялось», п.6).
- Математика `reorderSpaceIds` и заявленное правило стороны индикатора —
проверены вручную на нескольких комбинациях перестановки, расхождений с
ТЗ §4.3/§7 не найдено.
- Все 12 обязательных разделов §7.1 присутствуют и по существу.
- AC1–AC9 однозначны, у каждого назван способ доказательства из
допустимого набора; ни один не сформулирован как нефальсифицируемый.
- Явный блок принятых технических предположений (§19) отделён от
нормативных требований — соответствует правилу «догадка, выданная за
факт, — худший вид дефекта».
- Терминология («вкладка», «перетаскивание», `Touch editor: not exposed`)
взята из `docs/USER-GUIDE.ru.md` и `docs/TOUCH-SUPPORT.md`, а не
изобретена.
- Упомянутые в ТЗ команды гейтов (`mutation-gate.mjs --check/--id=`,
`smoke-select.mjs --base --head`) существуют в репозитории с такими
флагами; новые mutation-id не пересекаются с реестром.
- Класс изменения (C — только `docs/**`) и трейлеры коммита корректны;
продуктовый код в этом коммите не тронут.
- Лимит циклов ТЗ на обычном треке — 4 (не 2, трек не `small`) — учтён при
формировании вердикта.
## Чего не проверял
- Не запускал никакие браузерные смоки, unit, golden или mutation-gate —
на этапе ревью ТЗ кода ещё нет (ТЗ описывает будущую реализацию), гейты
§15 относятся к стадии «В разработке» и будут материалом код-ревью.
- Не проверял golden-харнесс на техническую способность реально удержать
`page.mouse.down` до `screenshot` и корректно освободить кнопку между
сценами (риск #18 п.5 ТЗ) — это техническая реализуемость, а не
продуктовая ambiguity, и относится к зоне ответственности автора и
будущего код-ревью.
- Не проверял, действительно ли `ShadowRoot.elementFromPoint()` даёт тот же
результат, что ручной hit-test по `getBoundingClientRect()` во всех
браузерах — ТЗ прямо оставляет выбор реализации автору (§19 п.4),
вопрос не продуктовый.
## Вердикт
Спецификация полна, диагноз проверен независимо от слов автора issue,
контракт стороны индикатора математически согласован с существующей
семантикой `reorderSpaceIds`, скоуп и не-скоуп чётко отделяют регрессионный
фикс от нового поведения, все обязательные разделы §7.1 на месте, AC
проверяемы и снабжены допустимым способом доказательства, открытых
продуктовых вопросов нет.
**Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0 → в задаче**