110 KiB
web-app Specification
Purpose
Веб-приложение хаба (web/, React + TypeScript + Vite) под префиксом /app/: вход по клиентскому токену, доска связей (перенос links.html) и страницы задач со служебными заметками; сборка в src/sdlc_hub/web/app/.
Requirements
Requirement: Вход по токену
Приложение SHALL работать с API хаба того же хоста под клиентским токеном: хранить его в localStorage под ключом
sdlc-hub-token (и в памяти, если хранилище закрыто) и передавать в заголовке Authorization: Bearer каждого запроса.
Если токена нет или любой запрос ответил 401, приложение SHALL показывать экран входа вместо страниц: поле токена с
подсказкой uv run sdlc-hub tokens, без панелей страниц и без фоновых запросов, которые могли бы изменить поле ввода.
Введённый токен SHALL проверяться запросом GET /settings и сохраняться только при успехе. Действие «Сменить токен»
SHALL очищать токен и показывать экран входа. Ответ 401 SHALL выводить на экран входа, только если запрос ушёл с текущим токеном: запоздавший 401 на
запрос с прежним токеном после входа с новым не учитывается. Если проверка токена получила ответ 4xx, кроме 401,
экран входа SHALL показывать текст ошибки хаба, а не «хаб не отвечает».
Ключи localStorage приложения с префиксом sdlc-hub- (sdlc-hub-token, sdlc-hub-sidebar, sdlc-hub-legend) SHALL
при первом чтении, если ключа нет, брать значение одноимённого ключа с префиксом jira-hub-, записывать его под новым
ключом и удалять прежний.
Scenario: Первый вход
- WHEN приложение открыто впервые, токена нет
- THEN показан экран входа; после ввода верного токена открывается запрошенная страница, токен сохранён
Scenario: Устаревший токен
- WHEN сохранённый токен не подходит и запрос ответил 401
- THEN показан экран входа с пояснением, что токен не подошёл; панели доски и задач не видны
Scenario: Токен от прежней доски
- WHEN в
localStorageнетsdlc-hub-token, но естьjira-hub-token(сохранён до переименования хаба) - THEN приложение входит с ним без запроса токена; значение перенесено в
sdlc-hub-token,jira-hub-tokenудалён
Scenario: Запоздавший 401 от прежнего токена
- WHEN запрос ушёл с прежним токеном, пользователь вошёл с новым, и после этого запрос ответил 401
- THEN приложение остаётся на странице, новый токен сохранён
Scenario: Хаб отверг проверку токена
- WHEN проверка
GET /settingsответила 400 сdetail - THEN экран входа показывает текст
detail, а не «хаб не отвечает»
Requirement: Оболочка и адреса
Приложение SHALL открываться под префиксом /app/. Главная страница — «Обзор» ядра (/app/?project=<метка>,
требование «Обзор проекта»). Страницы модулей SHALL лежать под префиксом имени модуля: модуль jira — /app/jira/links
(«Связи» — доска связей), /app/jira/backlog («Бэклог»), /app/jira/sprint («Спринт»), /app/jira/kanban («Kanban»;
три последних — требование «Спринты и бэклог»), /app/jira/issues (список задач), /app/jira/feed (лента изменений, требование «Лента изменений») и
/app/jira/issues/{key} (карточка задачи — окно поверх страницы, требование «Карточка задачи»); модуль wiki —
/app/wiki (требование «Вики проекта»). Прежние адреса /app/board и /app/jira/board (на /app/jira/links),
/app/issues, /app/issues/{key}, /app/feed SHALL перенаправлять на новые с сохранением строки запроса, без новой записи истории. Неизвестный путь — страница «не
найдено» со ссылками на «Обзор» и разделы модулей.
Боковая панель SHALL быть закреплена у левого края окна на всю его высоту и MUST NOT прокручиваться вместе со
страницей; если её содержимое не помещается по высоте, прокручивается сама панель. Сверху вниз панель SHALL содержать:
название «SDLC Hub» и кнопку «Свернуть панель»; выпадающий список проектов («<метка> — <название>», текущий отмечен, в
конце «+ Новый проект…»; требование «Проект в приложении»); пункт «Обзор»; разделы модулей группами — заголовок группы
— название модуля («Jira», «Вики»), под ним ссылки разделов модуля со значками (текущий отмечен
aria-current="page"); внизу — блок состояния модулей (требование «Состояние и обновление модулей»), переключатель
темы (требование «Тема») и меню с действием «Сменить токен». Свёрнутая панель SHALL быть узкой полосой с теми же
действиями значками: «Развернуть панель», метка текущего проекта (полностью — в подсказке), «Обзор», разделы
(группы разделены линией), по значку состояния на модуль, тема, меню; названия — в подсказках и aria-label. Свёртка
SHALL храниться в localStorage под ключом sdlc-hub-sidebar (collapsed; без записи — развёрнута).
На экранах уже 640 px панели в потоке нет: сверху SHALL быть закреплена строка с кнопкой «Меню», меткой текущего проекта и сводной точкой состояния модулей (ошибка, если она есть у какого-нибудь модуля; иначе — пакет в работе; иначе — все агенты на связи или нет), которая открывает панель. «Меню» открывает развёрнутую панель поверх страницы модальным окном: фокус переходит в панель и остаётся в ней, страница под ней недоступна; панель закрывается выбором раздела, клавишей Escape (если не открыто вложенное меню — тогда Escape закрывает только его) или щелчком мимо неё; фокус возвращается на «Меню». Горизонтальной прокрутки страницы на любой ширине нет.
Заголовок вкладки браузера SHALL быть «<название страницы> · <метка проекта> · SDLC Hub» (без проекта — без средней части); название страницы даёт оболочка («Обзор») или модуль. Неожиданная ошибка отрисовки страницы SHALL показываться сообщением с кнопкой «Перезагрузить», не пустым экраном.
Scenario: Прежний адрес доски связей
- WHEN открыт
/app/jira/board?project=AWPCA - THEN адрес —
/app/jira/links?project=AWPCA, показана доска связей, в меню отмечен пункт «Связи», заголовок вкладки — «Связи · AWPCA · SDLC Hub»
Scenario: Переход между разделами
- WHEN на доске в боковой панели выбран раздел «Задачи» группы «Jira»
- THEN адрес —
/app/jira/issuesс текущим проектом, показан список задач; «назад» возвращает на доску
Scenario: Раздел «Лента»
- WHEN на доске в боковой панели выбран раздел «Лента»
- THEN адрес —
/app/jira/feedс текущим проектом, показана лента изменений за сегодня; раздел отмечен в панели
Scenario: Сменить токен
- WHEN в меню боковой панели выбрано «Сменить токен»
- THEN токен очищен, показан экран входа
Scenario: Панель на месте при прокрутке
- WHEN доска прокручена до самого низа
- THEN боковая панель видна целиком на прежнем месте: разделы и блок состояния модулей доступны без прокрутки вверх
Scenario: Свёрнутая панель
- WHEN нажата «Свернуть панель», страница перезагружена
- THEN панель свёрнута в полосу значков, доска шире; значок «Задачи» открывает список задач; видны два значка состояния — Jira и Вики; «Развернуть панель» возвращает полную панель
Scenario: Узкий экран
- WHEN ширина окна 390 px, нажата «Меню», в панели выбран раздел «Задачи»
- THEN панель открылась поверх страницы, фокус в панели; после выбора панель закрыта, фокус на «Меню», показан список задач; горизонтальной прокрутки нет
Scenario: Прежний адрес
- WHEN открыт
/app/issues/AURORA-1536?project=AWPCA - THEN адрес заменён на
/app/jira/issues/AURORA-1536?project=AWPCA, карточка открыта поверх списка задач
Scenario: Заголовок вкладки
- WHEN открыта страница «Вики» проекта
AWPCA - THEN заголовок вкладки — «Вики · AWPCA · SDLC Hub»
Requirement: Ошибки хаба
Приложение SHALL различать ошибки запросов к хабу: 401 — экран входа; хаб не отвечает (сеть, 5xx) — сообщение
«хаб не отвечает» с возможностью повторить, ранее загруженные данные остаются на экране; прочие 4xx — текст detail
ответа рядом с действием, которое его вызвало.
Scenario: Хаб остановлен
- WHEN доска показана, хаб остановлен, нажато «Перечитать»
- THEN на доске прежние данные и сообщение «хаб не отвечает — показаны прежние данные»
Requirement: Справочники из настроек хаба
Приложение SHALL брать перевод меток видов работ (kind_labels) и типов связей Jira (link_types) из GET /settings
и набор закрытых статусов Jira (closed_statuses) из GET /settings
и не содержать собственных копий этих словарей. Если нужного словаря в настройках нет, страница SHALL сообщать, какого
словаря не хватает, вместо пустой доски или списка без подписей; исключение — closed_statuses: без него ни одна
задача не считается закрытой, сообщения нет.
Scenario: Новый тип связи в настройках
- WHEN в
link_typesнастроек добавлен тип и в зеркале есть связь этого типа - THEN после перезагрузки страницы доска показывает связь этого типа без изменения кода приложения
Requirement: Доска связей
Страница /app/board SHALL воспроизводить поведение доски связей хаба (links.html ревизии 3931a88) без изменений,
кроме перечисленных в этом требовании и в требовании «Источник данных доски»: построение модели из GET /board, CSV
фильтра Jira и JSON доски (arm-ka-links/1); дорожки по эпикам со свёрткой и перестановкой, служебные дорожки внизу;
карточки с перетаскиванием, растяжкой, размером по содержимому и привязкой к сетке; стрелки связей с выравниванием;
фильтры по спринтам и исполнителям; легенда со скрытием видов работ и типов связей; «соседи по связям», «только
передачи», «скрыть без связей», «критический путь», «по датам» с колонками дат и их растяжкой; ящик «Проблемы»; таблица
связей; экспорт и импорт JSON; «Сбросить раскладку». Раскладка, дорожки и фильтры SHALL храниться в
localStorage под теми же ключами graph-<имя>…, что у links.html, с той же миграцией старых ключей.
Результат построения модели, видимой выборки, критического пути и проблем SHALL совпадать с результатом links.html
на одних и тех же данных и фильтрах.
Шапка доски (закреплена при прокрутке) SHALL содержать заголовок «Доска связей», счётчики (задачи из всех, соседи по
связям, связи из всех, скрытые без связей), пометку источника, если на доске снимок из файла (с действием вернуться к
данным хаба), событие доски, кнопку «Проблемы» со счётчиком и меню с действиями «Перечитать», «Загрузить файл…»,
«Экспорт JSON», «Таблица связей», «Привязка к сетке» и «Сбросить раскладку» (с подтверждением). Под шапкой — панель
управления: мультиселекты «Спринты» и «Исполнители», переключатели группы «Показать» («Соседи по связям», «Только
передачи», «Скрыть без связей») и группы «Режим» («Критический путь», «По датам»), каждый с aria-pressed и прежней
подсказкой. Под панелью — легенда, которая сворачивается кнопкой «Легенда»; свёрнутая показывает число скрытых видов
работ и типов связей; состояние свёртки хранится в localStorage под ключом sdlc-hub-legend. «Обновить из Jira»,
тема и «Сменить токен» — в боковой панели приложения, на доске их нет.
У карточки задачи (не у заглушки) SHALL быть значок «Открыть карточку»,
открывающий /app/issues/{key} окном поверх доски; ключ карточки по-прежнему открывает задачу в Jira. Пока окно открыто
и после его закрытия доска SHALL сохранять прокрутку, раскладку, фильтры и строку состояния. Ещё два отличия от links.html (R-10, R-18): двойной щелчок по уголку
карточки меряет высоту по содержимому при новой ширине, а не при прежней; указатель при перетаскивании и растяжке
захватывается на нажатом элементе, а не на карточке, поэтому двойной щелчок по уголку сбрасывает размер. Загрузка файла CSV/JSON доступна только после входа, с экрана входа её нет.
Карточка задачи, статус Jira которой входит в closed_statuses, SHALL быть приглушена (полупрозрачна), под
указателем и с фокусом клавиатуры внутри карточки — в полную яркость; стрелка связи, у которой хотя бы один конец — такая задача, SHALL быть приглушена так же.
Приглушение — только вид: модель, видимая выборка, критический путь, проблемы, раскладка и экспорт JSON от него не
зависят; переключателя нет; правило одно для данных хаба, CSV и JSON.
При возврате на /app/board с другой страницы приложения в той же вкладке доска SHALL восстанавливать прежнюю
прокрутку; после перезагрузки страницы доска открывается сверху.
Дорожки SHALL разделяться линией; фон развёрнутых дорожек один, без чередования, чтобы подсветка колонок «по датам»
(прошедшие даты, сегодня) была видна на всех дорожках; свёрнутая дорожка выделяется фоном. У карточки задачи, статус
Jira которой не входит в closed_statuses, с плановой датой окончания раньше сегодняшней дата «до …» SHALL быть цветом
ошибки, с подсказкой и текстом для экранного чтения «просрочено»; это только вид, модель и раскладка от него не зависят.
Scenario: Раскладка переживает переход
- WHEN раскладка карточек сохранена страницей
links.html, открыта/app/boardс теми же данными - THEN карточки стоят на тех же местах, дорожки в том же порядке и свёртке, фильтры те же
Scenario: Совпадение со старой доской
- WHEN на одной фикстуре
GET /boardи одном наборе фильтров построены модель, видимая выборка, критический путь и проблемы старой страницей и приложением - THEN результаты совпадают
Scenario: Открыть карточку
- WHEN на карточке AURORA-1620 нажат значок «Открыть карточку»
- THEN адрес
/app/issues/AURORA-1620, карточка задачи открыта окном, под ним доска
Scenario: Возврат с карточки
- WHEN доска прокручена вниз, нажат значок «Открыть карточку», затем окно закрыто
- THEN адрес
/app/board, доска показана с той же прокруткой и раскладкой, без повторной загрузкиGET /board
Scenario: Возврат из другого раздела
- WHEN доска прокручена вниз, выбран раздел «Задачи», затем раздел «Доска связей»
- THEN доска показана с той же прокруткой
Scenario: Закрытые на доске приглушены
- WHEN на доске AURORA-1620 «В работе», AURORA-1621 «Готово», AURORA-1622 «Снято», связь AURORA-1620 → AURORA-1621
- THEN карточки AURORA-1621 и AURORA-1622 и стрелка AURORA-1620 → AURORA-1621 приглушены, AURORA-1620 — нет; под указателем AURORA-1621 — в полную яркость; критический путь и ящик «Проблемы» те же, что без приглушения
Scenario: Свёрнутая легенда
- WHEN в легенде скрыт вид работы «тестирование», легенда свёрнута, страница перезагружена
- THEN легенда свёрнута, рядом с кнопкой «Легенда» — «скрыто: 1»; карточек вида «тестирование» на доске нет
Scenario: Просрочка на доске
- WHEN сегодня 22.10, у AURORA-1620 статус «В работе» и срок 20.10, у AURORA-1621 «Готово» и срок 20.10, включено «По датам»
- THEN дата AURORA-1620 — цветом ошибки с пометкой «просрочено», у AURORA-1621 — обычная; подсветка колонки 20.10 видна на всех дорожках, дорожки одного фона
Requirement: Источник данных доски
Доска SHALL хранить явный источник данных — хаб или файл. Пока показан снимок из файла (CSV или JSON-снимок), ответы
хаба SHALL его не заменять; «Перечитать» SHALL возвращать источник «хаб» и загружать GET /board заново без обращения к
Jira. Импорт JSON в режиме хаба SHALL применять раскладку, дорожки и фильтры и перечитывать хаб. Шапка доски SHALL
показывать источник данных (пометка «из файла» с именем файла; данные хаба пометки не имеют) и одно текущее событие
доски (загрузка; хаб не отвечает — прежние данные, а при снимке из файла — «на доске снимок из файла»; ошибка разбора
файла — «файл не разобран: <причина>»; раскладка и фильтры из JSON применены; ошибка); новое событие заменяет
предыдущее целиком. События пакета обновления из Jira показывает боковая панель приложения (требование «Обновление из
Jira»), не шапка доски.
Scenario: Поздний ответ хаба не затирает файл
- WHEN запрос
GET /boardв пути, и в это время загружен CSV - THEN после ответа хаба на доске по-прежнему данные CSV с пометкой «из файла»
Scenario: Пакет завершился при снимке из файла
- WHEN на доске снимок из CSV и завершился пакет обновления из Jira
- THEN на доске по-прежнему данные CSV с пометкой «из файла»; после «Перечитать» — данные хаба
Scenario: Хаб недоступен при снимке из файла
- WHEN хаб не отвечает и загружен JSON-снимок доски
- THEN шапка показывает «хаб не отвечает — на доске снимок из файла», а не «показаны прежние данные»
Scenario: Нет второго полного прогона
- WHEN нажата «Обновить из Jira» и, пока пакет выполняется, доска перерисована (фильтр, «Перечитать»)
- THEN кнопка «Обновить из Jira» в боковой панели остаётся недоступной, в хабе один пакет, его опрашивает один опрос
Scenario: Пакет пропал
- WHEN опрос пакета получил 404, на доске данные хаба
- THEN доска показывает прежние данные без своего события об ошибке; сообщение о пропавшем пакете — в боковой панели
Scenario: Пакет после «Перечитать»
- WHEN пакет
viewerпоставлен с другой вкладки и не завершён, на доске нажато «Перечитать» - THEN боковая панель показывает «пакет #N выполняется», по завершении доска перечитывает данные
Scenario: «Перечитать» во время пакета
- WHEN пакет
viewerвыполняется и на доске нажато «Перечитать» - THEN боковая панель по-прежнему показывает «пакет #N выполняется», шапка доски — без события пакета
Scenario: Чужой пакет не подхватывается
- WHEN незавершённый пакет автора
viewerсостоит из командensure_fields, открыта доска - THEN доска и боковая панель его не опрашивают, кнопка «Обновить из Jira» доступна
Requirement: Список задач
Страница /app/issues SHALL показывать задачи текущего проекта из GET /issues?project=<текущий> таблицей: ключ
(открывает карточку задачи окном поверх списка) с пометкой «сосед» (относительно проекта), тема, статус Jira,
исполнитель, срок (planned_end, иначе duedate; просроченный срок незакрытой задачи выделен), спринт, эпик (имя
эпика, иначе ключ), вид работы, тип; статуса по факту в списке нет. Щелчок по строке (кроме ссылок и выделения текста)
SHALL открывать карточку задачи так же, как ключ. Шапка страницы SHALL показывать число показанных задач.
Фильтры SHALL соответствовать параметрам GET /issues (спринт, исполнитель, вид работы, статус Jira, метка, эпик,
только соседи, поиск по ключу или теме, скрыть закрытые), храниться в query-строке адреса вместе с project и
применяться сервером. На виду SHALL быть поиск, переключатель «Открытые | Все», переключатель «Только соседи» и
кнопка «Фильтры» с числом выбранных выпадающих списков; шесть выпадающих списков (спринт, исполнитель, вид работы,
статус Jira, метка, эпик) — в панели, которая раскрывается этой кнопкой; каждый выбранный список SHALL показываться
снимаемой плашкой «<подпись>: <значение>» и под строкой фильтров должно быть действие «Сбросить», снимающее все шесть
списков. Варианты выпадающих списков SHALL браться из снимка GET /board?project=<текущий>; значение фильтра из
адреса, которого нет среди вариантов, SHALL показываться в списке с пометкой «(нет на доске)». Положение «Открытые»
SHALL быть по умолчанию (в адресе ничего), «Все» — closed=1 в адресе; «Открытые» — запрос с hide_closed=1,
кроме случая, когда в фильтре «Статус Jira» выбран статус из closed_statuses. Параметр fact_status в адресе
игнорируется. Задачи SHALL загружаться порциями по 50 строк; «Показать ещё» SHALL быть доступна, пока последняя
порция полная.
Scenario: Фильтр в адресе
- WHEN выбран спринт «AWPCA_Спринт 2» и исполнитель
- THEN адрес содержит
project=,sprint=иassignee=, таблица — результатGET /issuesс этими параметрами; открытие этого адреса в новой вкладке показывает ту же выборку, кнопка «Фильтры» показывает 2, видны плашки спринта и исполнителя
Scenario: Снять фильтр плашкой
- WHEN выбраны спринт и исполнитель, нажат ✕ на плашке спринта
- THEN в адресе нет
sprint=,assignee=остался, таблица перезапрошена
Scenario: Конец списка
- WHEN вторая порция вернула 17 задач
- THEN в таблице 67 задач, «Показать ещё» не показана
Scenario: Фильтр по закрытому спринту
- WHEN открыт адрес
/app/issues?project=AWPCA&sprint=AWPCA_Спринт 0, а в снимке доски такого спринта нет - THEN выпадающий список «Спринт» показывает «AWPCA_Спринт 0 (нет на доске)» выбранным, таблица — по этому фильтру
Scenario: Карточка поверх списка
- WHEN список отфильтрован по спринту, прокручен и догружен «Показать ещё», открыта карточка задачи и окно закрыто
- THEN адрес списка с тем же фильтром, в таблице те же строки, прокрутка та же
Scenario: Щелчок по строке
- WHEN в таблице щёлкнута ячейка темы строки AURORA-1620
- THEN адрес
/app/issues/AURORA-1620, карточка открыта окном поверх списка
Scenario: Закрытые скрыты по умолчанию
- WHEN открыт
/app/issues, в зеркале задачи в статусах «В работе», «Готово», «Снято» - THEN выбрано «Открытые», запрос
GET /issuesсhide_closed=1, в таблице только «В работе»; после выбора «Все» адрес содержитclosed=1, в таблице все три
Scenario: Выбран закрытый статус
- WHEN выбрано «Открытые» и в «Статус Jira» выбрано «Готово»
- THEN запрос
GET /issues?status=Готовобезhide_closed, в таблице задачи в статусе «Готово»
Requirement: Карточка задачи
Адрес /app/issues/{key} SHALL открывать карточку задачи из GET /issues/{key} модальным окном поверх страницы, с
которой её открыли (список «Задачи» или доска); страница под окном SHALL оставаться смонтированной и сохранять фильтры,
загруженные строки, прокрутку и раскладку. Перезагрузка страницы с открытым окном SHALL сохранять страницу под окном. Открытый напрямую
адрес (новая вкладка, закладка) SHALL
показывать окно поверх списка задач без фильтров. Окно SHALL закрываться кнопкой ✕, клавишей Esc и щелчком по фону (нажатие и отпускание — на фоне; выделение текста,
отпущенное за краем окна, окно не закрывает) и
возвращать на страницу, с которой его открыли, даже после переходов по связям внутри окна; «назад» браузера после
закрытия MUST NOT открывать карточку снова. Переход по связанной задаче SHALL менять содержимое окна и адрес, «назад»
браузера — возвращать к прежней карточке. Пока окно открыто, фокус SHALL оставаться в окне, страница под ним не
прокручивается.
Содержимое окна:
- шапка: ключ, статус Jira, «Открыть в Jira», ✕, тема; строка
«исполнитель · срок · спринт · эпик» без пустых частей, срок —
planned_end, иначеduedate, с пометкой «просрочено», если он раньше сегодняшней даты и статус Jira не входит вclosed_statuses; эпик — темой (epic_summary), иначе ключом; - «Связи» по группам в порядке: «Зависит от» (входящие связи логических типов
блокирует началоиблокирует завершениеизlink_types, с меткой «начало» / «завершение»), «Блокирует» (исходящие тех же типов, с теми же метками), «Связана с» (типсвязанныев обе стороны), «Прочие» (тип Jira внеlink_types: имя типа и направление «→» / «←»); в строке — ключ (открывает связанную задачу в окне), тема и статус Jira, а если задачи нет в зеркале — ключ и «нет в зеркале»; пустые группы не показываются, без связей — «связей нет»; - «Описание» — текст, отрендеренный по требованию «Разметка Jira в карточке», с кнопкой «исходник» / «текст» в заголовке раздела; длиннее 8 строк — свёрнуто с кнопкой «развернуть» / «свернуть» в обоих режимах; без описания раздела нет;
- «Вложения (N)» — новые сверху: значок по типу файла (картинка, таблица, PDF, прочее), имя, размер, «автор · дата» и кнопка скачивания (требование «Скачивание вложений в карточке»); без вложений раздела нет;
- «Комментарии (N)» — новые сверху, автор, время, кнопка «исходник» / «текст» у каждого комментария, текст,
отрендеренный по требованию «Разметка Jira в карточке», под текстом — вложения, на которые ссылается комментарий
(
attachment_ids): строка «плашек» с именем и размером, щелчок — скачивание; кнопка «Скрыть» / «Показать» сворачивает раздел, выбор SHALL запоминаться в браузере для всех карточек; без комментариев — «комментариев нет»; - «Служебные заметки» (требование «Служебные заметки»);
- «Прочие поля» — непустые из: тип, приоритет, автор, метки, плановое начало, обновлена в Jira, сосед по связям;
- «Команды моста (N)» — 10 последних команд по задаче (id, пакет, тип, статус, время завершения, ошибка), раздел свёрнут при открытии; если команд не было — текст «команд по задаче не было». На ширине от 900 px «Связи», «Описание», «Вложения», «Комментарии» — в левой колонке, заметки, прочие поля и команды — в правой; уже — одна колонка: заметки, связи, описание, вложения, комментарии, прочие поля, команды. Если задачи нет в зеркале (404), окно SHALL сообщать об этом с кнопкой «Закрыть»; хаб не отвечает — сообщение и «Повторить», ранее загруженные данные остаются.
Scenario: Связанная задача
- WHEN у AURORA-1620 исходящая связь
ASUPR_Блокер_Началана AURORA-1621 «Расчёт свода» в статусе «В работе» - THEN в карточке в группе «Блокирует» строка «начало · AURORA-1621 · Расчёт свода · В работе»; щелчок по ключу
меняет содержимое окна на AURORA-1621, адрес —
/app/issues/AURORA-1621; «назад» браузера возвращает AURORA-1620
Scenario: Входящий блокер
- WHEN у AURORA-1620 входящая связь
ASUPR_Блокер_Завершенияот AURORA-1500 - THEN строка с AURORA-1500 и меткой «завершение» — в группе «Зависит от»
Scenario: Задача без команд
- WHEN по AURORA-1620 не было команд
- THEN в разделе «Команды моста» — текст «команд по задаче не было», таблицы нет
Scenario: Закрытие после переходов
- WHEN со списка «Задачи» открыта AURORA-1620, в окне — переход по связи на AURORA-1621, нажата Esc
- THEN окно закрыто, адрес — адрес списка с прежними фильтрами; «назад» браузера не открывает карточку
Scenario: Прямой адрес
- WHEN в новой вкладке открыт
/app/issues/AURORA-1620, затем нажат ✕ - THEN под окном был список задач без фильтров; после закрытия адрес
/app/issues, «назад» не открывает карточку
Scenario: Скрытые комментарии
- WHEN в карточке нажато «Скрыть» у комментариев, затем страница перезагружена и открыта другая карточка
- THEN комментарии в ней скрыты, видна кнопка «Показать» и число комментариев
Scenario: Просрочка закрытой задачи
- WHEN у AURORA-1620 срок 2026-10-01, сегодня 2026-10-17, статус Jira «Готово»
- THEN в строке под темой «срок 01.10» без пометки «просрочено»; при статусе «В работе» — с пометкой
Requirement: Служебные заметки
Карточка задачи SHALL показывать служебные заметки в режиме чтения: «Вид работы», «Сделано»,
«Осталось», «Заметка владельца» (пустые — «—») и время и автор последнего изменения; кнопка «Править» SHALL открывать
форму: «вид работы» — выбор из значений kind_labels настроек (и текущего значения, если его там нет, и пустого),
«сделано», «осталось», «заметка владельца» —
многострочный текст; «Отмена» SHALL возвращать режим чтения, при несохранённых правках — после подтверждения.
Сохранение SHALL отправлять PUT /issues/{key}/note только с изменёнными полями и updated_by: "web"; очищенное поле
отправляется как null. После успеха SHALL показываться режим чтения с сохранённой заметкой; список задач под окном SHALL
показывать новый «вид работы» без «Перечитать», доска под окном SHALL перечитать снимок
(GET /board) без потери раскладки и прокрутки; при 422 — текст ошибки у формы без
потери введённого. Закрытие окна, переход по связи и уход в другой раздел с несохранёнными правками SHALL требовать
подтверждения. Статуса по факту в заметках нет. Заметки пишутся только в хаб, в Jira приложение ничего не отправляет.
Scenario: Только изменённые поля
- WHEN в заметке нажато «Править», изменено только «осталось» и нажато «Сохранить»
- THEN тело
PUT /issues/{key}/note—{rest_text: "…", updated_by: "web"}; карточка снова в режиме чтения с новым «Осталось»
Scenario: Уход с несохранёнными правками
- WHEN в форме заметки есть несохранённая правка и нажата Esc
- THEN приложение спрашивает подтверждение; при отказе окно открыто, форма с введённым текстом
Scenario: Заметка видна на доске
- WHEN карточка AURORA-9002 открыта с доски, вид работы изменён на «тестирование» и сохранён
- THEN доска под окном остаётся на месте и перезапрашивается (
GET /boardотдаётwork_kind«тестирование»); окно закрывается без потери раскладки и прокрутки доски
Requirement: Сборка и разработка
Приложение SHALL быть npm-проектом в web/ (React, TypeScript, Vite) с точными версиями зависимостей и
package-lock.json. make web SHALL устанавливать зависимости (npm ci) и собирать приложение в
src/sdlc_hub/web/app/; каталог сборки не хранится в git. Шрифты (Mulish — текст интерфейса, IBM Plex Mono — ключи
задач и исходники), иконки и прочие ресурсы страницы SHALL входить в сборку: приложение не обращается к внешним
адресам. make check SHALL запускать uv run pytest, проверку типов и линтер фронта и тесты фронта; зелёным считается
только весь набор. npm run dev SHALL поднимать сервер разработки с прокси маршрутов API на хаб из SDLC_HUB_URL (по
умолчанию тестовый http://127.0.0.1:8791).
Scenario: Сборка с нуля
- WHEN в чистом checkout выполнен
make web - THEN в
src/sdlc_hub/web/app/естьindex.htmlиassets/;git statusне показывает файлов сборки
Scenario: Без интернета
- WHEN приложение открыто на машине без доступа в интернет
- THEN страница не запрашивает ресурсов вне хаба, текст набран Mulish, ключи задач — IBM Plex Mono
Requirement: Разметка Jira в карточке
Описание задачи и текст каждого комментария в карточке SHALL показываться отрендеренной вики-разметкой Jira (хаб хранит их как есть, в wiki) так, как их показывает Jira. Разбираются:
- блоки: заголовки
h1.–h6.в начале строки; списки — строки, начинающиеся (после необязательных пробелов) с маркеров*,#и пробела (уровень вложенности — числом маркеров,*— маркированный,#— нумерованный, смешанная вложенность*#— по последнему маркеру) или с одного-и пробела (маркированный, первый уровень);--,*-и прочие сочетания с-— не список; таблицы — строки, начинающиеся с|, ячейки||заголовок||и|ячейка|;{code}/{code:язык}…{code}и{noformat}…{noformat}— моноширинный блок без разбора разметки внутри;{quote}…{quote}и строкаbq.— цитата; открывающий тег блока — в начале строки, закрывающий — в любом месте той же или последующей строки, текст после закрывающего тега разбирается дальше как обычные строки; тег не в начале строки — текст как есть; строка----— горизонтальная черта; прочие строки — абзацы, разделённые пустыми строками, одиночный перенос внутри абзаца — перенос строки; - внутри строки:
*жирный*,_курсив_,-зачёркнутый-,+подчёркнутый+,^верхний^,~нижний~,{{моно}}({{…}}— без разбора внутри); маркер выделения действует, только если открывающий стоит в начале текста или после пробела либо знака препинания и за ним не пробел и не тот же символ (**x**— текст как есть), а закрывающий стоит после не-пробела и за ним не буква и не цифра (data_type,2026-10-15,счёт-фактура,a*b*c— обычный текст);\\— перенос строки;\перед символом разметки — сам символ; - ссылки:
[текст|адрес],[адрес]и голые адресаhttp://,https://— ссылка в новой вкладке, только для схемhttp,https,mailto, иначе текст как есть;[ключ задачи],[текст|ключ задачи]и ключ задачи в тексте видаПРОЕКТ-123, гдеПРОЕКТ—project_keyиз настроек хаба, — ссылка, открывающая карточку этой задачи в окне (UTF-8,SHA-256и ключи других проектов — текст);[~логин]— «@логин»; - ссылки на вложения задачи —
!имя!,!имя|параметры!,[^имя],[текст|^имя]с тем же разбором имени, что у хаба (hub-api, «Ссылки на вложения в тексте»), имя есть среди вложений задачи: ссылка-кнопка со значком скрепки и текстом — именем файла (у[текст|^имя]— текстом), щелчок — скачивание; картинка в тексте не показывается; имени нет среди вложений — исходник как есть. Всё, что разборщик не распознал ({panel},{color},!картинка!и[^имя]без такого вложения у задачи, незакрытые маркеры и блоки и прочее), SHALL показываться как текст исходника без потерь. Разметка MUST NOT превращаться в HTML строкой (innerHTML,dangerouslySetInnerHTML): текст задачи не может добавить на страницу элементы, атрибуты или скрипты. Если рендер блока завершился ошибкой, блок SHALL показываться исходником.
Кнопка «исходник» у описания и у каждого комментария SHALL переключать этот блок на исходник — текст с сохранёнными переносами строк, как без разбора разметки, — и обратно кнопкой «текст»; остальные блоки не переключаются. При каждом открытии карточки (в том числе при переходе по связи) все блоки показываются отрендеренными, выбор не запоминается.
Scenario: Описание со структурой
- WHEN описание AURORA-1620 —
h3. Контекст, строкаШаги:, затем# Загрузить ОСВи# Открыть документ, затем пустая строка и*Итог:* ошибка в {{data_type}} - THEN в «Описании» заголовок «Контекст», абзац «Шаги:», нумерованный список из двух пунктов и абзац, где «Итог:» —
жирным, «data_type» — моноширинным; символов
h3.,#,*,{{не видно
Scenario: Переключение на исходник
- WHEN в карточке AURORA-1620 у описания нажато «исходник»
- THEN описание показано исходником с переносами строк (видны
h3. Контексти# Загрузить ОСВ), кнопка — «текст»; комментарии остаются отрендеренными; после «текст» описание снова отрендерено
Scenario: Исходник у одного комментария
- WHEN у второго комментария нажато «исходник», затем открыта связанная задача и возврат «назад»
- THEN сразу после нажатия исходником показан только второй комментарий; после возврата все комментарии отрендерены
Scenario: Ключ задачи в тексте
- WHEN комментарий —
П. 1 ведётся в AURORA-1445; AURORA-1645 снята как дубль. - THEN «AURORA-1445» и «AURORA-1645» — ссылки; щелчок по первой меняет содержимое окна на AURORA-1445
Scenario: Неизвестная разметка и опасный адрес
- WHEN описание —
{color:red}важно{color},!схема.png!и[нажми|javascript:alert(1)] - THEN все три показаны текстом исходника как есть, ссылки и картинки нет, скрипт не выполняется
Scenario: Списки с отступом и пустыми строками
- WHEN описание —
Шаги воспроизведения:,* Загрузить ОСВ., пустая строка,* Дождаться обработки. - THEN абзац «Шаги воспроизведения:» и маркированные пункты «Загрузить ОСВ.» и «Дождаться обработки.» без звёздочек
Scenario: Обычный текст не портится
- WHEN описание —
Поле data_type = null, дата 2026-10-15, оборот 18 803 903,65 - 9 351 102,89, a*b*c, UTF-8, **важно** - THEN текст показан как есть, без курсива, зачёркивания, жирного и ссылок
Scenario: Скриншот в тексте комментария
- WHEN комментарий — «см. !скрин 1.png|thumbnail!», у задачи есть вложение «скрин 1.png»
- THEN вместо
!скрин 1.png|thumbnail!— кнопка со скрепкой «скрин 1.png»; под комментарием плашка «скрин 1.png · 234,4 КБ»; щелчок по любой из них скачивает файл
Requirement: Проект в приложении
«Обзор», доска, список задач, лента и вики SHALL показывать один проект — текущий. Текущий проект SHALL задаваться
параметром project в адресе страниц; без параметра — последний выбранный в этом браузере (localStorage), если он
есть в GET /projects, иначе первый из GET /projects; выбранный проект записывается в адрес без новой записи истории.
Проекта из адреса нет в GET /projects — страница SHALL показывать «проект <метка> не найден» и подсказку выбрать
проект в списке «Проект» боковой панели (на узком экране — в «Меню») вместо данных. Проектов нет — страницы SHALL
показывать приглашение создать проект («+ Новый проект…»). Переходы между страницами SHALL сохранять текущий проект;
смена проекта на странице, которой нет у нового проекта, открывает «Обзор»; исключение — «Бэклог», «Спринт», «Kanban»:
адрес остаётся, страница показывает «У проекта нет …-доски» (требование «Спринты и бэклог»). Карточка задачи
(/app/jira/issues/{key}) от проекта не зависит.
Доска SHALL загружать GET /board?project=<текущий>. Раскладка, дорожки и фильтры доски в localStorage SHALL
храниться отдельно для каждого проекта; у проекта AWPCA — на прежних ключах (раскладка, сделанная до проектов,
сохраняется). Фильтр спринтов SHALL показываться, только если хотя бы у одной своей задачи снимка (neighbor = false)
есть спринт; иначе — «колонки «Спринт» нет — показаны все задачи».
Форма «Новый проект» (пункт «+ Новый проект…» выпадающего списка) SHALL открываться окном поверх страницы: метка (с
подсказкой «метка задач в Jira и имя проекта в хабе») и название, затем поля каждого модуля в своей группе с
заголовком — названием модуля: «Jira» — номер доски Jira (необязательно; подсказка «scrum или kanban определится
сам»), «Вики» — корень вики (необязательно; подсказка «DOC-… или ссылка на страницу Eva»). Подсказка — текстом под
полем, не в placeholder. Пустое необязательное поле в запрос не попадает; числовое поле с нецелым значением — ошибка у
поля, запрос не уходит. «Создать» — POST /projects с автором viewer; ошибка хаба 409/422 — текст detail рядом с
кнопкой, окно остаётся открытым; успех — окно закрывается, текущим становится новый проект, каждый пакет из ответа
(batches) отслеживается своим модулем (требование «Состояние и обновление модулей»).
Scenario: Первый вход после выкатки
- WHEN открыт
/app/jira/linksбезproject, вlocalStorageвыбора нет, проектыAWPCAиSTOCKS_DRRK - THEN адрес —
/app/jira/links?project=AWPCA, доска —GET /board?project=AWPCAс прежней раскладкой карточек
Scenario: Переключение проекта
- WHEN на доске
AWPCAв выпадающем списке выбранSTOCKS_DRRK, затем открыт раздел «Задачи» - THEN доска —
GET /board?project=STOCKS_DRRKсо своей раскладкой; адрес «Задач» —/app/jira/issues?project=STOCKS_DRRK
Scenario: Неизвестный проект в адресе
- WHEN открыт
/app/jira/issues?project=NOPE - THEN «проект NOPE не найден» и подсказка выбрать проект в боковой панели; запроса
GET /issuesнет
Scenario: Создание проекта из формы
- WHEN в форме введены
STOCKS_DRRK, «Запасы ДРРК», доска и корень вики пусто; нажато «Создать» - THEN
POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", author: "viewer"}; текущий проект —STOCKS_DRRK; строка «Jira» блока состояния показывает «пакет #N выполняется»
Scenario: Проект с корнем вики из формы
- WHEN в форме введены
STOCKS_DRRK, «Запасы ДРРК», кореньDOC-001234; нажато «Создать» - THEN
POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", wiki_root: "DOC-001234", author: "viewer"}; строка «Jira» показывает пакет обновления, строка «Вики» — пакет синхронизации
Scenario: Метка занята
- WHEN в форме введена метка
awpca, хаб ответил 409 - THEN окно открыто, рядом с «Создать» — текст ошибки хаба
Scenario: Канбан-проект
- WHEN текущий проект —
STOCKS_DRRK, ни у одной своей задачи снимка нет спринта (у соседа изAWPCAспринт есть) - THEN вместо фильтра спринтов — «колонки «Спринт» нет — показаны все задачи»
Scenario: Нецелый номер доски
- WHEN в поле «Номер доски Jira» введено
51.5, нажато «Создать» - THEN у поля — «нужно целое число», запроса
POST /projectsнет
Requirement: Дизайн-система
Приложение SHALL следовать дизайн-системе AURA (handoff AURA-xlsx-tool 29.09.2026, Figma «Универсалки») в светлой и
тёмной темах. Токены — цвета по семантическим ролям для обеих тем, типографика, отступы с шагом 4 px, радиусы, высоты
контролов, фокус, движение — SHALL быть определены в web/src/styles/tokens.css (вместе с токенами приложения: палитра
видов работ, приоритеты, стрелки и дорожки доски); прочие файлы стилей MUST NOT содержать литералов цвета и SHALL
брать цвета из токенов. Экраны SHALL собираться из компонентов web/src/ui/ (кнопки, кнопка-иконка, сегментный
переключатель, переключатель темы, поле с подписью, поле ввода и поиска, выпадающий список, флажок, бейдж, снимаемая
плашка, сообщение, панель, шапка страницы, окно, меню). Геометрия: контролы 32 px (крупные 40 px) с радиусом 10 px,
кнопки — «таблетки», панели — радиус 16 px, окна — радиус 20 px, флажок 16×16 px; текст Regular 14/20, подписи
Semibold 14/20, заголовок страницы Bold 20/28. Страницы, кроме доски, SHALL занимать контейнер до 1440 px по центру с
боковым отступом 24 px (16 px на экранах уже 640 px); доска — всю ширину окна с отступом 24 px. Иконки — SVG из
сборки, не символы текста. Каждый интерактивный элемент SHALL иметь видимое кольцо фокуса; цвет MUST NOT быть
единственным признаком состояния.
Scenario: Цвет вне токенов
- WHEN в любом
*.cssвweb/src, кромеstyles/tokens.css, появился литерал цвета - THEN тесты фронта падают с именем файла и литералом
Scenario: Тёмная тема
- WHEN выбрана тёмная тема
- THEN канва
#181818, поверхности#1f1f1f, основной текст#f2f4f2, основное действие#69a93dна всех страницах, в окнах и меню
Requirement: Тема
Переключатель темы SHALL быть в боковой панели приложения: два положения — светлая и тёмная. Выбор SHALL храниться в
localStorage под ключом graph-theme (light / dark), как у прежней доски; без сохранённого выбора или со
значением "" тема SHALL следовать настройке системы, и переключатель показывает её. Тема применяется до первой
отрисовки, без мигания.
Scenario: Выбор темы сохраняется
- WHEN в системе светлая тема, переключатель переведён в «тёмная», страница перезагружена
- THEN приложение в тёмной теме, в
localStoragegraph-theme=dark
Requirement: Лента изменений
Страница /app/feed?project=<метка> SHALL показывать события журнала изменений своих задач текущего проекта за период
(GET /changes с project, since, until, kinds). Период SHALL выбираться переключателем «2 часа» / «Сегодня» /
«Вчера» / «7 дней» (по умолчанию «Сегодня»; границы — по местному времени браузера: «Сегодня» — с начала текущих суток
до сейчас, «Вчера» — с начала прошлых суток до начала текущих) и храниться в адресе (period=2h|today|yesterday|7d;
«Сегодня» в адрес не пишется). Виды событий SHALL выбираться флажками «Статусы», «Описание», «Сроки», «Комментарии»,
«Прочие поля» (по умолчанию все, кроме «Прочие поля») и храниться в адресе (kinds= через запятую; набор по умолчанию в
адрес не пишется; в адресе нет ни одного известного вида — набор по умолчанию); снятые все флажки — сообщение «выберите
хотя бы один вид», запроса нет.
События SHALL быть сгруппированы по задаче; задачи — по времени последнего события, новые сверху. У группы: ключ —
ссылка на карточку задачи (окно поверх ленты, требование «Карточка задачи»), тема и текущий статус; под ней события по
времени, новые сверху, каждое — время (ЧЧ:ММ, с датой ДД.ММ, если событие не в текущих сутках), автор и текст:
статус — «статус «было» → «стало»»; описание — «описание изменено»; срок, плановое начало и окончание — «срок ДД.ММ →
ДД.ММ» (дата по значению from_value/to_value, иначе строка журнала; пустое — «—»); комментарий — «комментарий:» и
первые 300 символов текста одной строкой с «…»; прочие поля — «<название поля>: «было» → «стало»» (значения до 80
символов с «…»), где название — подпись из словаря приложения (исполнитель, спринт, метки, приоритет, тема, эпик, связь,
вложение, резолюция), иначе имя поля Jira. Подзаголовок страницы — число событий и границы периода. Событий нет —
сообщение «за период изменений нет»; truncated — сообщение «показаны последние N событий — выберите период короче».
Ошибка хаба — сообщение с кнопкой «Повторить». Ленту SHALL перечитывать завершение «Обновить из Jira» (вместе со списками
задач) и возврат на вкладку браузера.
Scenario: Что закрылось за два часа
- WHEN открыта лента проекта AWPCA, выбран период «2 часа», сняты все флажки, кроме «Статусы»
- THEN адрес —
/app/feed?project=AWPCA&period=2h&kinds=status; запросGET /changes?project=AWPCA&since=<сейчас − 2 ч>&kinds=status; задачи сгруппированы, у каждой — события «ЧЧ:ММ автор: статус «В работе» → «Готово»»
Scenario: Карточка из ленты
- WHEN в ленте нажат ключ AURORA-9002
- THEN поверх ленты открыто окно карточки AURORA-9002; закрытие окна возвращает ленту с тем же периодом
Scenario: Пустой период
- WHEN за выбранный период событий нет
- THEN показано «за период изменений нет»
Scenario: Вчера
- WHEN выбран период «Вчера», сейчас 22.10 14:05 местного времени
- THEN запрос с
since= 21.10 00:00 иuntil= 22.10 00:00 местного времени
Requirement: История в карточке задачи
Карточка задачи SHALL показывать в основной колонке после комментариев блок «История (N)» — события changes из
GET /issues/{key} (все виды, новые сверху) в том же виде, что в ленте, всегда с датой. Видны 10 последних событий,
остальные — по кнопке «Показать все (N)»; без событий — подсказка «изменений нет»; ответ хаба без поля changes —
блока нет.
Scenario: Карточка с историей
- WHEN открыта карточка задачи с 12 событиями журнала
- THEN блок «История (12)» показывает 10 последних событий и кнопку «Показать все (12)»; после нажатия — все 12
Requirement: Скачивание вложений в карточке
Кнопка скачивания вложения (в разделе «Вложения», плашка под комментарием, ссылка в тексте) SHALL работать одинаково:
POST /attachments/{id}/fetch {author: "web"};state = ready—GET /attachments/{id}/contentс клиентским токеном, браузер сохраняет файл под именемfilename(черезBlobи временную ссылкуdownload; адрес с токеном в строке браузера MUST NOT появляться);state = fetching— у всех кнопок этого вложения в карточке вместо «Скачать» — «Загружается…» (неактивна), опросGET /attachments/{id}раз в 2 с, пока окно открыто на этой задаче;ready— шаг 2 сам, без второго нажатия; закрыли окно или ушли на другую задачу — опрос прекращается, команда остаётся в очереди (файл ляжет в хаб);failed— под вложением в разделе «Вложения» текст ошибки и кнопка «Повторить» (новый запрос); 409agent_too_oldи прочие ошибки хаба — их текст там же. Вложение сstate = readyв разделе «Вложения» помечается «у хаба» и скачивается сразу. Размер неизвестен — «размер неизвестен» и обычная кнопка. Вложение больше 10 МБ — вместо кнопки ссылка «Открыть в Jira» (<адрес Jira>/secure/attachment/<id>/<имя в URL-кодировке>, новая вкладка) с подсказкой «больше 10 МБ — только в Jira»; плашка и ссылка в тексте ведут туда же.
Scenario: Скачать скриншот
- WHEN в карточке AURORA-1650 нажато «Скачать» у «скрин 1.png», файла у хаба нет, агент онлайн
- THEN кнопка — «Загружается…», через несколько секунд браузер сохраняет «скрин 1.png», кнопка снова «Скачать», у вложения отметка «у хаба»
Scenario: Окно закрыли во время скачивания
- WHEN нажато «Скачать», затем окно карточки закрыто
- THEN опросов
GET /attachments/{id}больше нет; при следующем открытии карточки, если файл уже у хаба, вложение с отметкой «у хаба»
Scenario: Большой файл
- WHEN у вложения «запись.mp4» размер 25 МБ
- THEN вместо «Скачать» — ссылка «Открыть в Jira» с подсказкой «больше 10 МБ — только в Jira»
Scenario: Агент старой версии
- WHEN нажато «Скачать», хаб ответил 409 «агент v13 не умеет скачивать вложения — нужен v14»
- THEN под вложением этот текст и «Повторить»
Requirement: Вики проекта
Приложение SHALL иметь страницу «Вики» (/app/wiki?project=<метка>, раздел группы «Вики» боковой панели; текущий
проект — как у остальных страниц, «Проект в приложении»). Шапка страницы: заголовок «Вики», подзаголовок «<название
корня> · страниц · обновлено ДД.ММ ЧЧ:ММ» и действие «Обновить вики» (вторичная кнопка; требование «Состояние и
обновление модулей»). Страница SHALL показывать:
- дерево страниц проекта (
GET /wiki/tree) слева, свёрнутое до первого уровня; узел раскрывается по клику; узел выбранной страницы и его предки раскрыты; - выбранную страницу справа (
GET /wiki/pages/{code}): заголовок, дата синхронизации, ссылка «открыть в Eva», Markdown с таблицами, без сырого HTML; безpageв адресе — корневая страница; ссылкиpage:<код>открывают страницу вики (код в адресе&page=<код>), ссылкиattachment:<id>скачивают вложение (запрос, опрос состояния, сохранение файла браузером), состояниеfailed— текст ошибки рядом со ссылкой; - на экранах уже 640 px выбранная страница — над деревом;
- поиск над деревом (
GET /wiki/search): список результатов с фрагментами вместо дерева, пока строка поиска не пуста; после очистки поиска раскрытие дерева прежнее; - проекта без корня — форму «Корень вики (DOC-… или ссылка)» с кнопкой «Привязать» (
PATCH /projects/{label}); 422 — текст ошибки у поля; - корень есть, синхронизаций нет — «вики ещё не синхронизирована» и действие «Обновить вики»;
- последняя синхронизация с ошибкой — плашку с текстом ошибки над страницей.
Scenario: Открыть страницу по ссылке
- WHEN на странице DOC-3 нажата ссылка
page:DOC-2 - THEN адрес
/app/wiki?project=AWPCA&page=DOC-2, справа DOC-2, в дереве DOC-2 выделен
Scenario: Проект без корня
- WHEN открыта
/app/wiki?project=sfm_kedr, корня нет; введеноDOC-001234, нажато «Привязать» - THEN
PATCH /projects/sfm_kedr {wiki_root: "DOC-001234"}; строка «Вики» блока состояния показывает ход пакета синхронизации
Scenario: Поиск
- WHEN в строке поиска «согласов»
- THEN вместо дерева — результаты
GET /wiki/search?project=AWPCA&q=согласов; очистка строки возвращает дерево
Scenario: Без выбранной страницы
- WHEN открыта
/app/wiki?project=AWPCAбезpage - THEN справа корневая страница проекта, в дереве она выделена
Requirement: Состояние и обновление модулей
Каждый веб-модуль SHALL описывать своё состояние данными, а рисовать их SHALL ядро. Описание модуля: название для
показа («Jira», «Вики»); агент (имя модуля в GET /agents) или его отсутствие; свежесть данных для текущего проекта —
время, охват («все проекты» или «проект») и ошибка последнего обновления; действие обновления (название, охват,
постановка пакета) или его отсутствие; правило, какие пакеты принадлежат модулю; данные, которые надо перечитать после
пакета модуля; есть ли у модуля данные для проекта (у вики без корня — нет).
Ядро SHALL:
- опрашивать
GET /agentsпри входе и раз в 60 с — один опрос на приложение; агент модуля: «на связи», «офлайн» или «обновите агента» (естьwarningилиupdate_hint, текст — в подсказке); - вести один трекер пакетов на приложение: на модуль — не больше одного отслеживаемого пакета; пакет подхватывается при
входе, раз в 60 с и после действия обновления, если он не завершён, принадлежит модулю (поле
moduleвGET /batches) и текущему проекту (для модулей с охватом «проект») — любого автора, в том числе автообновления хаба и MCP; опросGET /batches/{id}каждые 3 с до терминального статуса; 4xx (404 — «пакета нет в хабе») и 5 ошибок «хаб не отвечает» подряд — опрос остановлен с сообщением; по завершении — перечитать данные модуля и список проектов; - ответ 409 на постановку пакета — отслеживать уже идущий пакет модуля, без сообщения об ошибке;
- агент модуля офлайн при обновлении — пакет всё равно ставится, событие «агент офлайн — пакет выполнится, когда он выйдет на связь; пакет #N в очереди…».
Блок состояния в развёрнутой боковой панели — по строке на модуль, у которого есть данные для проекта: название модуля,
точка агента (цвет и текст «агент на связи» / «агент офлайн» / «обновите агента» — не только цвет), кнопка-значок
обновления с подписью действия в подсказке и aria-label («Обновить из Jira — все проекты», «Обновить вики — проект
AWPCA»); под ними — «данные на ДД.ММ ЧЧ:ММ» с охватом («все проекты» / «проект»), «обновления ещё не было» или ошибка
последнего обновления; под ними — одно текущее событие пакета модуля: в очереди / выполняется (с оставшимся временем,
если хаб его дал, и пометками «агент офлайн», «хаб не отвечает, повтор»), выполнен (исчезает через 10 с), не выполнен
с текстом ошибки, отменён, опрос остановлен с причиной. Кнопка обновления модуля недоступна (с признаком загрузки), пока
запрос постановки в пути или выполняется пакет действия обновления модуля (у jira — пакет с первой командой refresh, у
wiki — wiki_sync); прочие пакеты модуля показываются, но обновление не блокируют: нажатие ставит пакет обновления, и
трекер переключается на него. Свёрнутая панель — по значку модуля с точкой агента, подробности в
подсказке, нажатие — обновление. На узком экране — сводная точка в верхней строке (требование «Оболочка и адреса»),
событие пакета — строкой под верхней строкой.
Модуль jira: агент jira; свежесть — jira_synced_at из GET /projects, охват «все проекты»; действие «Обновить из
Jira» — POST /refresh {full: true, author: "viewer"}; после пакета перечитываются снимки досок, списки задач, лента и
открытая карточка; доска, показывающая снимок из файла, его не заменяет. Модуль wiki: агент wiki; свежесть —
wiki_synced_at, wiki_sync_error из GET /projects, охват «проект»; данные для проекта есть, только если есть
wiki_root; действие «Обновить вики» — POST /wiki/sync {project, author: "viewer"}; после пакета перечитываются дерево,
страницы и поиск. Блок состояния MUST NOT загружать снимок доски или GET /wiki/status.
Страница модуля с действием обновления SHALL дублировать его вторичной кнопкой в шапке страницы; основной (primary) кнопки обновления в приложении нет.
Scenario: Обновление со списка задач
- WHEN открыт
/app/jira/issues, нажато «Обновить из Jira» в строке «Jira», пакет выполнен - THEN в хабе один пакет
refreshавтораviewer; список задач перезапрошен; строка «Jira» показывала «пакет #N выполняется», затем «пакет #N выполнен»; строка «Вики» не изменилась
Scenario: Один прогон на модуль
- WHEN нажата «Обновить из Jira» и, пока пакет выполняется, выполнен переход между разделами и нажата «Обновить из Jira» в шапке доски
- THEN кнопки «Обновить из Jira» недоступны, в хабе один пакет, его опрашивает один опрос
Scenario: Опрос получил 404
- WHEN опрос пакета вики получил 404
- THEN опрос остановлен, строка «Вики» показывает «пакета нет в хабе», «Обновить вики» снова доступна
Scenario: Автообновление хаба
- WHEN хаб поставил пакет «Автообновление» (автор
hub, модульjira), приложение открыто на странице «Вики» - THEN не позже чем через 60 с строка «Jira» показывает «пакет #N выполняется», по завершении — новое «данные на …»
Scenario: Пакет другого проекта
- WHEN идёт пакет
wiki_syncпроектаSTOCKS_DRRK, текущий проект —AWPCA - THEN строка «Вики» его не показывает, «Обновить вики» доступна
Scenario: Пакет с другими командами
- WHEN незавершённый пакет модуля jira автора
viewerсостоит из командensure_fields - THEN строка «Jira» показывает «пакет #N выполняется», кнопка «Обновить из Jira» доступна; её нажатие ставит пакет
refresh, и строка показывает его
Scenario: Агент офлайн
- WHEN агент
wikiофлайн и нажато «Обновить вики» - THEN пакет поставлен; строка «Вики» — точка с текстом «агент офлайн» и событие «агент офлайн — пакет выполнится, когда он выйдет на связь; пакет #N в очереди…»
Scenario: Синхронизация уже идёт
- WHEN нажато «Обновить вики», хаб ответил 409
- THEN строка «Вики» показывает идущий пакет, сообщения об ошибке нет
Scenario: Проект без корня вики
- WHEN текущий проект
sfm_kedrбез корня вики - THEN в блоке состояния только строка «Jira»; на свёрнутой панели — один значок состояния
Scenario: Агенту нужно обновление
- WHEN
GET /agentsотдаёт агентаjiraсwarning - THEN строка «Jira» — точка с текстом «обновите агента», текст
warningв подсказке
Requirement: Обзор проекта
Страница ядра «Обзор» (/app/?project=<метка>, пункт «Обзор» боковой панели) SHALL показывать шапку «Обзор» с
названием проекта и под ней карточку на каждый модуль, у которого есть данные для проекта: название модуля, агент (с
версией из GET /agents), свежесть с охватом, текущее событие пакета, вторичная кнопка обновления и сводка, которую
даёт модуль. Сводка jira: число своих задач проекта, из них открытых и просроченных (по снимку доски проекта), ссылки
«Доска связей», «Задачи», «Лента». Сводка wiki: число страниц, поле поиска по вики (Enter открывает «Вики» с этим
поиском), ссылка «Вики». Модуль без данных для проекта SHALL показывать карточку с объяснением и действием модуля (у
вики — «Привязать корень вики» ведёт на страницу «Вики»). Под карточками — «Последние пакеты проекта»: до 10 из
GET /batches?project=<метка>: номер, модуль, название, автор, статус, время создания.
Scenario: Главная
- WHEN открыт
/app/без проекта, последний выбранный —AWPCA - THEN адрес
/app/?project=AWPCA, две карточки — «Jira» и «Вики», список последних пакетов
Scenario: Поиск по вики с обзора
- WHEN на «Обзоре»
AWPCAв поле поиска вики введено «согласов» и нажат Enter - THEN открыта «Вики» с результатами поиска «согласов»
Scenario: Проект без вики
- WHEN «Обзор» проекта
sfm_kedrбез корня вики - THEN карточка «Вики» с объяснением и ссылкой «Привязать корень вики»
Requirement: Граница модулей в вебе
Ядро веба (web/src/{shell,ui,api,projects,auth,styles}) MUST NOT импортировать модули, кроме списка модулей
modules/index.ts, и MUST NOT содержать типов, справочников и терминов модулей (типы задач Jira, закрытые статусы,
словари видов работ и связей, «статус по факту»). Модуль MUST NOT импортировать другой модуль; общее (форматтеры дат
«ДД.ММ», «ДД.ММ ЧЧ:ММ», «ДД.ММ.ГГГГ» для дат не текущего года, скачивание файла) — в ядре. Имена модулей и адреса
разделов (to) SHALL быть уникальны. Имя класса CSS, объявленное в стилях модуля, MUST NOT объявляться в стилях
другого модуля или ядра. Даты в списке задач и карточке SHALL выводиться форматтерами ядра, не строкой ISO.
Scenario: Пересечение классов
- WHEN в
modules/jira/**.cssиmodules/wiki/**.cssобъявлен класс.wiki - THEN тесты фронта падают с именем класса и файлами
Scenario: Импорт чужого модуля
- WHEN файл в
modules/wiki/импортирует изmodules/jira/ - THEN тесты фронта падают с именем файла
Scenario: Дубль адреса раздела
- WHEN два модуля объявляют раздел с одинаковым
to - THEN тесты фронта падают с адресом и модулями
Requirement: Спринты и бэклог
Модуль jira SHALL показывать для проекта с доской страницы по образцу Jira — только для просмотра: на них MUST NOT быть
действий, меняющих Jira (перетаскивания, кнопок спринтов, смены статуса или ранга). Пункты меню группы «Jira» —
«Связи», «Задачи», «Лента» и по типу доски текущего проекта (board_type из GET /projects): scrum — «Бэклог» и
«Спринт»; kanban — «Kanban»; доски нет или тип неизвестен — новых пунктов нет, а прямой переход на их адрес
показывает «У проекта нет scrum-доски» («…kanban-доски» для «Kanban») со ссылкой на «Связи». Данные — GET /agile?project=<текущий>; если доска есть, но её данных от агента нет (board = null или board.fetched_at = null) —
«Нужен агент v15 и обновление из Jira» с кнопкой «Обновить» блока состояния модуля; данные доски есть (fetched_at задан), а колонок нет (columns = null) — «Не удалось
прочитать настройки доски из Jira» и та же кнопка. Пока проекты не загружены, условные пункты меню скрыты; смена проекта
на странице, которой у нового проекта нет, оставляет адрес и показывает сообщение «У проекта нет …-доски». Даты
спринтов (ISO-8601 Jira) показываются «ДД.ММ» в часовом поясе браузера; «осталось N дн.» — от сегодня до конца спринта.
Строка задачи и карточка задачи на этих страницах: ключ, значок типа, тема, исполнитель (или «—»), оценка (число или
«—»); выполненная — приглушена. Щелчок открывает карточку задачи (окно поверх страницы, как в списке задач). Оценки и
их суммы SHALL показываться числом не более чем с двумя знаками после запятой без хвостовых нулей (0.1 + 0.2 —
«0.3», 3 — «3»).
Значок типа SHALL различать по issue_type без учёта регистра: эпик («Эпик», «Epic», «Фича»), история
(«История», «Story»), задача («Задача», «Task»), ошибка («Ошибка», «Bug»), подзадача («Подзадача», «Sub-task»,
«Subtask»); прочие и пустой тип — нейтральный значок. Значок — цветной квадрат с символом цвета --color-text-on-primary, контрастным в обеих темах, у каждого вида свои
цвет и символ (эпик — молния, история — закладка, задача — галочка, ошибка — круг, подзадача — два квадрата), в обеих
темах; название типа — в aria-label и подсказке.
Панель фильтров — над содержимым «Бэклога», «Спринта» и «Kanban»: поиск «Ключ или тема» (подстрока без учёта регистра в ключе или теме), выбор «Исполнитель» («все», «Без исполнителя», исполнители задач страницы по алфавиту), переключатель «Скрыть выполненные» (только «Бэклог» и «Спринт»); на «Бэклоге» — кнопка «Эпики» (показать или скрыть панель эпиков); на «Спринте» — выбор спринтов; на «Спринте» и «Kanban» — меню «Колонки». Если задан хотя бы один фильтр (поиск, исполнитель, эпик, «Скрыть выполненные») — кнопка «Сбросить фильтры», снимающая их все. Фильтры действуют вместе (И). Сохранённое значение исполнителя или эпика, которого нет в данных, SHALL оставаться выбранным и видимым в списке.
Запоминание: свёрнутые секции, выбранный эпик, исполнитель, «Скрыть выполненные», видимость панели эпиков и скрытые
колонки SHALL храниться в localStorage под ключом sdlc-hub-agile — JSON по метке проекта и странице (backlog,
sprint, kanban); у разных страниц и проектов свои значения. Поиск и выбор спринтов не запоминаются. Нечитаемое или
закрытое хранилище — значения по умолчанию, страница работает.
«Бэклог» (/app/jira/backlog), сверху вниз: секции активных спринтов, будущих спринтов, «Бэклог», затем
свёрнутый список «Прошлые спринты (N)». Заголовок секции спринта: имя, состояние, даты «ДД.ММ–ДД.ММ», цель (если есть),
«N задач», «S SP (выполнено D)» и «без оценки: U», если U > 0 — итоги спринта, фильтры их не меняют; если фильтры
скрыли часть задач секции — ещё «показано K». Задачи секции — по рангу. Секция без задач показывает «Нет задач»; все
задачи скрыты фильтрами — «Нет задач по фильтрам». Любую секцию (спринта и «Бэклог») SHALL можно свернуть и развернуть
щелчком по заголовку (кнопка с aria-expanded); по умолчанию развёрнуты все. Прошлый спринт в списке — строка «имя ·
закрыт ДД.ММ · выполнено X из N · S SP»; раскрытие показывает его задачи с пометкой «не выполнено» у невыполненных.
Эпики (задачи вида «эпик») MUST NOT показываться строками в секциях «Бэклога». Панель «Эпики» — слева от секций:
пункт «Все задачи», затем эпики — каждая задача-эпик из данных страницы по рангу, затем эпики, на которые ссылается
epic_key задач, но строки которых в данных нет, по ключу; в конце — «Без эпика». Пункт эпика: тема (или ключ, если
темы нет), ключ, число задач эпика в секциях активных и будущих спринтов и «Бэклога». Выбор пункта (кнопка с
aria-pressed) фильтрует все секции, включая прошлые спринты; повторный выбор выбранного эпика возвращает «Все
задачи». При «Скрыть выполненные» выполненные эпики в панели не показываются (кроме выбранного). На узком экране
(контейнер страницы уже 900 px) панель SHALL становиться выбором «Эпик» в панели фильтров.
«Скрыть выполненные» на «Бэклоге» скрывает выполненные задачи в секциях активных и будущих спринтов и «Бэклога»; прошлые спринты показываются полностью.
«Спринт» (/app/jira/sprint): выбранные спринты доски — по умолчанию все активные. Выбор спринтов — список с
отметками: активные, затем будущие (по дате начала, без даты — в конце); выбор хранится в адресе
?sprint=<id>[,<id>…] (переход без новой записи истории); идентификаторы, которых нет среди активных и будущих,
отбрасываются, если не осталось ни одного — все активные. Над доской — заголовок каждого выбранного спринта (имя,
даты, «осталось N дн.» или «срок прошёл» — только у активного, цель, суммы оценок), под ними колонки доски Jira с числом
задач; задачи доски — задачи выбранных спринтов, карточки в колонке — по рангу; колонка «Без колонки» — для статусов вне
настроек доски, только если в ней есть задачи. Нет активного спринта и ничего не выбрано — «Нет активного спринта» и
ссылка на «Бэклог»; если есть будущие спринты, выбор спринтов при этом доступен.
«Kanban» (/app/jira/kanban): колонки доски из board_issues, карточки по рангу, у последней колонки подпись
«за 14 дней»; если kanban_backlog = true — над колонками свёрнутый список «Бэклог (N)» по рангу; поиск и исполнитель
действуют и на него.
Колонки доски («Спринт» и «Kanban»): заголовок колонки — h3. Меню «Колонки» — отметки по колонкам доски (и «Без
колонки», если она есть); снятая отметка скрывает колонку, последнюю видимую скрыть нельзя (отметка недоступна); если
скрыты колонки — на кнопке их число. Доска SHALL занимать высоту окна под заголовком и панелью фильтров (не меньше
360 px; на окне уже 640 px — не меньше 240 px), все колонки — во всю высоту доски; карточки, не помещающиеся по высоте, прокручиваются внутри своей колонки.
Колонки, не помещающиеся по ширине, SHALL прокручиваться по горизонтали внутри доски, не страницы, и полоса горизонтальной
прокрутки доски SHALL находиться в пределах окна при любом числе карточек. Заголовок вкладки — «Бэклог», «Спринт»,
«Kanban» по общему правилу оболочки.
Scenario: Меню scrum-проекта
- WHEN текущий проект
AWPCA,board_type = scrum - THEN в группе «Jira» пункты «Связи», «Бэклог», «Спринт», «Задачи», «Лента»; пункта «Kanban» нет
Scenario: Проект без доски
- WHEN текущий проект
STOCKS_DRRKбез доски, открыт/app/jira/backlog?project=STOCKS_DRRK - THEN пунктов «Бэклог», «Спринт», «Kanban» в меню нет; страница — «У проекта нет scrum-доски» со ссылкой на «Связи»
Scenario: Бэклог только смотрит
- WHEN открыт «Бэклог»
AWPCA - THEN на странице нет перетаскиваемых элементов; кнопки и поля — только свёртка секций, панель фильтров, пункты панели эпиков и открытие карточки задачи; ни одно из них не ставит команд
Scenario: Прошлый спринт
- WHEN спринт
AWPCA_Sprint_2_VGOзакрыт, в нём 73 задачи, 61 в последней колонке, оценки у 10 задач на 25 SP - THEN в «Прошлые спринты» строка «AWPCA_Sprint_2_VGO · закрыт ДД.ММ · выполнено 61 из 73 · 25 SP»; раскрытие — 73 задачи, 12 с пометкой «не выполнено»
Scenario: Агент без досок
- WHEN
AWPCAscrum, агент v14 (данных доски нет), открыт «Спринт» - THEN «Нужен агент v15 и обновление из Jira» и кнопка «Обновить»
Scenario: Свёрнутый активный спринт
- WHEN на «Бэклоге» свёрнута секция активного спринта, страница перезагружена
- THEN секция свёрнута (видны заголовок и итоги, задач нет), остальные развёрнуты
Scenario: Фильтр по эпику
- WHEN в бэклоге 3 задачи эпика
AURORA-10и 5 других, в активном спринте 2 задачиAURORA-10; в панели «Эпики» выбранAURORA-10 - THEN в секции спринта 2 задачи и «показано 2», в «Бэклоге» 3; строки самого
AURORA-10в секциях нет; у пунктаAURORA-10в панели — 5
Scenario: Скрыть выполненные
- WHEN в активном спринте 10 задач, 4 выполнены; включено «Скрыть выполненные»
- THEN в секции 6 задач, заголовок — «10 задач» и «показано 6»; «Прошлые спринты» показывают все свои задачи
Scenario: Исполнитель на доске спринта
- WHEN на «Спринте» выбран исполнитель
ivanov - THEN на доске только его карточки; числа в заголовках колонок — по видимым карточкам; на «Бэклоге» исполнитель не выбран (свой фильтр у каждой страницы)
Scenario: Будущий спринт на доске
- WHEN у доски активный спринт 101 и будущий 102; в выборе спринтов отмечен и 102
- THEN адрес —
?sprint=101,102; над доской два заголовка, «осталось N дн.» только у 101; на доске задачи обоих
Scenario: Скрытая колонка
- WHEN на «Спринте» в меню «Колонки» снята отметка «Выполнено», страница перезагружена
- THEN колонки «Выполнено» нет, на кнопке «Колонки» — 1; остальные колонки на месте
Scenario: Широкая доска
- WHEN окно 2560 × 1300 px, у доски 12 колонок, в одной из них 40 карточек
- THEN все колонки одной высоты и помещаются в окно по высоте; полоса горизонтальной прокрутки доски видна в окне; прокрутка доски вправо до конца показывает «Без колонки» целиком; страница горизонтально не прокручивается