mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
committed by
claude[bot]
parent
15ff625964
commit
aad1eeaa10
@@ -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). Остальные четыре пункта — внутренние.
|
||||
- Скриншоты не меняются: диалог в статике не открыт.
|
||||
|
||||
Reference in New Issue
Block a user