Files
chatballs/AGENTS.md
T
AndreyandClaude Opus 5 5d8362e8f6 ✨ feat(queue): очередь к оператору, эскалация и уведомления по макету
Очередь помнит, с какого момента ждёт клиент (waiting_since), и «дольше всех
ждущий» больше не считается по последнему сообщению: клиент, напомнивший о
себе, уезжал в конец очереди. Постановка в очередь сведена в одно место
(conversations.queue) вместо шести копий одного правила.

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

Уведомления доезжают событием по сокету, а не следующим опросом: сокет поднят
на уровень оболочки и работает на любом экране. Появились уведомления самого
браузера — без service worker и Web Push, чтобы не тащить на self-hosted
зависимость от чужого push-сервиса.

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

Забытый диалог больше не тонет в тишине: свип напоминает группе, потом всем,
потом руководству; сроки — настройка организации в новом разделе «Когда звать
на помощь». Назначение стало осмысленным актом — назначенного зовут лично, у
него есть срок, и не взял — диалог возвращается всем.

Присутствие берётся из открытого сокета и подсказывает двум местам: очереди —
что напоминать некому, и выбору ответственного — кто сейчас за рабочим местом.

Макет: design/baseline/Очередь и уведомления.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 01:49:52 +03:00

13 KiB
Raw Blame History

Если в окружении не доступен инструмент, например - Python, PHP, попробуй docker, если в проекте есть его файлы.

Тесты: гонять только те, что относятся к изменённому в текущем шаге. Полный прогон — только по моему явному указанию, никогда по своей инициативе. Полный набор идёт ~20 минут, и гонять его после каждой правки запрещено.

Без моего явного указания не меняй ничего! Если я задал вопросы, это не значит что ты можешь менять файлы! Любые правки только после моего явного указания, например - "делай". Соблюдай инструкцию по коммитам. Используй glab, gh если доступны.

Учетные данные / доступы:

  • Без явного указания владельца не менять, не сбрасывать, не bootstrap-ить и не пересоздавать никакие учетные данные, пароли, TOTP, recovery-коды, сессии и права доступа.
  • Если для проверки нужен вход в приложение, использовать только уже существующие учетные данные, предоставленные владельцем, и не выполнять команды, которые могут изменить пароль или состояние аккаунта.
  • Любые команды управления аккаунтами, включая bootstrap_owner, password reset/change, seed пользователей и изменение ролей, требуют отдельного явного подтверждения владельца перед запуском.

Production deployment / миграция:

  • Не принимать самостоятельно решения о способе переноса production-данных, bind mounts, путях хранения, переключении каталогов или удалении старого deployment.
  • Перед любым изменяющим действием в production сначала представить владельцу точный план миграции и получить его явное согласование. Разрешение на диагностику или общая просьба «исправить» не являются разрешением самостоятельно выбирать архитектуру миграции.

UI / дизайн:

  • Никакой отсебятины в UI: не добавлять экраны, блоки, карточки, иконки, тексты, анимации, цвета, layout-решения и состояния, которых нет в утверждённом дизайн-макете (design/baseline/<фича>/*.dc.html). Макет — источник истины, README рядом с ним лишь пересказ.
  • Если UI-этап еще не наступил, UI не считается реализованным и не должен маскироваться под готовый продуктовый интерфейс.
  • Для построения UI использовать существующие компоненты и их стили, если они уже реализованы; если подходящего компонента нет, создавать переиспользуемый компонент в рамках существующей системы.
  • Не упрощать UI, анимации, иконки, состояния или поведение по своему усмотрению. Любое отклонение от baseline требует явного согласования до правок.
  • Технические временные заглушки UI не считать готовой реализацией и не использовать как основу продуктового UI без отдельного явного разрешения.

Один стандарт на один элемент:

  • Ссылки во всём приложении выглядят одинаково. Единая база — класс .link из shared/links.css, одинаковый и для <a>, и для <button>. Роль задаётся модификатором: .is-strong (имя сущности), .is-neutral (тёмное имя, синее при наведении), .is-muted (второстепенная, «Назад», «Отмена»), .is-mono (идентификаторы), .has-icon (с иконкой). Заводить в фиче свой *-name-link, *-meta-link, *-link-button запрещено.
  • Действие, а не переход, оформляется <button className="link">, а не <a href="#"> с preventDefault.
  • Выпадающие меню — только antd Dropdown с overlayClassName="app-dropdown" и триггером .row-menu-button. Своих меню на position: absolute/fixed, ручном расчёте координат и подложек-скримов не делать: портал закрывается по клику вне и по Esc сам и не обрезается overflow.
  • Перед тем как написать свой элемент, искать существующий в shared/ и в соседних фичах. Второй стандарт того же элемента — это дефект, а не свобода реализации.

Никаких служебных записок в продукте:

  • В продуктовом UI не должно быть служебной лексики. Запрещены на экране: коды спек и решений (SPEC-HUB-, ADR-HUB-, DG-*), номера правил (P1-P5 и любые другие), слова «инвариант», «констрейнт», «миграция», «scope» в техническом смысле, имена таблиц, полей и enum-значений БД и API (allowCheckoutActions, Conversation.channel, PROTECT, OK/ERROR как есть), ссылки на внутренние документы и любые пометки для разработчика.
  • Пользователю показывается следствие и способ исправить, а не внутреннее правило. «Коммерческие действия недоступны непродуктовому каналу. Назначьте продукт» — да. «Запрещено инвариантом P1» — нет.
  • Технические коды остаются в коде, комментариях, логах и API-ответах, но не в текстах интерфейса.
  • Это относится и к baseline-макетам: служебный текст, попавший в макет, не является основанием выводить его на экран. Макет реализуется, дополняется по указанию владельца, служебная лексика в реализацию не переносится.
  • Правило действует на прод в первую очередь: ни одна служебная пометка не должна доезжать до пользователя.

Engineering rules / обязательные практики:

  • Соблюдать DRY, KISS, SRP, separation of concerns и NO GOD FILES.
  • Не реализовывать полноценную страницу, feature или крупный workflow одним большим компонентом/файлом.
  • Если компонент превышает примерно 200 строк или смешивает layout, state, data/model и subviews, разделить его до завершения задачи.
  • Если новый или измененный файл превышает примерно 300 строк из-за текущей работы, остановиться и вынести части в меньшие модули до финального ответа или коммита.
  • Page-компоненты должны быть преимущественно composition-only: subviews выносить в соседние компоненты, демо/static data в model/data файл, повторяемый UI в shared/local reusable components.
  • Не инлайнить сложный UI повторно. Выносить компонент, если структура повторяется или секция имеет отдельную ответственность.
  • Если реализация по задаче конфликтует с этими правилами, остановиться и явно сообщить blocker вместо поставки плохой структуры.

Язык интерфейса:

  • Текста, который видит человек, в коде не бывает. Строка живёт в словаре и подставляется через t(...): во фронтенде это src/i18n приложения (packages/ui — свой словарь), на бэкенде chatballs/i18n. Русский каталог — источник ключей, английский обязан его повторить: пропущенный перевод во фронтенде ловит tsc, на бэкенде — тест каталога.
  • Ключ пишется от смысла, а не от фразы: settings.storage_bucket_name, а не пересказ текста. Строку переформулируют чаще, чем переименовывают.
  • Число не склоняют вручную: формы задаёт словарь, форму выбирает tn(...). Дату, размер файла и разряды числа форматирует fmt — прибитые "ru-RU" и свои списки месяцев запрещены.
  • Что записано в базу, переводится по коду, а не текстом: системные события диалога, уведомления и подписи журнала аудита хранят код, а фразу собирает бэкенд на языке читателя. Писать в историю готовую русскую фразу нельзя — её потом не перевести.
  • Язык запроса выбирается цепочкой: профиль сотрудника → организация → установка → браузер. Письмо получает язык адресата, а не отправителя.
  • На бэкенде t(...) не вызывается на уровне модуля: язык там свой на каждый запрос, а константа посчиталась бы один раз при импорте.
  • Текст, уходящий наружу — клиенту в мессенджер, письмо, кнопку виджета, — берёт язык не запроса, а организации: t(..., language=customer_language(organization)). Язык оператора, нажавшего кнопку, к речи компании с её клиентом отношения не имеет, и половина такого текста вообще рождается в воркере, где запроса нет.
  • Системный промпт агента не переводится: его читает модель. Язык ответа задаёт отдельная директива по полю AIAgent.answer_language — по умолчанию «как у клиента».
  • Демо-набор ставится на языке организации: манифесты лежат в demo_seed/data/<язык>/, наборы ключей в них совпадают. Бинарные вложения (аватары, голосовые) общие, текстовые документы — свои на каждый язык.

Definition of Done:

  • Запускать только тесты, относящиеся к изменениям текущей итерации: изменил presence — гоняешь тесты присутствия, изменил тексты — гоняешь каталог i18n. Полный прогон всех тестов выполнять ТОЛЬКО по явному указанию владельца. Использовать --reuse-db; --create-db — лишняя минута на переигрывание миграций, она нужна только когда схема действительно поменялась.
  • Измененные файлы должны быть проверены на NO GOD violations.
  • Новый компонент не должен владеть несвязанными ответственностями.
  • Не должно быть придуманных текстов, иконок, layout-решений или состояний вне design/docs.
  • Любое отклонение от design/docs должно быть явно указано.