- PUT/DELETE /users/@me/blocks/{id}: блокировка снимает дружбу и заявки в обе
стороны, разблокировка возвращает возможность писать (AGENT.md 7.2)
- блокировка запрещает личные сообщения (ошибка dm.blocked), открытие беседы и
заявки в друзья с любой стороны
- клиент: раздел «Заблокированные» в друзьях со снятием блокировки, действие
«Заблокировать» в меню строки друга и в меню модерации участника
- тесты: Go (блокировка, заявки, беседа, сообщение) и Vitest (список, снятие,
блокировка из меню)
- участники сервера отдают список бейджей из badges_json (AGENT.md 7.2):
список расширяемый, назначается только системой
- клиент: каталог бейджей (щит администратора инстанса, корона владельца,
ранний сторонник, проверенный), неизвестные показываются нейтральной меткой
- бейджи видны в ленте, списке участников и друзьях вместо текстовой плашки
- тесты: Go (бейджи в списке участников) и Vitest (каталог и отрисовка)
- миграция 00014: background_file_id у комнаты (AGENT.md 7.5, 7.7)
- API: POST/DELETE /channels/{id}/background под MANAGE_CHANNEL_BACKGROUND,
фон отдаётся в списке комнат и рассылается событием CHANNEL_UPDATE
- клиент: фон рисуется за лентой с затемнением, блок «Фон комнаты» в настройках
комнаты (загрузка, замена, удаление)
- AnimatedImage: при «уменьшить движение» показывает статичный первый кадр,
нарисованный на canvas, вместо анимации
- тесты: Go (загрузка, права, снятие) и Vitest (лента, настройки, reduced-motion)
- каталог шаблонов «Пустой», «Сообщество», «Игровой» (AGENT.md 7.3): роли,
категории, текстовые и голосовые комнаты без сообщений
- POST /guilds принимает template_id, неизвестный шаблон отклоняется; аудит
фиксирует выбранный шаблон
- GET /guild-templates отдаёт каталог с предпросмотром структуры
- клиент: выбор шаблона в окне создания сервера
- тесты: Go (структура каждого шаблона) и Vitest (выбор шаблона)
- миграция 00013: guild_cosmetics (рамки и иконки) и user_styles («для друзей»
и пер-серверные стили) (AGENT.md 7.2, 7.4)
- store: CRUD галереи, личные стили и вычисление оформления поэлементно
(роль → пер-серверный стиль → «для друзей» → дефолт) с откатом, когда роль
больше не выдаёт элемент
- API: галерея сервера под MANAGE_ROLES, оформление роли в create/update,
GET/PUT/DELETE /users/@me/styles с доступными элементами по областям
- клиент: редактор личного стиля с предпросмотром, галерея оформления в
настройках сервера, оформление роли в разделе «Роли»
- рендер: рамка под аватаром, иконка рядом с ником, цвет и встроенные эффекты
ника (CSS, при reduced-motion статично) в ленте и списке участников
- миграция 00012: таблица webhooks, снимок имени и аватара в messages (AGENT.md 7.11)
- API: список/создание/правка/удаление и пересоздание токена (MANAGE_WEBHOOKS,
step-up при создании, аудит), загрузка аватара отдельной multipart-ручкой
- исполнение POST /webhooks/{id}/{token} без сессии: content, username,
avatar_url, файлы создателя вебхука, лимит 30/мин на вебхук
- клиент: пункт настроек «Комната» со списком вебхуков, копированием ссылки,
пересозданием токена и удалением
- сообщения вебхуков: имя, аватар и значок в ленте вместо «неизвестного автора»
- fix: неполный ответ REST больше не затирает данные READY-снапшота
- READY-снапшот больше не теряет banner/splash/accent: шапка сервера
показывает баннер сразу после подключения (AGENT.md 7.4)
- GUILD_UPDATE несёт всё оформление, клиент применяет его без перезагрузки
- кнопка приглашения в шапке сервера со ссылкой и копированием
- встроенные звуки интерфейса на Web Audio с разблокировкой по жесту
- object URL превью через безопасные обёртки (jsdom и урезанные webview)
Фаза 4 (AGENT.md 7.4): интерфейс для иконки, баннера, splash и акцентного цвета.
- секция «Оформление» в настройках сервера: загрузка и удаление иконки,
баннера и splash с превью, проверка типа и размера на клиенте, пресеты
акцентного цвета;
- иконка сервера показывается в рейке (заглушка-буквы остаётся, если файла
нет), акцентный цвет переопределяет `--color-accent` внутри приложения и
снимается при значении 0;
- предпросмотр приглашения показывает splash фоном и иконку сервера;
- ответ сервера обновляет карточку и список серверов — оформление видно без
перезагрузки;
- тесты: превью всех трёх видов, multipart-загрузка, удаление, применение и
снятие акцента, отказ для не-изображения, скрытие секции без MANAGE_GUILD.
Банить и исключать нужно того, кто прямо сейчас в сервере, а не только
разбирать готовый список банов (AGENT.md 7.17).
- `MemberModerationMenu`: тайм-аут (1/10/60/1440 минут и снятие), бан с причиной
и исключение; пункты скрыты без прав KICK_MEMBERS/BAN_MEMBERS/TIMEOUT_MEMBERS,
себя модерировать нельзя;
- после действия обновляются списки участников и банов (без F5);
- тесты: бан с причиной, тайм-аут, исключение и скрытие меню без прав.
Фаза 4 (AGENT.md 7.17, 7.18): модерация из интерфейса.
- `api/moderation.ts`: список банов, бан, разбан, тайм-аут, кик и журнал
аудита с фильтрами;
- «Баны»: причина и автор каждого бана, снятие бана и пресеты тайм-аута
(1/5/10/60/1440 минут) прямо из списка; ответ сервера обновляет кэш, поэтому
строка исчезает без перезагрузки;
- «Журнал аудита»: фильтры по действию, автору и цели уходят в запрос,
кнопки «Показать» и «Сбросить»;
- секции видны только с правами BAN_MEMBERS и VIEW_AUDIT_LOG
(`canBanMembers`, `canViewAuditLog`);
- тесты: список с причиной и автором, снятие бана с обновлением списка,
тайм-аут с проверкой времени, скрытие секций без прав, фильтры в запросе.
Постоянная подпись в плитке занимала место и была не нужна: задержка теперь
живёт в подсказке индикатора связи («Отличное соединение, 46 мс — задержка до
голосового сервера»), а плитка осталась компактной. Тесты и e2e проверяют
`title`/`aria-label` индикатора вместо видимого текста.
- дашборд инстанса: два ряда полукругов с подписями сверху — «Контейнер
инстанса» (лимиты профиля) и «Сервер целиком» (вся машина);
- голосовая комната: рядом с качеством связи видно пинг участника
(«Отличное соединение, 42 мс») и он обновляется раз в секунду. Свой RTT
измеряется по статистике WebRTC, чужой приходит по data-каналу LiveKit
(`glchat.latency`, lossy), поэтому пинг виден для каждого участника;
- статистика берётся с любой своей дорожки: в аудио-сессии RTT раньше был
недоступен (отчёт читался только с видео);
- в «Аудио-видео» появился тумблер «Показывать пинг в голосовой комнате»:
выключенный не опрашивает SDK и не публикует своё значение;
- тесты: ряд хоста и подписи, пинг в плитке своего и чужого участника,
публикация по служебной теме, выключенный пинг не дёргает SDK.
Вкладка «Инстанс» сверху показывает нагрузку сервера, ниже — диск и базу, внизу
healthcheck с пингом (AGENT.md 7.19).
- `Gauge` — полукруглый индикатор: заполняется по значению, меняет цвет на
порогах 70 % и 90 %, доступен как `role="meter"`;
- процессор и память обновляются раз в секунду (`useInstanceMetrics`), для
простоя вкладки опрос останавливается;
- два блока диска: «занято / всего» и вес базы (файл + WAL); видимых подписей
нет — они раскрываются в тултипе при наведении на значение;
- healthcheck: состояние каждой проверки, общий статус, техническая деталь и
живой пинг — время ответа инстанса на запрос дашборда в миллисекундах;
- переводы ru/en, тесты на индикаторы, формат «занято / всего», тултипы,
healthcheck и отсутствие раздела у обычного пользователя.
Клиентская часть Фазы 3 (AGENT.md 7.13, 7.14): звуки сервера, локальные
звуки интерфейса и телеметрия медиа.
- саундборд: `api/sounds.ts` (лимиты 512 КБ и 30 на вид, multipart-загрузка,
play, `SOUND_EVENTS`), `lib/soundboard.ts` (blob-кэш `/files/{id}`,
наложение до 8 звуков, громкость = `outputVolume`, тишина при дефене),
попап в панели комнаты (`USE_SOUNDBOARD`), таймер на `rate_limited`,
подпись «включил звук» и защита от двойного воспроизведения;
- звуковая палитра: `SOUNDBOARD_PLAY` и события сервера (вход/выход
участника, упоминание, сообщение, вход/выход из голосовой, свои
микрофон/камера/экран, серверный мьют) → `playEventSound`;
- секция «Звуки» в настройках сервера (`MANAGE_SOUNDS`): два вида со
счётчиками, drag&drop, имя, эмодзи, событие палитры, переименование,
удаление и клиентские проверки файла;
- push-to-talk: режим и клавиша (`event.code`), хук в `AppLayout`, вход в
комнату с выключенным микрофоном, удержание включает публикацию, отпускание
и потеря фокуса выключают, серверный мьют приоритетнее;
- статистика: поповер с разрешением, fps, кодеком, битрейтом, RTT, потерями
и jitter (опрос раз в секунду, остановка при закрытии), выбор качества
публикации low/medium/high, пресеты шаринга экрана 1080p60…480p30;
- данные: `guilds[].sounds` из READY, `GUILD_SOUNDS_UPDATE`, селектор
`useGuildSounds`, тумблеры звуков в «Аудио-видео» (localStorage);
- тесты: `soundboard`, `soundSettings`, `pushToTalk`, `voiceStats` и
расширенный фейковый LiveKit SDK.
- `livekit-client` подключён как ленивый чанк: SDK грузится только при входе в
комнату, стартовый бандл не вырос;
- `src/lib/livekit.ts` — единственное место знания о SDK: подключение, дорожки
микрофона/камеры/экрана, снимки участников (речь, флаги, качество связи),
выбор устройств на лету, разбор exp токена;
- `src/stores/voiceConnection.ts` — сессия голоса: статусы
idle/connecting/connected/reconnecting/error, продление токена за минуту до
истечения с переподключением и повторным применением флагов, серверный мьют и
глушение, громкость участников, перемещение, подсказка «говорите с выключенным
микрофоном» не чаще раза в 5 с;
- UI: состав голосовой комнаты в сайдбаре (речь, флаги, качество), панель
комнаты с плитками и модерацией, меню участника (личная громкость, мьют,
перемещение), drag&drop между голосовыми комнатами, кнопки панели пользователя
управляют реальной сессией, при выключенном голосе — понятная подсказка;
- 368 тестов Vitest (было 330), check/lint/test/build зелёные.
LiveKit читает livekit.yaml только при старте, а compose не замечает правок в
примонтированном файле — из-за этого вебхуки не включались после обновления.
Установщик сравнивает sha256 конфига с сохранённым и перезапускает сервис.
Установщик падал на 'TLS_ENABLED: unbound variable': расчёт LIVEKIT_PUBLIC_URL
стоял выше определения TLS_ENABLED. Значение перенесено после блока TLS,
dry-run проходит.
- лента оформляет виды сообщений от сервера: action (/me) — имя акцентом и
курсив, whisper — приглушённый блок с бейджем, scream — крупнее и жирнее,
private (/ls) — рамка, замок и бейдж «лично», system — центрированная строка
без аватара и панели действий; реакции, ответы, правка и удаление работают
как раньше, группировка не сливает нестандартные виды с обычными;
- автокомплиты в поле ввода с навигацией ↑/↓, Enter/Tab, Escape и мышью:
@ — участники сервера (в DM собеседник), # — видимые текстовые комнаты,
: — словарь на 113 эмодзи, / — команды с описанием; подсказки не появляются
внутри слова и в блоках кода, вставка восстанавливает каретку и не расходится
со слоем подсветки;
- `<#id>` рендерится как #имя комнаты из снапшота (иначе #id);
- 287 тестов Vitest (было 262), check/lint/test/build зелёные.
Правки пользователя:
- в «Профиль → Основной» сверху квадратный аватар слева, справа от него
настройки имени, «О себе», состояния и эмодзи во всю высоту; смена аватара —
клик по нему, форматы (JPEG, PNG, WebP, GIF до 8 МБ) — в подсказке;
- убраны дубли: часовой пояс только в «Язык и регион», статус — только в
мини-профиле панели;
- панель пользователя переехала в нижний левый угол под сайдбар, подсказка о
выходе из сервера — на её прежнее место; панель ужата под 240 px;
- шестерёнка сервера открывает новый пункт настроек «Сервер» (список серверов,
общее, участники, роли, опасная зона), а «Профиль → Сервер» оставлен только
для ника и «Применить ко всем серверам»;
- окно настроек получило статический размер 70% рабочей области со скроллом;
- ID пользователей скрыты везде, кроме раздела «Инстанс» у администратора;
- список друзей свёрнут под кнопкой «Друзья (N)» и раскрывается снизу вверх;
- 249 тестов Vitest (было 239), check/lint/test/build зелёные.
Раздел «Друзья» по запросу пользователя:
- первая иконка рейки — «Друзья», серверы ниже за разделителем; личные беседы
не засоряют список серверов;
- сайдбар друзей: беседы с точками статуса и бейджами непрочитанного,
список друзей с кликом на беседу;
- контент: пустое состояние «Добавь друзей для общения» с поиском, поиск по
логину (debounce), добавление в друзья, входящие и исходящие заявки,
меню «…» (удалить/заблокировать), поповер последнего входа в часовом поясе
пользователя (по умолчанию МСК), блок «Присоединиться по приглашению»
с предпросмотром сервера;
- личный чат на существующих компонентах: права по can_send канала, заголовок
с именем и статусом собеседника, поиск, пины, ack и typing работают;
- realtime: READY dm_channels, события DM_CHANNEL_CREATE, RELATIONSHIP_UPDATE,
PRESENCE_UPDATE и MESSAGE_CREATE в беседах;
- новый пользователь: «У вас пока нет серверов», создание сервера и вход по
приглашению (автовступления нет);
- 239 тестов Vitest (было 219), тест паритета ключей i18n (527 ключей).
Аватар нельзя было показать в списке друзей: файл без комнаты был доступен
только загрузившему. Добавлено назначение файла (`files.purpose`: attachment |
avatar | banner, миграция 00006): аватары и баннеры отдаются любому
авторизованному пользователю, вложения по-прежнему проверяются по правам
комнаты, а непривязанные загрузки остаются приватными.
По правкам пользователя:
- вход: поле «Код 2FA или резервный код» видно сразу, принимает 14-символьные
резервные коды (раньше влезало 6 цифр), пустое значение не ломает вход;
- нижняя панель: одна подложка, слева аватар и имя с мини-профилем и выбором
статуса, справа микрофон, камера, наушники (переключатели, стрелка с выбором
устройства и громкости, переход в «Аудио-видео») и шестерёнка;
- кнопка «Покинуть сервер» из сайдбара убрана (выход — в «Опасной зоне»);
- настройки открываются модальным окном со списком пунктов; «Профиль» имеет
вкладки «Основной» и «Сервер» (список серверов, ник и «Применить ко всем
серверам»), аватар загружается и удаляется, есть часовой пояс (МСК по
умолчанию);
- markdown: добавлены ==выделение==, __жирный__, ***оба***, ||спойлер||, #канал;
- двойной клик по сообщению ставит ответ (не срабатывает на ссылках, кнопках и
при выделенном тексте);
- 219 тестов Vitest (было 176), check/lint/test/build зелёные.
Полноценный текстовый чат в веб-клиенте:
- стор сообщений по комнатам (кэш 200, оптимистичная отправка с nonce,
применение всех событий Gateway, пагинация курсором, непрочитанные и
упоминания, сброс при логауте и INVALID_SESSION);
- лента: группировка подряд идущих сообщений одного автора, разделители дат,
разделитель «новые сообщения», подгрузка истории вверх, пустые состояния;
- разметка: жирный/курсив/код/блок кода/зачёркнутый/цитата/ссылки (только
http(s)) и упоминания <@id> — безопасный рендер React-узлами без
dangerouslySetInnerHTML;
- реакции чипами с подсветкой своих, быстрые эмодзи и пикер, панель действий и
контекстное меню (ответ, реакция, закрепление, правка, удаление, копирование);
- ответы с переходом к оригиналу, inline-правка, «печатает…» с POST /typing раз
в 3 с, пины в модалке, поиск по комнате с подсветкой и переходом;
- композер: автовысота, Enter/Shift+Enter, лимит 4000, drag&drop и скрепка
(POST /channels/{id}/files → attachment_ids), обработка 429 с таймером и
perm.denied с причиной, read-only без SEND_MESSAGES;
- бейджи непрочитанных и упоминаний в сайдбаре, авто-POST /ack при активном окне;
- 176 тестов Vitest (было 100), check/lint/test/build зелёные.
Клиент React 19 + TanStack Query + Zustand:
- вход (поле TOTP появляется на `auth.2fa_required`), регистрация с проверками
и учётом `registration_enabled`, онбординг первого входа с возможностью
пропустить, редирект `/` и страница `/status`;
- защита маршрутов: неавторизованных — на `/login`, без онбординга — на
`/onboarding`;
- оболочка `/app`: рейка серверов, сайдбар категорий и комнат, шапка сервера,
панель пользователя, модалки создания сервера и комнаты, экран «нет серверов»
со вступлением в главный сервер, заглушка комнаты до Фазы 2;
- настройки: профиль, безопасность (step-up, сессии, logout-all, 2FA с
локальным QR и кодами восстановления), внешний вид, админ-раздел инстанса;
- Gateway-клиент: HELLO/IDENTIFY/RESUME, heartbeat с ожиданием ACK,
экспоненциальное переподключение, разбор `INVALID_SESSION {reason,resumable}`;
- диспетчер событий: READY/RESUMED/USER_UPDATE/GUILD_UPDATE/GUILD_DELETE/
CHANNEL_* применяются к стору, GUILD_CREATE догружается по REST,
MEMBER_*/ROLE_* инвалидируют запросы участников, ролей и карточки сервера;
- i18n ru/en, 66 тестов Vitest, `check`/`lint`/`test`/`build` зелёные.
Серверные правки под клиент:
- `totp_enabled` в профиле (`GET /users/@me`, вход, регистрация) — клиенту
нужно знать, требовать ли код при step-up;
- `POST /auth/2fa/setup` отдаёт `otpauth_url` и `qr_png` (data-URI): QR-код
рисуется локально, внешние сервисы генерации QR не используются;
- права в ответах — имена (`VIEW_CHANNEL|SEND_MESSAGES`), клиент сверяет имена.
REST-слой Фазы 1 на huma (OpenAPI 3.1 генерируется из кода):
- профиль: GET/PATCH /users/@me, смена пароля со step-up, публичный профиль,
завершение онбординга (новая миграция 00003 с onboarding_completed_at);
- серверы: создание/изменение/удаление, join/leave, список серверов
пользователя, журнал действий;
- комнаты: список с учётом прав, создание/изменение/удаление;
- участники: список с профилями и ролями, никнейм, тайм-аут, исключение;
- роли: CRUD, выдача/снятие с проверкой иерархии и запретом выдачи прав выше
собственных;
- админ инстанса: публичная информация, настройки, серверы, пользователи,
аудит, выдача прав администратора со step-up; обход лимитов фиксируется в
аудите отдельной записью limits.bypass;
- движок прав: участие в сервере стало обязательным условием (IsMember),
не участник не получает прав роли @user; калькулятор прав общий для API и
Gateway, инвалидация кэша после изменений;
- Gateway: браузерный клиент аутентифицируется cookie на рукопожатии, IDENTIFY
без токена использует её; события GUILD/CHANNEL/MEMBER/ROLE рассылаются из
ручек, USER_UPDATE — адресно;
- ошибки huma отдаются в едином конверте {"error":{"code","message"}}.
Тесты: 8 сценариев API (профиль, жизненный цикл сервера и права, лимиты и
обход админом, иерархия ролей, тайм-аут, скрытие комнаты оверрайдом,
членство в движке прав, cookie-идентификация Gateway).