Спецификация · версия 1 · черновик на утверждение

Трекер salam0nn

Черновик на утверждение · 9 сентября 2026 · продолжает обзор «Анатомия трекера и базы знаний» от 8 сентября

Урезанная копия YouTrack для команды: задачи, канбан-доска в каждом проекте, база знаний по всем проектам, роли и регистрация по ссылке-приглашению. Хостится на tracker.salam0nn.ru, на том же сервере, где уже живут панель, стенд plibvi и зеркало артефактов.

Адресtracker.salam0nn.ru
СтекFastAPI + Postgres + React
Память стека≤ 0,5 ГБ
Первая версия≈ 23 рабочих дня
Требований188

Слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ и МОЖЕТ читаются по RFC 2119. Требования пронумерованы (например, AUTH-03), чтобы на них ссылались задачи, тесты и приёмка. Всё, что не помечено «v1.1» или «v2», входит в первую версию.

1. Коротко

Что строим. Одно веб-приложение с одной базой, в котором есть проекты, у каждого проекта — задачи с ключами вида TAZ-12, канбан-доска с колонками из состояний задач и дерево статей базы знаний с идентификаторами вида TAZ-A-7. Поверх этого — сквозные разделы: все задачи, все доски, вся база знаний, глобальный поиск, уведомления.

Кто пользуется. Команда 3–10 человек плюс временные участники (например, студенты на хакатоне MAX). Один владелец инстанса, в каждом проекте свои роли. Рабочий язык интерфейса — русский. Заметная часть работы идёт с телефона, как и в панели управления сервером.

Что не строим. Учёт времени, CRM, helpdesk, диаграммы Ганта, спринты, JS-воркфлоу, произвольные пользовательские поля, публичные проекты, интеграции с VCS, отчёты. Список того, что урезано относительно YouTrack, — в разделе 3.3.

Ключевые решения.

  • Стратегия C из обзора: своё приложение, потому что YouTrack в РФ не предоставляется, а готовые системы либо не влезают в сервер (Huly, OpenProject), либо не дают одной модели «задача + статья + роли» (GitHub Issues + Docmost). Цена решения честно посчитана в разделе 20: около 20–25 рабочих дней на первую версию.
  • Стек тот же, что в nashpartner: FastAPI + SQLAlchemy + Postgres + Redis на бэкенде, React 19 + Vite + TypeScript + Tailwind + Radix + TipTap на фронтенде. Команда и ассистент уже знают этот код, его части (аутентификация, редактор, docker-раскладка, nginx-край) переносятся почти без изменений.
  • Markdown — каноническое хранение всех текстов (описаний, комментариев, статей). Редактор визуальный, но на диске и в API — markdown. Это делает базу знаний переносимой (импорт plibvi/kb, экспорт в git) и открытой для ИИ-ассистента как соавтора.
  • Права двухуровневые: глобальная роль (Владелец или Пользователь) и роль в проекте (Администратор проекта, Участник, Репортёр, Читатель). Право всегда проверяется на сервере, в одном модуле, по одной матрице (раздел 5).
  • Регистрация только по приглашению: персональная одноразовая ссылка или многоразовая ссылка команды. Почтового сервера на хосте нет и в v1 не требуется — ссылку копируют и отправляют в мессенджере.
  • Деплой: docker compose (postgres, redis, api, worker) на loopback-портах 34000–34002, статика фронтенда за хостовым nginx, TLS от Let's Encrypt. DNS tracker.salam0nn.ru уже указывает на сервер (проверено 9 сентября). Бюджет памяти — не больше 0,5 ГБ.

2. Контекст и закрытые вопросы

Обзор 8 сентября закончился девятью вопросами первого раунда. Запрос от 9 сентября отвечает на большинство из них; остальное закрыто допущениями, которые помечены явно и вынесены в раздел 22.

# Вопрос раунда 1 Ответ Источник
1 Кто пользователи Команда до 10 человек и временные участники; один владелец, роли в проектах запрос («для моей команды», «с определёнными ролями»); размер — допущение
2 Агент как пользователь Да. Ассистент — отдельный пользователь-бот с ролью Участник, работает через персональный токен API (v1.1) и через импорт/экспорт markdown допущение из инвентаризации: ассистент уже соавтор plibvi/kb
3 Границы системы Задачи + доски + база знаний + роли + приглашения. Без времени, денег, клиентов, helpdesk запрос; обзор, раздел «CRM или нет»
4 Что такое «Проект» Контейнер с ключом, командой, своими состояниями и типами задач, досками и деревом статей. Один продукт или репозиторий = один проект запрос («доска канбан для каждого проекта»)
5 Единый беклог Сквозной список «Все задачи» с фильтрами и группировкой по проекту; ранжирование внутри проектов, а не между ними допущение
6 Ритм Канбан без спринтов. Еженедельный разбор беклога — привычка команды, а не функция системы запрос («доска канбан»)
7 Иерархия Два уровня: эпик или задача → подзадача. Третий уровень не строим обзор, «Остальные привычки»
8 Хостинг и бюджет Свой сервер, tracker.salam0nn.ru, до 0,5 ГБ памяти, ноль лицензий запрос
9 Где живёт база знаний В трекере, в Postgres, в markdown. CONTEXT.md и docs/adr остаются в репозиториях как первоисточник для кода; в базу знаний идёт всё, что читают люди: продукт, процессы, договоры, заказчики, дайджесты. plibvi/kb импортируется скриптом запрос («База знаний по всем нашим проектам, которую я смогу самостоятельно заполнять»)

Что изменилось относительно рабочей гипотезы обзора: там в фаворитах были стратегии A и B, а C считалась дорогой. Пользователь выбрал C. Спецификация делает C посильной тем, что вырезает всё, что в YouTrack составляет годы работы (воркфлоу-скрипты, произвольные поля, отчёты), и оставляет ядро, которое команда действительно использует.

3. Что взято из YouTrack

Раздел собран по документации YouTrack 2025–2026 и блогу JetBrains; ссылки в разделе 24. Он нужен, чтобы дизайн не изобретался заново, а копировал проверенные решения там, где они уместны.

3.1. Как выглядит YouTrack сегодня

Навигация. С версии 2025.1 верхнее меню заменено на сворачиваемую вертикальную панель слева. На ней всегда доступны кнопки создания задачи, статьи и проекта, разделы Issues, Dashboards, Agile Boards, Reports, Projects, Knowledge Base, а внизу — уведомления и настройки. В свёрнутом виде остаются иконки, подписи всплывают при наведении. Логотип компании крупный в развёрнутом виде и маленький в свёрнутом.

Проектно-центричная навигация (2025.2). Открыв проект, пользователь видит его страницу-обзор и боковую панель с разделами этого проекта: задачи, статьи, доски, настройки. В шапке — кликабельные «хлебные крошки» с выпадающим списком проектов, чтобы переключаться между проектами, не возвращаясь к списку. Настройки проекта администратор правит прямо из боковой панели; «General Info» вынесена в шапку страницы.

Список задач. Слева узкая панель с разделами Drafts, Projects, Tags, Saved Searches, ширина тянется мышью. Поисковая строка в двух режимах: простой (подсказки атрибутов и значений по мере ввода) и расширенный (свободный запрос с подсветкой синтаксиса: атрибуты обычным текстом, значения синим, текст золотым, ошибки красным подчёркиванием). Два макета списка: Table (колонки с сортировкой) и List с тремя масштабами S/M/L (одна строка; плюс значения полей и автор; плюс первые строки описания). Структура Flat или Tree (родитель–потомок). В строке задачи: иконка приоритета, ключ, заголовок, замок ограниченной видимости, метки, счётчики вложений, комментариев и голосов, время обновления. При выделении появляется тулбар: команда, связи, метка, исполнитель, экспорт, удаление. Предпросмотр задачи — в боковой панели или inline (стрелка вправо или F3).

Страница задачи. Шапка: переключатель боковой панели, «Back to issues», хлебные крошки, ключ и заголовок с кнопкой копирования, строка поиска. Под ней информационная полоса: кто создал и когда, кто обновил, «Visible to», голос, звезда. Тулбар: Edit issue, Add tag, Add links, Show more (копировать ключ, печать, вложения, история версий, клонировать, удалить), переключатель ширины текста. Тело: заголовок, описание, метки, панель связей (сгруппированы по типу), панель вложений (миниатюры или список), лента активности с фильтрами Comments, Spent time, Change history, VCS changes и настройкой порядка. Справа панель полей проекта с inline-редактированием и списком досок, где есть эта задача; панель тянется и по настройке профиля переезжает налево. Есть «distraction-free mode», скрывающий всё, кроме описания и активности.

Agile board. Колонки обычно соответствуют полю State; крайняя левая — не начатая работа, крайняя правая — завершённая. У колонки есть Min WIP и Max WIP: при нарушении в заголовке колонки появляется предупреждение. Колонки можно объединять («Merge with»). Дорожки (swimlanes) выключены, либо по значениям поля (state, enum, user, version, date, project, tag), либо по задачам (эпик как дорожка); отдельная дорожка для неразобранных карточек, сверху или снизу. На карточке: оценка, цветовая схема по проекту или по полю, набор полей (полное имя или первая буква). Беклог доски — сохранённый поиск, в режиме дерева или списка. Доступ: «Can view and use the board» и «Can edit board settings» отдельно, с вариантами «issue readers» и «project updaters». Графики: burndown и cumulative flow.

База знаний. Статья привязана к проекту; дерево статей неограниченной вложенности, папок нет — их роль играют родительские статьи; перетаскивание перестраивает дерево вместе с потомками. Слева — оглавление проектов, у которых есть хотя бы одна статья; ширина панели запоминается. Над статьёй хлебные крошки: список проектов → проект → база знаний → родители → идентификатор статьи с копированием. Метаданные: кто создал и когда, кто последним изменил, «Visible to article readers» либо унаследованные ограничения. Справа — оглавление по заголовкам до трёх уровней. Тулбар: Add to favorites (подписка на уведомления), Edit, Add tag, Show more (Add sub-article, Copy article ID, Export as PDF, Print, Show version history, View statistics, Delete). Внизу — список подстатей с автором и датой, затем лента: комментарии, inline-комментарии, история с диффом (добавленное зелёным, удалённое красным). Черновики сохраняются автоматически, у каждой статьи история ревизий; черновики отмечены в дереве и собраны в отдельном меню.

Роли и права. Роли по умолчанию: System Admin, Project Admin, Contributor (до 2023.1 — Developer), Reporter, Observer. Роль назначается пользователю или группе на проект; члены команды проекта наследуют роль команды. Контрибьютор умеет читать проект, создавать, читать, обновлять и удалять задачи, вложения, комментарии, связи, статьи; Project Admin дополнительно «Update Project»; System Admin — «Delete Project» и «Override Visibility Restrictions». Reporter создаёт задачи и комментарии и следит за ними; Observer видит профили и правит свой. Проект приватный, если роли не выданы группе «All Users» и гостю. У статьи пять прав: Read, Create, Update, Delete Article и Read Article Comment; каждое подразумевает Read Article. Видимость статьи можно сузить до пользователей, групп и команд; подстатьи наследуют ограничения родителя и не могут стать шире; автор всегда видит свою статью; менять видимость может тот, у кого Update Article.

Регистрация. Три пути: администратор создаёт пользователя вручную, приглашает по почте (аккаунт создаётся сразу, письмо содержит ссылку на страницу, где человек задаёт пароль) или включает самостоятельную регистрацию в Hub с reCAPTCHA. Роль и проекты назначаются администратором после.

Поля по умолчанию. Priority: Show-stopper, Critical, Major, Normal, Minor. Type: Bug, Cosmetics, Exception, Feature, Task, Usability Problem, Performance Problem, Epic. State нерешённые: Submitted, Open, In Progress, To be discussed, Reopened; решённые: Can't Reproduce, Duplicate, Fixed, Won't fix, Incomplete, Obsolete, Verified. Kanban-шаблон: Stage — Backlog, Develop, Review, Test, Staging, Done; Kanban State — Blocked, Ready to pull.

3.2. Что переносим как есть

В YouTrack В нашей v1 Зачем
Вертикальная сворачиваемая панель с кнопками создания Так же (раздел 14.1) Проверенная навигация, экономит место на телефоне
Проектно-центричная навигация, хлебные крошки с выпадающим списком проектов Так же Команда живёт в 4–5 проектах, переключение должно быть в один клик
Ключ задачи PRJ-123, ключ статьи PRJ-A-12 Так же Главный интеграционный контракт: коммиты, чаты, статьи ссылаются по ключу
Панель полей справа от описания, inline-редактирование Так же, поля фиксированы Скорость правки без формы
Лента активности с фильтрами Комментарии и история изменений (без времени и VCS) Ответ на «кто и когда это поменял»
Колонки доски = состояния, Min/Max WIP, объединение состояний в колонку Так же Лимит незавершённой работы — главная практика канбана
Дорожки по исполнителю, приоритету, типу, эпику Так же, четыре варианта Достаточно для команды до 10 человек
Дерево статей без папок, перетаскивание с потомками Так же Простая модель, понятная людям вне разработки
Черновики с автосохранением и история ревизий с диффом Так же База, которую заполняют самостоятельно, обязана прощать ошибки
Избранное со подпиской на уведомления Так же Единый механизм «слежу за этим»
Роли на проект: администратор, участник, репортёр, читатель Так же (раздел 5) Ровно тот набор, что просил пользователь
Ограничение видимости статьи с наследованием на подстатьи Упрощено: флаг «только администраторы проекта», наследуется вниз Договоры и финансы в базе есть, а групп пользователей — нет
Приглашение ссылкой, задаёт пароль сам приглашённый Так же, плюс роль и проекты зашиты в приглашение Без почтового сервера письмо заменяет мессенджер
Два режима поиска: подсказки и запрос v1 — фильтры-чипы с подсказками; v1.1 — текстовый запрос с той же грамматикой Запрос дорог в реализации, чипы закрывают 90 % случаев
Markdown в описаниях и статьях с визуальным редактором Так же Переносимость и работа ассистента
Distraction-free режим страницы Кнопка «Широкий текст», скрывает панель полей Дёшево, полезно на ноутбуке

3.3. Что урезано

Возможность YouTrack Решение Причина
Произвольные пользовательские поля (custom fields) v2. В v1 фиксированный набор: тип, состояние, приоритет, исполнитель, срок, оценка, метки, родитель Главный источник сложности в трекерах; команда обходится фиксированным набором
Workflows на JavaScript, state-machine по типам Нет. Список состояний настраивается, переходы разрешены любые Автоматизация — через API и ассистента, не через встроенный скриптовый движок
Учёт времени, оценки в burndown Нет. Поле «оценка, ч» хранится, но не суммируется Граница с PSA проходит здесь (обзор)
Спринты, burndown, cumulative flow Нет Команда работает по канбану
Отчёты и дашборды Только домашняя страница «Моё» (раздел 14.3) Отчёты YouTrack — отдельный продукт
Диаграмма Ганта, Helpdesk, AI Assistant Нет Не в границах
Группы пользователей, команды как субъект прав Нет. Роль выдаётся человеку До 10 человек группы не нужны; появятся в v2 вместе с видимостью по группам
Публичные проекты, гость, голосование Нет. Все проекты приватные, вход только по сессии Инстанс внутренний
Интеграции с VCS, mailbox, Slack v1.1: Telegram-бот и токены API; VCS через упоминание ключа в коммитах — по договорённости, без интеграции Код команды на GitHub под личным аккаунтом, интеграция дороже пользы
Сохранённые поиски, теги как сохранённые фильтры v1.1 Фильтры в URL закрывают потребность, ссылку можно сохранить в браузере
Экспорт в PDF, статистика статьи Нет; экспорт статьи в markdown — да Markdown важнее PDF для этой команды
Дубликаты и связи всех типов Три типа связей: «связана», «блокирует / заблокирована», «дублирует» Достаточно по обзору
Несколько значений в полях (multi-enum, versions, builds) Нет Нет таких полей
Клонирование задач и статей, шаблоны задач Шаблоны статей — да (раздел 11.6); клонирование — v1.1 Дёшево добавить позже

4. Практики, положенные в основу

Каждая практика ниже влияет на конкретные требования, ссылки на них даны в скобках.

  • Лимит незавершённой работы. WIP-лимиты — самая сильная практика канбана: колонка с лимитом не принимает новую карточку, пока не уйдёт старая; результат — короче цикл, меньше переключений. Для малой команды стартовое правило — около одной активной карточки на человека в колонке «В работе», лимиты пересматриваются регулярно. Отсюда Min/Max WIP на колонке, подсветка нарушения и рекомендация по умолчанию max = число участников проекта (BRD-07).
  • Три–пять колонок. Начинать с «Беклог → В работе → Готово», добавлять колонку только под реальную очередь (ревью, ожидание внешних). Отсюда состояния по умолчанию (WF-02) и запрет создавать доску без хотя бы одной колонки.
  • Явные политики. У каждой колонки должно быть определение «что значит, что задача здесь». Отсюда поле «политика колонки» — короткий текст, показываемый по наведению на заголовок (BRD-06).
  • DEEP-беклог и задачи вместо user stories. Верх беклога расписан, низ обозначен; задача содержит конкретный результат и критерий готовности. Отсюда карточка задачи с блоком «Критерий готово» в шаблоне описания (ISS-05) и ручное ранжирование в колонке (BRD-10).
  • Триаж входящего. Всё внешнее попадает не в беклог, а в состояние «Входящие» (по умолчанию выключено, включается в настройках проекта) с четырьмя действиями: принять, отклонить, дубликат, отложить (WF-05).
  • Ответственный и дата пересмотра у статьи. Страница без владельца никогда не обновляется; устаревшая страница вредит больше, чем её отсутствие. Отсюда обязательный ответственный, дата пересмотра и пометка «устарело» с напоминанием (KB-12, KB-13).
  • Одна точка входа, прослеживаемость, глоссарий, типы документов (Diátaxis). Отсюда корневая статья-индекс в каждом проекте, шаблоны «Инструкция», «Справочник», «Объяснение», «Решение (ADR)», «Встреча» и блок «Источники» в шаблонах (KB-15).
  • Двухуровневые роли и минимальные привилегии. Роль на уровне инстанса и роль на уровне проекта независимы; набор ролей минимален (4–6); проверка идёт от самой конкретной области к общей; матрица «роль × ресурс × действие» — основа и для кода, и для аудита (RBAC-01…RBAC-09).
  • Приглашение как транзакция. Токен 256 бит из криптографического генератора, в базе — только хеш, срок 7 дней, одноразовость, роль зашита в приглашение (приглашённый не выбирает её сам), нельзя выдать роль выше своей, лимит на частоту, чёткое сообщение об истёкшей ссылке вместо общей ошибки. Все пункты — требования INV-01…INV-14.
  • Postgres достаточно. Полнотекстовый поиск с GIN-индексом и pg_trgm для опечаток закрывает поиск для десятков тысяч документов без Elasticsearch (SRCH-01…SRCH-06).
  • SSE вместо WebSocket. Для одностороннего потока «сервер → доска» хватает Server-Sent Events через Redis pub/sub: меньше протокольного шума, проще прокси и тесты (RT-01…RT-05).

5. Роли и права

5.1. Модель

Права проверяются в двух областях, и они независимы друг от друга.

  • Глобальная роль есть у каждого пользователя, ровно одна: Владелец или Пользователь. Владелец видит и делает всё; Пользователь сам по себе не видит ни одного проекта.
  • Роль в проекте есть у пользователя в каждом проекте, куда его добавили, ровно одна: Администратор проекта, Участник, Репортёр или Читатель. Один человек может быть администратором в одном проекте и читателем в другом.
  • Ограничение на объекте сужает круг ещё уже: статья с флагом «только администраторы» невидима Участникам, Репортёрам и Читателям проекта, но видна автору.
Запрос к /api/v1/…Есть сессия или токен?401 unauthorizedнетдаГлобальная роль — Владелец?Разрешеновсё, включая чужие проектыданетЕсть членство в проекте объекта?404 not_found: проект невидимнетдаМатрица 5.3 разрешает действие роли?403 permission_deniedнетдаОграничение объекта проходит?«только администраторы», «своё»403 или 404нетпроходитРазрешено
Порядок проверки права в tracker/authz.py: сессия, глобальная роль, членство, матрица, ограничение объекта. Отсутствие членства маскируется под несуществующий адрес.

RBAC-01 Каждая проверка права ДОЛЖНА выполняться на сервере в одном модуле (tracker/authz.py) функцией can(user, action, obj) -> bool и её вариантом require(...), поднимающим исключение. Роутеры НЕ ДОЛЖНЫ содержать собственных проверок ролей.

RBAC-02 Порядок вычисления: сессия → глобальная роль → членство в проекте объекта → матрица → ограничение объекта. Владелец проходит матрицу целиком, включая права, отсутствующие у Администратора проекта.

RBAC-03 Отсутствие членства в проекте ДОЛЖНО возвращать 404, а не 403, чтобы не раскрывать существование проектов и ключей задач. Отказ внутри проекта, где пользователь состоит, возвращает 403.

RBAC-04 Права *.own относятся к объектам, у которых author_id равен текущему пользователю; для задач «своя» — созданная пользователем, для комментариев и статей — то же.

RBAC-05 Фронтенд ДОЛЖЕН получать в ответе /api/v1/me список проектов с ролью в каждом и скрывать недоступные действия, но это только удобство: единственный источник истины — сервер.

RBAC-06 Матрица 5.3 ДОЛЖНА храниться в коде как данные (словарь ROLE_PERMISSIONS), и по ней ДОЛЖЕН существовать табличный тест, перебирающий все пары «роль × действие» (раздел 20.1).

RBAC-07 Нельзя удалить или понизить последнего Владельца; нельзя понизить последнего Администратора проекта, если в проекте нет Владельца. Попытка возвращает 409 last_admin.

RBAC-08 Пользователь МОЖЕТ покинуть проект сам («Покинуть проект»), кроме случая RBAC-07.

RBAC-09 Деактивированный пользователь (is_active = false) не может войти, его сессии отзываются немедленно, но авторство, комментарии и история сохраняются с его именем.

5.2. Роли словами

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

Администратор проекта. Отвечает за один проект. Настраивает состояния, типы, метки, доски, участников и их роли (до Администратора включительно), выпускает приглашения в свой проект, переносит и удаляет любые задачи и статьи проекта, ставит и снимает ограничение «только администраторы». Не видит другие проекты, пока его туда не добавят.

Участник. Основная рабочая роль. Создаёт и правит любые задачи проекта, меняет состояния и двигает карточки, комментирует, прикрепляет файлы, создаёт связи; создаёт и правит статьи, переставляет их в дереве; удаляет только своё. Не меняет настройки проекта и доски.

Репортёр. Заказчик или тестировщик. Видит задачи и статьи, создаёт задачи, правит свои, комментирует, прикрепляет файлы к своим задачам и комментариям. Не двигает чужие карточки и не пишет статьи.

Читатель. Только чтение задач, досок и статей. Может добавлять в избранное и получать уведомления.

5.3. Матрица прав

Обозначения: ✓ — разрешено; свои — только для объектов, где пользователь автор; — запрещено. Владелец имеет все права во всех проектах и не показан отдельной колонкой.

Действие (код) Администратор проекта Участник Репортёр Читатель
Проект
project.read — видеть проект, его задачи, доски, статьи да да да да
project.update — название, описание, ключ¹, иконка да нет нет нет
project.settings — состояния, типы, метки, «Входящие» да нет нет нет
project.members — добавлять, менять роль, удалять участников да нет нет нет
project.invite — приглашать в проект (роль ≤ своей) да нет нет нет
project.archive — архивировать и разархивировать да нет нет нет
project.delete — удалить проект со всем содержимым нет только Владелец нет нет нет
Задачи
issue.read да да да да
issue.create да да да нет
issue.update — все поля любой задачи да да свои нет
issue.state — менять состояние, двигать по доске да да свои нет
issue.assign — назначать исполнителя да да свои нет
issue.link — связи, родитель, подзадачи да да свои нет
issue.move — перенос в другой проект² да нет нет нет
issue.delete да свои свои нет
issue.watch — следить, избранное да да да да
Комментарии к задачам
comment.create да да да нет
comment.update да свои свои нет
comment.delete да свои свои нет
Вложения
attachment.add — к задаче, комментарию, статье да да к своим нет
attachment.delete да свои свои нет
Доски
board.view да да да да
board.use — перетаскивать карточки, менять ранг да да свои задачи нет
board.create да да нет нет
board.manage — колонки, WIP, дорожки, удаление да свои доски нет нет
База знаний
article.read да да да да
article.read_restricted — статьи «только администраторы» да автор автор нет
article.create да да нет нет
article.update — текст, заголовок, метки, ответственный да да нет нет
article.move — перенос в дереве, смена родителя да да нет нет
article.restrict — ставить и снимать «только администраторы» да нет нет нет
article.delete да свои нет нет
article.comment.create да да да нет
article.comment.update / delete да свои свои нет
article.history.restore — откат к ревизии да да нет нет
article.favorite да да да да
Приглашения и участники
invite.create_global — без привязки к проекту, роль Владелец нет только Владелец нет нет нет
invite.revoke — отзывать приглашения своего проекта нет нет нет
invite.list — видеть приглашения проекта да нет нет нет
Глобально (только Владелец)
users.manage — список, деактивация, смена глобальной роли, сброс пароля нет нет нет нет
projects.create нет нет нет нет
settings.manage — название инстанса, логотип, лимиты нет нет нет нет
audit.read — журнал аудита нет нет нет нет

¹ Смена ключа проекта меняет ключи всех задач и статей; старые ключи ДОЛЖНЫ продолжать открываться редиректом (таблица project_key_aliases). ² Перенос задачи в другой проект выдаёт ей новый номер в целевом проекте; старый ключ остаётся алиасом.

5.4. Правила, которые не выражаются матрицей

RBAC-10 Администратор проекта не может выдать роль выше своей: максимум «Администратор проекта». Роль Владельца выдаёт только Владелец через глобальное приглашение или страницу пользователей.

RBAC-11 Автор статьи с ограничением «только администраторы» продолжает видеть и править её, даже если он Участник (правило YouTrack «articles are always visible to their authors»). Право снять ограничение остаётся у Администратора.

RBAC-12 Подстатьи наследуют ограничение родителя и не могут быть открыты шире родителя; при переносе открытой статьи под ограниченного родителя она становится ограниченной, и об этом предупреждают до переноса.

RBAC-13 Ключ проекта участвует в URL, поэтому пользователь без членства получает 404 на любой адрес /p/{key}/... — ровно тот же ответ, что на несуществующий ключ (RBAC-03).

RBAC-14 Владелец, действуя в проекте, где он не участник, отображается в истории как «Владелец», и его действия попадают в журнал аудита с пометкой via_owner.

RBAC-15 Служебный пользователь-бот (ассистент) — обычный пользователь с глобальной ролью Пользователь и ролью Участник в нужных проектах; отдельного типа аккаунта не вводится. Его отличает только флаг is_bot, который скрывает его из подсказок исполнителя по умолчанию.

6. Регистрация по ссылке-приглашению

6.1. Сценарии

  • Персональное приглашение. Администратор проекта или Владелец создаёт приглашение: имя (необязательно), почта (необязательно, только как подсказка), роль, список проектов с ролью в каждом. Получает ссылку https://tracker.salam0nn.ru/invite/<токен>, копирует и отправляет в Telegram или MAX. Ссылка одноразовая, живёт 7 дней.
  • Ссылка команды. Для группы (например, четырёх студентов на хакатон): одна ссылка на N использований до даты, с ролью по умолчанию Репортёр или Читатель. Владелец может позже поднять роли вручную.
  • Первый Владелец. Создаётся из консоли: docker compose exec api tracker create-owner --email … --name …. Веб-регистрации без приглашения нет вовсе.
  • Уже вошедший пользователь открыл приглашение. Ему не создают второй аккаунт: показывают «Принять приглашение в проекты X и Y как текущий пользователь», и после подтверждения добавляют членства. Роль, уже более высокая, чем в приглашении, не понижается.
Созданоpending · token_hash, роль, проектыПринятоacceptedОтозваноrevokedИстеклоexpiredпринято: uses = max_usesотозвано приглашающим или Владельцемсрок истёкпроверка при открытии и раз в часкомандная ссылка: принято, uses < max_usesпересоздать ссылку: новый токен, срок зановоперсональная: max_uses = 1, срок 7 дней
Жизненный цикл приглашения. Из «Создано» есть ровно три выхода; командная ссылка возвращается в то же состояние, пока не исчерпает счётчик использований.

6.2. Требования

INV-01 Токен приглашения ДОЛЖЕН генерироваться secrets.token_urlsafe(32) (256 бит). В базе хранится только sha256(token); сырой токен показывается один раз при создании и при «пересоздать ссылку».

INV-02 Приглашение ДОЛЖНО хранить: кто пригласил, когда, срок действия (по умолчанию 7 дней, максимум 30), глобальную роль (user или owner), список {project_id, role}, тип (personal или team), max_uses (для персонального — 1), uses_count, статус (pending, accepted, revoked, expired), необязательные подсказки name_hint, email_hint, комментарий для себя.

INV-03 Роль в приглашении НЕ ДОЛЖНА превышать роль приглашающего в соответствующем проекте; глобальную роль owner может выдать только Владелец. Нарушение — 403 role_escalation.

INV-04 Приглашение с owner НЕ МОЖЕТ быть командным (max_uses > 1).

INV-05 Страница /invite/<токен> ДОЛЖНА без входа показать: название инстанса, кто приглашает, в какие проекты и с какой ролью; форму «Имя, почта, пароль, повтор пароля»; кнопку «Создать аккаунт». Для истёкшего, отозванного или исчерпанного приглашения — понятное сообщение («Ссылка истекла 12.09.2026. Попросите пригласившего создать новую») без раскрытия прочих деталей.

INV-06 Принятие ДОЛЖНО быть одной транзакцией: проверка токена и статуса FOR UPDATE, создание пользователя, создание членств, uses_count += 1, смена статуса на accepted при достижении max_uses, запись в аудит, создание сессии. Повторный запрос с тем же токеном после этого — 410 invite_used.

INV-07 Почта — уникальный логин (без учёта регистра). Если почта уже занята, страница предлагает войти и принять приглашение существующим аккаунтом (INV-11); второй аккаунт НЕ создаётся.

INV-08 Подтверждение почты в v1 не требуется (почтового сервера нет). Поле email_verified хранится и остаётся false; при появлении SMTP письмо с подтверждением включается настройкой.

INV-09 Лимиты: не более 20 приглашений в час на одного приглашающего; не более 10 попыток открыть /invite/* в минуту с одного IP; не более 5 попыток принять в минуту с одного IP. Превышение — 429 с Retry-After.

INV-10 Сравнение хеша токена ДОЛЖНО быть выборкой по индексу invitations(token_hash), а не перебором; ответ на несуществующий и на истёкший токен занимает одинаковое время с точностью до запроса к базе.

INV-11 Если приглашение открыто в браузере с активной сессией, показывается «Принять как <имя>» и «Это не я, выйти». Принятие добавляет членства к текущему аккаунту и не меняет уже более высокие роли.

INV-12 Приглашающий видит список своих приглашений с состоянием, сроком, счётчиком использований и действиями «Скопировать ссылку», «Пересоздать ссылку», «Отозвать». Владелец видит все приглашения инстанса.

INV-13 Истечение отмечается фоновой задачей раз в час (expire_invitations), но проверка при открытии ссылки сверяет expires_at сама, не полагаясь на задачу.

INV-14 О принятии приглашения приглашающий получает уведомление «Иван Петров принял приглашение в проект TAZ».

INV-15 Пароль: минимум 10 символов, проверка по списку из 10 000 самых частых паролей, без требований к классам символов; хеш argon2id с параметрами по умолчанию argon2-cffi (time_cost 3, memory 64 МБ, parallelism 4).

6.3. Вход, сессии, профиль

AUTH-01 Вход по почте и паролю на /login. Ответ на неверную пару — одинаковый текст «Неверная почта или пароль», без указания, что именно не так. После 10 неудач за 15 минут с одного IP или на одну почту — задержка 30 с и 429.

AUTH-02 Сессия хранится на сервере (Redis, ключ sess:<id>, id = token_urlsafe(32)), в браузере — cookie tracker_session: HttpOnly, Secure, SameSite=Lax, Path=/, срок 30 дней скользящий (продлевается при активности не чаще раза в час). Одновременных сессий у пользователя может быть несколько; в профиле есть список сессий с «Завершить» и «Завершить все, кроме этой».

AUTH-03 Защита от CSRF: SameSite=Lax плюс обязательный заголовок X-Requested-With: tracker на всех изменяющих запросах (POST, PATCH, PUT, DELETE); запрос без него — 403 csrf. Дополнительно сверяется Origin с PUBLIC_URL.

AUTH-04 Выход — POST /api/v1/auth/logout удаляет сессию из Redis и cookie.

AUTH-05 Смена пароля требует текущий пароль и завершает все остальные сессии. Владелец может сбросить пароль пользователю: генерируется одноразовая ссылка /reset/<токен> на 24 часа, показывается Владельцу для передачи (тот же механизм, что у приглашений).

AUTH-06 Профиль: имя, почта, аватар (загрузка, ≤ 2 МБ, ужимается до 256×256), часовой пояс (по умолчанию Europe/Moscow), тема (системная, светлая, тёмная), настройки уведомлений.

AUTH-07 Персональные токены API (v1.1): tracker_pat_…, хранятся хешем, показываются один раз, срок и область (проекты) задаются при создании; передаются заголовком Authorization: Bearer; на них не действует CSRF-проверка AUTH-03; действия попадают в аудит с пометкой via_token.

AUTH-08 Все ответы API ДОЛЖНЫ нести Cache-Control: no-store; nginx добавляет X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: same-origin, HSTS.

7. Доменная модель

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

ПользовательПриглашениеЧленствороль в проектеПроектключ TAZнастройки проектаСостояниекатегорияТипМеткаДоскаКолонкаWIP, политикаЗадачаTAZ-128содержимое задачиКомментарийВложениеСвязьродитель, блокируетСтатьяTAZ-A-7содержимое статьиРевизияКомментарийОбратная ссылкавыдаёт1..nn..11..n1..n статей1..n досок1..nодно или несколькопо ключу
Проект — центр модели: участники с ролями, настройки, доски, задачи и статьи принадлежат ему. Пользователь входит в задачу как автор, исполнитель и наблюдатель, в статью как автор и ответственный; позиции карточек на досках, избранное и уведомления — отдельные таблицы связи.

7.1. Сущности и поля

Типы указаны по Postgres. id везде bigint generated always as identity, если не сказано иное. У всех таблиц created_at, updated_at (timestamptz, UTC).

Пользователь users

Поле Тип Правила
email citext, unique логин; регистр не учитывается
name text 1–80 символов, показывается везде
password_hash text argon2id
global_role enum owner, user
is_active bool деактивация вместо удаления
is_bot bool скрывает из подсказок исполнителя
avatar_path text null относительный путь в хранилище
timezone text IANA, по умолчанию Europe/Moscow
theme enum system, light, dark
notify_prefs jsonb раздел 13.2
last_seen_at timestamptz обновляется не чаще раза в 5 минут

Приглашение invitations — раздел 6.2: token_hash (unique), kind, status, global_role, project_roles jsonb [{project_id, role}], max_uses, uses_count, expires_at, invited_by_id, name_hint, email_hint, note, accepted_user_ids bigint[].

Проект projects

Поле Тип Правила
key text unique 2–8 символов [A-Z][A-Z0-9]*; в URL и ключах задач
name text 1–80
description_md text markdown
icon text эмодзи или буква; цвет из color
color text hex, для аватара проекта и карточек
issue_counter int последний выданный номер задачи
article_counter int последний выданный номер статьи
inbox_enabled bool включает состояние «Входящие» (WF-05)
is_archived bool архивный проект только для чтения, скрыт из списков по умолчанию
default_board_id bigint null доска, открывающаяся по вкладке «Доска»

Членство project_members: project_id, user_id, role enum admin, member, reporter, viewer, added_by_id; unique (project_id, user_id).

Состояние issue_states: project_id, name (уникально в проекте), position int, category enum unstarted, started, done, cancelled, color, is_default (ровно одно в проекте). category определяет «решённость»: done и cancelled — решённые.

Тип задачи issue_types: project_id, name, position, icon, color, is_default, is_epic bool (тип, который может быть родителем).

Метка tags: project_id, name (уникально в проекте без учёта регистра), color.

Задача issues

Поле Тип Правила
project_id fk
number int уникально в проекте; ключ = project.key || '-' || number
title text 1–200
description_md text markdown, до 100 КБ
type_id fk
state_id fk
priority enum critical, high, normal, low по умолчанию normal
assignee_id fk null
author_id fk
parent_id fk null один уровень: у подзадачи не может быть своих подзадач (ISS-08)
due_date date null
estimate_hours numeric(6,2) null хранится, не суммируется
resolved_at timestamptz null ставится при переходе в done/cancelled, снимается при обратном
search_vector tsvector generated раздел 12
comments_count, attachments_count int денормализованы триггером

Индексы: unique (project_id, number); (project_id, state_id); (assignee_id) where resolved_at is null; (parent_id); GIN search_vector; GIN title gin_trgm_ops.

Метки задачи issue_tags: (issue_id, tag_id).

Связь issue_links: source_id, target_id, kind enum relates, blocks, duplicates; unique (source_id, target_id, kind); обратная сторона вычисляется (blocks → «заблокирована»). Связь между проектами разрешена, если у пользователя есть issue.read на обе стороны.

Наблюдатель issue_watchers: (issue_id, user_id); автор и исполнитель — наблюдатели по умолчанию.

Комментарий comments: issue_id, author_id, body_md, edited_at. Аналогично article_comments с article_id.

Вложение attachments: id uuid, project_id, владелец — ровно одно из issue_id, comment_id, article_id, article_comment_id; filename, content_type, size, storage_path (<project_key>/<uuid><ext>), sha256, uploaded_by_id, width, height для картинок.

Доска boards: project_id, name, owner_id, is_default, swimlane enum none, assignee, priority, type, parent, card_fields jsonb (что показывать на карточке), done_days int (сколько дней держать решённые на доске, по умолчанию 14), query jsonb (фильтр карточек, тот же формат, что у списка).

Колонка board_columns: board_id, name, position, state_ids bigint[] (одно или несколько состояний; каждое состояние не более чем в одной колонке доски), wip_min int null, wip_max int null, policy_md text (короткое «что значит, что задача здесь»), collapsed_default bool.

Позиция на доске board_positions: board_id, issue_id, rank float8; unique (board_id, issue_id). Ранг задаётся при первом попадании на доску и при ручном перетаскивании (BRD-10).

Статья articles

Поле Тип Правила
project_id fk
number int ключ TAZ-A-7
parent_id fk null дерево без ограничения глубины
position float8 порядок среди соседей
title text 1–200
slug text транслит заголовка, для красивых ссылок /p/TAZ/kb/7-kak-deployit
content_md text до 1 МБ; каноническое содержимое
draft_md text null несохранённые изменения (автосохранение)
draft_updated_at, draft_by_id кто и когда правит черновик
is_published bool false — статья-черновик, видна автору и администраторам
restricted bool «только администраторы»; наследуется вниз (RBAC-12)
owner_id fk ответственный, обязательный
author_id, last_editor_id fk
review_at date null дата пересмотра (KB-12)
template text null из какого шаблона создана
search_vector tsvector generated
version int оптимистическая блокировка (KB-09)

Индексы: unique (project_id, number); (parent_id, position); GIN search_vector; GIN title gin_trgm_ops.

Ревизия article_revisions: article_id, version, title, content_md, author_id, created_at, summary (необязательная подпись «что изменил»). Хранится каждая публикация; диффы считаются на лету.

Метки статьи article_tags, избранное favorites (user_id, kind enum issue, article, board, project, object_id), ссылки между объектами references (from_kind, from_id, to_kind, to_id; пересчитываются при сохранении из упоминаний ключей и вики-ссылок; дают обратные ссылки KB-11).

Уведомление notifications: user_id, kind, actor_id, object_kind, object_id, payload jsonb, read_at, created_at. Раздел 13.

Событие истории activity: project_id, object_kind, object_id, actor_id, kind (created, field_changed, comment_added, moved, …), field, old_value, new_value (jsonb), created_at. Это и лента активности задачи, и материал для аудита.

Аудит audit_log: только действия безопасности и администрирования: вход, выход, неудачный вход, создание и принятие приглашения, смена роли, деактивация, смена пароля, удаление проекта, действия Владельца в чужих проектах, доступ по токену. Поля: actor_id, action, target, ip, user_agent, meta.

Алиасы ключей project_key_aliases: old_key, project_id; для редиректов после смены ключа (RBAC-13 и сноска ¹ к матрице).

Настройки инстанса settings: одна строка: instance_name, logo_path, invite_ttl_days, max_upload_mb, allowed_upload_types, smtp jsonb null, telegram jsonb null.

7.2. Ключи и нумерация

DM-01 Номер задачи выдаётся в той же транзакции, что и вставка: update projects set issue_counter = issue_counter + 1 where id = :p returning issue_counter. Строка проекта блокируется на время транзакции, дыр в нумерации при откате не бывает, гонок — тоже. Аналогично для статей.

DM-02 Ключ задачи KEY-N и ключ статьи KEY-A-N уникальны навсегда: при переносе или смене ключа проекта старые ключи остаются алиасами и открываются редиректом 301.

DM-03 Ключи распознаются в любом markdown-тексте регулярным выражением \b([A-Z][A-Z0-9]{1,7})-(A-)?(\d+)\b и превращаются в ссылки при рендере; для несуществующих или недоступных ключей ссылка не создаётся.

DM-04 Удаление задачи, статьи, проекта — мягкое: deleted_at, объекты пропадают из выдачи, физически чистятся фоновой задачей через 30 дней. Владелец видит «Корзину» проекта и может восстановить.

8. Рабочий процесс задач

WF-01 Состояния настраиваются в каждом проекте отдельно: название, цвет, категория, порядок. Категорий четыре и они неизменны: не начато, в работе, готово, отменено. Категория даёт смысл, который понимают доски, фильтры и статистика: «нерешённые» — это не начато + в работе.

WF-02 Состояния по умолчанию в новом проекте: Беклог (не начато, по умолчанию), В работе (в работе), На проверке (в работе), Готово (готово), Отклонена (отменено). Их можно переименовать и дополнить; удалить состояние можно только указав, куда перевести его задачи.

WF-03 Переходы между состояниями разрешены любые. Никаких правил «нельзя из Беклога сразу в Готово» в v1 нет — в команде до 10 человек такие правила дороже пользы. Каждый переход пишется в activity с автором и временем.

WF-04 Переход в категорию готово или отменено ставит resolved_at; обратный переход снимает. Закрытие родителя с открытыми подзадачами показывает предупреждение и предлагает закрыть их разом; автоматически ничего не закрывается.

WF-05 «Входящие» — необязательное состояние категории не начато (inbox_enabled). Задачи, созданные Репортёрами и через API-токены, попадают в него; на его карточках и в списке видны четыре кнопки триажа: Принять (→ состояние по умолчанию), Отклонить (→ Отклонена, обязателен комментарий), Дубликат (→ Отклонена + связь duplicates), Отложить до даты (остаётся во Входящих, due_date = дата, скрывается из списка до неё).

WF-06 Типы по умолчанию: Задача (по умолчанию), Ошибка, Улучшение, Эпик (is_epic). Родителем может быть любая задача типа с is_epic, а также любая задача для своих подзадач; глубина — один уровень (ISS-08).

WF-07 Приоритеты глобальные и фиксированные: Критический, Высокий, Обычный, Низкий — с цветами красный, оранжевый, серый, синий. На карточке и в списке приоритет виден полосой слева.

WF-08 Смена состояния возможна четырьмя способами, все они — одна и та же операция PATCH /issues/{key} {state_id}: из панели полей, перетаскиванием на доске, из списка по клавише, кнопкой триажа.

9. Задачи

9.1. Создание

ISS-01 «Создать задачу» доступно с любой страницы: кнопка на боковой панели и клавиша c. Открывается диалог, не уводящий со страницы: проект (запомнен последний), заголовок, описание (редактор), тип, приоритет, исполнитель, срок, метки, родитель. Обязателен только заголовок; проект подставляется из контекста (внутри проекта — этот проект).

ISS-02 Быстрое создание в колонке доски и в списке: поле «+ Задача» принимает заголовок, Enter создаёт задачу в состоянии колонки (или по умолчанию) и оставляет поле открытым для следующей.

ISS-03 Диалог сохраняет незавершённый ввод в localStorage (черновик) и предлагает восстановить его при следующем открытии.

ISS-04 Вставка картинки из буфера обмена в описание или комментарий загружает её как вложение и вставляет ![](…).

ISS-05 Шаблон описания по умолчанию для типов Задача и Улучшение — три заголовка: «Зачем», «Что сделать», «Критерий готово»; для Ошибки — «Шаги», «Ожидалось», «Получилось», «Окружение». Шаблон правится в настройках проекта. Карточка эпика повторяет схему беклога auto-parser: «Зачем пользователю, Что уже есть, Что в плане, Критерий готово, Риски».

9.2. Страница задачи

Адрес /p/{KEY}/issues/{KEY}-{N}; короткий адрес /{KEY}-{N} редиректит на полный.

Эскиз интерфейса
┌──────────────────────────────────────────────────────────────────────────┐
│ < Задачи   Проекты > tazzz.ru v > Задачи > TAZ-128          / поиск     │
├──────────────────────────────────────────────────────────────────────────┤
│ Создал Салам · 04.09.2026 14:02 · Изменил Иван · вчера     * Следить (3) │
│                                                                          │
│ TAZ-128  Фото из Dongchedi не попадают в карусель      [Изменить] [:]    │
│ ┌────────────────────────────────────────────┐  ┌──────────────────────┐ │
│ │ ## Зачем                                   │  │ Проект    tazzz.ru   │ │
│ │ Карусель пустая у 12 % объявлений…         │  │ Тип       Ошибка     │ │
│ │ ## Что сделать                             │  │ Состояние В работе v │ │
│ │ - [x] найти, где теряется ключ S3          │  │ Приоритет Высокий    │ │
│ │ - [ ] добавить тест на пустую карусель     │  │ Исполн.   Иван v     │ │
│ │ ## Критерий готово                         │  │ Срок      12.09.2026 │ │
│ │ Ни одного пустого блока на 500 карточках   │  │ Оценка    4 ч        │ │
│ ├────────────────────────────────────────────┤  │ Метки     фото s3    │ │
│ │ Метки: фото · s3           Родитель: TAZ-90│  │ Родитель  TAZ-90     │ │
│ │ Связи: блокирует TAZ-131 · связана TAZ-77  │  ├──────────────────────┤ │
│ │ Подзадачи (1/2): x TAZ-129  _ TAZ-130      │  │ На досках: Канбан    │ │
│ │ Вложения (2): shakal.png · log.txt         │  └──────────────────────┘ │
│ ├────────────────────────────────────────────┤                          │
│ │ Активность   [Комментарии] [История]  ^    │                          │
│ │ Иван · вчера 18:40                         │                          │
│ │   Ключ теряется в tiered store, см. ADR-32 │                          │
│ │ Салам · вчера 18:55   Состояние: Беклог →  │                          │
│ │   В работе                                 │                          │
│ │ ┌────────────────────────────────────────┐ │                          │
│ │ │ Написать комментарий…  @упомянуть  +  │ │                          │
│ │ └────────────────────────────────────────┘ │                          │
│ └────────────────────────────────────────────┘                          │
└──────────────────────────────────────────────────────────────────────────┘

ISS-06 Шапка: кнопка «‹ Задачи» (назад к последнему списку с сохранёнными фильтрами), хлебные крошки с выпадающим списком проектов, ключ с копированием по клику, звезда «Следить» со счётчиком наблюдателей, строка поиска.

ISS-07 Заголовок и описание редактируются по кнопке «Изменить» или по клику в текст; сохранение Ctrl+Enter, отмена Esc. Пока пользователь правит, другим показывается «Редактирует Иван» (через SSE); при сохранении поверх более новой версии — предупреждение с диффом, сохранение с подтверждением.

ISS-08 Подзадачи: блок с прогрессом сделано/всего, галочки прямо в блоке (переводят в состояние категории готово), быстрое добавление. Подзадача не может иметь свои подзадачи; попытка выбрать родителя у подзадачи — ошибка 400 depth.

ISS-09 Панель полей справа, каждое поле правится по клику без формы; на экране уже 900 px панель уезжает под описание, сворачиваемая. Поле «На досках» перечисляет доски проекта, где эта задача видна, со ссылкой на колонку.

ISS-10 Меню «⋯»: копировать ключ, копировать ссылку, перенести в другой проект (Администратор), клонировать (v1.1), скачать все вложения, удалить (с подтверждением, в корзину).

ISS-11 Активность: две вкладки-фильтра «Комментарии» и «История», переключатель порядка (новые сверху или снизу), настройка запоминается. История показывает «поле: было → стало» с автором и временем; переходы состояния выделены цветом состояния.

ISS-12 Комментарий: markdown-редактор с упоминаниями @, вложениями, предпросмотром; «Изменено» с временем после правки; удаление — мягкое, с текстом «Комментарий удалён» в ленте, чтобы не ломать контекст ответов.

ISS-13 Упоминание @имя подписывает упомянутого на задачу и шлёт уведомление (NTF-02). Упоминать можно только участников проекта.

ISS-14 Вложения: до 25 МБ на файл (настраивается), типы из белого списка (картинки, pdf, офисные, txt/md/csv/json, архивы, логи); картинки показываются миниатюрами с просмотром на месте; остальное — списком с размером. Отдача файлов — через сервер с проверкой права (SEC-06).

9.3. Список задач

Адреса: /issues (все проекты), /p/{KEY}/issues (проект). Фильтры сериализуются в query-параметры, так что любой набор фильтров — это ссылка, которой можно поделиться.

Эскиз интерфейса
┌───────────────────────────────────────────────────────────────────────────┐
│ Задачи · tazzz.ru                                     [Таблица|Список] [+]│
│ [/ текст] [Состояние: нерешённые ×] [Исполнитель: я ×] [+ фильтр]        │
│ Группировать: состояние v   Сортировать: приоритет v   47 задач            │
├───────────────────────────────────────────────────────────────────────────┤
│ > В работе (5)                                                            │
│ ▌TAZ-128  Фото из Dongchedi не попадают в карусель  * Иван  фото  c2 12.09│
│ ▌TAZ-131  Тест на пустую карусель                   o —          ^TAZ-128 │
│ > Беклог (42)                                                             │
│ ▌TAZ-133  Спецкаталог Autoru: слить по имени         * Салам  каталог      │
│ …                                                                         │
└───────────────────────────────────────────────────────────────────────────┘

LST-01 Фильтры v1: текст (по заголовку, описанию и комментариям через FTS), проект (несколько), состояние (несколько; пресеты «нерешённые», «решённые»), категория состояния, тип, приоритет, исполнитель (включая «я» и «не назначен»), автор, метки, родитель, срок (до даты, просрочено, без срока), обновлено (за день, неделю, месяц), наблюдаю, есть вложения. Каждый фильтр — чип с подсказками значений при вводе.

LST-02 Группировка: без группировки, по состоянию, по исполнителю, по приоритету, по типу, по проекту, по родителю. Группы сворачиваются, состояние свёрнутости запоминается в localStorage.

LST-03 Сортировка: по обновлению (по умолчанию), созданию, приоритету, сроку, состоянию, ключу; направление переключается. Внутри группировки по состоянию доступен ручной порядок — тот же rank с доски по умолчанию проекта.

LST-04 Два макета: «Список» (строка: полоса приоритета, ключ, заголовок, исполнитель-аватар, метки, счётчики, срок) и «Таблица» (выбор колонок: все поля, ширины запоминаются). На телефоне только «Список».

LST-05 Клик по строке открывает задачу в боковой панели предпросмотра (на широком экране) или на своей странице (на узком); настройка «Открывать в панели» в профиле. ↑/↓ двигают выделение, Enter открывает, Esc закрывает панель.

LST-06 Массовые действия: выделение чекбоксами (Shift — диапазон), затем «Состояние», «Исполнитель», «Приоритет», «Метки ±», «Перенести», «Удалить»; каждое действие — одна операция POST /issues/bulk с проверкой права на каждую задачу; результат сообщает, сколько изменено и сколько отклонено.

LST-07 Экспорт текущей выборки в CSV (v1) и XLSX (v1.1), колонки — как в таблице.

LST-08 Пагинация курсорная, по 50; общий счётчик показывается всегда.

LST-09 Текстовый запрос (v1.1) с грамматикой, совместимой с чипами: проект: TAZ состояние: {В работе, На проверке} исполнитель: я #нерешённые срок: < 2026-09-15 сортировать: приоритет desc текст: "карусель". Английские синонимы атрибутов (project, state, assignee, sort by) допускаются. Запрос и чипы — два представления одного объекта фильтра IssueQuery, поэтому переключение между ними без потерь.

10. Канбан-доска

10.1. Устройство

BRD-01 При создании проекта автоматически создаётся доска «Канбан» с колонками из состояний по умолчанию: Беклог | В работе | На проверке | Готово; состояние Отклонена в колонки не входит. Эта доска — default_board_id и открывается по вкладке «Доска».

BRD-02 Досок в проекте может быть несколько (например, «Канбан» и «Релиз 2.0» с фильтром по эпику). Доска принадлежит проекту; кросс-проектные доски — v2.

BRD-03 Колонка содержит одно или несколько состояний; каждое состояние — не более чем в одной колонке доски. Перетаскивание карточки в колонку с несколькими состояниями ставит первое из них; на карточке в такой колонке видно фактическое состояние.

BRD-04 Задача попадает на доску, если её состояние входит в одну из колонок и она проходит фильтр доски (query). Задачи в состояниях категории готово/отменено показываются в своей колонке только если resolved_at не старше done_days (по умолчанию 14); в заголовке колонки — «показать все».

BRD-05 Заголовок колонки: название, счётчик карточек, при заданном лимите — 3 / 5; при нарушении Max WIP колонка подсвечивается тёплым фоном, при нарушении Min — пунктирной рамкой; перетаскивание сверх лимита разрешено, но требует подтверждения («В колонке уже 5 карточек, лимит 5. Всё равно перенести?»). Настройка «жёсткий лимит» (запрет) — v1.1.

BRD-06 У колонки есть политика — до 200 символов markdown; показывается по наведению на заголовок и в свёрнутом виде в настройках. По умолчанию для колонок из BRD-01: «Беклог — обсуждено, есть критерий готово», «В работе — есть исполнитель, взято не больше лимита», «На проверке — ждёт ревью или проверки заказчиком», «Готово — критерий готово выполнен».

BRD-07 При создании доски Max WIP колонки «В работе» СЛЕДУЕТ предзаполнять числом участников проекта с ролью Участник или выше; администратор может стереть.

BRD-08 Дорожки: нет; по исполнителю (плюс дорожка «Не назначено»); по приоритету; по типу; по родителю (эпик как дорожка, плюс дорожка «Без эпика»). Дорожки сворачиваются, порядок дорожек по исполнителю — по имени, по приоритету — по убыванию, по родителю — по рангу родителя.

BRD-09 Карточка: полоса приоритета слева, ключ (моноширинный), заголовок (до трёх строк), иконка типа, аватар исполнителя, метки, срок (красный, если просрочен), прогресс подзадач 1/2, счётчики вложений и комментариев; по настройке card_fields любые из них скрываются. Клик открывает задачу в боковой панели, Shift-клик — на отдельной странице.

BRD-10 Порядок карточек в колонке ручной (rank): при перетаскивании ранг = среднее соседей; при разнице рангов меньше 1e-6 колонка перенумеровывается. Новая задача получает ранг ниже всех в колонке (в конец), задача из «Входящих» при принятии — в конец Беклога.

BRD-11 Фильтры на доске: текст, «мои», исполнитель, тип, приоритет, метка — те же чипы, что в списке; сохраняются в URL. Кнопка «Свернуть решённые».

BRD-12 Быстрое создание в любой колонке (ISS-02); контекстное меню карточки: назначить мне, приоритет, состояние, переместить в верх или низ, открыть на странице.

BRD-13 Клавиатура: j/k — карточка ниже и выше, h/l — колонка, Enter — открыть, [ и ] — перенести на колонку влево и вправо, m — назначить мне, Esc — снять выделение.

BRD-14 Телефон: колонки горизонтальной лентой со snap-прокруткой по одной, заголовки колонок — липкая полоса сверху с точками-индикаторами; перетаскивание карточек — долгим нажатием; альтернатива без drag — меню карточки «В колонку…».

10.2. Настройки доски

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

BRD-15 Состояния, не вошедшие ни в одну колонку, перечислены под списком колонок как «Не на доске» с предупреждением, если среди них есть состояния категории в работе.

BRD-16 Удаление доски не трогает задачи; удаление доски по умолчанию запрещено, пока не назначена другая.

10.3. Обновление в реальном времени

RT-01 Клиент открывает GET /api/v1/events?projects=TAZ,DUSHA (Server-Sent Events) один раз на вкладку; сервер шлёт event: issue, event: article, event: board, event: presence, event: notification с data = {project_key, key, action, actor_id, version, ts} — без содержимого. Клиент инвалидирует кэш TanStack Query по ключу и дозапрашивает нужное.

RT-02 Источник событий — Redis pub/sub канал events:<project_id> (и events:user:<id> для уведомлений); публикует сервис после фиксации транзакции (after_commit), не внутри неё.

RT-03 Сервер шлёт : ping каждые 25 с; nginx для этого пути — proxy_buffering off, proxy_read_timeout 3600s. Клиент переподключается с Last-Event-ID и экспоненциальной паузой до 30 с; при недоступности SSE — опрос GET /api/v1/changes?since= раз в 30 с.

RT-04 Перетаскивание карточки применяется оптимистично; ответ сервера с 409 version (кто-то уже передвинул) откатывает карточку и показывает тост «Иван уже перенёс TAZ-128 в Готово».

RT-05 Присутствие: сервер отмечает в Redis presence:<object> с TTL 60 с, кто открыл задачу или статью; по событию presence в шапке показываются аватары («Смотрят: Иван, Ольга»), а в режиме редактирования — «Редактирует Иван» (ISS-07, KB-09).

11. База знаний

11.1. Устройство

База знаний — это дерево статей в каждом проекте плюс сквозные представления: страница «База знаний» со всеми проектами, глобальный поиск, избранное, «недавно изменённые». Для знаний, не привязанных к продукту (онбординг, процессы, глоссарий команды, шаблоны), при установке создаётся проект Команда с ключом TEAM; в нём нет задач по умолчанию, но технически это обычный проект.

Статья — это markdown-документ с заголовком, ответственным, датой пересмотра, метками, положением в дереве, историей ревизий, комментариями и вложениями. Папок нет: их роль играют родительские статьи, как в YouTrack.

Эскиз интерфейса
┌────────────┬───────────────────────────────────────────────┬─────────────┐
│ База знаний│ Проекты > По душе v > База знаний > Бэкенд >  │ Содержание  │
│ / поиск   │ DUSHA-A-14                                    │ Архитектура │
│            │                                               │  Сервисы    │
│ v По душе  │ Создала Ольга 29.07 · Изменил Салам вчера     │  Очереди    │
│  v Проект  │ Ответственный: Ольга · Пересмотр: 01.12.2026  │ Окружения   │
│   Договор!│                                               │ Источники   │
│   Хроника  │ Бэкенд: архитектура            * [Изменить] : │             │
│  v Продукт │ ───────────────────────────────────────────── │             │
│   События  │ Бэкенд называется nikea-backend, потому что…  │             │
│   Прототипы│ ## Сервисы                                    │             │
│  v Бэкенд  │ | Сервис | Порт | Зачем |                     │             │
│   * Архит… │ …                                             │             │
│   API      │ См. задачу DUSHA-42 и [[Решение: очереди]]         │             │
│   Админка  │                                               │             │
│  > Клиенты │ Подстатьи (3)                                 │             │
│  > Решения │  API · Иван · 02.09    Админка · Ольга · 28.08│             │
│ > tazzz.ru │  Очереди · Салам · вчера                      │             │
│ > Команда  │ ───────────────────────────────────────────── │             │
│ Черновики 2│ Ссылаются сюда: DUSHA-A-3, DUSHA-42, TEAM-A-1 │             │
│ + Статья   │ Комментарии (2) · История (7)                 │             │
└────────────┴───────────────────────────────────────────────┴─────────────┘

11.2. Дерево

KB-01 Левая панель показывает дерево статей текущего проекта и свёрнутые корни остальных проектов, где у пользователя есть article.read. Ширина панели тянется и запоминается; на телефоне панель — выдвижной ящик.

KB-02 Перетаскивание в дереве меняет родителя и позицию вместе со всеми потомками; операция одна — PATCH /articles/{key} {parent_id, position} — и проверяет RBAC-12 и отсутствие циклов (400 cycle).

KB-03 У каждой статьи есть кнопка «Добавить подстатью»; новая статья создаётся в конце списка потомков как черновик с заголовком «Без названия» и сразу открывается в редакторе.

KB-04 В дереве черновики (неопубликованные) показаны курсивом с точкой, ограниченные — замком, статьи с просроченной датой пересмотра — предупреждающим значком. Внизу панели — «Черновики (N)»: все черновики пользователя во всех проектах.

KB-05 Корневая статья проекта: при создании проекта автоматически создаётся статья-индекс «О проекте <название>» из шаблона «Индекс» — одна точка входа. Её нельзя удалить, пока в дереве есть другие статьи.

11.3. Страница статьи

KB-06 Хлебные крошки: проекты → проект (выпадающий список) → база знаний → цепочка родителей → ключ с копированием. Под ними метаданные: кто создал и когда, кто и когда изменил, ответственный, дата пересмотра, замок и текст «Только администраторы проекта», если ограничена.

KB-07 Справа — оглавление по заголовкам h2–h4 статьи с подсветкой текущего раздела при прокрутке; скрывается на узком экране под кнопку.

KB-08 Тулбар: звезда «В избранное» (подписка на изменения и комментарии), «Изменить», «Метка», меню «⋯»: добавить подстатью, копировать ключ, копировать ссылку, копировать как markdown, скачать .md со вложениями (zip), история, переместить, ограничить или снять ограничение (Администратор), удалить.

KB-09 Редактирование: одна статья редактируется одним человеком одновременно. Открытие редактора ставит «Редактирует Иван» через присутствие RT-05; второй пользователь видит предупреждение и может открыть в режиме чтения либо «всё равно редактировать». Сохранение проверяет version (оптимистическая блокировка): при конфликте показывается дифф и выбор «перезаписать» или «открыть свою копию в новой вкладке». Совместное редактирование в реальном времени (Yjs) — v2.

KB-10 Черновик автосохраняется раз в 3 с после изменения в draft_md, не создавая ревизии; кнопка «Опубликовать» переносит черновик в content_md, создаёт ревизию с необязательной подписью и рассылает уведомления; «Отменить изменения» стирает черновик. При закрытии вкладки с несохранённым черновиком он остаётся на сервере, и при следующем открытии редактор предлагает продолжить.

KB-11 Блок «Ссылаются сюда» — обратные ссылки из статей и задач, вычисленные из references; обновляются при каждой публикации ссылающегося документа.

KB-12 Ответственный обязателен (по умолчанию автор). Дата пересмотра необязательна, но шаблоны «Инструкция» и «Справочник» ставят её на +90 дней, «Решение» — не ставят. Просроченная дата помечает статью «Пора пересмотреть» в дереве, в списках и на самой странице; ответственный получает уведомление в день срока и раз в неделю после. Кнопка «Актуально» продлевает дату на тот же интервал и пишет запись в историю без новой ревизии.

KB-13 Страница проекта «База знаний» показывает счётчики: всего статей, черновиков, просроченных пересмотров, без ответственного (после импорта), а также список «Устаревшие» для администратора.

KB-14 Комментарии к статье — как у задач (ISS-12), лента под статьёй; inline-комментарии к фрагментам текста — v2.

11.4. Редактор

ED-01 Один редактор для описаний задач, комментариев и статей: TipTap 2 (ProseMirror) с сериализацией в markdown (tiptap-markdown) и переключателем «Markdown» на сырой текст с предпросмотром. Содержимое всегда сохраняется как markdown; что нельзя выразить в markdown, в редакторе не существует.

ED-02 Поддерживаемый markdown: CommonMark + GFM (таблицы, зачёркивание, чек-листы, автоссылки), заголовки h1–h4, цитаты, код с подсветкой (highlight.js на клиенте), картинки, сноски — нет, HTML — вырезается при рендере (nh3), кроме <details> и <br>.

ED-03 Расширения: упоминания @имя (участники проекта); ссылки на задачи и статьи по ключу (TAZ-128, DUSHA-A-14) с всплывающей карточкой; вики-ссылки [[Заголовок статьи]] внутри проекта с подсказками по заголовкам, при сохранении превращаются в [Заголовок](/p/KEY/kb/N-slug), ключ статьи в квадратных скобках тоже понимается; диаграммы в блоке ```mermaid рендерятся на клиенте; чек-листы в описании задачи кликабельны в режиме чтения.

ED-04 Слэш-меню /: заголовок, список, чек-лист, таблица, код, цитата, картинка, разделитель, диаграмма, вставить шаблон, дата. Панель форматирования появляется над выделением.

ED-05 Вставка картинок и файлов перетаскиванием или из буфера; картинки ужимаются на клиенте до 2000 px по большей стороне, если больше.

ED-06 Вставка HTML из Confluence, Google Docs или Word превращается в markdown на клиенте (turndown); таблицы сохраняются.

ED-07 Горячие клавиши стандартные: Ctrl+B/I/K, Ctrl+Shift+7/8 списки, Ctrl+Enter сохранить, Esc отмена; Tab в списках — уровень.

11.5. Импорт и экспорт

KB-16 Консольная команда tracker import-markdown <dir> --project DUSHA [--parent DUSHA-A-1] [--owner ольга@…] [--restricted-glob "10-project/contract*"] импортирует папку: каждая .md — статья, каждая папка — статья-раздел (из README.md или INDEX.md папки, иначе пустая с названием папки), числовые префиксы 10-project убираются из заголовка, но задают порядок; frontmatter title, updated, owner, tags читается; [[wiki-ссылки]] разрешаются по заголовкам и именам файлов; относительные картинки загружаются как вложения; sources/ в plibvi/kb помечается меткой «источник» и ограничением по маске. Запускается повторно без дублей (сопоставление по относительному пути в import_path).

KB-17 Экспорт: одной статьи с потомками или всего проекта в zip с той же структурой папок и frontmatter; кнопка на странице проекта и команда tracker export-markdown. Экспорт — способ держать копию базы в git, если команда захочет.

KB-18 Импорт auto-parser/docs (HTML-снимки беклога) не автоматизируется: карточки эпиков переносятся руками как задачи типа Эпик по шаблону ISS-05.

11.6. Шаблоны статей

Шаблон — markdown-заготовка с полями, подставляемыми при создании. Хранятся в проекте TEAM как статьи под разделом «Шаблоны» с меткой шаблон; Владелец и администраторы TEAM их правят, проекты ими пользуются. Набор по умолчанию, по Diátaxis и практикам команды:

Шаблон Для чего Разделы
Индекс корневая статья проекта О чём проект · С чего начать · Разделы · Люди и роли · Ссылки (репозиторий, стенд, договор)
Инструкция как сделать X Когда применять · Шаги · Проверка · Что может пойти не так · Источники
Справочник факты: окружения, порты, доступы Таблица · Как обновлять · Источники; пересмотр +90 дней
Объяснение почему устроено так Контекст · Как устроено · Компромиссы · Что почитать
Решение (ADR) зафиксировать решение Контекст · Решение · Последствия · Триггеры пересмотра · Связанные задачи
Встреча итоги обсуждения Участники · Решили · Задачи (ключи) · Открытое
Заказчик справочник заказчика Контакты · Договор и сроки · Что поставляем · Особенности · Связанные проекты
Дайджест сессии итог рабочей сессии с ассистентом Что сделано · Решения · Что дальше · Источники
Глоссарий термины проекта Термин · Определение · Не путать с

KB-15 У каждой статьи, созданной из шаблона, в конце есть раздел «Источники» (кроме Встречи и Глоссария), потому что «любой факт в заметке должен быть прослеживаем до источника» — правило plibvi/kb, которое переносится как есть.

12. Поиск

SRCH-01 Глобальный поиск открывается клавишей / или полем в шапке; ищет по задачам (заголовок, описание, комментарии) и статьям (заголовок, текст, комментарии) во всех проектах, где есть право чтения; результаты — двумя группами с подсветкой совпадений и фильтром по проекту.

SRCH-02 Ввод точного ключа (TAZ-128, dusha-a-14 в любом регистре) открывает объект сразу.

SRCH-03 Реализация — Postgres: search_vector — генерируемая колонка setweight(to_tsvector('russian', title), 'A') || setweight(to_tsvector('russian', content), 'B') || setweight(to_tsvector('english', title || ' ' || content), 'C') с GIN-индексом; запрос — websearch_to_tsquery('russian', :q) в объединении с english; ранжирование ts_rank_cd плюс бонус за свежесть. Комментарии индексируются в собственной таблице и подмешиваются к родителю.

SRCH-04 Опечатки и префиксы: pg_trgm по заголовкам (similarity(title, :q) > 0.3) как второй источник кандидатов, когда FTS дал меньше 5 результатов.

SRCH-05 Подсказки при вводе (GET /search/suggest?q=) — до 8 заголовков по триграммам, ответ не дольше 100 мс на 10 000 документов.

SRCH-06 Права применяются в SQL (join project_members), а не пост-фильтром, чтобы пагинация была честной; ограниченные статьи отбираются условием not restricted or role = 'admin' or author_id = :me.

13. Уведомления и активность

13.1. События

Код Когда Кому
mention упомянули в описании, комментарии, статье упомянутому
assigned назначили исполнителем исполнителю
issue_commented комментарий к задаче наблюдателям, кроме автора комментария
issue_state смена состояния наблюдателям
issue_changed смена приоритета, срока, заголовка, родителя наблюдателям (сгруппировано, NTF-04)
article_published опубликована ревизия подписчикам (избранное) и ответственному
article_commented комментарий к статье подписчикам и ответственному
article_review_due наступила дата пересмотра ответственному, раз в неделю до продления
invite_accepted приглашение принято приглашающему
added_to_project добавили в проект или сменили роль пользователю
inbox_new новая задача во «Входящих» администраторам проекта

13.2. Доставка

NTF-01 В приложении: колокольчик с счётчиком непрочитанных, панель со списком (группировка по дню), «прочитать всё», клик открывает объект. Хранятся 90 дней.

NTF-02 Уведомление о упоминании и назначении доставляется немедленно; остальные события по одной задаче или статье схлопываются в одно уведомление, если произошли в течение 5 минут от одного актора (NTF-04).

NTF-03 Каналы: в приложении (всегда), Telegram-бот (v1.1: пользователь привязывает аккаунт кодом из профиля; бот шлёт текст с ссылкой), почта (только если настроен SMTP; в v1 выключено). Браузерные push-уведомления не делаются (пользователь отказался от них для панели).

NTF-04 Схлопывание: события одного вида по одному объекту от одного актора в течение 5 минут дополняют существующее непрочитанное уведомление, а не создают новое.

NTF-05 Настройки в профиле (notify_prefs): для каждого вида — «в приложении», «Telegram»; глобальный «тихий режим» с интервалом по часовому поясу пользователя; «не уведомлять о моих собственных действиях» включено всегда.

13.3. Лента активности

ACT-01 У проекта есть страница «Активность»: хронология событий activity по задачам и статьям с фильтрами по виду, автору, объекту; служит и для еженедельного разбора («что изменилось за неделю»).

ACT-02 Домашняя страница «Моё» показывает: назначенные мне нерешённые задачи (по сроку и приоритету), задачи, где я автор и которые во «Входящих» или без исполнителя, статьи, за которые я ответственен, с ближайшим пересмотром, последние 20 событий в моих проектах, непрочитанные уведомления.

ACT-03 Журнал аудита (SEC-09) — отдельная страница Владельца с фильтрами по действию, пользователю, дате и экспортом в CSV.

14. Интерфейс

14.1. Общая раскладка

Эскиз интерфейса
┌──────────┬────────────────────────────────────────────────────────────────┐
│ = Трекер │ Проекты > tazzz.ru v > Доска                      /  ! 3  o │
│          ├────────────────────────────────────────────────────────────────┤
│ + Создать│                                                                │
│          │                                                                │
│ ~ Моё    │                                                                │
│ _ Задачи │                     содержимое страницы                        │
│ # Доски  │                                                                │
│ = База   │                                                                │
│   знаний │                                                                │
│ o Проекты│                                                                │
│ ──────── │                                                                │
│ * tazzz  │                                                                │
│ * По душе│                                                                │
│ ──────── │                                                                │
│ * Админ. │                                                                │
│ o Салам  │                                                                │
└──────────┴────────────────────────────────────────────────────────────────┘

UI-01 Боковая панель слева, сворачивается кнопкой и клавишей [; состояние запоминается. Развёрнутая — 232 px с подписями, свёрнутая — 56 px с иконками и подсказками. Порядок: логотип и название инстанса; «+ Создать» (выпадающее: Задача c, Статья, Проект — последнее только Владельцу); Моё; Задачи; Доски; База знаний; Проекты; разделитель; избранные проекты, доски и статьи (звезда); разделитель; Администрирование (Владелец); аватар и имя (меню: профиль, тема, выйти).

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

UI-03 Внутри проекта у шапки есть вторая строка-вкладки: Обзор · Задачи · Доска · База знаний · Активность · Участники · Настройки (последние две — по правам). Вкладка помнит последнее состояние (фильтры, выбранная доска).

UI-04 Телефон (уже 720 px): боковая панель превращается в нижнюю навигацию с пятью кнопками (Моё, Задачи, Доски, База, Ещё), вкладки проекта — в горизонтальную прокручиваемую полосу, панель полей задачи — под описание, дерево статей — в выдвижной ящик, таблицы — в карточки. Все касаемые цели не меньше 44 px. Enter в редакторе переносит строку, отправка — кнопкой (правило панели).

UI-05 Тема светлая и тёмная, по системной настройке с ручным переключением в профиле; цвета состояний, приоритетов и меток подобраны парами для обеих тем; контраст текста не ниже 4.5:1.

UI-06 Язык интерфейса — русский; даты в формате дд.мм.гггг и относительные («вчера», «5 мин назад») с полной датой по наведению; часовой пояс из профиля.

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

UI-08 Загрузка: скелетоны для списков и доски, без спиннера на весь экран; переходы между страницами без перезагрузки (SPA), данные кешируются TanStack Query с фоновым обновлением.

UI-09 Доступность: все действия доступны с клавиатуры, видимый фокус, aria-label у иконок, prefers-reduced-motion отключает анимации перетаскивания.

14.2. Страницы и адреса

Адрес Страница Права
/login Вход нет
/invite/{token} Принятие приглашения нет
/reset/{token} Сброс пароля нет
/ Моё вход
/issues Все задачи с фильтрами вход
/boards Все доски (карточки по проектам) вход
/kb Вся база знаний: проекты, недавние, черновики, устаревшие вход
/projects Список проектов (активные, архив) вход
/p/{KEY} Обзор проекта: описание, счётчики, участники, последние события project.read
/p/{KEY}/issues Задачи проекта project.read
/p/{KEY}/issues/{KEY}-{N} и /{KEY}-{N} Задача issue.read
/p/{KEY}/board и /p/{KEY}/boards/{id} Доска board.view
/p/{KEY}/boards/{id}/settings Настройки доски board.manage
/p/{KEY}/kb База знаний проекта (корневая статья) article.read
/p/{KEY}/kb/{N}-{slug} и /{KEY}-A-{N} Статья article.read
/p/{KEY}/kb/{N}/edit Редактор статьи article.update
/p/{KEY}/kb/{N}/history История ревизий с диффом article.read
/p/{KEY}/activity Активность проекта project.read
/p/{KEY}/members Участники и приглашения проекта project.members
/p/{KEY}/settings Настройки: общее, состояния, типы, метки, шаблоны, доски, корзина project.settings
/search?q= Результаты поиска вход
/notifications Все уведомления вход
/me Профиль, сессии, токены, уведомления вход
/admin/users Пользователи Владелец
/admin/invitations Все приглашения Владелец
/admin/settings Настройки инстанса Владелец
/admin/audit Журнал аудита Владелец

14.3. Горячие клавиши

Клавиши Действие
c Создать задачу
/ Поиск
g m / g i / g b / g k / g p Моё / Задачи / Доски / База знаний / Проекты
[ Свернуть или развернуть боковую панель
e Изменить (задача, статья)
a Назначить исполнителя
s Сменить состояние
l Добавить метку
Ctrl+Enter / Esc Сохранить / отменить в редакторе
j k h l [ ] m На доске (BRD-13)
↑ ↓ Enter В списке (LST-05)
? Показать список клавиш

14.4. Страница приглашения

Эскиз интерфейса
┌──────────────────────────────────────────────┐
│           Трекер salam0nn                    │
│                                              │
│  Салам приглашает вас в проекты:             │
│   • tazzz.ru — Участник                      │
│   • Команда — Читатель                       │
│                                              │
│  Имя           [ Иван Петров           ]     │
│  Почта         [ ivan@example.ru       ]     │
│  Пароль        [ ••••••••••••          ]     │
│  Ещё раз       [ ••••••••••••          ]     │
│                                              │
│  [ Создать аккаунт и войти ]                 │
│  Уже есть аккаунт? Войти и принять           │
│  Ссылка действует до 16.09.2026              │
└──────────────────────────────────────────────┘

15. API

15.1. Общие правила

API-01 REST под префиксом /api/v1, JSON, OpenAPI-схема на /api/v1/openapi.json и Swagger UI на /api/v1/docs (только для вошедших). Один и тот же API используют фронтенд, консольные команды и внешние клиенты по токену.

API-02 Аутентификация: cookie-сессия (AUTH-02) или Authorization: Bearer tracker_pat_… (AUTH-07). Изменяющие запросы с cookie требуют X-Requested-With: tracker (AUTH-03).

API-03 Ошибки — единый формат: {"error": {"code": "permission_denied", "message": "Нет права менять состояние задачи", "details": {...}}}. Коды: validation (422), unauthorized (401), permission_denied (403), csrf (403), not_found (404), conflict (409, с details.current_version), last_admin (409), rate_limited (429), invite_expired, invite_used, invite_revoked (410), too_large (413), unsupported_type (415).

API-04 Списки — курсорная пагинация: ?limit=50&cursor=…, ответ {"items": [...], "next_cursor": "...", "total": 47}. Фильтры — query-параметры с повторением для множеств (state=1&state=2) или объект IssueQuery в POST /issues/query для длинных запросов.

API-05 Обновления — PATCH с частичным телом; для конкурентных объектов (задача, статья, доска) обязателен version в теле или заголовок If-Match; несовпадение — 409 conflict.

API-06 Все объекты отдают key (для задач и статей), url, created_at, updated_at, version; вложенные пользователи — {id, name, avatar_url}; ссылки на другие объекты — по ключам.

API-07 Ограничение частоты: 600 запросов в минуту на сессию или токен; /auth/* и /invite/* — по AUTH-01 и INV-09. Заголовки X-RateLimit-Remaining, Retry-After.

API-08 Идемпотентность создания: заголовок Idempotency-Key (UUID) на POST /issues, POST /articles, POST /attachments; повтор с тем же ключом в течение 24 ч возвращает первый результат.

15.2. Ресурсы

Метод и путь Что делает Право
POST /auth/login, POST /auth/logout вход, выход — / вход
GET /me, PATCH /me, POST /me/password, GET /me/sessions, DELETE /me/sessions/{id} профиль, пароль, сессии вход
GET,POST /me/tokens, DELETE /me/tokens/{id} персональные токены (v1.1) вход
GET /invite/{token}, POST /invite/{token}/accept просмотр и принятие приглашения нет
GET,POST /invitations, POST /invitations/{id}/resend, POST /invitations/{id}/revoke приглашения project.invite / Владелец
GET,POST /projects, GET,PATCH,DELETE /projects/{key}, POST /projects/{key}/archive проекты см. матрицу
GET,POST /projects/{key}/members, PATCH,DELETE /projects/{key}/members/{user_id}, POST /projects/{key}/leave участники project.members
GET,POST,PATCH,DELETE /projects/{key}/states, …/types, …/tags, POST …/states/reorder настройки проекта project.settings
GET /issues, POST /issues/query, POST /issues список и создание issue.read / issue.create
GET,PATCH,DELETE /issues/{key}, POST /issues/{key}/move, POST /issues/{key}/watch, DELETE …/watch задача см. матрицу
GET,POST /issues/{key}/comments, PATCH,DELETE /comments/{id} комментарии comment.*
GET,POST /issues/{key}/links, DELETE /links/{id} связи issue.link
GET /issues/{key}/activity история issue.read
POST /issues/bulk массовые изменения по каждой задаче
GET /issues/export.csv экспорт выборки issue.read
GET,POST /projects/{key}/boards, GET,PATCH,DELETE /boards/{id} доски board.*
GET /boards/{id}/cards карточки с колонками и дорожками одним ответом board.view
POST /boards/{id}/move {issue_key, column_id, before_key?, after_key?} перенос карточки board.use
GET /projects/{key}/articles/tree, GET,POST /projects/{key}/articles дерево и создание article.*
GET,PATCH,DELETE /articles/{key}, PUT /articles/{key}/draft, POST /articles/{key}/publish, POST /articles/{key}/discard-draft статья, черновик, публикация article.*
GET /articles/{key}/revisions, GET /articles/{key}/revisions/{v}, POST /articles/{key}/restore/{v} история article.read / article.history.restore
GET /articles/{key}/backlinks, GET /articles/{key}/export.zip обратные ссылки, экспорт article.read
GET,POST /articles/{key}/comments комментарии article.comment.*
POST /attachments (multipart, owner_kind, owner_key), GET /attachments/{uuid}, DELETE /attachments/{uuid} вложения attachment.*
POST /favorites, DELETE /favorites/{kind}/{id}, GET /favorites избранное вход
GET /search?q=&project=&kind=, GET /search/suggest?q= поиск вход
GET /notifications, POST /notifications/read, POST /notifications/read-all уведомления вход
GET /projects/{key}/activity, GET /changes?since= лента, дельта для опроса project.read
GET /events?projects= SSE вход
GET /admin/users, PATCH /admin/users/{id}, POST /admin/users/{id}/reset-link, GET,PATCH /admin/settings, GET /admin/audit администрирование Владелец
GET /health {"status":"ok","db":true,"redis":true,"version":"…"} — (только с loopback nginx)

API-09 Ответ GET /boards/{id}/cards собирается одним SQL-запросом с оконной сортировкой по rank и отдаёт {columns: [{id, name, wip_min, wip_max, count, states}], swimlanes: [...], cards: [{key, title, column_id, swimlane_id, rank, priority, type, assignee, tags, due_date, subtasks: {done, total}, counts}]}; на 500 карточек ответ не дольше 300 мс.

API-10 Консольные команды (tracker … внутри контейнера api): create-owner, create-project KEY "Название", import-markdown, export-markdown, expire-invitations, reindex-search, purge-deleted, backup (обёртка над pg_dump + архив вложений).

API-11 MCP-сервер для Claude Code (v1.1): тонкая обёртка над этим же API с инструментами search, get_issue, create_issue, update_issue, comment, get_article, upsert_article, list_board; авторизуется персональным токеном пользователя-бота. Это закрывает вопрос «агент как пользователь» из раунда 1.

16. Архитектура и стек

16.1. Компоненты

БраузерReact SPA/var/www/trackerстатика, сборка bunnginx на хостеtracker.salam0nn.ru · TLS/try_files → index.html/api/proxy_pass 127.0.0.1:34000/api/v1/eventsSSE: без буферизации, 1 ч/_uploads/internal, X-Accel-Redirectdocker compose «tracker» · только loopbackapitracker.main:appworkerarq, фоновые задачиuploadsbind-mount с хостаpostgres 16данные, FTSredis 7сессии, pub/subHTTPSотдаёт файлыHTTP :34000поток SSEчитает файлSQL, FTSсессии, pub/subпишеточередьпишет
Один хостовый nginx терминирует TLS, отдаёт статику и проксирует API в контейнер на loopback-порту 34000; поток SSE идёт тем же путём без буферизации; вложения nginx отдаёт сам после проверки права в api.

ARCH-01 Бэкенд: Python 3.12, FastAPI, SQLAlchemy 2 (async, asyncpg), Alembic, Pydantic v2, argon2-cffi, redis-py, arq (фоновые задачи), nh3 (санитайзер HTML), markdown-it-py (рендер markdown на сервере для превью и уведомлений), structlog, python-multipart, Pillow (миниатюры и аватары), uvicorn. Зависимости — pyproject.toml + uv.lock, как в nashpartner.

ARCH-02 Python-пакет называется tracker, а точка входа — tracker.main:app. Не app.main:app: соседние проекты перезапускают свои серверы командой pkill -f "uvicorn app.main:app", и она уже дважды роняла панель; процессы контейнеров видны на хосте, так что совпадение имени убило бы и трекер.

ARCH-03 Слои бэкенда: tracker/api/* — роутеры и схемы (тонкие); tracker/services/* — сценарии (создать задачу, перенести карточку, опубликовать статью) с проверкой прав через tracker/authz.py и записью в activity; tracker/models/* — SQLAlchemy; tracker/search.py, tracker/events.py (публикация в Redis, SSE-генератор), tracker/markdown.py (рендер, извлечение упоминаний и ключей), tracker/storage.py (файлы), tracker/cli.py (Typer), worker/ (arq-задачи).

ARCH-04 Фронтенд: React 19, TypeScript, Vite, Tailwind, Radix UI (shadcn-компоненты), TanStack Query и Router, react-hook-form + zod, TipTap 2 + tiptap-markdown, @dnd-kit (перетаскивание), lucide-react, sonner (тосты), date-fns с русской локалью, highlight.js, mermaid (ленивая загрузка), turndown. Сборка bun, вывод — статика.

ARCH-05 Структура репозитория:

tracker/
  CONTEXT.md            словарь предметной области (раздел 23)
  README.md             как запустить, как деплоить
  docs/SPEC.md          этот документ
  docs/adr/             журнал решений
  backend/
    pyproject.toml  uv.lock  alembic/  alembic.ini  Dockerfile
    tracker/  worker/  tests/
  frontend/
    package.json  vite.config.ts  tailwind.config.js  src/  public/
  deploy/
    docker-compose.yml  .env.example  nginx-tracker.salam0nn.ru.conf
    deploy.sh  backup.sh  tracker.cron

ARCH-06 Конфигурация — переменные окружения (.env, не в git): DATABASE_URL, REDIS_URL, SECRET_KEY (для подписи служебных ссылок), PUBLIC_URL=https://tracker.salam0nn.ru, UPLOAD_DIR=/data/uploads, MAX_UPLOAD_MB=25, SESSION_TTL_DAYS=30, INVITE_TTL_DAYS=7, SMTP_URL (пусто), TELEGRAM_BOT_TOKEN (пусто), LOG_LEVEL.

ARCH-07 Миграции Alembic применяются при старте контейнера api (alembic upgrade head в entrypoint до запуска uvicorn); откат — alembic downgrade -1 вручную.

ARCH-08 Логи — JSON в stdout (structlog), docker compose logs; в каждой записи request_id, user_id, path, status, duration_ms. Ошибки 5xx дополнительно в audit_log не пишутся, но пишутся с трейсом.

16.2. Бюджет ресурсов

Сервер: 2 ядра, 7,8 ГБ, свободно около 4,8 ГБ (замер 9 сентября), без swap. Уже работают панель, стенд plibvi, nashpartner (postgres, redis, minio), хостовые Postgres и Redis.

Контейнер Образ Память Примечание
tracker_postgres postgres:16-alpine ≤ 200 МБ shared_buffers=128MB, max_connections=40, work_mem=8MB
tracker_redis redis:7-alpine ≤ 30 МБ maxmemory 64mb, allkeys-lru только для лимитов; сессии без вытеснения (noeviction на отдельной базе 1)
tracker_api свой, python:3.12-slim ≤ 200 МБ 1 процесс uvicorn, 2 воркера при необходимости
tracker_worker тот же образ ≤ 100 МБ 1 процесс arq
Итого ≤ 0,5 ГБ остаётся запас больше 4 ГБ

Своя база, а не хостовый Postgres, — потому что бэкап и перенос всего трекера тогда сводятся к docker compose и одной папке; цена — 150 МБ памяти.

17. Безопасность и файлы

SEC-01 Пароли — argon2id (INV-15); секреты — только в .env с правами 600; в git — .env.example.

SEC-02 Весь HTML, отрендеренный из markdown, проходит nh3 с белым списком тегов и атрибутов; javascript: и data: в ссылках вырезаются; картинки с чужих доменов разрешены, но грузятся с referrerpolicy="no-referrer".

SEC-03 Content-Security-Policy для SPA: default-src 'self'; img-src 'self' https: data:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'. Никаких CDN: библиотеки в бандле.

SEC-04 Загрузка файлов: проверка размера до чтения тела (Content-Length и потоковое чтение с обрывом), белый список MIME по сигнатуре (python-magic), а не по расширению; SVG запрещён (XSS); имя файла хранится отдельно от пути на диске; путь — <KEY>/<uuid><ext>.

SEC-05 Хранилище вложений — локальная папка /srv/tracker/uploads (bind-mount в контейнер как /data/uploads). MinIO не используется: одна папка проще бэкапить, а объёмы малые. Переход на S3 — за интерфейсом Storage в tracker/storage.py.

SEC-06 Отдача вложений: GET /api/v1/attachments/{uuid} проверяет право читать владельца вложения и отвечает X-Accel-Redirect: /_uploads/<path> с Content-Disposition и X-Content-Type-Options: nosniff; nginx отдаёт файл сам (location /_uploads/ { internal; alias /srv/tracker/uploads/; }). Картинки в статьях подставляются тем же адресом, поэтому в <img> нет прямых путей.

SEC-07 Лимиты частоты (AUTH-01, INV-09, API-07) хранятся в Redis скользящим окном; ключ — сессия или токен, для анонимных путей — IP из X-Real-IP (доверяем только nginx на loopback).

SEC-08 Сессии и токены отзываются мгновенно (Redis); смена пароля и деактивация вызывают отзыв (AUTH-05, RBAC-09).

SEC-09 Журнал аудита (ACT-03) неизменяем из приложения: только вставка; чистится не раньше чем через 365 дней.

SEC-10 Заголовки: HSTS (nginx), X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: same-origin, Permissions-Policy: camera=(), microphone=(), geolocation=().

SEC-11 Зависимости проверяются pip-audit и bun audit в deploy.sh перед сборкой; результат печатается, сборка не блокируется, но предупреждение попадает в лог деплоя.

SEC-12 Нет внешней телеметрии, аналитики и шрифтов с чужих доменов; всё, что нужно странице, лежит в бандле.

18. Деплой на tracker.salam0nn.ru

18.1. Что уже есть

  • DNS: tracker.salam0nn.ru → 2.25.117.83 (проверено 9 сентября, тот же адрес, что у salam0nn.ru и llm.salam0nn.ru).
  • nginx 1.24 на хосте с vhost-ами salam0nn.ru и llm.salam0nn.ru; certbot с webroot /var/www/certbot и таймером автопродления.
  • Docker 29 и Compose 2.40; свободные loopback-порты 34000–34002 (занятые: 8080, 18080–18099, 33000–33005, 5432–5434).
  • Bun 1.4 для сборки фронтенда; Node 22 в /opt/node22 для Playwright.
  • Панель salam0nn.ru/my-server/ показывает контейнеры, порты и сертификаты — мониторинг из коробки.

18.2. Шаги

  1. Репозиторий /root/tracker (или клон с GitHub), .env из .env.example с сгенерированными секретами (openssl rand -hex 32).
  2. Compose deploy/docker-compose.yml: сервисы postgres, redis, api (127.0.0.1:34000:8000), worker; тома tracker_pg, bind /srv/tracker/uploads:/data/uploads; restart: always; healthcheck у всех; сеть internal. Postgres и Redis наружу не публикуются вовсе; для отладки — docker compose exec postgres psql.
  3. Фронтенд: cd frontend && bun install && bun run build, результат dist/ → rsync --delete в /var/www/tracker/.
  4. nginx deploy/nginx-tracker.salam0nn.ru.conf → /etc/nginx/sites-available/tracker.salam0nn.ru, симлинк в sites-enabled; шаблон — vhost llm.salam0nn.ru (те же TLS-параметры, HSTS, server_tokens off):
server {
    listen 80; listen [::]:80;
    server_name tracker.salam0nn.ru;
    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    location / { return 301 https://$host$request_uri; }
}
server {
    listen 443 ssl http2; listen [::]:443 ssl http2;
    server_name tracker.salam0nn.ru;
    ssl_certificate     /etc/letsencrypt/live/tracker.salam0nn.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tracker.salam0nn.ru/privkey.pem;
    # ssl_* — как в llm.salam0nn.ru
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    server_tokens off;
    client_max_body_size 30M;

    root /var/www/tracker;
    location / { try_files $uri /index.html; }
    location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; }

    location /api/v1/events {
        proxy_pass http://127.0.0.1:34000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_buffering off; proxy_cache off;
        proxy_read_timeout 3600s;
        proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    location /api/ {
        proxy_pass http://127.0.0.1:34000;
        proxy_http_version 1.1;
        proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 120s;
    }
    location /_uploads/ { internal; alias /srv/tracker/uploads/; }
}
  1. Сертификат: сначала включить только HTTP-блок, nginx -t && systemctl reload nginx, затем certbot certonly --webroot -w /var/www/certbot -d tracker.salam0nn.ru, включить HTTPS-блок, снова nginx -t && reload. Продление подхватит существующий certbot.timer.
  2. Запуск: docker compose -p tracker -f deploy/docker-compose.yml up -d --build; миграции применятся сами (ARCH-07); docker compose exec api tracker create-owner --email … --name ….
  3. Проверка: curl -s https://tracker.salam0nn.ru/api/v1/health, вход владельцем, создание проекта TEAM (создаётся автоматически при первом запуске, если его нет), первое приглашение.
  4. Firewall: ничего не меняется — ufw уже пропускает 80 и 443, контейнеры слушают только loopback.

DEP-01 deploy/deploy.sh выполняет шаги 3–6 идемпотентно: git pull (если репозиторий), аудит зависимостей, сборка фронтенда, сборка образа, up -d, ожидание health, nginx -t && reload; при падении здоровья за 60 с — откат на предыдущий образ (docker compose up -d с тегом previous).

DEP-02 Порт 34000 фиксирован в .env (API_PORT), чтобы не столкнуться с соседями при добавлении сервисов; следующий проект берёт 35000.

DEP-03 Страница «Скоро» на tracker.salam0nn.ru СЛЕДУЕТ выложить в первый день (шаги 4–5), до готовности приложения: домен, сертификат и nginx проверяются отдельно от кода.

19. Эксплуатация

OPS-01 Бэкап ежедневно в 03:30 по cron на хосте: pg_dump -Fc из контейнера в /root/backups/tracker/db-<дата>.dump и tar папки вложений (инкрементально, --listed-incremental); хранить 14 ежедневных и 3 месячных; скрипт deploy/backup.sh; после бэкапа — pg_restore --list как проверка целостности. Копия за пределы сервера — вопрос 22.5.

OPS-02 Восстановление задокументировано в README и проверяется раз в квартал: поднять compose с пустым томом, pg_restore, распаковать вложения, reindex-search.

OPS-03 Обновление: deploy.sh; миграции обратимы; перед миграциями со сменой схемы задач или статей — бэкап автоматически.

OPS-04 Мониторинг: панель salam0nn.ru/my-server/ (контейнеры, память, сертификат); GET /api/v1/health в проверку панели; при unhealthy compose перезапускает контейнер сам. Алерты в Telegram — вместе с ботом уведомлений (v1.1).

OPS-05 Логи хранятся docker-драйвером json-file с max-size 20m, max-file 5.

OPS-06 Фоновые задачи (worker): expire_invitations каждый час, review_reminders ежедневно в 09:00 по Москве, purge_deleted еженедельно, cleanup_sessions ежедневно, send_notifications по очереди (Telegram, почта), rebuild_backlinks по событию публикации.

20. Тестирование и приёмка

20.1. Уровни

TST-01 Модульные тесты бэкенда (pytest, pytest-asyncio) на сервисы и authz; интеграционные — через httpx.AsyncClient против тестовой базы (Postgres в docker, как plibvi-test-pg); фронтенд — vitest для утилит фильтров и сериализации markdown; e2e — Playwright на Node 22 (/opt/node22/bin/node) по сценариям 20.3.

TST-02 Табличный тест прав: для каждой роли и каждого действия из матрицы 5.3 — ожидаемый результат; плюс отдельные случаи RBAC-07, RBAC-10…RBAC-14. Изменение матрицы без изменения теста — ошибка сборки.

TST-03 Тесты приглашений: одноразовость, истечение, отзыв, командная ссылка с лимитом, принятие существующим аккаунтом, повышение роли запрещено, лимиты частоты.

TST-04 Тесты доски: перенос между колонками меняет состояние; перенос в объединённую колонку ставит первое состояние; ранги без коллизий при 1000 перестановок; конфликт версий даёт 409.

TST-05 Тесты базы знаний: перенос поддерева, запрет цикла, наследование ограничения, конфликт версий, автосохранение черновика, ревизии и откат, импорт папки plibvi/kb (фикстура из 30 файлов) без дублей при повторном запуске.

TST-06 Тесты поиска: русская морфология («карусели» находит «карусель»), опечатка в заголовке, права в выдаче, точный ключ.

20.2. Целевые показатели

Показатель Цель Как измеряем
Список 50 задач с фильтрами, сервер ≤ 150 мс p95 тест с 5 000 задач
Доска на 300 карточек, сервер ≤ 300 мс p95 API-09
Доска на 300 карточек, первая отрисовка ≤ 1 с на ноутбуке, ≤ 2 с на телефоне Lighthouse, throttling 4G
Глобальный поиск ≤ 200 мс p95 на 10 000 документов SRCH-05
Открытие статьи 100 КБ ≤ 500 мс до текста e2e
SSE: от изменения до обновления у соседа ≤ 1 с e2e с двумя контекстами
Память стека ≤ 0,5 ГБ docker stats

20.3. Сценарии приёмки

Каждый сценарий проходится вручную на ноутбуке и телефоне перед выпуском версии и повторяется e2e-тестом.

  1. Владелец создан из консоли, входит, создаёт проект TAZ с иконкой; проект получил состояния, типы, доску «Канбан» и корневую статью.
  2. Владелец создаёт персональное приглашение «Участник в TAZ, Читатель в TEAM», копирует ссылку; в приватном окне по ссылке создаётся аккаунт Ивана; Иван видит TAZ и TEAM, не видит DUSHA; вторая попытка по той же ссылке — «ссылка уже использована».
  3. Владелец создаёт командную ссылку на 4 использования с ролью Репортёр; три студента регистрируются; четвёртый — после отзыва ссылки — получает «ссылка отозвана».
  4. Иван создаёт задачу клавишей c, вставляет скриншот из буфера, упоминает @Салам; Салам получает уведомление, открывает задачу из колокольчика.
  5. На доске Салам тянет карточку в «В работе» при лимите 2/2 — видит подтверждение; у Ивана в другом браузере карточка переезжает сама в течение секунды.
  6. Репортёр Ольга создаёт задачу — она попадает во «Входящие»; администратор принимает её кнопкой, задача оказывается в конце Беклога.
  7. Иван пишет статью из шаблона «Инструкция», вставляет таблицу и [[Бэкенд: архитектура]], публикует; в родительской статье появилась подстатья, в целевой — обратная ссылка; черновик, брошенный при закрытии вкладки, восстанавливается.
  8. Администратор ставит на статью «Договор» ограничение; Иван перестаёт видеть её и подстатьи в дереве и в поиске; автор статьи видит по-прежнему.
  9. Импорт plibvi/kb даёт дерево из разделов 00–90 с сохранёнными ссылками и картинками; повторный импорт ничего не дублирует.
  10. Поиск «карусели» находит TAZ-128; ввод taz-128 открывает задачу; поиск в проекте, где пользователя нет, ничего не показывает.
  11. Владелец деактивирует пользователя — тот вылетает из сессии немедленно; его комментарии остались с именем.
  12. Всё то же с телефона: нижняя навигация, доска лентой, перенос карточки долгим нажатием и через меню, редактор с кнопкой отправки.
  13. Бэкап и восстановление на чистом compose: все задачи, статьи, вложения и история на месте, поиск работает после reindex-search.

21. План разработки

Оценка — рабочие дни одного разработчика с Claude Code, при том что стек и части кода переносятся из nashpartner. Очереди выпускаются по одной; каждая заканчивается выкаткой на tracker.salam0nn.ru и проходом соответствующих сценариев 20.3.

Очередь Содержание Требования Дни
M0 Каркас и вход репозиторий, compose, nginx и сертификат, страница «Скоро», модели пользователей, вход и сессии, владелец из консоли, приглашения обоих видов, страница приглашения, админка пользователей, аудит AUTH, INV, RBAC-01…09, DEP, SEC 4
M1 Проекты и задачи проекты, участники и роли, состояния, типы, метки, задачи, список с фильтрами и группировкой, страница задачи, комментарии, вложения, связи, подзадачи, история, упоминания, уведомления в приложении, «Моё» DM, WF, ISS, LST, NTF, ACT 6
M2 Канбан доски, колонки, WIP, политики, дорожки, перетаскивание, ранги, быстрое создание, SSE и присутствие, телефон BRD, RT 4
M3 База знаний дерево, статья, редактор, черновики, ревизии и диффы, ограничение, шаблоны, избранное, обратные ссылки, ответственный и пересмотр, импорт и экспорт markdown, комментарии KB, ED 6
M4 Поиск и полировка глобальный поиск и подсказки, массовые действия, экспорт CSV, корзина, горячие клавиши, тёмная тема, пустые состояния, e2e, бэкап и восстановление, README SRCH, UI, TST, OPS 3
Итого v1 23
v1.1 текстовый запрос, персональные токены и MCP, Telegram-бот, жёсткие WIP-лимиты, клонирование, XLSX, сохранённые фильтры 6
v2 пользовательские поля (JSONB со схемой на проект), группы и видимость по группам, совместное редактирование, inline-комментарии, кросс-проектные доски по решению

Первый пригодный к работе результат — после M1 (10 дней): задачи и роли уже есть, база знаний временно остаётся в markdown. Если нужно раньше показать команде именно базу знаний, M3 можно поменять местами с M2: они независимы.

22. Открытые вопросы и допущения

Ни один вопрос не блокирует M0. Ответы уточняют M1–M3.

  1. Ключи и состав проектов. Предлагаются TAZ (tazzz.ru, auto-parser), DUSHA («По душе» / «По любви», plibvi), HORECA (nashpartner), MAX (хакатон), TEAM (команда). Подтвердить названия и кто в каждом администратор.
  2. Импорт plibvi/kb при запуске. База лежит вне git из-за договора и финансов. Предлагается импортировать целиком, а 10-project/ (договор, заказчик) и sources/ пометить «только администраторы». Подтвердить, что это достаточная защита, или оставить эти разделы вне трекера.
  3. Хакатонная команда. Нужен ли студентам доступ в MAX как Участникам (двигают карточки) или как Репортёрам; и удалять ли их аккаунты после финала 29 октября (предлагается деактивация).
  4. Мессенджер для уведомлений. Telegram-бот (у plibvi уже есть опыт) или MAX-бот (по мотивам хакатона). Влияет только на v1.1.
  5. Копия бэкапов вне сервера. Куда: Яндекс Object Storage (уже используется в auto-parser для фото) или на макмини по Tailscale. Без ответа бэкапы остаются на том же диске.
  6. Почтовый сервер. Если появится SMTP (например, для сброса пароля без участия Владельца), включаются письма-приглашения и подтверждение почты; в v1 не нужен.
  7. Ассистент как пользователь. Подтвердить, что бот-пользователь claude с ролью Участник во всех проектах и персональным токеном (v1.1) — приемлемо, и не нужен ли ему более узкий доступ.

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

23. Глоссарий

Словарь ляжет в CONTEXT.md репозитория в формате команды («термин, определение, чего избегать»).

Термин Определение Избегать
Инстанс Одна установка трекера со своими пользователями и проектами воркспейс, тенант
Проект Контейнер с ключом, участниками, состояниями, типами, метками, досками и деревом статей очередь, пространство
Ключ проекта 2–8 заглавных латинских букв и цифр, например TAZ код, префикс
Задача Единица работы в проекте с ключом TAZ-128 тикет, issue, таск
Подзадача Задача с родителем; своих подзадач не имеет сабтаск
Эпик Тип задачи, который объединяет другие задачи как родитель инициатива, фича
Состояние Положение задачи в рабочем процессе; у состояния есть категория статус, стадия
Категория состояния Одна из четырёх: не начато, в работе, готово, отменено резолюция
Нерешённая задача Задача в состоянии категории «не начато» или «в работе» открытая
Входящие Необязательное состояние для триажа внешних задач инбокс, интейк
Тип Задача, Ошибка, Улучшение, Эпик и добавленные в проекте вид, класс
Приоритет Критический, Высокий, Обычный, Низкий важность, severity
Исполнитель Пользователь, отвечающий за задачу сейчас ассайни, ответственный (это термин статьи)
Автор Кто создал задачу, комментарий или статью репортёр (это роль)
Наблюдатель Кто получает уведомления по задаче подписчик, вотчер
Метка Цветной ярлык проекта на задаче или статье тег, лейбл
Связь Направленное отношение между задачами: связана, блокирует, дублирует линк
Доска Представление задач проекта колонками по состояниям борд, канбан (это метод)
Колонка Одно или несколько состояний на доске с лимитом и политикой статус доски
Лимит WIP Минимум и максимум карточек в колонке ограничение
Политика колонки Короткое правило «что значит, что задача здесь» definition of done (шире)
Дорожка Горизонтальное деление доски: по исполнителю, приоритету, типу, эпику свимлейн
Карточка Задача на доске тикет
Ранг Ручной порядок карточек в колонке позиция, вес
Статья Документ базы знаний с ключом TAZ-A-7 страница, документ, заметка
Подстатья Статья с родителем; дерево без ограничения глубины дочерняя страница, папка
Корневая статья Статья-индекс проекта, одна точка входа главная
Черновик Несохранённые правки статьи или неопубликованная статья драфт
Ревизия Сохранённая версия статьи версия (у задач — version для блокировки)
Ответственный Человек, отвечающий за актуальность статьи владелец (это глобальная роль)
Дата пересмотра Срок, после которого статья помечается «пора пересмотреть» дедлайн
Ограниченная статья Статья «только для администраторов проекта» и автора приватная, скрытая
Шаблон Заготовка статьи из проекта TEAM темплейт
Роль Владелец, Пользователь (глобально); Администратор проекта, Участник, Репортёр, Читатель (в проекте) группа, уровень доступа
Право Код действия из матрицы 5.3, например issue.state пермишен
Приглашение Ссылка с токеном, ролью и проектами; персональная или командная инвайт, регистрация
Сессия Вход пользователя в браузере, cookie плюс запись в Redis токен (это про API)
Токен API Персональный ключ для скриптов и ассистента (v1.1) API key
Активность История изменений объекта и лента проекта лог (это про аудит)
Аудит Журнал действий безопасности Владельца активность

24. Источники

Документация YouTrack (JetBrains), проверено 9 сентября 2026:

Практики:

Свои материалы: Анатомия трекера и базы знаний (8 сентября), plibvi/kb/README.md, nashpartner (стек и деплой), /etc/nginx/sites-available/llm.salam0nn.ru (шаблон vhost), память о падениях панели от чужого pkill.