diff --git a/README.md b/README.md index 23913de..22a1792 100644 --- a/README.md +++ b/README.md @@ -443,3 +443,8 @@ поверх затемнения вместо выпадашки у триггера: на телефоне выпадашка у правого края экрана уезжает за вьюпорт, и потребители правят координаты инлайном. Спецификация, значения и приёмка: [`docs/tasks/gn-dropdown-mobile-sheet.md`](docs/tasks/gn-dropdown-mobile-sheet.md). +- **Плашка запроса браузерного разрешения** — `GnPermissionBanner`: призыв с двумя + ответами, клик по кнопке согласия и есть пользовательский жест, без которого + браузер не показывает свой запрос. Политику (когда спрашивать, что помнить об + отказе, в каком хранилище) кит не берёт — только жест, вид и контракт. + Спецификация, значения и приёмка: [`docs/tasks/gn-permission-banner.md`](docs/tasks/gn-permission-banner.md). diff --git a/docs/tasks/gn-permission-banner.md b/docs/tasks/gn-permission-banner.md new file mode 100644 index 0000000..daf845b --- /dev/null +++ b/docs/tasks/gn-permission-banner.md @@ -0,0 +1,253 @@ +# Задача: `GnPermissionBanner` — плашка запроса браузерного разрешения + +**Статус:** не начата. +**Тип:** новый компонент (поведение + стили; vanilla и Vue-адаптер). +**Повод:** потребитель кита (navi) сделал у себя плашку запроса разрешения на +уведомления и просит перенести её в кит, чтобы у следующего сервиса она уже была. +Запись ведётся по правилу платформы: находка про общий ресурс живёт в доке ресурса, +у потребителя остаётся только рабочий обход и ссылка сюда. + +**Источник:** navi webclient — `webclient/src/components/ui/NotificationPromptBanner.vue` +и `webclient/src/composables/useNotificationPrompt.js`; ui-kit 1.0.0 (коммит `ca418ad`). +**Автор находки:** ИИ-агент (сессия разработки navi). **Автор задачи:** владелец navi. +**Дата:** 2026-10-10. **Задача в трекере:** не заведена. + +**Суть.** Разрешение на браузерные уведомления нельзя запросить «самим фактом +загрузки страницы»: Firefox с 72-й версии и Safari игнорируют вызов без +пользовательского действия, а Chrome показывает вместо окна «тихий» перечёркнутый +колокольчик. Нужна видимая кнопка, клик по которой и является жестом. Кит не даёт ни +такой плашки, ни обёртки над `Notification.requestPermission()`, поэтому каждый +потребитель изобретает их заново. + +**Доказательства.** Поведение самих браузеров (требование user activation для +`Notification.requestPermission()`); в navi до этой плашки разрешение запрашивалось +только тумблером в настройках, автозапрос отсутствовал — то есть без кнопки запрос +попросту не работает. Рабочая реализация у потребителя: бар во всю ширину с +заголовком, строкой текста и двумя кнопками, клик по первой уходит в +`Notification.requestPermission()` синхронно (проверено в Chrome на сборке navi, +1440px и 390px: полоса 1060×65 и 390×127, текст не обрезан, горизонтальной +прокрутки нет). + +**Предложение.** Отдать в ките `GnPermissionBanner` — презентационную плашку с +двумя ответами и **без политики**: когда спрашивать, что помнить об отказе и в каком +хранилище это держать, решает потребитель (см. «Границы задачи»). + +## Проблема + +`GnAlert` — единственное близкое к плашке в ките — инлайновый блок сообщения: у него +есть `variant` и `role`, но нет ни действий, ни закрытия, и он не проектировался как +призыв. Запроса разрешения кит не касается вовсе. + +Из-за этого у каждого потребителя получается свой велосипед, и ошибки в нём одни и +те же: + +- запрос уходит без жеста (в Firefox/Safari — молча ничего, в Chrome — «тихий» + режим, из которого сайт уже не выйдет: после нескольких отказов браузер + перестаёт показывать окно); +- между кликом и `requestPermission()` появляется `await` (загрузка ключа, + запрос к API) — user activation к этому моменту истрачен, окно не открывается; +- «отказ» трактуется по-разному: кто-то считает отказом закрытие системного окна, + и потом либо молчит навсегда зря, либо спрашивает при каждой загрузке. + +## Целевое поведение + +Компонент — плашка с призывом и двумя ответами: + +1. **Ничего не запрашивает сам.** Ни на маунте, ни на показе. Единственный вызов + `requestPermission()` — обработчик клика по кнопке согласия, и он же — первый + `await` в цепочке. +2. **Показом управляет потребитель:** проп `open` (controlled) или `show()` в + vanilla. Компонент не решает, уместно ли спрашивать. +3. **Два ответа:** «включить» (первичная кнопка) и «не сейчас» (вторичная). Второй + ответ ничего не записывает — это забота потребителя. +4. **Пока ждём ответа системы**, плашка занята: обе кнопки disabled (в Vue — + `busy`; в vanilla — `data-busy` на корне). Системное окно в это время открыто. +5. **Разметка — строка во всю ширину** контейнера: иконка, заголовок, текст, + действия справа; на узком экране блок текста и кнопки переносятся на + следующую строку, горизонтальной прокрутки не появляется. +6. **Доступность:** `role="region"`, `aria-live="polite"`, `aria-label` из + заголовка; кнопки — настоящие `