mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
docs: specify the branch-independent danger confirmation (#402)
User-Visible: no Issue: #402
This commit is contained in:
+167
@@ -0,0 +1,167 @@
|
||||
# ТЗ #402 — Подтверждение опасного действия живёт вне зависимости от ветки render()
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/402
|
||||
- Приоритет: P1, bug (регресс против поведения до #32); полный трек — класс A
|
||||
- Ревизия: 1 (2026-08-31)
|
||||
|
||||
## Сценарий
|
||||
|
||||
Хозяин ставит карточку в первый раз: пространств ещё нет, идёт онбординг. В
|
||||
списке сохранённых на сервере планов лежит чужой, ошибочно загруженный файл —
|
||||
он жмёт корзину. Ничего не происходит. Ни диалога, ни удаления, ни сообщения:
|
||||
кнопка мертва, и понять почему нельзя. До #32 здесь спрашивал браузерный
|
||||
`confirm()`, и удаление работало.
|
||||
|
||||
## Что человек увидит до и после
|
||||
|
||||
**До**: в состоянии «нет ни одного пространства» (а также при недоступном
|
||||
`fixed_floor` и при потерянном пространстве) любое опасное действие молча не
|
||||
делает ничего; если подтверждение было открыто до перехода в такое состояние —
|
||||
диалог исчезает сам, а действие остаётся ни выполненным, ни отменённым.
|
||||
**После**: подтверждение спрашивается и в этих состояниях; если карточка вообще
|
||||
ничего не рисует, действие отклоняется явно, а не подвисает.
|
||||
|
||||
## Проблема и контракт
|
||||
|
||||
`HpConfirmController` (`src/danger-confirm.ts:36`) корректен: `cancel()`
|
||||
резолвит промис `false`, `resolve()` сверяет токен, новый запрос отменяет
|
||||
предыдущий. Промис виснет не потому, что контроллер теряет решение, а потому
|
||||
что решение неоткуда взяться: оно приходит событием `hp-confirm-decision` от
|
||||
элемента `<hp-confirm>`, а элемента в DOM нет.
|
||||
|
||||
`render()` (`src/houseplan-card.ts:11165`) — цепочка ранних `return`, и
|
||||
`hp-confirm` стоит только в её конце (`:11872`):
|
||||
|
||||
| Строка | Ветка | Рисует |
|
||||
|---|---|---|
|
||||
| `:11166` | нет `_config`/`hass` | `nothing` |
|
||||
| `:11170` | языковой гейт `cold` | загрузчик языка |
|
||||
| `:11171` | языковой гейт `warm` | `noChange` |
|
||||
| `:11192` | `fixed_floor` pending | своя карточка |
|
||||
| `:11203` | `fixed_floor` invalid | своя карточка |
|
||||
| `:11216` | `!model.length` (онбординг) | своя карточка |
|
||||
| `:11247` | `!space` | `nothing` |
|
||||
| `:11872` | основная | **единственное место с `hp-confirm`** |
|
||||
|
||||
Удаление сохранённого плана вызывается из онбординга
|
||||
(`src/houseplan-onboarding-runtime.ts:273` → `_deleteServerPlan`, который
|
||||
ждёт `_confirmDanger`), то есть буквальный сценарий issue лежит в ветке
|
||||
`:11216`.
|
||||
|
||||
**Воспроизведено исполнением** (браузер, реальная карточка): очищаю `spaces`,
|
||||
вызываю `_confirmDanger({...})` →
|
||||
|
||||
```
|
||||
controllerHasRequest: true
|
||||
confirmDialogInDom: false
|
||||
promiseSettledOrDialogShown: false (ожидание 200 мс)
|
||||
```
|
||||
|
||||
**Вторая половина того же дефекта** (в issue не описана, найдена при разборе):
|
||||
уже открытое подтверждение исчезает, если карточка переходит в любую раннюю
|
||||
ветку — потеряла пространство, ушла в `fixed_floor` pending. Пользователь
|
||||
видит, как диалог пропал сам; вызывающий остаётся с неразрешённым промисом.
|
||||
Поэтому «добавить рендер в ветку онбординга» — недостаточное решение: оно
|
||||
чинит один вход и оставляет класс.
|
||||
|
||||
**Контракт**: `hp-confirm` не принадлежит ни одной ветке `render()`. Он
|
||||
рендерится всегда, когда карточка рисует хоть что-то, и переживает смену
|
||||
ветки. Разделение ответственности прежнее: контроллер владеет состоянием и
|
||||
токеном, вызывающий — ревалидацией после `await` (введена #32 и не меняется).
|
||||
|
||||
**Граница**: две ветки рисуют «ничего» осознанно — `noChange` языкового гейта
|
||||
(`:11171`, Lit-сигнал «не трогай DOM», его нельзя обернуть в шаблон) и
|
||||
`nothing` до инициализации (`:11166`). В них подтверждение показать
|
||||
невозможно, и правильное поведение — **отклонить запрос сразу** (промис
|
||||
резолвится `false`), а не оставить его висеть. Пользователь в этот момент
|
||||
ничего не нажимал: карточка ещё не готова.
|
||||
|
||||
## Скоуп / не-скоуп
|
||||
|
||||
**В скоупе**: структура `render()` в `src/houseplan-card.ts` (вынос
|
||||
подтверждения из цепочки), поведение `_confirmDanger` в неготовых состояниях,
|
||||
смоки и мутанты.
|
||||
|
||||
**Не в скоупе**: содержимое и оформление диалога (#32), ревалидация после
|
||||
`await` у вызывающих (#32), роль `alertdialog` и `aria-describedby` (#406),
|
||||
`_tapConfirm` и `_vacCalConfirm` — у них своя механика и свои ветки.
|
||||
|
||||
## UX
|
||||
|
||||
Видимых изменений в оформлении нет. Меняется только то, что подтверждение
|
||||
появляется там, где раньше действие молча не работало.
|
||||
|
||||
## Модель данных и миграция
|
||||
|
||||
Не применимо.
|
||||
|
||||
## i18n
|
||||
|
||||
Новых строк нет.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- **AC1**. В состоянии «нет ни одного пространства» запрос подтверждения
|
||||
показывает диалог; «Отмена» резолвит промис `false`, подтверждение — `true`.
|
||||
Доказательство: браузерный смок на реальной карточке.
|
||||
- **AC2**. Тот же инвариант в ветках `fixed_floor` pending и invalid и в ветке
|
||||
`!space`. Доказательство: тот же смок, три ветки.
|
||||
- **AC3**. Удаление сохранённого плана из онбординга доходит до конца:
|
||||
подтверждение показано, после согласия план удалён. Доказательство: смок на
|
||||
буквальном сценарии issue (`_deleteServerPlan`).
|
||||
- **AC4**. Открытое подтверждение переживает смену ветки: карточка уходит в
|
||||
`fixed_floor` pending при открытом диалоге — диалог остаётся, решение
|
||||
по-прежнему резолвит промис. Доказательство: смок.
|
||||
- **AC5**. В неготовых состояниях (`!_config || !hass`, языковой гейт `warm`)
|
||||
запрос отклоняется немедленно: промис резолвится `false`, DOM не трогается.
|
||||
Доказательство: юнит/смок с проверкой, что `noChange` из гейта не подменён
|
||||
шаблоном.
|
||||
- **AC6**. Существующее поведение основной ветки не изменилось: начальный фокус
|
||||
на «Отмена», Esc отменяет, замена запроса отменяет предыдущий, вложенный
|
||||
диалог не ломается. Доказательство: `demo/smoke_danger_confirmation.mjs`
|
||||
остаётся зелёным без правок его утверждений.
|
||||
- **AC7**. Бюджет initial не растёт (правка структурная). Доказательство:
|
||||
`npm run bundle:budget` до и после.
|
||||
|
||||
## План автотестов
|
||||
|
||||
**Browser smoke** (`demo/smoke_danger_confirmation.mjs` — дополнение):
|
||||
|
||||
1. Пустая модель → `_confirmDanger` показывает диалог, «Отмена» → `false`
|
||||
(AC1).
|
||||
2. `fixed_floor` pending / invalid / `!space` → то же (AC2).
|
||||
3. Онбординг: клик по корзине плана → диалог → согласие → план удалён (AC3).
|
||||
4. Диалог открыт → карточка уходит в `fixed_floor` pending → диалог на месте,
|
||||
решение резолвит промис (AC4).
|
||||
5. Языковой гейт `warm` → запрос резолвится `false` сразу, `render()`
|
||||
по-прежнему возвращает `noChange` (AC5).
|
||||
|
||||
**Мутант** (`scripts/mutation-gate.mjs`):
|
||||
|
||||
- `danger-confirm-back-into-the-branch`: вернуть `hp-confirm` внутрь основной
|
||||
ветки → смок AC1 красный.
|
||||
|
||||
## Риски
|
||||
|
||||
- **`noChange` нельзя оборачивать.** Оборачивание сигнала в шаблон сломает
|
||||
оптимизацию Lit и, вероятно, вызовет лишние перерисовки. Смягчение: ветки
|
||||
`noChange`/`nothing` остаются нетронутыми, для них — явный отказ запроса;
|
||||
AC5 это фиксирует.
|
||||
- **Двойной рендер диалога.** Если подтверждение окажется и в общей обёртке, и
|
||||
в основной ветке, получится два элемента и два источника событий. Смягчение:
|
||||
смок проверяет ровно один `hp-confirm` в DOM.
|
||||
- **Отказ в неготовом состоянии может удивить вызывающего.** Действие
|
||||
отклоняется без участия пользователя. Смягчение: это состояние наступает до
|
||||
того, как карточка вообще отрисована, то есть нажать было физически нельзя;
|
||||
вызывающие уже обрабатывают `false` как «пользователь отказался».
|
||||
|
||||
## Откат
|
||||
|
||||
Возврат `hp-confirm` в основную ветку — одно перемещение блока. Данные
|
||||
пользователя не затрагиваются.
|
||||
|
||||
## Release-артефакты
|
||||
|
||||
- `docs/CHANGELOG.md` / `docs/CHANGELOG.ru.md`: пункт о том, что подтверждения
|
||||
работают и до создания первого пространства (User-Visible: yes).
|
||||
- Скриншоты не меняются: в статике диалог не открыт.
|
||||
Reference in New Issue
Block a user