Перейти к содержанию

Чаты: бизнес-логика

Обзор

Чаты предоставляют механизм обмена сообщениями между пользователями вне контекста задач. Технически чат -- это задача (Tasks) в специальной подкатегории, сообщения -- комментарии (Comments), участники -- подписчики (MailSubscribersUsers/Groups). Поддерживаются 4 типа чатов с разными правилами.

Типы чатов (ChatType2)

Enum: Valhalla.Integration/Enums/Chats/ChatType2.cs

Тип Описание Определение Max участников
Personal Личный чат 1-на-1 subcatId == settings.ChatSubcatId 2
Group Открытый групповой чат subcatId == settings.GroupChatSubCatID Без ограничений
Group (закрытый) Закрытый групповой чат subcatId == settings.ClosedGroupChatSubcatId Без ограничений
Channel Канал (broadcast) Настраивается в категории Без ограничений

Особенности каналов (Channel)

Каналы реализуют модель broadcast, аналогичную Telegram. Ключевое ограничение: неподписанные пользователи и пользователи без права писать в чат не могут отвечать на сообщения.

  • Если пользователь не подписчик канала И/ИЛИ не имеет права писать в чат (CanWriteChat = false), в контекстном меню сообщения (ПКМ) скрываются пункты:
  • Ответить
  • Ответить всем
  • Начать обсуждение
  • Переслать → Текущий чат

Это ограничение применяется на уровне контекстного меню в веб-интерфейсе и мобильных приложениях. Сообщения в канале остаются доступными для чтения всем пользователям (если канал публичный), но взаимодействие с ними разрешено только подписчикам с правами на запись. | SocialNetwork | Публикация в соцсети | Настраивается в категории | Без ограничений |

Как определяется тип чата

Файл: TCClassLib/Chat/ChatSettingsBuildService.cs:77-105

if (subcatId == settings.ChatSubcatId) → Personal
if (subcatId == settings.GroupChatSubCatID) → Group
else → определяется по настройкам категории (Channel / SocialNetwork)

SubcatId чата хранится в конфигурации: ConfigurationService.Get().ChatSubcatId, GroupChatSubCatID и ClosedGroupChatSubcatId.

Роли участников чата (ChatRole)

Enum: Valhalla.Integration/Enums/Chats/ChatRole.cs

Роль Значение Может назначать
Owner 0 Owner, Admin, Moderator, Author
Admin 1 Admin, Moderator, Author
Moderator 2 Author
Author 3 Никого

Иерархия: меньшее числовое значение = больше прав. Владелец задачи-чата автоматически получает роль Owner.

Роли хранятся в таблицах ChatRolesUsers и ChatRolesGroups. Пользователь может наследовать роль от замещаемого (кроме Owner).

Матрица возможностей по типу чата

Видимость МТФ-панели

В чатовых подкатегориях (типы представления: Личный чат, Пространство, Групповой чат, Канал соц. сети) администратор управляет отображением правой боковой панели — МТФ-панели — с карточкой задачи-чата.

Правила видимости: - Показывать МТФ-панель (showMtfPanel) — определяет начальное состояние панели при открытии чата: включено = раскрыта, выключено = свёрнута. - Группы доступа (mtfPanelGroupIds) — каким участникам чата панель видна. Пустой список = панель скрыта для всех. - Для не-чатовых подкатегорий настройка не применяется и в админке не отображается.

Источник настройки: Admin SPA → подкатегория → секция «Настройки МТФ-панели».

Видимость МТФ-панели

В чатовых подкатегориях (типы представления: Личный чат, Пространство, Групповой чат, Канал соц. сети) администратор управляет отображением правой боковой панели — МТФ-панели — с карточкой задачи-чата.

Правила видимости: - Показывать МТФ-панель (showMtfPanel) — определяет начальное состояние панели при открытии чата: включено = раскрыта, выключено = свёрнута. - Группы доступа (mtfPanelGroupIds) — каким участникам чата панель видна. Пустой список = панель скрыта для всех. - Для не-чатовых подкатегорий настройка не применяется и в админке не отображается.

Источник настройки: Admin SPA → подкатегория → секция «Настройки МТФ-панели».

Файл: TCClassLib/Chat/ChatSettingsBuildService.cs:233-262 (дефолтные настройки)

Для пользователей, не подписанных на групповой чат или канал (видят кнопку «Подписаться» вместо поля ввода), в контекстном меню сообщения скрываются пункты, требующие роли подписчика: подпункт «В текущий чат» меню «Переслать» и пункт «Удалить» (в каналах «Удалить» может быть отдельным пунктом — тоже скрывается). Пересылка в другие чаты, где у пользователя есть право на публикацию, остаётся доступной. Задача.источник: .

Возможность Personal Group Channel SocialNetwork
Добавление подписчиков НЕТ ДА* НЕТ ДА
Редактирование профиля НЕТ ДА* ДА НЕТ
Изменение настроек НЕТ ДА** ДА** ДА**
Обсуждения (треды) ДА ДА ДА НЕТ
Корневые сообщения ДА ДА НЕТ НЕТ
Конференция (ВКС) ДА ДА НЕТ НЕТ
Вложения файлов ДА ДА НЕТ НЕТ
Закрепление сообщений ДА ДА ДА ДА
Опросы ДА ДА ДА ДА
Реакции ДА ДА ДА ДА
Голосовые сообщения ДА ДА ДА ДА
Приватность Всегда private Настраиваемо Public Настраиваемо

* — если IsAddSubscribersEnabled/IsEditProfileEnabled включено И чат не закрыт. ** — только Owner/Admin.

Права на действия с подписчиками

Файл: TCClassLib/Permissions/ChatPermissionsService.cs:120-170

Личный чат (Personal)

CanAddSubscribers = false     ← ЗАБЛОКИРОВАНО, нельзя переопределить
CanEditAppearance = false     ← ЗАБЛОКИРОВАНО
CanEditSettings = false       ← ЗАБЛОКИРОВАНО

Ключевое правило для тикетов: в личном чате добавление подписчиков заблокировано на уровне ChatPermissionsService. Это не ошибка, а by design.

Групповой чат (Group, Channel, SocialNetwork)

Действие Owner/Admin Подписчик Не подписчик
Добавить подписчика ДА Если IsAddSubscribersEnabled И чат открыт НЕТ
Изменить профиль ДА Если IsEditProfileEnabled И чат открыт НЕТ
Изменить настройки ДА НЕТ НЕТ

Управление подписчиками задач/чатов (общее)

Файл: TCClassLib/Services/TaskSubscriberService.cs:50-82, Permissions.cs:539-554

Условие Кто может менять подписчиков
Обычная задача/чат Любой с доступом к задаче
Конфиденциальная задача/чат Только заказчик (owner) задачи
Системный пользователь Всегда может

Создание чата

Endpoint: POST /api/chatsChatManageService.CreateChat

Payload

{
  "chatGuid": "uuid-v4",
  "text": "Первое сообщение",
  "subscribers": [28918, 28919],
  "subscribersGroups": [],
  "chatName": "Название чата",
  "chatDescription": "",
  "chatType": "Personal | Group",
  "isPrivate": true,
  "avatarFileId": null
}
Поле Тип Обязательно Описание
chatGuid string (UUID) да Уникальный идентификатор чата (клиент генерирует)
text string нет Текст первого сообщения
subscribers int[] да User ID участников (без создателя — он добавляется автоматически)
subscribersGroups int[] нет ID групп пользователей
chatName string для Group Название чата (отображается в UI для групповых)
chatType string да "Personal" (1-to-1) или "Group" (многопользовательский)
isPrivate bool нет Приватность (default true)

Response: { "data": { "chatId": N, "commentId": N } } (DTO: CreateChatResponseDto)

Anti-capability: без chatType бэкенд дефолтит в Personal → при 2+ subscribers вернёт 400 "В личном чате может быть только два подписчика". Для группового чата обязательно передавать "chatType": "Group".

Ограничения по типу

  • Personal: максимум 2 подписчика (исключение OnlyTwoSubscriberInChat)
  • Group: без ограничений
  • Channel/SocialNetwork: без ограничений

Проверка дубликатов

При создании личного чата система проверяет, существует ли уже чат между теми же участниками. Если да -- возвращает существующий.

Закрытие и выход

Действие Кто может Результат
Close (закрыть чат) Owner или с правом на переход по шагу Задача-чат переводится в финальный шаг
Left (выход из чата) Любой подписчик Удаление подписки, mark read, дочистка вопросов

Конфиденциальность чата

Настройка на уровне категории (cats-properties-general):

Режим Описание
Отключено Нет ограничений
Выборочно Можно флагировать конкретные чаты
Обязательно Все чаты конфиденциальны

При включённой конфиденциальности: - Только подписчики видят содержимое - Подписчики (кроме owner) не могут приглашать не-подписчиков - Эскалация просроченных чатов отключена

Уведомления (ChatNotificationMode)

Персональная настройка каждого участника (кнопка «Выключить уведомления» в меню чата):

Режим Описание
All Все уведомления
MentionsOnly Только при упоминании
None Уведомления отключены

Что контролирует ChatNotificationMode

Влияет на: push-уведомления, непрочитанность сообщений, уведомления рабочего стола.

Не влияет на: email-уведомления. Email управляется двумя отдельными механизмами: 1. Настройка категории «Не посылать почтовые сообщения» (per-категория) — см. subcategory-mail-messages-settings 2. Пользовательская настройка «Не присылать почтовые сообщения» (глобальная) — см. уведомления

Особенности групповых чатов

Даже при ChatNotificationMode = None уведомления всегда приходят при: - Личном обращении (сообщение адресовано конкретно пользователю) - Отправке сообщения «Всем»

Это задокументированное поведение (см. уведомления, тип событий «Сообщения»).

Виртуальные группы «Всем» и «Никому»

В групповых чатах поддерживаются две виртуальные группы адресации, которые не имеют физических записей в таблицах групп, но их ID материализуются через AdminGroups.GetVirtualGroupId на основе мнемоник из GroupTypesService:

// core/TCClassLib/Groups/GroupTypesService.cs:36-46
/// <summary>
/// Признак виртуальной группы для отправки комментария "Всем".
/// Используется как персистентный признак, что комментарий был адресован "Всем" в момент отправки.
/// </summary>
public const string VirtualGroupEverybodyMnemo = "VirtalEverybody";

/// <summary>
/// Признак виртуальной группы для отправки комментария "Никому".
/// Используется как персистентный признак, что комментарий был адресован "Никому" в момент отправки.
/// </summary>
public const string VirtualGroupNobodyMnemo = "VirtalNobody";

Обе группы имеют тип VirtualGroupTypeId. Их ID кэшируются в core/TCClassLib/AdminGroups.cs:237-241 через Lazy<int> и читаются как AdminGroups.NobodyVirtualGroupId / AdminGroups.EverybodyVirtualGroupId.

Выбор в UI Что происходит Push-уведомления Мьют чата
Выбрана группа «Всем» (VirtalEverybody) Все подписчики чата получают сообщение Всем подписчикам, без исключений — даже если ChatNotificationMode = Muted Пробивает мьют чата
Никто не выбран (пустой выбор, VirtalNobody) Комментарий без адресатов; VirtalNobody сохраняется как persisted-маркер для рендера Всем подписчикам, с уважением их персональных настроек мьюта Уважает мьют

Бизнес-философия (Денис, #2087311 комментарий 32008044): как в Telegram — все чаты на мьюте, мьют пробивает только адресное обращение и «Всем», чтобы усложнить жизнь спамеру. Обычное сообщение без адресатов рассылается всем подписчикам, но уважает их персональный выбор мьюта.

В Telegram нет явной опции @всем; в Slack/Discord есть @channel/@here. В 1Форме @всем (VirtalEverybody) пробивает мьют так же, как и обращение к конкретному пользователю.

Реализация: - Расчёт получателей: core/TCClassLib/Comments.cs:1583 (ветка isNobodyGroup) и core/TCClassLib/Comments.cs:2254-2259 (резолв "Никому" через виртуальную группу) — метод AddCommentRecipients. - Логика push: core/TCClassLib/MobileApps/Push/PushRecipientResolver.cs:155-159 — расчёт ignoreMute/forceMute. Для VirtalNobody ignoreMute = false, мьют гасит push; для VirtalEverybody ignoreMute = true через isSentToEverybody. - Свежий фикс: коммит 4b1bc12a35 от 05.05.2026 (Шарафутдинов Игорь, ветка feature/2056424-passwork, сборка 2.268 «Скульптор») — добавлена явная ветка if (isNobodyGroup) { BuildCommentRecipients(... customIsUnread: false, isRealRecipient: false) } в Comments.cs:1585-1610. Эффект: для подписчиков комментария «Никому» гарантированно IsUnread = 0 (непрочитанность не растёт). Push-логика этим коммитом не меняется. - Историческая серия 2024-08: задача #1707893, коммиты 4a64d20a02/73b877c442/bdc706d795/1a20ad23d1/0343e01179/b4fdb4f33a — убрана UI-настройка NobodyCommentsUnread и фиксы поведения «"никому" не должен пробивать пуш». - Поведение переподтверждено в задаче #2087311 14.05.2026 (видеоконференция Крылов/Тимонов/Денис). Подробнее: domains/notifications/data-flow.md § 7.4.

Legacy-настройка: поле NobodyCommentsUnread в core/TCDataAccess/Kernel/Domain/Entities/SettingEntity.cs:660-661 помечено [Obsolete("Не используется")]. Все 4 точки использования закомментированы (Comments.cs:3555, PushRecipientResolver.cs:155, 158, 182). Раньше эта настройка превращала комментарии «Никому» в «Всем» (со снятием мьюта); сейчас поведение зафиксировано: «Никому» — всегда рассылается всем подписчикам с уважением мьюта.

UI-селектор адресатов: опция «Никому» в селекторе отсутствует — поиск по recipientSelector/RecipientPicker/comment-recipients в SPA пустой. «Никому» — это вычисляемое состояние «пустой выбор адресатов». В SPA VirtalNobody используется только для рендера уже сохранённого комментария — скрытие блока «recipients» в apps/spa/src/app/common/components/feed-comments/feed-comment/feed-comment.component.ts:655 (геттер isNobodyVirtualGroup) и шаблоне .html:84, аналогично feed-task-comments-view.service.ts:681 и feed-task-comments.component.html:348-352.

Типичный тикет: «mute чата не отключает email»

Кнопка «Выключить уведомления» в чате не отключает email. Для отключения email из групповых чатов рекомендуется настройка «Не посылать почтовые сообщения» на категории групповых чатов (GroupChatSubCatID). Подробнее: support-guide секция 1.10

Настройки чата (ChatSettings)

Таблица dbo.ChatSettings хранит per-chat настройки:

Настройка Тип Описание
IsPrivate bool Приватный/публичный чат
IsAddSubscribersEnabled bool Можно ли участникам добавлять подписчиков
IsEditProfileEnabled bool Можно ли участникам менять профиль чата
IsSubscribersVisibile bool Видимость списка подписчиков
IsRootCommentsEnabled bool Можно ли писать корневые сообщения. Если отключено — поле ввода скрыто для подписчиков, писать могут только администраторы/модераторы. Для неподписанных пользователей в открытых чатах вместо поля ввода отображается кнопка «Подписаться»
IsThreadsEnabled bool Включены ли обсуждения (треды)
IsThreadCommentsEnabled bool Можно ли отвечать в тредах
IsReactionsEnabled bool Включены ли реакции
IsMessagePinEnabled bool Закрепление сообщений для всех участников
IsAttachmentsEnabled bool Вложения файлов
IsSurveysEnabled bool Опросы
IsVoiceMessagesEnabled bool Голосовые сообщения
IsConferenceEnabled bool ВКС
IsAnonymousAllowed bool Разрешить создавать сообщения анонимно (для чатов типа Group)

IsMessagePinEnabled (v2.268+): при создании канала параметр устанавливается в false по умолчанию. Это означает, что обычные подписчики канала по умолчанию не могут закреплять сообщения для всех участников — это право остаётся только у администраторов и модераторов, если они явно не включат эту опцию в настройках чата. В редакторе настроек настройка «Закреплять сообщения для всех» скрыта, если = false — отображается только при = true для администраторов старых инсталляций, где значение было проставлено автоматически.

IsAnonymousAllowed (v2.268+): для чатов типа «Группа» настройка «Разрешить создавать сообщения анонимно» теперь корректно сохраняется и применяется. В UI эта опция отображается в настройках группового чата и доступна для изменения администраторами и владельцами чата.

Применение настроек в UI (v2.267+)

Настройки чата доступны через меню Настройки в шапке группового чата или канала (в личных чатах раздел «Настройки» заблокирован для редактирования). Параметры управляют видимостью UI-элементов для участников с ролью Подписчик. Владелец (Owner) и Администратор (Admin) видят все элементы независимо от этих настроек.

Название в интерфейсе Поле ChatSettings Что скрывается для подписчика
Скрыть список подписчиков IsSubscribersVisibile Список участников в окне «Информация» недоступен
Добавлять других подписчиков IsAddSubscribersEnabled Кнопка «Добавить» в окне «Информация» скрыта
Отправлять сообщения IsRootCommentsEnabled Поле ввода скрыто; корневые сообщения доступны только администраторам и модераторам
Звонки IsConferenceEnabled Кнопка ВКС в шапке чата и в окне «Информация» скрыта

Переключение открытый/закрытый чат

Файл: TCClassLib/Chat/ChatSettingsManageService.csTryChangeChatSubcategoryOnPrivacyChange()

При изменении настройки IsPrivate у группового чата (Group) или канала (Channel) система автоматически перемещает чат-задачу между подкатегориями:

Новое значение IsPrivate Целевая подкатегория Конфигурация
false (открытый) Открытые группы settings.GroupChatSubCatID
true (закрытый) Закрытые группы settings.ClosedGroupChatSubcatId

Если целевая подкатегория не настроена — выбрасывается TCLogicException с сообщением «Подкатегория «Закрытые группы» не настроена. Обратитесь к администратору.»

Редактирование сообщений и быстрые клавиши

В чатах поддерживается редактирование собственных сообщений. При переходе в режим редактирования содержимое сообщения переносится в поле ввода внизу, а над полем отображается плашка с превью редактируемого сообщения. По нажатию на плашку интерфейс прокручивается к исходному сообщению. Отправка сохраняет изменения в исходном сообщении, а отмена очищает режим редактирования без сохранения.

В режиме чата (вид баблов) при редактировании в поле ввода подставляется текст сообщения, а также значения полей «Кому» и «Копия». В режиме комментариев в карточке задачи поля формы редактирования подменяются значениями редактируемого сообщения, без отдельного показа исходного сообщения. Для ответа и ответа всем сохраняется прежнее поведение: пользователь видит, на какое сообщение отвечает, и вводит новый текст отдельно.

Если поле ввода пустое и в нём установлен курсор, клавиша вверх (↑) переводит пользователя в режим редактирования последнего сообщения, отправленного им в текущем чате, при наличии прав на редактирование. В режиме комментариев в карточке задачи аналогичную функцию выполняет клавиша вниз (↓). Если в поле ввода уже есть хотя бы один символ, клавиши вверх и вниз не переключают режим редактирования и используются для обычного перемещения курсора по тексту.`

Первоначальная миграция (MoveClosedGroupChatsToNewSubcategory)

При первом запуске (если ClosedGroupChatSubcatId ещё не задан) автоматически: 1. Создаётся подкатегория «Закрытые группы» (клон настроек открытых групп) 2. Значение ClosedGroupChatSubcatId записывается в Settings 3. Все существующие приватные (IsPrivate = true) чаты типа Group/Channel переносятся в новую подкатегорию

Представление категории «Чат»

В представлении категории Чат задачи отображаются в виде мессенджера.

Структура и вкладки списка

Левая панель мессенджера содержит вкладки: Все, Личные, Группы, ИИ (при включённом ИИ), Подписка, Избранное. Вкладка Закрытые скрыта начиная с v2.268 — закрытые чаты не отображаются ни в одной из вкладок. По умолчанию при входе в раздел открывается вкладка «Все» — независимо от ранее выбранной. Это поведение сохраняется и при переходе из результатов поиска или из мини-поиска.

Вкладка Что отображается
Все Действующие закреплённые чаты: групповые, личные, избранные. Любой групповой чат можно убрать из вкладки («Убрать из чатов» в контекстном меню) — он останется в «Группах»
Личные Личные чаты 1-на-1
Группы Все групповые чаты, в которых пользователь подписчик
ИИ Активные AI-сессии пользователя (личные чаты с Анфисой). Показывается только при включённом флаге ИИ на платформе. Закрытые AI-сессии из вкладки исчезают
Подписка Задачи и чаты, на которые пользователь подписан
Избранное Чаты, добавленные в избранное; у каждого чата отображается цвет звёздочки, заданный при добавлении
~~Закрытые~~ ~~Закрытые групповые чаты, чаты с уволенными сотрудниками, чаты, из которых пользователь вышел~~ — скрыта с v2.268; вкладка и иконка не отображаются

Системные папки — вкладки-иконки (v2.268)

Вкладки «Подписка», «Избранное» и «ИИ» отображаются иконками (без текстовой подписи) — в отличие от текстовых вкладок «Все», «Личные», «Группы». Вкладка «Закрытые» скрыта — закрытые чаты не выводятся в интерфейсе мессенджера.

Особенности отображения строки чата в системных папках: - Показывается превью последнего сообщения (однострочный текст, как в основном списке). - В «Подписке» общий бейдж на вкладке убран; счётчик непрочитанных отображается только в отдельных строках чатов. - Контекстное меню на сообщение содержит полный набор пунктов, включая «Ответить» (ранее часть пунктов отсутствовала).

Вкладка — это фильтрованный список чатов, а не отдельная сущность. Задача может одновременно присутствовать в нескольких вкладках (например, в «Подписке» и в «ИИ», если пользователь подписан на AI-чат).

При первом открытии раздел показывает индикатор загрузки; при повторных открытиях список восстанавливается мгновенно из кеша, фоновое обновление обозначается тонкой полосой прогресса. Если ранее пользователь открывал какой-либо чат, при возврате в раздел автоматически открывается последний просмотренный — адресная строка обновляется до URL конкретного чата.

В период замещения чаты замещаемого пользователя по умолчанию недоступны заместителю.

Отображение p2p-чатов в ленте задач

Для личных чатов (тип Personal, подкатегория ChatSubcatId) в API ленты задач поле taskText содержит не полное название задачи-чата, а displayName собеседника — второго участника чата. Один и тот же p2p-чат каждому из двух участников показывается именем «другого человека»: для Иванова чат с Петровой отображается в ленте как «Петрова», для Петровой — как «Иванов». Подмена выполняется на бэкенде в MobileLenta.Tasks через SubscriberInfoService.GetDirectSubscribedUserIds: для записи с subcatId == ChatSubcatId ищется второй подписчик и его displayName подставляется в taskText ответа. Для групповых чатов, каналов и социальных сетей taskText остаётся стандартным — отдаётся название задачи.

При ответе на p2p-сообщение из ленты поля «Кому» и «Копия» скрыты — получателя изменить нельзя, форма ответа ведёт себя как внутри чата (v2.268+).

Лента сообщений

При получении нового сообщения лента автоматически прокручивается к нему, если пользователь находится в конце переписки; если пролистаны более ранние сообщения — позиция сохраняется. При повторном открытии чата лента восстанавливается на последней позиции чтения.

Когда пользователь не в конце ленты, отображается кнопка прокрутки вниз с бейджем — числом непрочитанных сообщений. Нажатие переносит к последнему сообщению.

Особенности отображения сообщений (форматирование, реакции, эмодзи-only) описаны в comments/business.md.

Индикатор набора и Анфиса

Пока собеседник набирает сообщение, отображается строка «Имя печатает…» с анимированными точками. Если в чате присутствует AI-ассистент Анфиса, индикатор расширяется — например, «Анфиса печатает: turn 3...» или «Анфиса печатает: web_search...». Дополнительно над полем ввода появляются оранжевые информационные плашки с названием текущего действия Анфисы; они накапливаются стопкой, держатся 15 секунд после последнего сигнала и скрываются досрочно при начале набора пользователем или при поступлении ответа.

Шаблоны сообщений

Рядом с полем ввода — кнопка шаблонов; список можно также вызвать вводом /. По мере набора результаты фильтруются, выбор клавишами /, подтверждение Enter. Текст шаблона только подставляется в поле ввода — отправка не выполняется автоматически. В чатах задач список шаблонов фильтруется под категорию чата и обновляется при переходе.

Вид баблов

В настройках интерфейса пользователя есть переключатель «Вид чатов» — переключает чат с отображения «как карточка задачи» на формат баблов. На поведение редактирования сообщения в этом режиме см. раздел «Редактирование сообщений и быстрые клавиши».

Режим выбора сообщений

Переход в режим выбора активирует чекбоксы рядом с каждым сообщением ленты (мультиселект для пересылки или удаления).

Расположение чекбоксов (v2.268+): все чекбоксы выровнены по левому краю ленты (отступ 10px), независимо от стороны бабла. На среднем экране (баблы с разных сторон) и на широком экране (все баблы слева) чекбоксы всегда слева.

Ширина сообщений: ширина блоков баблов учитывает пространство, занятое чекбоксами — при открытом левом сайдбаре баблы не перекрывают чекбоксы и не выходят за границу видимой области.

Поиск по сообщениям

Строка поиска в верхней части мессенджера ищет не только чаты, но и сообщения по тексту, имени автора, получателям («Кому», «Копия»). Найденные сообщения выводятся в выпадающем списке с фрагментом и подсветкой совпадения. Выбор результата открывает чат и прокручивает ленту к найденному сообщению — без перехода из текущего чата до этого момента.

Создание личного чата

Способы инициировать личный чат:

  1. Из профиля пользователя — кнопка «Чат». Если у инициатора нет права подписывать целевого пользователя к задачам (внешний пользователь из другой корневой оргструктуры), появится ошибка; администратор снимает ограничение спецправом «Можно подписывать всем».
  2. Из меню «Сотрудники» — поиск по части имени, фамилии, оргединицы, email или логина; кнопка отправки сообщения при наведении.
  3. Из поиска в чате — поиск без учёта раскладки. Если активного чата нет — открывается пустой чат. Первым сообщением нельзя отправить только файл без текста.

Если активный чат между пользователями уже существует, создать новый личный чат нельзя — будет открыт существующий. Закрытый личный чат недоступен для повторного открытия: повторное создание породит новый чат, без переноса истории.

Информация о собеседнике

В шапке личного чата рядом с именем — номер задачи-чата (#12345) с иконкой копирования. Под именем отображается: - статус «В сети» или время последней активности; - статус «На видеозвонке», если собеседник в данный момент в ВКС.

Меню действий чата

Кнопка в правом верхнем углу открытого чата раскрывает меню. Состав пунктов зависит от типа чата (личный/групповой/канал) и текущей вкладки:

Пункт Описание
Информация Страница с шапкой (аватар, тип, номер), кнопками управления (Добавить, Встреча, ВКС, Колокольчик, Обсуждения), блоком участников и блоком «Файлы» (см. ниже)
Настройки Боковая панель с настройками чата. В личных чатах заблокирована для редактирования
Редактировать название Только групповые чаты. Зависит от прав на редактирование текста задач в категории
Изменить аватар Только групповые. Загрузить изображение или выбрать цвет/иконку из набора
Избранное Закрепить чат вверху со звёздочкой
Убрать из чатов Удалить из вкладки «Все» (только групповые). Чат остаётся доступен в «Группах»; вернуть — пунктом «Добавить в чаты» из карточки задачи
Выключить уведомления Per-chat выключение
Вложения Окно вложений
Выйти Покинуть групповой чат
Закрыть чат Покинуть личный чат (см. «Закрытие и выход»)
Конфиденциально Включить режим, если разрешено в категории

Страница «Информация»

Шапка содержит полное имя чата и аватар. Клик по аватару открывает редактор цвета/иконки (для личных — неактивно). Под шапкой — тип чата и номер, далее кнопки: Добавить (групповые), Встреча, ВКС, Колокольчик (уведомления), Обсуждения (счётчик активных).

Под кнопками — блок участников: клик открывает окно со списком и ролями (Владелец, Администратор, Модератор). Через рядом с пользователем — назначение роли (Владелец/Администратор → могут назначать; обычные участники не видят).

Далее — блок «Файлы»: список всех файлов из сообщений чата. Скрывается, если в категории запрещено вложение файлов. Файлы попадают в блок только через сообщения — добавить напрямую нельзя.

Окно «Все обсуждения»

Кнопка обсуждений в шапке (рядом — счётчик активных) открывает модальное окно. Вкладки:

Вкладка Содержимое
Все Все созданные обсуждения чата
Активные Только открытые. ПКМ → «Закрыть обсуждение»
Закрытые Завершённые (серым). ПКМ → «Открыть обсуждение»

Поиск по названиям обсуждений — в строке вверху окна.

Обсуждение, в котором последнее сообщение было более 2 недель назад, автоматически закрывается.

После создания нового обсуждения сразу открывается оно само; вернуться к чату — клик по его имени в шапке (в личных — клик по имени собеседника).

Сортировка результатов поиска по чатам

Глобальный поиск по сообщениям сортирует результаты в порядке:

  1. Активные чаты 1-на-1 с искомым пользователем.
  2. Контакты.
  3. Прочие чаты.

Поиск работает без учёта раскладки клавиатуры; ищет также по названиям обсуждений.

Создание группового чата

Создание — кнопкой над списком вкладок или через «Создать» в панели навигации. При создании указывается название чата и участники (отдельные пользователи или группы — изменение состава группы автоматически меняет участников чата).

Под полем ввода названия — счётчик символов с лимитом 128 символов (обратный отсчёт; при достижении нуля ввод блокируется).

Создав групповой чат с единственным участником (собой), можно получить персональный «чат-избранное».

Все групповые чаты по умолчанию имеют системный аватар; владелец может загрузить свой или выбрать цвет и иконку из набора через настройки. Если иконка из набора не найдена, показывается заглушка 404.

Сортировка списка чатов

Задачи в списке сортируются по времени последнего сообщения (не по дате создания или обновления задачи). Чаты с последними сообщениями отображаются выше.

Превью сообщений

Последнее сообщение отображается в формате однострочного превью: - HTML- и Markdown-разметка убирается — служебные теги и символы разметки не показываются, текст отображается в преобразованном виде - Переносы строк не сохраняются — длинное сообщение показывается в одну строку и при необходимости обрезается многоточием

⚠️ Это особенно важно для автоматически сформированных сообщений, где в исходном тексте могут использоваться переносы строк, списки или элементы форматирования.

Закрепление задач как чатов

Обычные задачи можно закрепить в списке чатов для быстрого доступа (актуально для мобильного приложения). Способы закрепления: - Из мессенджера — через контекстное меню - При создании задачи — если включён режим «Закрепить как чат по умолчанию» - Из карточки задачи — через окно «Участники задачи»

Автооткрепление при завершении

При завершении задачи она автоматически открепляется из списка чатов у всех участников.

Контекстное меню в списке чатов

В списке чатов доступно контекстное меню по правой кнопке мыши. Нажатие ПКМ на чат открывает меню с действиями:

  • Открыть в новой вкладке — открыть чат в отдельной вкладке браузера.
  • Закрепить — добавляет чат в избранное с жёлтым цветом.
  • Скопировать ссылку — скопировать прямую ссылку на чат.
  • Скопировать номер — скопировать ID задачи.
  • Выйти — для участников чата.
  • Удалить — для владельца чата.
  • Присоединиться к ВКС — пункт появляется, если в чате идёт видеоконференция.

Типичные тикеты и диагностика

"Не могу добавить подписчика в личном чате"

Ответ: это корректное поведение. Личный чат (Personal) не поддерживает добавление подписчиков by design.

Диагностика: 1. Определить тип чата: subcatId задачи-чата == ChatSubcatId из конфига → Personal 2. Если пользователь хочет групповой чат — нужно создать новый групповой чат с нужными участниками

-- Определить тип чата
select
        t.TaskID,
        t.SubcatID,
        case
            when t.SubcatID = (select top 1 cast(v.Value as int) from Settings v where v.[Key] = 'ChatSubcatId') then 'Personal'
            when t.SubcatID = (select top 1 cast(v.Value as int) from Settings v where v.[Key] = 'GroupChatSubCatID') then 'Group'
            else 'Channel/SocialNetwork'
        end as ChatType
from Tasks t
where t.TaskID = @chatTaskId;

-- Подписчики чата
select
        s.UserID,
        u.FullName,
        s.IsDeleted,
        s.CreatedDate
from MailSubscribersUsers s
        join Users u
            on u.UserID = s.UserID
where s.TaskID = @chatTaskId and
      s.IsDeleted = cast(0 as bit)
order by s.CreatedDate;

-- Настройки чата
select *
from ChatSettings
where TaskId = @chatTaskId;

"Не вижу чат в списке"

Возможные причины: 1. Пользователь не подписчик чата 2. Чат закрыт (IsClosed) 3. Чат конфиденциальный и пользователь не подписчик 4. Фильтр subcatIds в запросе не включает категорию чата

Открытие ленты: позиция и маркер «новые сообщения»

Когда пользователь открывает чат или ленту сообщений задачи, стартовая позиция определяется наличием непрочитанных сообщений.

  • Если непрочитанных сообщений нет — лента открывается на последних сообщениях (в нижней части экрана).
  • Если в ленте есть непрочитанные сообщения — система переносит пользователя к первому непрочитанному сообщению и показывает маркер «новые сообщения» над ним.
  • Если непрочитанных много или первое непрочитанное длинное — пользователь видит начало этого сообщения и маркер «новые сообщения» в верхней части экрана.
  • Если сообщений мало или одно короткое и прокрутка ограничена — лента остаётся в нижней части экрана, но маркер «новые сообщения» всё равно сохраняется.

Позиция рассчитывается на стороне сервера по реальной непрочитанности (CommentRecipients.IsUnread). Локальная позиция просмотра на клиенте для этого больше не используется — одинаковое поведение в веб-интерфейсе и при переходе между устройствами.

Поведение маркера при появлении новых сообщений

Маркер «новые сообщения» позиционирует ленту один раз — в момент открытия чата. После этого новые сообщения (входящие или отправленные самим пользователем) не возвращают скролл к маркеру.

Дальнейшее поведение ленты подчиняется общему правилу прокрутки: - если пользователь находится в конце ленты, новые сообщения довставляются снизу и видны автоматически; - если пользователь пролистал ленту вверх (выше конца), его позиция сохраняется, а о новых сообщениях сигнализирует кнопка прокрутки вниз с бейджем непрочитанных.

Сам маркер «новые сообщения» остаётся на месте — над первым непрочитанным сообщением на момент открытия чата — и исчезает при следующем открытии после прочтения.

Закреплённые сообщения

В чатах поддерживается закрепление важных сообщений для всех участников чата. Закреплённые сообщения отображаются в специальной панели под шапкой чата и в отдельном списке закреплённых сообщений.

Закреплять и откреплять сообщения могут: - владелец чата; - администратор чата; - модератор чата; - участник чата, если в настройках включена опция «Закреплять сообщения для всех».

Закрепление и открепление выполняется из контекстного меню сообщения или через меню по кнопке с тремя точками. После закрепления в чате публикуется системное сообщение о том, кто выполнил закрепление. Такое сообщение не отправляет уведомления участникам.

У закреплённого сообщения отображается специальная иконка. Под шапкой чата выводится панель закреплённых сообщений. В панели может отображаться: - превью вложения, если оно есть у сообщения; - текст сообщения; - счётчик, если закреплено несколько сообщений.

При нажатии на панель выполняется переход к закреплённому сообщению в ленте чата. Если закреплённых сообщений несколько, при последовательных переходах счётчик и превью переключаются на следующее сообщение. При повторном открытии чата счётчик начинается заново.

По кнопке закреплённых сообщений открывается отдельный список всех закреплённых сообщений чата.

Пользователь может скрыть закреплённое сообщение из панели и из своего списка закреплённых сообщений. Это скрытие действует только для конкретного пользователя и не снимает само закрепление: у сообщения остаётся иконка закрепления, а другие участники продолжают видеть его в списке. Скрытое сообщение можно вернуть обратно через меню сообщения в чате или через меню в списке закреплённых сообщений.

Если сообщение закреплено внутри треда чата, оно отображается как закреплённое только внутри этого треда и не попадает в общий список закреплённых сообщений основного чата.

Перекрёстные ссылки

  • docs/domains/chat/backend.md — контроллеры, сервисы, SQL-зависимости
  • docs/domains/chat/data-flow.md — сквозные сценарии с диагностикой
  • docs/domains/comments/business.md — бизнес-правила комментариев (сообщения чата = комментарии)
  • архивное руководство администратора: cats-properties-general.md — конфиденциальность категорий
  • docs/reference/database/comments.md — структура таблиц Comments, CommentRecipients