mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 05:08:53 +00:00
230 lines
19 KiB
Markdown
230 lines
19 KiB
Markdown
# SPEC-REVIEW-522-r1
|
||
|
||
## Якоря
|
||
|
||
- Issue: [#522](https://github.com/Matysh/houseplan-card/issues/522) «Клик по
|
||
маркеру устройства ставит курсор в поле имени: мигающая каретка и
|
||
всплывающая клавиатура на планшете», метка `S4-spec-review` (подтверждено
|
||
`gh issue view 522 --repo Matysh/houseplan-card --json labels`:
|
||
`bug`, `P3`, `S4-spec-review`).
|
||
- ТЗ: тело issue #522, раздел `## ТЗ` (строки 47–118 полученного тела,
|
||
`gh issue view 522 --repo Matysh/houseplan-card --json body -q .body`).
|
||
- sha256 нормализованного тела (как получено `gh issue view --json body -q
|
||
.body`, без дополнительной нормализации):
|
||
`f2efc9f21eed616183e678956a0bb1e273d36563dc2129216a1d2314bb890ffe`
|
||
- Заход r1. Бюджет циклов §4 до этого ревью: 0/4.
|
||
- Единственный комментарий issue — `S2-анализ` (Codex, 2026-09-10,
|
||
`dev d5575f63`), переводящий задачу на полный трек с названным нарушенным
|
||
критерием §5 («новый UX-контракт», «touch impact»). Продуктовых вопросов
|
||
владельцу в комментарии не задано.
|
||
|
||
## Скоуп ревью
|
||
|
||
Ревью ТЗ (PROCESS.md §2.4) для полного трека. Предмет — раздел `## ТЗ`:
|
||
контракт К1–К5, AC1–AC7, откат, release-артефакты — против §7.1
|
||
(обязательные разделы), §2.5 (DoR) и `docs/SCOPE.md`/`docs/TOUCH-SUPPORT.md`
|
||
(продуктовая рамка и touch-контракт).
|
||
|
||
## Как проверялось
|
||
|
||
1. Прочитано тело issue #522 целиком (разделы «Симптом», «Что происходит на
|
||
самом деле», «Причина», «Это не свежий регресс», «## ТЗ») и единственный
|
||
комментарий.
|
||
2. Сверено с `docs/SCOPE.md` (персона/поверхность, партиальный бэклог
|
||
«Touch ergonomics of the editors»), `PROCESS.md` §1, §2.3–2.5, §4, §5,
|
||
§7.1, `AGENTS.md` (разделы Specs, Gates).
|
||
3. Прочитан `docs/TOUCH-SUPPORT.md` целиком — safety floor и правило
|
||
deliberate degradation не нарушаются: изменение чинит поведение внутри уже
|
||
объявленного best-effort редакторского контура, View/kiosk не задеты.
|
||
4. Прочитан `src/hp-dialog.ts` целиком. Текущая логика `_focusInitial()`
|
||
(строки 396–404), `_focusableElements()` (347–394) и Tab-ловушка
|
||
`_onKeyDown` (461–492) дословно совпадает с тем, что описывает issue:
|
||
порядок `autofocus || focusable[0] || .close(native) || .surface ||
|
||
ha-dialog`, `.close` — вне `_focusableElements()` (рендерится в shadow
|
||
DOM хоста, а не в light DOM/slot), `.surface` уже несёт
|
||
`tabindex="-1"`.
|
||
5. `grep -n "autofocus" -r src/` — подтверждены все 6 фактических деклараций:
|
||
`hp-confirm.ts:54` (кнопка «Отмена», используется всеми диалогами
|
||
подтверждения), `houseplan-editor-runtime.ts:9826` (экспорт бэкапа,
|
||
кнопка), `:9966` (импорт бэкапа, кнопка), `:12351` (поиск в инбоксе,
|
||
поле, `type="search"`), `:5531` (текст декора, `textarea`),
|
||
`space-copy-runtime.ts:91` (имя копии пространства, поле) — сверено с
|
||
перечислением К3.
|
||
6. `grep -n "namein\|marker.name_label"` в `houseplan-editor-runtime.ts` —
|
||
подтверждена строка 12999–13000: первое поле диалога маркера —
|
||
`input.namein type="text"` без `autofocus`, точно как в «Причине» issue.
|
||
7. Прочитан `docs/USER-GUIDE.ru.md` вокруг строк 439–442 (фокус-ловушка во
|
||
всех диалогах — `Tab`/`Shift+Tab`/`Esc`/возврат фокуса — не меняется,
|
||
К4 это подтверждает) и 2198–2206 («Отмена» получает начальный фокус в
|
||
диалогах подтверждения удаления/разблокировки — К3 явно сохраняет
|
||
поведение диалогов с объявленным `autofocus`, эта строка гайда не
|
||
протухает).
|
||
8. Проверена реализуемость AC1–AC5 существующей техникой проекта:
|
||
`demo/smoke_help_affordance.mjs` (строки 1–29) уже создаёт голый
|
||
`<hp-dialog>`, наполняет его вручную и вызывает `dialog._focusInitial()`
|
||
напрямую — тот же приём закрывает AC3/AC5 без браузерного клика;
|
||
`test/hp-dialog-contract.test.mjs` — существующий файл именно с этим
|
||
именем, который ТЗ называет свидетелем AC3, подтверждён `ls test/`.
|
||
9. Прочитана шапка `scripts/mutation-gate.mjs` (комментарий-документация,
|
||
строки 1–29) — формат AC6 («мутант + названный гард-смок») соответствует
|
||
механике реестра и требованию `PROCESS.md` §2.7 про защитные AC на
|
||
дорогом гейте (smoke).
|
||
10. `grep` по `demo/smoke_*.mjs` и `test/*.test.mjs` на `.close`/`.surface`/
|
||
`_focusInitial` — ни один существующий тест не фиксирует конкретно путь
|
||
«фокусируемых элементов в light DOM вообще нет → `.close`» (нужен для
|
||
находки Low-2 ниже).
|
||
11. Сверено содержание ТЗ с чек-листом DoR §2.5 пункт за пунктом (см.
|
||
«Что проверено и корректно»).
|
||
12. `git status` — рабочая копия чистая; кода по этой задаче ещё нет (стадия
|
||
ТЗ, ветки не существует).
|
||
|
||
## Находки
|
||
|
||
### Low-1 — К3 называет неверное число диалогов с объявленным `autofocus`
|
||
|
||
**Файл:** тело issue #522, `## ТЗ` → `### Контракт`, К3: «Их сегодня
|
||
четыре: подтверждения (`hp-confirm.ts`), экспорт и импорт бэкапа — на
|
||
кнопке «Отмена»; поиск в инбоксе устройств, имя копии пространства, текст
|
||
декора — на поле.»
|
||
|
||
Перечисление называет шесть отдельных мест: `hp-confirm.ts` (общий
|
||
компонент, кнопка), экспорт бэкапа (кнопка), импорт бэкапа (кнопка), поиск
|
||
в инбоксе (поле), имя копии пространства (поле), текст декора (поле) — grep
|
||
по `src/` (шаг 5 выше) подтверждает ровно эти шесть строк с `autofocus`, а
|
||
не четыре. Даже если считать «экспорт и импорт» одной категорией, получится
|
||
пять, не четыре.
|
||
|
||
**Почему не блокирует.** Правило К3 сформулировано как общее утверждение
|
||
(«любой диалог, объявивший `autofocus`, не меняет поведения»), а не как
|
||
перечень с фиксированной длиной — механизм не зависит от числа. Ни один AC
|
||
не проверяет счётчик. Опечатка декоративная.
|
||
|
||
**Решение:** Low, waived. Для протокола — заменить «четыре» на «шесть» или
|
||
убрать число вовсе, оставив перечисление.
|
||
|
||
### Low-2 — К1 называет `.close`/`ha-dialog` «фолбэками без изменений», хотя их достижимость меняется
|
||
|
||
**Файл:** тело issue #522, `## ТЗ` → `### Контракт`, К1, пункт 4.
|
||
|
||
Сегодняшний порядок в коде (`src/hp-dialog.ts:399–402`): `autofocus →
|
||
focusable[0] → .close (только нативный диалог) → .surface → ha-dialog`.
|
||
Новый порядок по К1: `autofocus → первый нетекстовый → .surface → далее
|
||
существующие фолбэки без изменений (.close, ha-dialog)`. Дословно это
|
||
переставляет `.surface` **перед** `.close`, а не оставляет `.close` на его
|
||
сегодняшнем месте — контракт называет это «без изменений», но фактически
|
||
меняет порядок.
|
||
|
||
Единственный сценарий, где раньше побеждал `.close`, — нативный диалог, у
|
||
которого в light DOM нет вообще ни одного элемента, подходящего под селектор
|
||
`_focusableElements()` (не только текстовых — вообще никаких: ни кнопки, ни
|
||
чекбокса). В новом порядке этот же сценарий (шаг 2 не находит нетекстовый,
|
||
потому что массив пуст) отдаёт фокус `.surface`, а не `.close`, потому что
|
||
`.surface` рендерится безусловно в том же нативном branch и теперь стоит
|
||
раньше. Практическое отличие ничтожно: `.surface` — та же ловушка
|
||
(`tabindex="-1"`), `Tab` по-прежнему находит `.close` (он отдельно
|
||
подставляется первым элементом списка в `_onKeyDown`, строка 476), а такого
|
||
полностью пустого диалога в текущем инвентаре компонентов не нашлось (шаг
|
||
10 выше — ни один смок/юнит не фиксирует именно этот путь, значит он либо
|
||
не используется, либо не критичен). Контракт при этом полностью
|
||
детерминирован и реализуем буквально как написан — исполнителю нечего
|
||
угадывать.
|
||
|
||
**Решение:** Low, waived. Для протокола — точнее было бы «`.close` и
|
||
`ha-dialog` остаются в конце списка, но `.surface` теперь проверяется перед
|
||
ними», а не «без изменений».
|
||
|
||
## Что проверено и корректно
|
||
|
||
- **К1–К5 непротиворечивы и полностью детерминированы.** К2 закрывает
|
||
единственный реальный источник будущей двусмысленности — новые/неизвестные
|
||
HTML input-типы — явным правилом «список нетекстовый, а не наоборот»:
|
||
`search`, `tel`, `url`, `email`, `password`, `date`/`time`/`datetime-local`,
|
||
`number` и любой будущий тип автоматически считаются текстовыми без
|
||
правки правила.
|
||
- **AC1–AC7 пронумерованы, у каждого назван способ доказательства**
|
||
(`smoke`/`unit`/мутант/ревью кода) — требование DoR §2.5 выполнено
|
||
построчно.
|
||
- **AC6 самостоятельно закрывает требование §2.7** про обязательный мутант
|
||
для защитного AC, проверяемого дорогим гейтом (AC1/AC2 — `smoke`): автор
|
||
явно называет, какой мутант должен покраснеть на каком свидетеле, до
|
||
начала код-ревью, а не постфактум — соответствует уроку #435/#430,
|
||
явно процитированному в PROCESS.md.
|
||
- **Продуктовая рамка присутствует, хоть и не под отдельным заголовком.**
|
||
Персона (Home admin) и поверхность (редактор устройств, диалог маркера,
|
||
клик) читаются из разделов «Симптом»/«Что происходит на самом деле» —
|
||
они в том же теле issue, до заголовка `## ТЗ`. «Что человек увидит до/
|
||
после» отвечено одной фразой без терминов реализации: «мигающая каретка/
|
||
клавиатура» → «клавиатура больше не поднимается» (раздел «Touch»). То же
|
||
обращение с рамкой, разнесённой по телу issue, а не сведённой в один
|
||
подраздел `## ТЗ`, уже было принято в `SPEC-REVIEW-520-r1` — прецедент
|
||
того же формата «спецификация в теле issue».
|
||
- **Риск 1 (потеря мгновенного набора текста в ~7–8 существующих диалогах)
|
||
прицельно проверен как кандидат в продуктовый вопрос владельцу**
|
||
(«сколько видимых изменений входит в issue», §7.1) — решение прозрачно,
|
||
с названной ценой и причиной («это цена того, что тап по любому диалогу
|
||
перестаёт открывать клавиатуру»), не выдано за факт без обоснования. Это
|
||
разумный дефолт инженера, а не замаскированная догадка — не считаю
|
||
находкой.
|
||
- **К5 корректно ограничивает скоуп**: `autofocus` существующим диалогам не
|
||
добавляется в этой задаче, расширение — отдельный issue. Соответствует
|
||
правилу «скоуп не расширяется» (PROCESS.md §3.9).
|
||
- **i18n, миграция/совместимость, touch, откат, release-артефакты** —
|
||
присутствуют, ни один пункт не оставлен пустым без явного «нет» и
|
||
обоснования.
|
||
- **Выбор полного трека обоснован** названным нарушенным критерием §5
|
||
(«новый UX-контракт начального фокуса» + влияние на touch-контракт) —
|
||
сделано в S2-анализе, ТЗ ему не противоречит.
|
||
- **Ни одного вопроса владельцу не задано** — и после отдельной проверки
|
||
двух кандидатов (Риск 1 выше; сама природа изменения — правка одного
|
||
внутреннего механизма без новой видимой сущности) не нахожу нерешённого
|
||
продуктового вопроса, который стоило бы вынести. Единственный технический
|
||
спор, который я нашёл сам (Low-2), — технический по типу (что считать
|
||
«без изменений» в порядке фолбэков), не продуктовый, решается ревью, а не
|
||
владельцем.
|
||
- **«Принято предположительно» оформлено по формату §7.1** — все четыре
|
||
пункта помечены явно, ревьюер вправе их оспорить; ни один не выдаёт
|
||
догадку за факт.
|
||
|
||
## Чего не проверял
|
||
|
||
- **Гейты кода** (`tsc --noEmit`, `npm test`, `npm run build`,
|
||
`check-docs.mjs`) — не гонял: на стадии ТЗ нет ни строки правки в
|
||
`src/**`, ветки задачи не существует. Предмет этого этапа — текст ТЗ, не
|
||
код; гейты — предмет код-ревью после `S5-ready`.
|
||
- **`npm run invariants`** — не применимо: геометрия и ссылки на неё не
|
||
затронуты.
|
||
- **Регистрация мутанта AC6 в `scripts/mutation-gate.mjs`** — не может
|
||
существовать на этапе ТЗ (мутант патчит ещё не написанный код); проверка
|
||
— предмет код-ревью.
|
||
- **Реальный браузерный прогон смоков AC1/AC2/AC4/AC5** — они ещё не
|
||
написаны; оценивал реализуемость по прямому прецеденту
|
||
(`demo/smoke_help_affordance.mjs`), не запускал ничего нового.
|
||
- **Golden/скриншоты** — не проверял: ТЗ заявляет «визуально ничего не
|
||
меняется», согласуется с характером правки (перераспределение фокуса,
|
||
не разметки/стилей); отдельно не перепроверял рендер.
|
||
|
||
## Унаследовано из r<N-1>
|
||
|
||
Не применимо — первый раунд (r1) ревью ТЗ #522.
|
||
|
||
## Вердикт
|
||
|
||
Зелёный. High: 0, Medium: 0, Low: 2 (обе — неточности формулировок в К1 и
|
||
К3, не влияют на реализуемость, проверяемость AC или продуктовое поведение;
|
||
сняты решением ревьюера с записью выше, без правки текста ТЗ). Переход:
|
||
«Готово к разработке» (`S5-ready`).
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `dev`, коммит `b2346eaac32c` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `94fcf05d432a71d43e1d7324aa3f670f7bc7b402`
|
||
```
|
||
git log --all --format='%H %T' | grep 94fcf05d432a
|
||
```
|
||
- Тело issue: `d7b95ee0bb522938c91da3c1a5b8fd3dc4bc5acde4fad93a04f7740178dee14f`
|
||
- Вердикт конвейера: `green` · High 0
|