ограниченные посетители
Список ограничений на посетителей сайта — спам-заглушки и бессрочные блокировки, — с возможностью снять любое действующее. Экран надзора: администратор открывает его, чтобы проверить или отменить, а не следить в реальном времени, поэтому здесь нет автообновления — только ручное обновление после снятия.
Сегодня шапка списка диалогов несёт только аватар аккаунта (26-90 убрал старую пару «точка + меню»). Этот срез добавляет меню ⋮ обратно и вешает в него один пункт под правом site:configure. Это не строка в «Ещё».
navigation.md: ConvList — «⋮ · site:configure» → Restricted. Меню рисуется только у оператора с правом site:configure — у остальных его нет вовсе (скрыто, а не заблокировано), как более короткое меню в консоли.
Один пункт в меню — «Ограниченные посетители». Открывает экран-вкладыш поверх списка (третье состояние ConversationsTabHost: openRestricted), «назад» возвращает в список. Оператор без site:configure не видит саму иконку ⋮.
Позже сюда же встанет «лупа» поиска (navigation.md, ещё не построена) — вторая иконка рядом с этим меню; учтено, чтобы не столкнулись.
Каждое ограничение — карточка: код посетителя (8 символов моноширинным, как в консоли visitorId.slice(0,8)), метка вида (спам / блок) своим тоном, статус, когда и кем, до какого срока или бессрочно. Кнопка «Снять» — только у действующих строк.
Тон несёт вид ограничения: Block — danger, Spam — accent, ровно как KIND_TONE в консоли. Статус («Активно» / «Истекло» / «Снято») считается на клиенте от IClock для показа — порядок же задаёт ключ сервера (id), не часы.
«Загрузить ещё» идёт по nextBeforeId до конца — бессрочная блокировка не потеряется, даже если её строку вытеснили новые.
Кнопка «Снять» — только у действующих строк и только у оператора с правом conversation:block или mark_spam (скрыта, а не заблокирована). После снятия список перечитывается сверху — один источник правды, без оптимистичного переключения.
Подтверждение — Material AlertDialog (в консоли это Dialog). Отклоняется системной «назад» раньше самого экрана (клаузула контракта «назад»). Отказ сервера (Conversation.Forbidden — не то право; Visitor.NotRestricted — уже снято/истекло) показывается баннером его же словами.
Ни один посетитель этого сайта сейчас не ограничен. Ограничения появляются здесь, когда оператор помечает диалог как спам или блокирует посетителя.
Пустой список — это Loaded(rows = []), отдельное терминальное состояние, а не бесконечная загрузка. Загрузка и ошибка чтения — свои состояния (спиннер / баннер с повтором), как на всех экранах приложения.
Порт VisitorRestrictionApi уже есть (26-145, панель контакта: block / lift / isRestricted). Этот срез переиспользует lift и добавляет один метод чтения list(before, limit) в тот же порт и адаптер — один порт на один ресурс сервера, новых привязок DI не нужно.