mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
committed by
claude[bot]
parent
bd780c6013
commit
c3bb62a3b6
@@ -1,19 +1,18 @@
|
||||
# ТЗ #406 — Полиш v1.70.0-beta.2: мёртвые строки, роль диалога, непокрытая ветка, растущий снапшот
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/406
|
||||
- Приоритет: P3, polish; полный трек — четыре несвязанные поверхности (словари
|
||||
i18n, `hp-dialog`, снапшоты переезда area, приёмка скриншотов), критерий
|
||||
- Приоритет: P3, polish; полный трек — три несвязанные поверхности (словари
|
||||
i18n, `hp-dialog`, снапшоты переезда area), критерий
|
||||
«одна поверхность» для `small` не выполняется — прецеденты #385, #400
|
||||
- Ревизия: 2 (2026-09-01), после SPEC-REVIEW-406-r1
|
||||
- Ревизия: 3 (2026-09-01), после SPEC-REVIEW-406-r2
|
||||
|
||||
## Сценарий
|
||||
|
||||
Пять мелочей, оставшихся после крупной работы #32 и #126. Переводчик
|
||||
Четыре мелочи, оставшиеся после крупной работы #32 и #126. Переводчик
|
||||
поддерживает строки, которых никто не показывает. Незрячий пользователь слышит
|
||||
заголовок диалога удаления, но не слышит, что именно исчезнет. Смоки проверяют
|
||||
запасную ветку диалога, а не ту, что работает у людей. Снапшот переезда копит
|
||||
записи об устройствах, которых давно нет. Повторная приёмка скриншотов стирает
|
||||
след того, зачем их перерисовывали.
|
||||
записи об устройствах, которых давно нет.
|
||||
|
||||
## Что человек увидит до и после
|
||||
|
||||
@@ -21,12 +20,11 @@
|
||||
и поддерживают; (2) скринридер объявляет диалог удаления как обычный `dialog` и
|
||||
не читает текст последствий; (3) HA-ветка диалога не покрыта ни одним смоком —
|
||||
проверяется только запасная; (4) записи снапшота исчезнувших устройств живут
|
||||
вечно; (5) повторная приёмка кадров затирает список «что меняли».
|
||||
вечно.
|
||||
**После**: словари содержат только используемое, и это проверяется гейтом;
|
||||
подтверждение опасного действия — удаления или разблокировки — объявляется как
|
||||
`alertdialog` вместе с последствиями; смок ходит по обеим веткам диалога;
|
||||
снапшот убирает записи устройств, которых достоверно нет; след приёмки не
|
||||
теряется.
|
||||
снапшот убирает записи устройств, которых достоверно нет.
|
||||
|
||||
## Проблема и контракты по пунктам
|
||||
|
||||
@@ -109,10 +107,26 @@
|
||||
роль обязывает screen reader прервать чтение. Обычным считается диалог, который
|
||||
не представлен `hp-confirm`; внутри `hp-confirm` ветки «обычного» вида нет.
|
||||
|
||||
Проверена ровно закреплённая проектом версия
|
||||
`home-assistant-frontend==20260729.7` из `tests_backend/requirements.txt`.
|
||||
[Её `src/components/ha-dialog.ts`](https://github.com/home-assistant/frontend/blob/20260729.7/src/components/ha-dialog.ts)
|
||||
показывает, что публичный API `ha-dialog` содержит свойства `.ariaDescribedBy` и
|
||||
`.type: "alert" | "standard"`; первое передаётся внутреннему `wa-dialog` как
|
||||
`aria-describedby`. Однако `type` только отражается на host и используется в
|
||||
CSS: в `render()` нет ни `this.type`, ни передачи `role` внутреннему
|
||||
`wa-dialog`. Поэтому привязка `.type="alert"` или `role` на внешнем
|
||||
`<ha-dialog>` **не считается** доказательством роли настоящего диалога.
|
||||
|
||||
Практически: `hp-dialog` получает семантический признак alert/обычный и id
|
||||
описания; `hp-confirm` передаёт alert-семантику для обоих своих kind и связывает
|
||||
с ней `.danger-confirm-body`. Обе ветки — HA и запасная — объявляют роль и
|
||||
описание одинаково.
|
||||
с ней `.danger-confirm-body`. Для обычного окна при зарегистрированном
|
||||
`ha-dialog` сохраняется HA-ветка с `.ariaLabelledBy` и, когда описание задано,
|
||||
`.ariaDescribedBy`. Для alert-подтверждения `hp-dialog` намеренно выбирает
|
||||
нативный `<dialog role="alertdialog">` даже при наличии `ha-dialog`: это
|
||||
единственный поддерживаемый из нашего дерева путь, который гарантирует роль и
|
||||
не лезет во внутренний shadow DOM стороннего компонента. При отсутствии
|
||||
`ha-dialog` используется та же нативная ветка. Внешность нативной ветки уже
|
||||
является поддерживаемой частью `hp-dialog`; отдельного CSS-дубля не появляется.
|
||||
|
||||
### (в) HA-ветка диалога не покрыта
|
||||
|
||||
@@ -126,9 +140,14 @@
|
||||
Итог: покрыта только запасная ветка, существующая ради standalone-демо, а не
|
||||
та, что работает у пользователя.
|
||||
|
||||
**Контракт**: инварианты подтверждения (диалог показан, «Отмена» резолвит
|
||||
`false`, начальный фокус на «Отмена», Esc отменяет) проверяются на **обеих**
|
||||
ветках. Прецедент стаба `ha-dialog` уже написан и лежит в соседнем файле.
|
||||
**Контракт**: смок с зарегистрированным `ha-dialog` проверяет обе реально
|
||||
достижимые развилки: обычный `hp-dialog` пользуется HA-компонентом, а
|
||||
alert-подтверждение остаётся нативным и сохраняет `alertdialog`, описание,
|
||||
начальный фокус на «Отмена» и Esc → `false`. Смок без `ha-dialog` проверяет те
|
||||
же инварианты нативного подтверждения. Прецедент стаба `ha-dialog` уже написан
|
||||
и лежит в соседнем файле; стаб обязан моделировать публичные свойства
|
||||
`.ariaLabelledBy`, `.ariaDescribedBy` и `.type`, а не произвольную роль во
|
||||
внутреннем shadow DOM.
|
||||
|
||||
### (г) Снапшот копит записи исчезнувших устройств
|
||||
|
||||
@@ -154,35 +173,21 @@ decisions не попадает — его запись `marker_area_snapshot[id
|
||||
записи, а не самые старые: мусор вытесняет живое. Правило переворачивается
|
||||
заодно — при обрезке сохраняются последние записи.
|
||||
|
||||
### (д) Затирание следа приёмки
|
||||
|
||||
`scripts/docs-accept.mjs:145` пишет `declared: [...decision.replace]`. Модельный
|
||||
прогон повторной приёмки неизменённого набора:
|
||||
|
||||
```
|
||||
отказ: НЕТ | replace=[] | witnesses=10 | floor=1
|
||||
→ в манифест уйдёт acceptance.declared = []
|
||||
```
|
||||
|
||||
Безобидный повтор `docs:accept` проходит успешно и стирает единственную запись
|
||||
о том, зачем кадры перерисовывались.
|
||||
|
||||
**Контракт**: приёмка, ничего не заменившая, не переписывает историю
|
||||
предыдущей. Свежие числа (`witnesses`, `floor`) обновляются, список
|
||||
`declared` при пустом наборе замен сохраняется.
|
||||
|
||||
## Скоуп / не-скоуп
|
||||
|
||||
**В скоупе**: удаление доказанно мёртвых ключей из четырёх словарей и гейт,
|
||||
который не даёт им вернуться; роль и `aria-describedby` в `hp-dialog` и
|
||||
`hp-confirm`; смок HA-ветки подтверждения; уборка снапшотов при авторитетном
|
||||
реестре и порядок обрезки; сохранение `acceptance.declared`.
|
||||
`hp-confirm`; смок выбора HA/нативной ветки; уборка снапшотов при авторитетном
|
||||
реестре и порядок обрезки.
|
||||
|
||||
**Не в скоупе**: изменение содержимого или оформления help-подсказок — их 19
|
||||
пар `.help` + `.help.aria` уже подключены и сохраняются; содержимое и оформление
|
||||
диалога (#32); переезд area и его отказы (#403); порог свидетелей (#405 —
|
||||
соседняя строка того же файла, другой контракт); рост `known_devices` — тот же
|
||||
класс, но своя поверхность.
|
||||
класс, но своя поверхность; история screenshot-acceptance — дефект уже исправлен
|
||||
в #409 коммитом `8119c523`, включая сохранение всего предыдущего блока и
|
||||
`lastWriteWasFingerprintOnly`, поэтому #406 не меняет и не дублирует этот
|
||||
контракт.
|
||||
|
||||
## UX
|
||||
|
||||
@@ -227,14 +232,18 @@ decisions не попадает — его запись `marker_area_snapshot[id
|
||||
- **AC6**. Оба вида `hp-confirm` — `destructive` (например, удаление) и
|
||||
`warning` (разблокировка двери) — объявляются как `alertdialog`, а их текст
|
||||
последствий связан через `aria-describedby`. Доказательство: смок читает
|
||||
атрибуты и доступное описание у настоящего диалога для обоих kind.
|
||||
атрибуты и доступное описание у нативного диалога для обоих kind, в том числе
|
||||
когда `ha-dialog` зарегистрирован.
|
||||
- **AC7**. Диалоги вне `hp-confirm` (маркер, калибровка) остались `dialog` и не
|
||||
получают alert-семантику. Доказательство: тот же смок, отдельная проверка
|
||||
обычного `hp-dialog`.
|
||||
- **AC8**. Инварианты подтверждения проверены на **обеих** ветках: диалог
|
||||
показан, «Отмена» резолвит `false`, начальный фокус на «Отмена», Esc
|
||||
отменяет. Доказательство: смок подтверждений со стабом `ha-dialog` (прецедент
|
||||
`smoke_free_walls.mjs:20-22`) плюс существующий прогон запасной ветки.
|
||||
получают alert-семантику. Доказательство: при зарегистрированном стабе
|
||||
обычный `hp-dialog` рендерит `ha-dialog`, передаёт ему `.ariaLabelledBy` и
|
||||
при наличии описания `.ariaDescribedBy`; нативного диалога в этой ветке нет.
|
||||
- **AC8**. Инварианты подтверждения проверены в обоих окружениях: с
|
||||
зарегистрированным `ha-dialog` и без него диалог показан, «Отмена» резолвит
|
||||
`false`, начальный фокус на «Отмена», Esc отменяет. Доказательство: смок
|
||||
подтверждений с конформным стабом `ha-dialog` (прецедент
|
||||
`smoke_free_walls.mjs:20-22`) плюс существующий прогон без стаба; в первом
|
||||
случае дополнительно доказано, что alert не уходит в HA-ветку.
|
||||
- **AC9**. Запись снапшота исчезнувшего устройства убирается при авторитетном
|
||||
реестре. Доказательство: смок — устройство пропадает из `_devices`, после
|
||||
прохода записи в `marker_area_snapshot` нет.
|
||||
@@ -243,13 +252,7 @@ decisions не попадает — его запись `marker_area_snapshot[id
|
||||
- **AC11**. При обрезке снапшота сверх лимита сохраняются последние записи, а
|
||||
не первые. Доказательство: юнит на `markerAreaSnapshotOf` с входом больше
|
||||
лимита.
|
||||
- **AC12**. Повторная приёмка неизменённого набора не затирает
|
||||
`acceptance.declared`. Доказательство: тест на функции приёмки —
|
||||
`replace: []` оставляет прежний список.
|
||||
- **AC13**. Штатная приёмка изменённого кадра по-прежнему записывает свой
|
||||
список. Доказательство: существующие тесты `test/docs-acceptance.test.mjs`
|
||||
зелёные без правок их утверждений.
|
||||
- **AC14**. Бюджет initial не растёт: удаление тринадцати ключей из словарей
|
||||
- **AC12**. Бюджет initial не растёт: удаление тринадцати ключей из словарей
|
||||
уменьшает его, роль и `aria-describedby` добавляют единицы байт.
|
||||
Доказательство: `npm run bundle:budget` до и после.
|
||||
|
||||
@@ -263,9 +266,11 @@ decisions не попадает — его запись `marker_area_snapshot[id
|
||||
**Browser smoke** (`demo/smoke_danger_confirm_branches.mjs` — дополнение; файл
|
||||
уже создан #402 и держит свои фикстуры):
|
||||
|
||||
2. Роль и `aria-describedby` у destructive- и warning-подтверждения (AC6),
|
||||
роль у обычного диалога вне `hp-confirm` (AC7).
|
||||
3. Те же инварианты со стабом `ha-dialog` (AC8).
|
||||
2. Роль и `aria-describedby` у destructive- и warning-подтверждения без
|
||||
`ha-dialog` (AC6, AC8).
|
||||
3. Конформный стаб `ha-dialog` принимает публичные `.ariaLabelledBy`,
|
||||
`.ariaDescribedBy` и `.type`: обычный `hp-dialog` выбирает HA-ветку, а оба
|
||||
alert-подтверждения — нативную; фокус, Esc и результат не расходятся (AC6–8).
|
||||
|
||||
**Browser smoke** (`demo/smoke_area_relocation.mjs` — дополнение):
|
||||
|
||||
@@ -275,13 +280,13 @@ decisions не попадает — его запись `marker_area_snapshot[id
|
||||
**Юниты**:
|
||||
|
||||
6. `markerAreaSnapshotOf` на входе больше лимита — сохранены последние (AC11).
|
||||
7. Приёмка с `replace: []` — прежний `declared` сохранён (AC12).
|
||||
|
||||
**Мутанты** (`scripts/mutation-gate.mjs`):
|
||||
|
||||
- `i18n-dead-key-returns`: вернуть `confirm.unlock` в словари → гейт краснеет;
|
||||
- `confirm-dialog-loses-alertdialog`: вернуть `role="dialog"` подтверждению →
|
||||
смок AC6 краснеет;
|
||||
- `confirm-dialog-loses-alertdialog`: вернуть `role="dialog"` подтверждению
|
||||
либо разрешить alert уйти в зарегистрированный `ha-dialog` → смок AC6
|
||||
краснеет;
|
||||
- `area-snapshot-cleanup-ignores-authority`: убрать условие авторитетности →
|
||||
смок AC10 краснеет.
|
||||
|
||||
@@ -300,6 +305,11 @@ decisions не попадает — его запись `marker_area_snapshot[id
|
||||
прерывает текущее чтение. Смягчение: роль назначается только двум видам
|
||||
`hp-confirm` (`destructive` и `warning`); обычные диалоги вне подтверждения
|
||||
проверяются отдельным AC.
|
||||
- **Будущая версия HA может исправить `ha-dialog.type="alert"`.** Текущий
|
||||
выбор нативной ветки останется корректным, только консервативным. Переход на
|
||||
HA alert в будущем — отдельное изменение после проверки новой закреплённой
|
||||
версии; #406 не патчит shadow DOM и не распознаёт приватную структуру
|
||||
`wa-dialog`.
|
||||
- **Уборка снапшотов может стереть законные записи.** Это ровно тот класс, что
|
||||
чинит #403. Смягчение: уборка только при авторитетном реестре, AC10
|
||||
проверяет обратный случай напрямую, мутант закреплён.
|
||||
@@ -310,14 +320,14 @@ decisions не попадает — его запись `marker_area_snapshot[id
|
||||
|
||||
## Откат
|
||||
|
||||
Пять правок независимы и откатываются по отдельности: удаление ключей —
|
||||
обратным коммитом, роль и `aria-describedby` — одной строкой каждая, уборка
|
||||
снапшотов — снятием условия, сохранение `declared` — возвратом прежнего
|
||||
выражения.
|
||||
Четыре правки независимы и откатываются по отдельности: удаление ключей —
|
||||
обратным коммитом, выбор нативной alert-ветки и `aria-describedby` — одним
|
||||
локальным изменением `hp-dialog`/`hp-confirm`, HA-smoke — удалением сценария,
|
||||
уборка снапшотов — снятием условия.
|
||||
|
||||
## Release-артефакты
|
||||
|
||||
- `docs/CHANGELOG.md` / `docs/CHANGELOG.ru.md`: пункт о том, что подтверждение
|
||||
удаления или разблокировки объявляется скринридером вместе с последствиями
|
||||
(User-Visible: yes). Остальные четыре пункта — внутренние.
|
||||
(User-Visible: yes). Остальные три пункта — внутренние.
|
||||
- Скриншоты не меняются: диалог в статике не открыт.
|
||||
|
||||
Reference in New Issue
Block a user