From a449edc5458a03bcfb9f7f42914dabb9b8d7d367 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sat, 15 Aug 2026 03:43:01 +0300 Subject: [PATCH] docs: specify toggle confirmation states Issue: #103 User-Visible: no --- docs/specs/103-toggle-confirmation-state.md | 234 ++++++++++++++++++++ docs/specs/README.md | 8 +- 2 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 docs/specs/103-toggle-confirmation-state.md diff --git a/docs/specs/103-toggle-confirmation-state.md b/docs/specs/103-toggle-confirmation-state.md new file mode 100644 index 00000000..a612fc05 --- /dev/null +++ b/docs/specs/103-toggle-confirmation-state.md @@ -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. diff --git a/docs/specs/README.md b/docs/specs/README.md index 14c09456..67ffbfd5 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -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.