Два требования desktop-клиента (docs/client-tauri.md §5), которые живут в
веб-слое — том же, что и в браузере:
- **Палитра команд.** Ctrl/Cmd+K (на документе или сообщением `openPalette` от
обёртки) открывает быстрый переход: серверы (в первую доступную комнату),
комнаты выбранного сервера с группировкой по категориям, личные беседы из уже
загруженного снапшота, действия (настройки профиля/безопасности/внешнего вида,
настройки сервера, создание сервера, друзья, микрофон и «не слышу» при
активной голосовой сессии). Новых запросов палитра не делает; поиск по
подстроке без учёта регистра, 8 пунктов на раздел, стрелки со
`scrollIntoView`, `role=listbox/option` и `aria-activedescendant`;
- **Подтверждение выхода.** Формы с локальным черновиком регистрируются в общем
реестре (`stores/unsaved.ts` + хук `useUnsavedChanges`): платформа получает
один `setDirty` на переход «есть правки / нет правок», а на сообщение обёртки
`closeRequested` клиент отвечает тряской контента и диалогом «Сохранить /
Выйти без сохранения». При `prefers-reduced-motion: reduce` тряску заменяет
подсветка рамки; в браузере вместо сообщения обёртки работает штатный
`beforeunload`. Хук добавлен в формы с явной кнопкой «Сохранить»:
профиль, общие настройки сервера, ник, эмодзи, звуки, оформление, вебхуки,
лимиты инстанса, переименование сервера (автосохраняемые переключатели
намеренно не помечаются);
- Deep link `glchat://dm/<channelId>` (личная беседа без сервера) — его
присылает обёртка при клике по уведомлению о личном сообщении.
Тесты: 525 passed (501 база + 24 новых) — палитра, реестр правок, диалог
закрытия, `beforeunload`, разбор ссылок; `npm run check` и `npm run lint` зелёные.
Сервер:
- миграция 00017: таблица instance_bans (причина, автор, дата);
- auth: ErrUserBanned, проверка бана в Login (код user.banned, 403) и в
ResolveSession — забаненный не получает сессию ни по cookie, ни по Bearer,
ни в Gateway, а прежняя сессия удаляется;
- store: BannedAt/BanReason в модели пользователя, BanInstanceUser,
UnbanInstanceUser, IsInstanceBanned, поиск и фильтр в ListUsers,
CountUsersFiltered; мягкое удаление аккаунта убирает и запись о бане;
- API: POST /instance/users/{id}/ban и /unban со step-up (AGENT.md 7.1),
отзыв сессий и SESSION_INVALIDATED, аудит instance.user_ban с причиной и
instance.user_unban; себя и инстанс-админа забанить нельзя;
- GET /instance/users: q (логин и отображаемое имя), banned=true, total.
Клиент:
- панель: поиск, фильтр «только забаненные», бейдж бана с причиной, кнопки
«Забанить» (с причиной) и «Разбанить» через общий шаг подтверждения
личности; i18n ru/en, включая текст ошибки user.banned;
- keepPreviousData в списке пользователей: без этого поле поиска
размонтировалось на первом же символе и набор обрывался.
Тесты: 4 Go-теста (бан блокирует вход и сессии, защита админов, поиск,
уборка бана при удалении), web-тест панели, живая проверка на стенде 19/19.
- лимиты медиа живут в настройках инстанса (миграция 00016): аватары,
оформление сервера, эмодзи, звуки и галерея; все загрузки берут предел
оттуда, значения по умолчанию — из AGENT.md 7.7
- действия администратора: временный пароль (показывается один раз, сессии
отзываются), выход со всех устройств, мягкое удаление пользователя
(сообщения и аудит остаются), переименование и удаление любого сервера
- админ-панель разбита на вкладки: обзор и здоровье, лимиты, пользователи,
серверы, аудит; смена администраторов подтверждается личностью (step-up)
- тесты: Go (действия администратора, лимит эмодзи из настроек) и Vitest
(вкладки, сброс пароля, выход, удаление, переименование сервера)
- дашборд инстанса: два ряда полукругов с подписями сверху — «Контейнер
инстанса» (лимиты профиля) и «Сервер целиком» (вся машина);
- голосовая комната: рядом с качеством связи видно пинг участника
(«Отличное соединение, 42 мс») и он обновляется раз в секунду. Свой RTT
измеряется по статистике WebRTC, чужой приходит по data-каналу LiveKit
(`glchat.latency`, lossy), поэтому пинг виден для каждого участника;
- статистика берётся с любой своей дорожки: в аудио-сессии RTT раньше был
недоступен (отчёт читался только с видео);
- в «Аудио-видео» появился тумблер «Показывать пинг в голосовой комнате»:
выключенный не опрашивает SDK и не публикует своё значение;
- тесты: ряд хоста и подписи, пинг в плитке своего и чужого участника,
публикация по служебной теме, выключенный пинг не дёргает SDK.
Ноль — такое же значение нагрузки, поэтому до прихода первой порции данных
индикаторы и блоки диска показывают «—», а не 0 %: иначе интерфейс мигал
нулевой нагрузкой при открытии раздела.
Вкладка «Инстанс» сверху показывает нагрузку сервера, ниже — диск и базу, внизу
healthcheck с пингом (AGENT.md 7.19).
- `Gauge` — полукруглый индикатор: заполняется по значению, меняет цвет на
порогах 70 % и 90 %, доступен как `role="meter"`;
- процессор и память обновляются раз в секунду (`useInstanceMetrics`), для
простоя вкладки опрос останавливается;
- два блока диска: «занято / всего» и вес базы (файл + WAL); видимых подписей
нет — они раскрываются в тултипе при наведении на значение;
- healthcheck: состояние каждой проверки, общий статус, техническая деталь и
живой пинг — время ответа инстанса на запрос дашборда в миллисекундах;
- переводы ru/en, тесты на индикаторы, формат «занято / всего», тултипы,
healthcheck и отсутствие раздела у обычного пользователя.