docs: revise beta2 polish spec

Issue: #406
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-09-01 16:32:25 +00:00
committed by claude[bot]
parent 15ff625964
commit aad1eeaa10
+72 -53
View File
@@ -4,7 +4,7 @@
- Приоритет: P3, polish; полный трек — четыре несвязанные поверхности (словари
i18n, `hp-dialog`, снапшоты переезда area, приёмка скриншотов), критерий
«одна поверхность» для `small` не выполняется — прецеденты #385, #400
- Ревизия: 1 (2026-08-31)
- Ревизия: 2 (2026-09-01), после SPEC-REVIEW-406-r1
## Сценарий
@@ -17,37 +17,37 @@
## Что человек увидит до и после
**До**: (1) 28 строк в четырёх словарях никому не показываются, но их переводят
**До**: (1) 52 строки в четырёх словарях никому не показываются, но их переводят
и поддерживают; (2) скринридер объявляет диалог удаления как обычный `dialog` и
не читает текст последствий; (3) HA-ветка диалога не покрыта ни одним смоком —
проверяется только запасная; (4) записи снапшота исчезнувших устройств живут
вечно; (5) повторная приёмка кадров затирает список «что меняли».
**После**: словари содержат только используемое, и это проверяется гейтом;
подтверждение разрушающего действия объявляется как `alertdialog` вместе с
последствиями; смок ходит по обеим веткам диалога; снапшот убирает записи
устройств, которых достоверно нет; след приёмки не теряется.
подтверждение опасного действия — удаления или разблокировки — объявляется как
`alertdialog` вместе с последствиями; смок ходит по обеим веткам диалога;
снапшот убирает записи устройств, которых достоверно нет; след приёмки не
теряется.
## Проблема и контракты по пунктам
### (а) Мёртвые строки в словарях
Сверка ключей `src/i18n/en.json` с литералами в `src/*.ts` (динамические
шаблоны вида `` `furn.cat_${id}` `` учтены — их 48 семейств):
Сверка ключей `src/i18n/en.json` с литералами и производными ключами в
`src/*.ts` (динамические шаблоны вида `` `furn.cat_${id}` `` и механически
производные суффиксы учтены):
| Ключей в словаре | Использовано литералом | Не используется никак |
| Ключей в словаре | Есть потребитель | Не используется никак |
|---|---|---|
| 1201 | 937 | **30** |
| 1201 | 1188 | **13** |
Из тридцати семь — предмет issue (`confirm.delete_draft`,
Из тринадцати семь — предмет issue (`confirm.delete_draft`,
`confirm.delete_draft_segment`, `confirm.delete_plan`, `confirm.delete_room`,
`confirm.delete_space`, `confirm.remove_marker`, `confirm.unlock`): это
односоставные строки браузерного `confirm()`, которые #32 заменил парами
`…_title` + `…_body`.
Остальные двадцать три — тот же узор в других семействах:
Остальные шесть — тот же узор в других семействах:
- 13 ключей `*.help.aria` (`marker.*`, `space.*`, `gs.*`, `device_inbox.*`) —
задел, не подключённый ни одним потребителем;
- `marker.display_hint`, `marker.display_hint_icon` — вытеснены
`marker.display_hint_badge/_icon_ripple/_value/_static_icon`
(`houseplan-card.ts:404-407`);
@@ -56,16 +56,23 @@
- `markup.delete` — вытеснен `markup.delete_room` (`:11877`);
- `title.markup`, `history.partition_add` — остатки прежнего инструмента стен.
**Важное динамическое семейство, которое удалять нельзя**: все 19 ключей
`*.help.aria` используются фабрикой `_help('literal.help')`. В
`houseplan-editor-runtime.ts:1194` и onboarding-ветке доступный ключ выводится
как `` `${key}.aria` ``; для каждого из 19 базовых `*.help` есть литеральный
вызов `_help()`. Гейт обязан распознавать эту производную пару так же, как
шаблоны `furn.cat_*`, а не требовать буквального появления полного aria-ключа.
**Контракт**: словарь содержит только то, что кто-то показывает. Проверяется
гейтом, который строит множество литералов и динамических префиксов из `src/`
и объявляет остальное мёртвым.
гейтом, который строит множество литералов, динамических префиксов и
механически производных ключей из `src/` и объявляет остальное мёртвым.
**Скоуп сознательно шире тела issue, и вот почему**: гейт нельзя ввести
наполовину. Если он судит весь словарь — он покраснеет на всех тридцати; если
наполовину. Если он судит весь словарь — он покраснеет на всех тринадцати; если
только на `confirm.*` — он не поймает следующий такой ключ, а именно за этим
его и заводят. Поэтому в задаче удаляются все доказанно мёртвые ключи, а не
только семь. Временный список исключений не заводится: это долг, который никто
не разберёт.
только семь. Живые производные семейства, включая `*.help.aria`, описываются
правилом извлечения, а не временным списком исключений.
**Известная коллизия, которую обязана разрешить реализация**:
`test/unified-wall-tool-source.test.mjs:32` **требует наличия**
@@ -90,15 +97,22 @@
и не объявит предложение, ради которого диалог и показывают («Устройство
исчезнет с плана вместе с настройками»).
**Контракт**: диалог, требующий решения о разрушающем действии, объявляется как
`alertdialog`, и его текст последствий связан с диалогом через
`aria-describedby`. Роль **выбирается, а не меняется глобально**: `hp-dialog`
несёт и обычные диалоги (маркер, калибровка пылесоса), для которых `alertdialog`
неверен — эта роль обязывает screen reader прервать чтение.
**Контракт**: любой запрос `hp-confirm` — и `HpConfirmKind.destructive`, и
`HpConfirmKind.warning` — объявляется как `alertdialog`, и его текст последствий
связан с диалогом через `aria-describedby`. `warning` здесь не означает обычное
информационное окно: единственный такой запрос — разблокировка двери
(`houseplan-card.ts:13042-13046`) с последствиями из `confirm.unlock_body`, то
есть санкционированная `SCOPE.md` поверхность опасного действия.
Практически: `hp-dialog` получает признак вида (destructive / обычный), который
проставляет `hp-confirm` для своих запросов; обе ветки — HA и запасная —
объявляют роль одинаково.
Роль **выбирается, а не меняется глобально**: `hp-dialog` несёт и обычные
диалоги (маркер, калибровка пылесоса), для которых `alertdialog` неверен — эта
роль обязывает screen reader прервать чтение. Обычным считается диалог, который
не представлен `hp-confirm`; внутри `hp-confirm` ветки «обычного» вида нет.
Практически: `hp-dialog` получает семантический признак alert/обычный и id
описания; `hp-confirm` передаёт alert-семантику для обоих своих kind и связывает
с ней `.danger-confirm-body`. Обе ветки — HA и запасная — объявляют роль и
описание одинаково.
### (в) HA-ветка диалога не покрыта
@@ -164,8 +178,8 @@ decisions не попадает — его запись `marker_area_snapshot[id
`hp-confirm`; смок HA-ветки подтверждения; уборка снапшотов при авторитетном
реестре и порядок обрезки; сохранение `acceptance.declared`.
**Не в скоупе**: подключение `*.help.aria` к интерфейсу (если задел нужен —
отдельный issue; строки восстанавливаются из истории); содержимое и оформление
**Не в скоупе**: изменение содержимого или оформления help-подсказок — их 19
пар `.help` + `.help.aria` уже подключены и сохраняются; содержимое и оформление
диалога (#32); переезд area и его отказы (#403); порог свидетелей (#405 —
соседняя строка того же файла, другой контракт); рост `known_devices` — тот же
класс, но своя поверхность.
@@ -173,7 +187,7 @@ decisions не попадает — его запись `marker_area_snapshot[id
## UX
Оформление не меняется. Меняется то, что слышит пользователь скринридера:
подтверждение разрушающего действия объявляется как оповещение и читается
подтверждение удаления или разблокировки объявляется как оповещение и читается
вместе с последствиями. Терминология берётся из `docs/USER-GUIDE.ru.md`, новых
формулировок не вводится.
@@ -188,31 +202,35 @@ decisions не попадает — его запись `marker_area_snapshot[id
## i18n
Новых строк нет; тридцать (по числу мёртвых ключей) удаляются из каждого из
четырёх словарей. Паритет сохраняется — удаление синхронное во всех четырёх.
Новых строк нет; тринадцать мёртвых ключей удаляются из каждого из четырёх
словарей — 52 строки суммарно. Все 19 пар `*.help` + `*.help.aria` сохраняются.
Паритет сохраняется — удаление синхронное во всех четырёх словарях.
## Критерии приёмки
- **AC1**. Ни один ключ словаря не остаётся без потребителя: гейт строит
множество литералов и динамических префиксов из `src/` и краснеет на любом
ключе, не покрытом ни тем, ни другим. Доказательство: гейт зелёный после
чистки.
множество литералов, динамических префиксов и производных ключей из `src/` и
краснеет на любом ключе, не покрытом этими правилами. Доказательство: гейт
зелёный после чистки.
- **AC2**. **Отрицательный прогон обязателен**: возвращённый в словарь мёртвый
ключ роняет гейт. Доказательство: мутант через штатный раннер.
- **AC3**. Гейт не обвиняет динамические ключи: `furn.cat_*`, `furn.sym_*`,
`wall_model.reason.*`, `resize.disabled.*`, `junction.limit_*`, `decor.*` и
прочие 48 семейств остаются зелёными. Доказательство: гейт зелёный на текущем
словаре после удаления только доказанно мёртвых.
- **AC3**. Гейт не обвиняет динамические и производные ключи: `furn.cat_*`,
`furn.sym_*`, `wall_model.reason.*`, `resize.disabled.*`,
`junction.limit_*`, `decor.*`, а также все 19 `*.help.aria`, получаемые из
литеральных `_help('*.help')`, остаются зелёными. Доказательство: гейт
зелёный на текущем словаре после удаления ровно 13 доказанно мёртвых ключей.
- **AC4**. Коллизия с `test/unified-wall-tool-source.test.mjs` разрешена явно:
либо показан потребитель `history.partition_add`, либо ключ удалён вместе со
строкой теста. Доказательство: оба гейта зелёные одновременно.
- **AC5**. Паритет словарей сохранён: `test/i18n.test.mjs` зелёный без правок
его утверждений, в четырёх словарях одинаковый набор ключей.
- **AC6**. Подтверждение разрушающего действия объявляется как `alertdialog`, а
текст последствий связан через `aria-describedby`. Доказательство: смок
читает атрибуты у настоящего диалога.
- **AC7**. Обычные диалоги (маркер, калибровка) остались `dialog`.
Доказательство: тот же смок, вторая проверка.
- **AC6**. Оба вида `hp-confirm` — `destructive` (например, удаление) и
`warning` (разблокировка двери) — объявляются как `alertdialog`, а их текст
последствий связан через `aria-describedby`. Доказательство: смок читает
атрибуты и доступное описание у настоящего диалога для обоих kind.
- **AC7**. Диалоги вне `hp-confirm` (маркер, калибровка) остались `dialog` и не
получают alert-семантику. Доказательство: тот же смок, отдельная проверка
обычного `hp-dialog`.
- **AC8**. Инварианты подтверждения проверены на **обеих** ветках: диалог
показан, «Отмена» резолвит `false`, начальный фокус на «Отмена», Esc
отменяет. Доказательство: смок подтверждений со стабом `ha-dialog` (прецедент
@@ -231,7 +249,7 @@ decisions не попадает — его запись `marker_area_snapshot[id
- **AC13**. Штатная приёмка изменённого кадра по-прежнему записывает свой
список. Доказательство: существующие тесты `test/docs-acceptance.test.mjs`
зелёные без правок их утверждений.
- **AC14**. Бюджет initial не растёт: удаление тридцати строк из словарей
- **AC14**. Бюджет initial не растёт: удаление тринадцати ключей из словарей
уменьшает его, роль и `aria-describedby` добавляют единицы байт.
Доказательство: `npm run bundle:budget` до и после.
@@ -245,8 +263,8 @@ decisions не попадает — его запись `marker_area_snapshot[id
**Browser smoke** (`demo/smoke_danger_confirm_branches.mjs` — дополнение; файл
уже создан #402 и держит свои фикстуры):
2. Роль и `aria-describedby` у подтверждения (AC6), роль у обычного диалога
(AC7).
2. Роль и `aria-describedby` у destructive- и warning-подтверждения (AC6),
роль у обычного диалога вне `hp-confirm` (AC7).
3. Те же инварианты со стабом `ha-dialog` (AC8).
**Browser smoke** (`demo/smoke_area_relocation.mjs` — дополнение):
@@ -279,15 +297,16 @@ decisions не попадает — его запись `marker_area_snapshot[id
автоматически (48 семейств уже покрыты), а сообщение об отказе называет
ключ и подсказывает обе законные дороги — использовать или удалить.
- **`alertdialog` меняет поведение скринридера сильнее, чем кажется**: он
прерывает текущее чтение. Смягчение: роль назначается только подтверждениям
разрушающих действий, обычные диалоги проверяются отдельным AC.
прерывает текущее чтение. Смягчение: роль назначается только двум видам
`hp-confirm` (`destructive` и `warning`); обычные диалоги вне подтверждения
проверяются отдельным AC.
- **Уборка снапшотов может стереть законные записи.** Это ровно тот класс, что
чинит #403. Смягчение: уборка только при авторитетном реестре, AC10
проверяет обратный случай напрямую, мутант закреплён.
- **Удаление 13 ключей `*.help.aria` окажется преждевременным**, если задел
планировался к подключению. Смягчение: восстановление из истории тривиально,
а поддерживать непоказываемый перевод в четырёх языках дороже, чем вернуть
строки в тот день, когда для них появится потребитель.
- **Гейт примет производный ключ за мёртвый.** Наиболее опасный текущий пример —
19 живых `*.help.aria`, полный ключ которых собирается в `_help()`. Смягчение:
производное правило `.help` → `.help.aria` является частью AC3 и проверяется
всем текущим набором, а не ручным исключением отдельных имён.
## Откат
@@ -299,6 +318,6 @@ decisions не попадает — его запись `marker_area_snapshot[id
## Release-артефакты
- `docs/CHANGELOG.md` / `docs/CHANGELOG.ru.md`: пункт о том, что подтверждение
разрушающего действия объявляется скринридером вместе с последствиями
удаления или разблокировки объявляется скринридером вместе с последствиями
(User-Visible: yes). Остальные четыре пункта — внутренние.
- Скриншоты не меняются: диалог в статике не открыт.