From c3bb62a3b6b009c2ce82a3f7fb8e09d23c6d27fb Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Tue, 1 Sep 2026 18:32:56 +0300 Subject: [PATCH] docs: resolve beta2 polish review Issue: #406 User-Visible: no --- docs/specs/406-beta2-polish.md | 128 ++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/docs/specs/406-beta2-polish.md b/docs/specs/406-beta2-polish.md index 61bbfb66..fd46ca81 100644 --- a/docs/specs/406-beta2-polish.md +++ b/docs/specs/406-beta2-polish.md @@ -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` на внешнем +`` **не считается** доказательством роли настоящего диалога. + Практически: `hp-dialog` получает семантический признак alert/обычный и id описания; `hp-confirm` передаёт alert-семантику для обоих своих kind и связывает -с ней `.danger-confirm-body`. Обе ветки — HA и запасная — объявляют роль и -описание одинаково. +с ней `.danger-confirm-body`. Для обычного окна при зарегистрированном +`ha-dialog` сохраняется HA-ветка с `.ariaLabelledBy` и, когда описание задано, +`.ariaDescribedBy`. Для alert-подтверждения `hp-dialog` намеренно выбирает +нативный `` даже при наличии `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). Остальные три пункта — внутренние. - Скриншоты не меняются: диалог в статике не открыт.