mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
@@ -0,0 +1,234 @@
|
||||
# Issue #103 — текущее и ожидаемое состояние в Toggle confirmation
|
||||
|
||||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/103
|
||||
- **Статус документа:** готово к будущей реализации; issue остаётся на `S3-spec`
|
||||
- **Приоритет:** P3
|
||||
- **Тип:** feature/polish, обычный трек
|
||||
- **Пользовательское изменение:** да
|
||||
|
||||
## 1. Контекст
|
||||
|
||||
Универсальный toggle из #94 уже строит один `ResolvedToggleIntent` для hint,
|
||||
confirmation guard и фактической service call. При `tap_confirm` текущий диалог
|
||||
показывает только имя цели. Пользователь не видит исходное состояние и
|
||||
направление команды, особенно у cover/valve и смешанных групп.
|
||||
|
||||
#103 расширяет только содержимое существующего confirmation. Resolver, target
|
||||
selection, кнопки и safety re-resolve остаются прежними.
|
||||
|
||||
## 2. Пользовательский результат
|
||||
|
||||
Одиночная цель:
|
||||
|
||||
> Переключить «Лампа в прихожей»?
|
||||
>
|
||||
> Текущее состояние: Выключено
|
||||
>
|
||||
> После переключения: Включено
|
||||
|
||||
Группа:
|
||||
|
||||
> Текущее состояние: включено 2 из 4
|
||||
>
|
||||
> После переключения: все выключены
|
||||
|
||||
Строки являются обычным доступным текстом, а не цветом, и читаются до кнопок
|
||||
Cancel/Confirm.
|
||||
|
||||
## 3. Цели
|
||||
|
||||
1. Объяснить текущее и ожидаемое состояние каждой исполняемой toggle-команды.
|
||||
2. Использовать только `ResolvedToggleIntent`, без UI-domain эвристики.
|
||||
3. Не обещать результат для skipped/unknown целей.
|
||||
4. Сохранить re-resolve перед service call и target-set race protection #94.
|
||||
|
||||
## 4. Не входит в задачу
|
||||
|
||||
- изменение resolver, command или service;
|
||||
- прогноз scripts/scenes и произвольных actions;
|
||||
- история состояний;
|
||||
- confirmations удаления/unlock/run и общий dialog redesign #32;
|
||||
- live-анимация состояния в открытом диалоге;
|
||||
- изменение `tap_confirm` schema.
|
||||
|
||||
## 5. Источник данных
|
||||
|
||||
Confirmation snapshot строится из `ResolvedToggleIntent`:
|
||||
|
||||
- `targets[].state` и `targets[].name`;
|
||||
- `kind` и `semantics`;
|
||||
- `nextEffect`;
|
||||
- `skippedTargets`;
|
||||
- stable identity из `sameToggleOperationTargets()`.
|
||||
|
||||
`src/device-toggle.ts` остаётся владельцем line selection. Допустимо расширить
|
||||
`formatToggleIntent()` либо добавить рядом pure `formatToggleConfirmation()`, но
|
||||
`houseplan-card.ts` не выводит next state по domain самостоятельно.
|
||||
|
||||
State label берётся через HA formatter, когда state object доступен. Raw state
|
||||
допустим только как безопасный fallback; неизвестный будущий результат никогда
|
||||
не подменяется уверенным On/Off.
|
||||
|
||||
## 6. Нормативная матрица
|
||||
|
||||
| Intent | Текущее состояние | После переключения |
|
||||
| --- | --- | --- |
|
||||
| power `off` + `turn-on` | Выключено | Включено |
|
||||
| active power + `turn-off` | HA-formatted current | Выключено |
|
||||
| cover `closed/closing` + `open` | Закрыто/Закрывается | Открыто |
|
||||
| cover `open/opening` + `close` | Открыто/Открывается | Закрыто |
|
||||
| cover + `stop` | HA-formatted current | Остановлено |
|
||||
| valve + `open/close` | Закрыто/Открыто | Открыто/Закрыто |
|
||||
| group, все off | Все выключены | Все включены |
|
||||
| group, есть active | Включено N из M | Все выключены |
|
||||
| partial group | доступное подмножество + Недоступно N | результат только command targets |
|
||||
| `toggle` | HA-formatted current | Состояние определит Home Assistant |
|
||||
| no operation | confirmation не открывается | — |
|
||||
|
||||
Direction всегда следует `nextEffect`; UI не пересчитывает её из текущей строки.
|
||||
|
||||
Для группы denominator результата равен числу фактических `targets`, а skipped
|
||||
выводятся отдельно. Формулировка не обещает, что unavailable/disabled/missing
|
||||
entities изменятся.
|
||||
|
||||
## 7. Dialog state и race
|
||||
|
||||
`_tapConfirm` расширяется структурированным snapshot, а не одним HTML string:
|
||||
|
||||
```ts
|
||||
interface TapToggleConfirmation {
|
||||
kind: 'toggle';
|
||||
title: string;
|
||||
lines: string[];
|
||||
initialIntent: ResolvedToggleIntent;
|
||||
deviceId: string;
|
||||
exec: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
Минимальный обязательный контракт:
|
||||
|
||||
- при открытии title/lines фиксируются из initial intent;
|
||||
- при Confirm текущий device и intent разрешаются заново;
|
||||
- если operation targets изменились, service call отсутствует и показывается
|
||||
существующий `toast.tap_target_changed`;
|
||||
- если targets прежние, выполняется актуальное direction/command, даже если
|
||||
state изменился после открытия;
|
||||
- dialog snapshot не обязан live-обновлять строки.
|
||||
|
||||
`run` confirmation продолжает использовать нынешнюю простую форму. Общий union
|
||||
не должен заставлять run/delete dialogs притворяться toggle.
|
||||
|
||||
## 8. UX, i18n и accessibility
|
||||
|
||||
- title сохраняет нынешнее «Переключить …?»;
|
||||
- current/expected/skipped — отдельные строки/paragraphs;
|
||||
- long friendly name и group text переносятся без horizontal scroll;
|
||||
- accessible DOM order: title → current → expected → skipped → buttons;
|
||||
- смысл не выражается только иконкой/цветом/стрелкой;
|
||||
- narrow mobile footer сохраняет две доступные кнопки;
|
||||
- keyboard focus и Escape/scrim contract не меняются.
|
||||
|
||||
Минимальные новые RU/EN keys:
|
||||
|
||||
- `confirm.current_state`;
|
||||
- `confirm.expected_state`;
|
||||
- `confirm.group_current`;
|
||||
- `confirm.group_all_on` / `confirm.group_all_off`;
|
||||
- `confirm.unavailable_targets`;
|
||||
- `confirm.expected_by_ha`;
|
||||
- state/effect labels, которых ещё нет в общей toggle vocabulary.
|
||||
|
||||
Не вводятся plural rules; строки следуют текущему counter-safe style.
|
||||
|
||||
## 9. Совместимость и безопасность
|
||||
|
||||
- stored `tap_confirm` и config не меняются;
|
||||
- no-op по-прежнему тихий и не открывает modal;
|
||||
- secure/disabled/missing filtering остаётся resolver-owned;
|
||||
- confirmation не исполняет команду из initial snapshot;
|
||||
- operation target identity проверяется перед actuation;
|
||||
- virtual-light intent использует тот же current/expected formatter и current
|
||||
backend snapshot, не создавая HA service;
|
||||
- никаких новых permissions или network requests.
|
||||
|
||||
## 10. Acceptance criteria
|
||||
|
||||
1. Toggle confirmation для operation показывает current и expected lines.
|
||||
2. Expected line строго соответствует `nextEffect`.
|
||||
3. Power, cover, valve, virtual light и group имеют локализованные формулировки.
|
||||
4. Partial group явно показывает skipped count и не обещает их изменение.
|
||||
5. `toggle` сообщает, что результат определит HA.
|
||||
6. No-operation intent не открывает confirmation.
|
||||
7. Confirm выполняет заново разрешённый current intent.
|
||||
8. Изменившийся target set отменяет actuation и показывает прежний toast.
|
||||
9. Desktop/mobile layout, keyboard и screen-reader order не регрессируют.
|
||||
10. Run и другие confirmations сохраняют прежнее содержимое.
|
||||
|
||||
## 11. План тестирования
|
||||
|
||||
### Unit
|
||||
|
||||
- pure formatter для каждого `ToggleNextEffect`;
|
||||
- single power/cover/valve/virtual-light;
|
||||
- all-off, mixed и partial group;
|
||||
- formatted current state и raw fallback;
|
||||
- unknown `toggle` result;
|
||||
- no-operation returns no confirmation lines;
|
||||
- EN/RU placeholder parity.
|
||||
|
||||
### Integration/browser
|
||||
|
||||
- confirmation DOM order на desktop и narrow mobile;
|
||||
- initial snapshot + state changes + same targets → current command executes;
|
||||
- changed targets → zero service calls + toast;
|
||||
- skipped target is not included in command/result promise;
|
||||
- keyboard focus, Escape, Cancel and scrim;
|
||||
- run confirmation unchanged.
|
||||
|
||||
### Регрессия
|
||||
|
||||
- `test/device-toggle.test.mjs`;
|
||||
- existing HA-controls/toggle smoke;
|
||||
- typecheck, full unit и build.
|
||||
|
||||
Golden не требуется, если modal reflow покрыт narrow render smoke и не меняет
|
||||
принятый внешний layout. При необходимости добавляется одна deterministic dialog
|
||||
scene без переакцептации несвязанных baseline.
|
||||
|
||||
## 12. План реализации
|
||||
|
||||
1. Добавить pure confirmation formatter рядом с resolver.
|
||||
2. Расширить `_tapConfirm` discriminated state для toggle.
|
||||
3. Отрисовать semantic lines в текущем dialog.
|
||||
4. Добавить RU/EN strings и parity tests.
|
||||
5. Проверить race contract и existing confirmations.
|
||||
|
||||
## 13. Документация и release-артефакты
|
||||
|
||||
- оба changelog получают user-visible пункт;
|
||||
- `docs/USER-GUIDE.ru.md` показывает current→expected confirmation;
|
||||
- `docs/TESTING.md` получает group/race/narrow dialog matrix;
|
||||
- RU/EN dictionaries меняются в одном implementation commit;
|
||||
- screenshot/golden — только для точечной modal scene при необходимости;
|
||||
- backend, migration, performance profile и security artifact не требуются.
|
||||
|
||||
## 14. Риски и откат
|
||||
|
||||
| Риск | Мера |
|
||||
| --- | --- |
|
||||
| Dialog обещает не ту команду | nextEffect-only formatter |
|
||||
| Initial snapshot исполняется после race | mandatory re-resolve |
|
||||
| Skipped входят в denominator | separate targets/skipped assertions |
|
||||
| UI дублирует resolver | pure device-toggle formatter |
|
||||
| Long group ломает mobile | narrow viewport smoke |
|
||||
|
||||
Откат возвращает `_tapConfirm` к одному title string. Config и resolver не
|
||||
меняются, поэтому data rollback отсутствует.
|
||||
|
||||
## 15. Принятые технические предположения
|
||||
|
||||
- первая версия показывает snapshot и не live-обновляет открытый dialog;
|
||||
- current state использует HA formatter при наличии;
|
||||
- `stop` локализуется как честный ожидаемый effect, не как конечная позиция;
|
||||
- group result описывает только фактические command targets.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Спецификации задач P1 и P2
|
||||
# Спецификации задач
|
||||
|
||||
Актуально на 2026-08-17.
|
||||
|
||||
@@ -100,6 +100,12 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| [#174](https://github.com/Matysh/houseplan-card/issues/174) Связанный виртуальный источник следует реальному контроллеру | [174-linked-virtual-light-controller.md](174-linked-virtual-light-controller.md) |
|
||||
| [#178](https://github.com/Matysh/houseplan-card/issues/178) Выбор сущности для действия «Переключить состояние» | [178-toggle-entity.md](178-toggle-entity.md) |
|
||||
|
||||
## P3
|
||||
|
||||
| Issue | ТЗ |
|
||||
|---|---|
|
||||
| [#103](https://github.com/Matysh/houseplan-card/issues/103) Состояния в Toggle confirmation | [103-toggle-confirmation-state.md](103-toggle-confirmation-state.md) |
|
||||
|
||||
## Правило актуализации
|
||||
|
||||
При изменении продуктового решения сначала обновляется соответствующее issue, затем ТЗ. Реализация не считается завершённой только по наличию кода: нужны выполненные acceptance criteria, предусмотренная ТЗ проверка и актуальный статус Project v2.
|
||||
|
||||
Reference in New Issue
Block a user