505 Commits
Author SHA1 Message Date
AndreyandClaude Fable 5.1 1a61780dec 🚚 chore!: переименование инфраструктуры CustoCRM/hub → Chatballs с миграцией
- каталог code/custocrm → code/chatballs; CLI custocrm → chatballs; пакеты
  hub_platform → chatballs, hub_backend → chatballs_backend (app labels прежние)
- переменные CUS_* и CUSTOCRM_* → CHATBALLS_*; образы chatballs-*; compose-проект
  и база chatballs (были edevs_hub); роли Postgres chatballs_* (были custocrm_*);
  схема RLS chatballs и GUC chatballs.organization_id; cookie chatballs_*
- CI: APP_DIR code/chatballs, DEPLOY_ROOT /opt/chatballs
- deploy/migrate/rename-to-chatballs.sh — миграция существующей установки без
  потери данных: остановка старого проекта, .env (с резервной копией),
  переименование суперпользователя initdb через временную роль, остальных ролей,
  базы, схемы и функции RLS; проверено на локальном стеке
- dev-стек хранит Postgres в bind-mount data/postgres, как prod (именованный том
  compose.dev был устаревшим снимком и вводил в заблуждение)
- снятие демо удаляет объекты, созданные поверх демо-данных тестировавшим
  (звонки по демо-диалогу), вместо падения на PROTECT
- реальные домены *.custocrm.ru и идентификаторы документов не тронуты

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 22:21:12 +03:00
AndreyandClaude Fable 5.1 dbd7ae3150 🚚 chore: переименование Chatbolls → Chatballs во всём проекте
Бренд, API-схема, TOTP issuer, письма, тексты бота уведомлений, загрузчик виджета
(window.ChatballsChat, типы postMessage), ключи localStorage, id/параметры
виджета помощи, health-service, README, скрипты, демо (пароль
Chatballs-Demo-2026). Макеты в design/baseline не тронуты — это файлы владельца.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 21:43:33 +03:00
AndreyandClaude Fable 5.1 22e0a12a16 💄 fix(ui): вход — логотип ближе к названию
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 21:35:55 +03:00
AndreyandClaude Fable 5.1 14f0dd4a95 ⏪ fix(ui): лента чата — прежний фон (surface-feed #f5f6f8 / #15161a), без обоев; обои остаются на экранах входа
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 21:30:37 +03:00
AndreyandClaude Fable 5.1 94ec4430f9 💄 fix(ui): экран входа — «Рады вас видеть!», ссылка восстановления 12px, логотип 52px, стекло 72%% + blur
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 21:26:19 +03:00
AndreyandClaude Fable 5.1 19106620a9 💄 feat(ui): логотип Chatballs (файл владельца) без подложки, цвет темы; имя сервиса в UI — Chatballs
- LogoIcon: два круга и перемычка с вырезами, fill currentColor; в сайдбаре,
  рейке, экране входа/мастера, портале помощи — без акцентной/тёмной подложки,
  цветом текста темы (--n-1); favicon без фона, цвет по prefers-color-scheme
- видимое имя сервиса — Chatballs (заголовки, тексты, title); внутренние ключи
  (localStorage, postMessage, id виджета) пока прежние — переименование проекта позже

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 21:22:19 +03:00
AndreyandClaude Fable 5.1 4d4eead827 💄 fix(ui): мобильный кадр M2 — пузырь до 84%, волна голосового ужимается
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 21:10:34 +03:00
AndreyandClaude Fable 5.1 29d061096d 📝 docs(readme): демо повторяет кадры дизайн-базлайна, голосовые из Common Voice (CC0)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 20:56:29 +03:00
AndreyandClaude Fable 5.1 99203aa561 💄 feat(ui): по макету — обои из lucide-иконок, 6 пунктов навигации, фото сотрудников, карточка контакта
- обои «Chatbolls — Lucide Pattern» (макет владельца): филлотаксис по золотому углу
  с тремя вихрями, те же 25 иконок, размеры, прозрачности и цвета двух тем
  (серо-зелёная #dedfd9/#5d635d, графитовая #25292e/#f1f2f3); на экранах входа и
  за лентой чата; лента принимает фон обоев (--surface-feed)
- верхней панели нет ни у одной роли; заголовок «Настройки» — в самой странице;
  «Знания» и «Аудит» — ссылки из «Настроек», навигация менеджера — ровно 6 пунктов
- фото сотрудников: Avatar с img, загрузка/удаление в «Настройки → Профиль»,
  в сайдбаре, подписях сообщений, поле «Ответственный», поповере охвата
- карточка контакта (решение 5): описание, компания, город; карандаш у имени —
  inline-правка имени/описания/телефона/компании/города
- цвет группы из настроек (свотчи в модалке группы) — точки в дереве, поповере,
  строках списка и меню переноса
- композер: кнопка «Эмодзи» (antd Dropdown с сеткой), как в макете
- история контакта (кадр F): тема — первая реплика клиента, «вела Анна Ким» / «AI»
- список: превью без системных событий; сообщения с одинаковым временем — по id
- демо: длительности голосовых, история Марии в архиве (в списке ровно 7 строк),
  Дмитрий ждёт 6 мин; БД: SET NULL на FK аудита/outbox к пользователю и права на
  каскад удаления — снятие демо runtime-ролью

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 20:51:14 +03:00
AndreyandClaude Fable 5.1 464d4d89fd ✨ feat(backend): каст демо по дизайн-базлайну v2, фото сотрудников, карточка контакта, цвет группы
- демо «Ателье Норд» повторяет кадры: сотрудники Елена Кузнецова и Анна Ким (фото),
  группы Операторы/Поддержка/VIP-клиенты с цветами, агенты Консультант · Поддержка
  сайта · Личный кабинет, семь видимых диалогов A–H с точными сообщениями, история
  Марии (кадр F), метки строчными; покрытие остальных функций — в архиве и спаме
- голосовые из Mozilla Common Voice 26.0 (ru, CC0): 4 клипа OGG/Opus, расшифровки
  совпадают с записью; README с источниками
- HumanUser.avatar (+ хранилище users/ вне тенантного ограждения), профиль:
  GET/POST/DELETE /auth/profile/avatar/, коллегам — /employees/<id>/avatar/;
  avatarUrl в сессии, сотрудниках, ответственном, авторе сообщения, счётчиках
- Contact.description/company/city, правка из диалога POST <id>/contact/
- EmployeeGroup.color (HEX) в API групп, счётчиках, справочнике, диалоге
- гость виджета называется «Гость · xxxxxx» (кадр M1); гость может представиться в сиде

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 20:17:23 +03:00
AndreyandClaude Fable 5.1 1175e51e4a 💄 feat(ui): поповер охвата админа по кадру A1 — секция «Ответственный», цвета групп и агентов
- поповер 250px: «Все диалоги · Группы · Агенты · Ответственный», подзаголовки 11/600,
  пункты 32 r7, отмеченный пункт primary, счётчики 11.5/600
- цветные точки групп и тонированные бейджи агентов (как в дереве сотрудника) —
  и в дереве сайдбара
- охват «Ответственный»: фильтр списка `assigned=<userId>`, счётчики
  `assignees` в /conversations/counters/, `code` агента в счётчиках
- меню приложения: специфичность правил поповера, чтобы antd не перебивал

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 19:24:16 +03:00
AndreyandClaude Fable 5.1 54f19f783e 💄 fix(ui): чат — адаптив S2/M1/M2, кадры S1 и G по дизайн-базлайну v2
- снят глобальный min-width 1024 (медиазапросы ≤768 не срабатывали); ограничение
  осталось только у страниц вне чата
- M1: мобильная шапка ☰ · «Все диалоги ▾» + «Организация · N диалогов» · аватар 32;
  ☰ открывает дерево «Диалоги» выезжающим сайдбаром (состояние поднято в Shell);
  строки 12/16, аватар 42, шрифты по макету
- M2: шапка ленты «назад · аватар 36 · имя/статус · контакт · ⋯», компактный
  композер (поле + кнопка-иконка 38 r10, плейсхолдер «Сообщение…»)
- S2: затемнение ленты при открытой панели, крестик в шапке панели, кнопка
  «Контакт» подсвечена только при открытой панели
- S1: пустой список с иконкой в круге 44; без выбранного диалога — только
  «Выберите диалог», без панели и композера
- бренд сайдбара: логотип + переключатель организации (153×32, шеврон 14);
  дерево показывает «Группы»/«Агенты» и без элементов
- G: меню полей блока «Диалог» — 283px, заголовок «Перенести в группу», галочка
  у выбранного, подпись «Диалог без группы видят все сотрудники»; все меню
  приложения по макету: 220px, пункты 33 r7, разделитель n-9, тень 0 6 20
- textarea/select наследуют шрифт (композер был моноширинным)
- backend: GET /conversations/directory/ — все группы и активные коллеги для
  оператора (сотруднику менеджерские списки закрыты, а переносить/назначать он
  может); чат грузит справочник для всех ролей

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 19:14:32 +03:00
AndreyandClaude Fable 5.1 5dcc98d594 💄 fix(ui): чат по дизайн-базлайну v2 — аудит вычисленных стилей против макета
Покадровое сравнение кадров A1/A2 и A–H макета с приложением по вычисленным
стилям (размеры, цвета, границы, радиусы, шрифты, композиция) в обеих темах.

Сайдбар: 220px на surface2, шапка «лого 28 + имя», пункты 36/10px без
акцентной полосы, бейдж очереди — жёлтая плашка, профиль с онлайн-точкой и
«роль · e-mail», меню ⋯ (уведомления переехали сюда — верхней панели в чате у
менеджера по макету нет).

Список: 323px, переключатель охвата со счётчиком на подложке n9, сортировка и
«Скрыть список», подсказка «/» в поиске, вкладки-подчёркивание, строка
диалога: канал + агент → имя · время («вчера») → превью с «↩» и непрочитанными →
таймер ожидания · группа · метки (h20, r5); активная строка — n9 без полосы.

Лента: шапка с точкой режима и строкой «статус · канал · агент · группа»,
одно действие «Взять диалог» (решение 2) и «Вернуть AI», звонки — в карточке
контакта; у входящих нет аватара и обводки (msg-in), исходящие — тинт акцента
22% / AI 16% с подписью «AI · Консультант» и «Имя · вы», разделители дней,
системные события-чипы с тоном. Первое сообщение сотрудника атомарно
перехватывает диалог (backend: ConversationMessageView → claim).

Композер: поле сверху, инструменты снизу (голосовое, «Шаблоны»), «Отправить ⏎»,
строка-предупреждение о режиме; кадры D/E — карточка с замком.

Контекст-панель 320px: карточка контакта (аватар 64, плашка канала, имя 20,
поля с копированием, «Позвонить»/«Видеозвонок»), блок «Диалог» (селекты
36px с точками/индикатором, Агент и Режим read-only, метки-чипы с
«+ Добавить», «Начат»), «Заметка» — жёлтая карточка с карандашом, «История» —
карточки с плашкой канала.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 18:22:40 +03:00
AndreyandClaude Fable 5.1 77a67a7413 ✨ feat(ai): «Демо-провайдер» — живой AI без ключей для знакомства с системой
Новый LLM-провайдер IntegrationProvider.DEMO (без секрета, проверка всегда
OK): отвечает детерминированно по знаниям агента из системных сообщений
runtime — выбирает предложения с наибольшим пересечением слов с вопросом,
режет служебные заголовки фрагментов и markdown-таблицы; когда знаний нет или
клиент просит человека, завершает ответ токеном передачи оператору по протоколу
runtime. Эмбеддинги детерминированные, расшифровка голосовых — явное сообщение
о необходимости BYOK-провайдера. В UI помечен как демо с ограничением качества.

Демо-набор «Ателье Норд» использует его как провайдера агентов: после
установки AI отвечает сразу, без ключей OpenRouter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 17:10:48 +03:00
AndreyandClaude Fable 5.1 0ca9eb865f ✨ feat(setup)!: мастер первого запуска и демо-набор «Ателье Норд» с точным удалением
Open source: «развернуть за минуту». Никаких параметров в .env и CLI —
при пустом инстансе браузер показывает мастер (/api/v1/setup/): название
организации, имя, e-mail, пароль владельца и флаг «Установить демо-данные».
Запись идёт на соединении platform (роль app не создаёт организации) через
tenancy/routing.use_database; после первого владельца мастер закрыт (409).
bootstrap_owner и захардкоженные Edevs/Котова из установки удалены
(bootstrap_edevs_owner остаётся тестовым helper'ом).

Демо ставится в организацию установщика; реестр DemoRecord (post_save во
время сида) даёт точное удаление в обратном порядке с разрывом PROTECT-циклов
только между демо-объектами. Установка/удаление — outbox → worker; статус и
витринные учётки (админ + сотрудники разных групп, пароль Chatbolls-Demo-2026)
— карточка «Демо-данные» в «Настройках» (/company/demo/).

Сид переписан под дизайн-базлайн v2 («Ателье Норд»): 6 сотрудников (группы,
блокировка, TOTP, приглашения), 4 агента (активные/черновик/выключенный),
подключения TG/MAX/почта/два веб-виджета (анонимный и авторизованный) с
ошибкой у одного, знания с иерархией категорий и вложениями (md/txt/pdf),
30 дней истории LLM, 13 диалогов во всех состояниях с метками, приоритетами,
заметками, ответственными, историей контакта, спамом и архивом, веб-гость
через настоящую сессию виджета, шаблоны «/», портал поддержки со статьями
(опубликованные/черновик/архив, вторая ревизия, оценки), звонки с метриками и
приглашением, уведомления всех типов, привязки уведомителя. Аватары
контактов — портреты владельца, публичный /api/v1/demo-media/. Голосовые —
слоты под клипы владельца (media/voice/README.md).

Тест покрытия: каждая модель hub_platform получает демо-запись; удаление
возвращает счётчики к исходным. compose-профиль demo-seed и seed-demo.ps1
удалены; seed_demo --organization --apply/--remove для разработки; start.sh
для Linux/macOS; README — раздел «Быстрый старт».

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 16:55:31 +03:00
AndreyandClaude Fable 5.1 b2e9d120a6 🐛 fix(ui): /api/v1/agents/ — арендаторский префикс организации в api-клиенте
Пространство agents отсутствовало в tenantNamespaces, запросы карточек агентов
уходили без /organizations/<id>/. Мёртвые channels и access-profiles убраны.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 15:34:14 +03:00
AndreyandClaude Fable 5.1 e5e51ce695 🔥 feat(ui)!: снос командного центра и «Обзора поддержки» (SPEC-HUB-0031)
По спеке навигация менеджера — шесть пунктов, командный центр удаляется.
Удалены features/command, SupportOverviewPage, роуты command/supportOverview
и бэкенд-сводка conversations/command-overview/ (conversations/command.py).
Корень «/» и дефолтный маршрут менеджера — «Чат»; устаревшие
/command и /departments/support ведут в «Чат» и на «Доску».

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 14:14:57 +03:00
AndreyandClaude Fable 5.1 3c2d6ea511 🔥 refactor(api)!: снос старых роутов /channels/ и /ai/agents/ — единственный API агента /agents/
Карточка агента (/agents/) — единственный HTTP-слой поверх Channel+AIAgent
(ADR-HUB-0041 §4). Удалены дублирующие роуты channels/ (list/detail/
connections/counters) и ai/agents/ (list/detail/update/activate/deactivate)
вместе с views/serializers/api_inputs и мёртвым сервисным кодом каналов
(create_channel, delete_channel, filter_channels). Тест-чат перенесён в
AgentCardTestChatView (/agents/<id>/test-chat/). Bulk-роут
ai/agents/<aiAgentId>/knowledge/select-category/ сохранён — он адресует
AIAgent, как и knowledge/bulk/agent/.

Фронт: bulk-attach знаний и статей портала берёт агентов из /agents/
(id для bulk — aiAgentId карточки), AgentLinkOption без channelName.

Тесты старых роутов удалены; уникальное покрытие портировано на карточки
(активация без провайдера, семантика knowledgeIds, атомарный откат PATCH,
payload статей портала).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 14:14:56 +03:00
AndreyandClaude Fable 5 ce8c753357 ✨ feat(chat): голосовые в web-виджете (дизайн-базлайн v2, кадр H)
Виджет записывает голосовое (MediaRecorder, зеркало хука internal-ui) и
шлёт multipart в /webchat/messages/; входящее байтами — voice_content в
InboundMessage, скачивание не нужно. Аудио отдаётся по токену сессии
(/webchat/messages/<id>/audio/), в пузыре — нативный плеер. Оператор
может отправлять голосовые и в WEB-канал (доставка поллингом виджета).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:48:57 +03:00
AndreyandClaude Fable 5 ba405bb32b ♻️ feat(ui)!: «Настройки» одним экраном — Организация, Группы, AI-провайдер, Интеграции, Профиль (§8.6)
Вертикальные секции вместо отдельных разделов: «Организация» и
«Интеграции» упразднены (старые URL ведут в настройки), IntegrationsPage
разобран на IntegrationsSection по роду, добавлено управление группами
(создание одним полем, переименование, удаление). «Ещё» в сайдбаре:
Обзор, Обзор поддержки, Знания, Использование AI, Аудит. Сотруднику —
только профильная часть (тема, акцент, безопасность).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:40:54 +03:00
AndreyandClaude Fable 5 45605e9a45 ✨ feat(chat): полнотекстовый поиск по сообщениям (russian, GIN)
Поиск в списке диалогов: имена контактов — подстрокой, тексты сообщений —
через websearch-запрос с russian-конфигом и выражительным GIN-индексом
conv_message_text_fts; join+distinct заменён на Exists-подзапрос.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:33:39 +03:00
AndreyandClaude Fable 5 1dca4a042e 💄 feat(ui): клавиатура в чате — ↑/↓ по диалогам, Enter, «/» в поиск (§9)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:33:04 +03:00
AndreyandClaude Fable 5 8c846cc381 💄 feat(ui): Hint-реестр — inline-подсказки первого визита (кадр A1, §6)
Компонент Hint с реестром закрытий в localStorage; первая подсказка —
про видимость диалогов, под вкладками списка, только менеджерам
(сотрудникам подсказки не показываются по §6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:29:11 +03:00
AndreyandClaude Fable 5 09e9819fdd 💄 feat(ui): мобильный чат — список ↔ диалог (кадры M1/M2, S1)
На ≤768px сайдбар в чате скрыт: экран списка с шапкой бренда (логотип,
организация, меню профиля), лента открывается выбором диалога, назад —
стрелкой в шапке. Контекст — по кнопке в шапке (drawer S2 на всю ширину).
Пустой список — текст кадра S1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:27:30 +03:00
AndreyandClaude Fable 5 12107e02aa 💄 feat(ui): адаптив ≤1024 — рейка 60px и выдвижная контекст-панель (кадр S2)
Сайдбар на узком экране сжимается в рейку (логотип, «Чат», развернуть,
аватар), «развернуть» раскрывает полный сайдбар поверх контента.
Контекст-панель диалога становится drawer'ом с кнопкой в шапке ленты;
закрывается при смене диалога.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:23:55 +03:00
AndreyandClaude Fable 5 60bb7e8a54 ✨ feat(chat): отправка голосовых в MAX (дизайн-базлайн v2, кадр H)
Транспорт MAX: /uploads?type=audio -> multipart-загрузка -> отправка
attachment c токеном, с повторами на attachment.not.ready. Диспетчер
send_voice/supports_voice_send переведён на карту провайдеров; запись
в композере доступна для каналов TG и MAX.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:23:55 +03:00
AndreyandClaude Fable 5 445e3c83c2 ✨ feat(chat)!: единый чат и оболочка сотрудника по дизайн-базлайну v2
Sales- и support-диалоги объединены в один экран «Чат» (маршрут /chat,
старые адреса — алиасы). Контекст-панель сама выбирает представление по
identity диалога. У сотрудника нет верхней панели и разделов — сайдбар
это дерево «Все диалоги · Группы · Агенты», управляющее охватом списка;
у менеджера — плоские шесть пунктов макета и группа «Ещё» для служебных
разделов. Роуты уведомлений и карточек обзора на бэкенде — chat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 01:17:10 +03:00
AndreyandClaude Fable 5 981d149a38 ✨ feat(chat): голосовые сообщения с расшифровкой через BYOK (дизайн-базлайн v2, кадр H)
- приём голосовых из Telegram (getFile → скачивание, с прокси) и MAX (url
  вложения); файл хранится в tenant-storage, сбой скачивания оставляет
  текстовую заглушку; AI голос не разбирает — диалог уходит оператору;
- отдача аудио /conversations/messages/{id}/audio/ с проверкой видимости
  диалога; в payload сообщения kind=voice, длительность и расшифровка;
- расшифровка по кнопке через BYOK-провайдера организации (OpenAI-совместимый
  /audio/transcriptions, CUS_AI_TRANSCRIPTION_MODEL, по умолчанию whisper-1);
  идемпотентна, статусы NONE/READY/FAILED;
- отправка голосового оператором в Telegram (sendVoice, ручной multipart);
- фронт: плеер с волной и длительностью в ленте, превью «Голосовое сообщение
  · 0:14» в списке, запись в композере (MediaRecorder: таймер, отмена,
  отправка) для Telegram-диалогов, три состояния расшифровки.

Точечные тесты зелёные; фронт tsc 0, vitest 67 OK, build OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 00:13:21 +03:00
AndreyandClaude Fable 5 670091935b ✨ feat(ui): темизация — тёмная тема и акцент per-user (SPEC-HUB-0031 §7)
- поле ui_theme (светлая/тёмная/системная) и ui_accent (HEX) на пользователе,
  эндпоинт /auth/profile/appearance/, значения в session-payload;
- применение через data-theme на <html> и переменную --primary; производные
  оттенки (hover/bg/border/text) считаются color-mix'ом и работают в обеих
  темах с любым акцентом; antd переключается на darkAlgorithm;
- полный dark-набор токенов из дизайн-базлайна v2 в base.css (нейтрали,
  поверхности, статусные полутона, тени) + токены surface-*/border-strong;
- массовая токенизация: ~1050 захардкоженных цветов в 45+ CSS-файлах и все
  TSX-словари статусов переведены на var(--…); осознанно остались белый текст
  на насыщенных фонах, QR-код, фирменные цвета каналов, аватары, iframe писем;
- карточка «Внешний вид» в настройках: три темы, четыре пресета акцента из
  макета и произвольный HEX.

tsc 0, vitest 67 OK, build OK; backend-прогон зелёный (2 падения — артефакт
параллельного редактирования, воспроизведены и закрыты следующим прогоном).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 23:48:06 +03:00
AndreyandClaude Fable 5 ac8a739255 ✨ feat(chat): оболочка чата по дизайн-базлайну v2 — охват, шаблоны, чек-лист запуска
Вторая волна нового дизайна:
- переключатель охвата в заголовке списка диалогов (Все диалоги · Группы с
  точками и счётчиками · Без группы · Агенты) — серверная фильтрация,
  счётчики из /conversations/counters/;
- шаблоны ответов в композере: кнопка и ввод «/» открывают список с фильтром
  по названию, Enter вставляет первый, Esc закрывает;
- блок «Запуск» в сайдбаре менеджера: три шага с автоотметкой по факту
  (/company/launch-checklist/), прогресс, primary-действие текущего шага,
  «Скрыть» — per-организация в localStorage; исчезает после прохождения.

tsc 0, vitest 67 OK, build OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 20:34:03 +03:00
AndreyandClaude Fable 5 477b8f3ba4 ✨ feat(chat): примитивы чата по дизайн-базлайну v2 — приоритет, метки, заметка, архив
Первая волна нового дизайна (design/baseline/Chatbolls Baseline v2, решения
владельца 2026-09-04):
- Conversation: приоритет (HIGH/MEDIUM/LOW/NONE), метки (словарь организации,
  создание на лету), внутренняя заметка, архив («удалить» = архив, видят и
  восстанавливают только администраторы); RLS/парные триггеры (tenancy/0021);
- шаблоны ответов: общие на организацию, CRUD у администраторов;
- серверные фильтры списка: агент, мои, ждущие, поиск, архив; спам скрыт из
  вкладок по умолчанию; счётчики дерева фильтров /conversations/counters/;
- фронт: вкладки Все · Мои · Ждут оператора, бейджи приоритета и меток в строке
  диалога, блок «Диалог» в контекст-панели (ответственный, группа, приоритет,
  метки, заметка) в обоих чатах, «Вернуть в очередь» и «Удалить диалог» в меню.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 20:28:57 +03:00
AndreyandClaude Fable 5 14f5f33ce7 🔥 feat(platform)!: полный снос тарифного контура (этап 4 пивота, ADR-HUB-0042)
Тарифы, подписки, entitlements, quotas и managed AI удалены целиком:
- приложение subscriptions снесено (12 таблиц дропаются в tenancy/0020);
- CustoAI и credential_mode удалены (ai/0016): AI только через BYOK-провайдера
  организации; активация агента требует настроенного провайдера;
- вычищены все quota/entitlement-гейты: диалоги, порталы, интеграции, продукты,
  звонки (p2p-слоты), storage (остаётся технический учёт без лимитов);
- provisioning без плана/подписки; приглашение владельца без активации тарифа;
- эндпоинт и раздел «Тариф и оплата» удалены (фронт + бэк), HasEntitlement снят;
- уведомление LIMIT_REACHED удалено (notifications/0009);
- заодно: чек-лист запуска /company/launch-checklist/ (SPEC-HUB-0031 §5)
  и фикс сохранения provider_integration при обновлении агента.

Backend 550 тестов OK (докер, миграции с нуля), frontend tsc + vitest 67 OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 20:28:56 +03:00
AndreyandClaude Fable 5 774eb3215a ✨ feat(agents)!: агент и канал — одна сущность (этап 3 пивота, ADR-HUB-0041 §4)
- новый агрегированный API /api/v1/agents/: мастер одного шага (имя+группа,
  код из имени), объединённый PATCH канала и AI-конфигурации, удаление с
  блокерами, активация AI, привязка/отвязка подключений, тест-чат;
- data-миграция ai/0015: DRAFT-агент каждому каналу без агента;
- UI: разделы «Каналы» и «AI-агенты» слиты в «Агенты» (список + карточка-центр
  администрирования); legacy-URL редиректят; форма интеграции выбирает агента;
- чистка: features/channels и features/ai/{list,create,detail} удалены,
  битые @import styles.css (этапы 1-2) починены — прод-сборка снова собирается;
- дизайн-базлайн v2 владельца добавлен в design/baseline/Chatbolls Baseline v2/.

Backend 582 теста OK (докер, миграции с нуля), frontend tsc + vitest 69 OK, build OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 19:14:52 +03:00
AndreyandClaude Fable 5 07f4cd5f34 ✨ feat(identity): группы сотрудников вместо отделов (этап 2 пивота, ADR-HUB-0043)
Отделы и capability/scope-модель полностью удалены:
- снесены Department, AccessProfile, EmployeeAccessAssignment и scope-авторизация;
- новые EmployeeGroup/EmployeeGroupMember: настраиваемые группы, CRUD у OWNER/ADMIN;
- ролевая policy: OWNER == ADMIN (кроме ownership.transfer), EMPLOYEE ограничен чатом;
- группа у канала и диалога (наследуется при ingest, переносится вручную),
  видимость EMPLOYEE: свои группы + без группы + назначенные ему диалоги;
- эндпоинты /company/groups/, /conversations/{id}/group/ и /assignee/;
- звонки проверяют групповую видимость диалога (закрыта дыра в calls);
- RLS/гранты/триггеры групп (tenancy/0018), вьюха порталов без отделов (tenancy/0019);
- знания: библиотека общая для организации, visibility/departments удалены;
- фронт: employees/channels/knowledge переведены на группы, features/departments удалён.

Backend 564 теста OK (докер, миграции с нуля), frontend tsc + vitest 91 OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 14:01:30 +03:00
AndreyandClaude Fable 5 446a438776 🔥 feat(platform)!: контакт-центр Chatbolls — снос домена продаж и ребрендинг
Пивот по ADR-HUB-0041/0042: продукт становится open-source контакт-центром,
домен продаж удаляется целиком, прод-данные по решению владельца не сохраняются.

Backend:
- удалены приложения sales и orders (модели, API, ingest, команды, миграции);
- из products удалены Offer/Price/MarketplacePublication и ingest_token_hash
  (products/0011); таблицы, ingress-вьюхи и служебные записи Django снимаются
  миграцией tenancy/0017_drop_commerce;
- вычищены метрики выручки и заказов из conversations (stats/clients/command),
  типы уведомлений PAYMENT_* (notifications/0007), capabilities sales.*
  (identity/0019), коммерческие блокеры каналов, ingress-роуты продаж;
- демо-сид очищен от commerce; исторические миграции tenancy/0002-0004
  вычищены от таблиц orders_*/sales_* для чистой установки, tenancy/0003
  получила явные зависимости sessions/auth/contenttypes.

Frontend (internal-ui):
- удалены разделы «Продукты», «Обзор продаж», «Заказы», реестр и карточка
  продажи, вкладка «Продажи» и статус лид/клиент в контактах, коммерция
  в командном центре; роуты, навигация и доступы приведены в соответствие.

Ребрендинг CustoCRM → Chatbolls (видимый слой): UI, письма, TOTP issuer,
тексты ботов, заголовок API, README; виджет — window.ChatbollsChat
с legacy-алиасом CustoCRMChat. Технические идентификаторы (схема БД,
роли, cookie, CUS_-переменные, домены) — отдельным шагом (ADR-HUB-0042 §5).

Тесты: internal-ui 100/100, web-chat 5/5, backend RLS-сьют 10/10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 21:36:22 +03:00
AndreyandClaude Opus 5 e567e076ae 🐛 fix(webchat): чинит молчаливое «Чат временно недоступен» у Web-виджета
Разрешённые домены виджета нельзя было задать из интерфейса: поля не было, а
показанное для WEB поле «Base URL» в конфиг Web-виджета не уходило. Любое новое
подключение сохранялось с пустым allowed_origins, а пустой список в проде
запрещает вообще все origin'ы (origin_allowed) — виджет на сайте молча отвечал
«Чат временно недоступен», при зелёном статусе подключения в CRM.

- поле «Разрешённые домены» в форме подключения: разбор через запятую/перенос,
  дедупликация и валидация трёх форм, которые понимает origin_allowed
  (example.com, *.example.com, https://example.com:8443); сохранение
  заблокировано, пока не введён хотя бы один валидный домен
- мёртвое поле «Base URL» для WEB убрано
- «Проверить» больше не даёт зелёный статус при пустом списке доменов, а пишет
  причину в строку подключения
- проверка Web-виджета запускается сразу после сохранения: раньше любая правка
  роняла подключение в UNCHECKED, а виджет в DRAFT, и чат на сайте умирал до
  ручного нажатия «Проверить» (раздача требует PUBLISHED + OK)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 12:57:13 +03:00
Andrey e149c5d387 🐛 fix(webchat): align portal options test with widgets 2026-08-01 23:33:06 +03:00
Andrey f2187f3e83 ✨ feat(webchat): support multiple widget entry points 2026-08-01 21:44:53 +03:00
AndreyandClaude Opus 5 9cc92be044 🚑 fix(platform): не редиректить ask-эндпоинт Caddy на https
Caddy спрашивает разрешение на on-demand сертификат Help Center по plain
HTTP из внутренней сети, X-Forwarded-Proto не шлёт и по редиректам не
ходит. SECURE_SSL_REDIRECT отвечал 301, Caddy считал домен
неавторизованным и сертификат портала не выпускался: браузер получал
ERR_SSL_PROTOCOL_ERROR на foxray.help.custocrm.ru.

Исключаем /api/v1/gateway/ из SSL-редиректа на platform-сёрфейсе.
Проверено на стенде: ask отдаёт 204 для опубликованного портала и 404
для постороннего домена, сертификат выпущен.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 20:02:29 +03:00
AndreyandClaude Opus 5 c090f5904b ✨ feat(ai): массовое прикрепление знаний и статей поддержки к агенту
Статья портала становится самостоятельным источником знаний агента: агент
ссылается на неё, а не на копию (ADR-HUB-0039, SPEC-HUB-0029).

- KnowledgeFragment получает второй источник portal_article, инвариант
  «ровно один источник» закреплён CHECK-constraint; retrieval и каталог
  системного промпта работают по обоим источникам одним запросом
- фрагменты статьи строятся при публикации ревизии и снимаются при
  архивации: агент отвечает только опубликованным текстом
- новые эндпоинты bulk/agent для знаний и статей: attach/detach одному
  агенту, недоступные по отделу возвращаются в skippedIds, а не роняют
  операцию
- вкладка знаний в карточке агента стала нередактируемой, состав меняется
  только из библиотек; мастер создания агента оставляет канал и credential

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 16:00:39 +03:00
AndreyandClaude Opus 5 8c4775f8a4 💄 feat(webchat): pill-лаунчер, открытие на последних сообщениях, окно шире
- launcher: круглая кнопка → пилюля с иконкой Tabler brand-hipchat и словом
  «Чат» (inline SVG — на сайте продукта шрифта Tabler нет), hover/focus-visible,
  анимации bump/shake переделаны под новую форму
- окно 384x600 → 440x720
- фикс: панель открывалась на начале истории. Лоадер держит iframe скрытым
  (display:none), у ленты scrollHeight === 0 и автоскролл не срабатывал —
  виджет теперь слушает edevs-chat-opened и доскроллит после показа, лоадер
  повторяет сигнал на load iframe
- лента ближе к мессенджеру: время у сообщений, растущее под текст поле ввода,
  крупнее типографика и шире пузыри
- support-режим переиспользует шапку/пузыри/composer из ChatView вместо своих копий

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 15:59:07 +03:00
Andrey bb7ed5e8f9 fix(call): fix audio calls 2026-08-01 00:37:24 +03:00
Andrey 7db1a3de88 🚑 fix(calls): show Accept button for incoming audio call + load DS tokens on call page
On the client call page an incoming invite never showed Принять — only
Отменить. resolveAudioCallViewMode returned "ringing" for REQUESTED/RINGING
before start, but ringing renders a single Отменить button. An invite is an
incoming call for the client (the staff initiates it), so it must be incoming
which renders the Принять/Отклонить pair. AudioCallStage now also passes
onDecline, which the incoming PairBar needs.

The call page also missed the design tokens that audio-call.css consumes
(--font-sans, --radius-4xl/pill/lg, --success/--error): web-chat never defined
them, so the audio scene fell back to inherited system font and a 28px radius.
Added the tokens to web-chat styles.css :root straight from the baseline
(typography/effects tokens); no external webfont per ADR-HUB-0013.
2026-07-31 22:39:10 +03:00
Andrey c34b181681 🚑 fix(calls): bootstrap call before kind dispatch in web-chat call page
CallApp returned <AudioCallStage> before the bootstrap effect ran: on first
render call was null, so call?.kind !== 'VIDEO' was always true and the audio
scene rendered with empty call/accessToken/iceServers — RTC never started and
the screen stayed idle. The invite banner and the call page also checked kind
inversely (kind !== 'AUDIO' vs kind !== 'VIDEO'), diverging whenever kind was
undefined, so the widget labelled an underdetermined call as video.

- api.ts: single isVideoCall(call) helper (default AUDIO); used by both the
  invite banner and the call page so they agree on the kind.
- CallApp: composition-only dispatcher — bootstrap (invite/access token,
  polling) runs unconditionally, all hooks called, branching only in JSX by
  isVideoCall. No early return from the middle of the hook body.
- VideoCallStage: video path factored out of CallApp (symmetric to
  AudioCallStage), reusing the loaded call/access/iceServers.
- useConnectionTimer: shared connection timer; removes the duplicate from
  AudioCallStage.
2026-07-31 21:35:16 +03:00
Andrey b434656d4b 🚑 fix(deploy): mount postgres assets from CUSTOCRM_RELEASE_DIR
init-runtime-roles.sh и reassign-schema-ownership.sql монтировались относительным путём ./deploy/postgres/, который резолвится относительно --project-directory (INSTANCE_DIR). Ассеты лежат только в релизе, в instance их нет — Docker создавал пустые директории, psql -f падал с «Is a directory», деплой валился на нормализации владения схемой.

Приведено к единому эталонному паттерну ${CUSTOCRM_RELEASE_DIR} (как Caddyfile). compose.dev.yaml ассеты postgres не монтирует — дефекта там нет.
2026-07-31 19:51:32 +03:00
Andrey e697807811 ✨ feat(knowledge): article YAML import in support portals + template download link
Add a standalone backend endpoint POST /api/v1/support/portals/<id>/articles/import/ that upserts articles by (portal, locale, slug): creates a new article or appends a revision, resolving category by categoryPath. No DB migrations, reuses existing create_article/add_revision services.

Frontend: new portal article import modal + parseArticleYaml validator + article template (knowledge-template.yml / portal-articles-template.yml). Add a ".link has-icon" "Скачать шаблон" link to both the AI knowledge import modal and the new portal import modal, using the existing shared links.css standard and the existing download icon.
2026-07-31 18:36:35 +03:00
Andrey 3e0e5f4af9 💄 fix(products): center product icon and bind Каналы column to single source
- центрирование иконки в квадрате .product-icon вынесено в shared/table-controls.css (единое правило для таблиц продуктов и интеграций)
- из таблицы продуктов убраны пустые колонки SALES-AGENT и FULFILLMENT (нет привязки к данным / атрибут оффера, а не продукта)
- колонка КАНАЛЫ наполнена реальными данными через единый источник: product_payload.channels (id, code, name, isActive, agentId, connections) + prefetch channels__ai_agent, channels__connections
- ProductChannelsTab переведён на product.channels, дублирующий channelsApi.ts (запрос /api/v1/channels/ + клиентская фильтрация) удалён
- висячие стили .agent-state/.fulfillment-state/.channel-tags убраны, min-width таблицы убавлен под 6 колонок
2026-07-31 14:51:45 +03:00
Andrey 52adc210a8 ✨ feat(calls): owner can claim any dialog + audio/video call buttons
Two related conversation-header improvements.

Owner claim (business rule)
- backend: claim_locked_conversation lets EmployeeRole.OWNER take a dialog
  already assigned to another operator (other roles keep the existing guard)
- frontend: route SessionUser.role through ShellRouteContent → sales/support
  dialogs pages → ConversationWorkspace → ConversationThread; the owner sees a
  single «Перехватить диалог» button on any open dialog not assigned to them,
  including ones held by another operator

Audio/video call buttons (design-system IconButton)
- conversation header uses the approved IconButton component with outline icons;
  new is-audio/is-video hover modifiers (by the is-danger pattern, success and
  primary tokens from the design-system base.css): green hover for audio, blue
  for video
- audio-call modal: drop stray white padding so the dark 9:16 scene fills the
  modal content (no white side bars)
2026-07-31 13:52:44 +03:00
Andrey c4d11a8543 🚑 fix(deploy): normalize schema ownership before migrations
Production deploy failed at migrate with «must be owner of table
identity_organization»: the table (and others created/imported outside the
migration role) were not owned by custocrm_schema, which migration_user must
belong to to run AddField/AlterField. This is a pre-existing condition that
surfaced on the administration migration and recurred across pipelines
(#922, #937, #938) — not caused by the calls feature.

Fix makes deploy self-healing: before running migrate, reassign ownership of
all public-schema objects (tables, sequences, functions) to custocrm_schema
under the postgres superuser. Idempotent and safe on every deploy.

- deploy/postgres/reassign-schema-ownership.sql: reassign public-schema
  ownership to custocrm_schema (verified against PG16)
- compose.yaml: mount the SQL into the postgres container
- deploy/cli/lib/deploy.sh: run the normalization step once postgres is
  healthy, before the one-shot migrate
2026-07-31 12:53:51 +03:00
Andrey e78eb9f17d ✨ feat(calls): add audio calls alongside video calls
Introduce a call kind (AUDIO default, VIDEO) so dialogs support both audio
and video calls. Video path is unchanged; audio is a separate full-screen
component built 1:1 on the «Аудиозвонок.dc.html» baseline.

Backend
- CallSession.kind (CallKind AUDIO|VIDEO, default AUDIO) + migration 0005
- create_call_request(kind), CallCreateView validates kind from request body
- serializers expose kind in call_payload, public_invite_payload,
  public_call_state_payload (drop stale hardcoded capabilities)
- timeline system message reflects kind («аудио-/видеозвонок»)
- tests updated for the kind field and new message wording

@edevs/ui
- useCallRtcSession gains videoEnabled (default true; video unchanged);
  audio requests audio-only getUserMedia, no camera fallback
- AudioCallView, AudioCallWaveform (live AnalyserNode oscilloscope),
  AudioCallIcons (mic/speaker/phone/status), audioCallStates, audio-call.css
- exported via index.ts

internal-ui
- two call buttons in the conversation header (audio + video)
- CallOverlay becomes a kind dispatcher: VideoCallOverlay (unchanged) /
  AudioCallOverlay; kind threaded through model/hook/workspace

web-chat
- CallInfo.kind; invite banner shows audio vs video title and icon
- CallApp dispatches to AudioCallStage for audio; audio resolve/build in model

Infra
- compose.yaml: widen Coturn relay UDP range to 49160-49999 (~420 relay
  calls) with explanatory comment
2026-07-31 01:35:29 +03:00