616 lines
70 KiB
Markdown
616 lines
70 KiB
Markdown
# web-app Specification
|
||
|
||
## Purpose
|
||
Веб-приложение хаба (`web/`, React + TypeScript + Vite) под префиксом `/app/`: вход по клиентскому токену, доска связей (перенос `links.html`) и страницы задач со служебными заметками; сборка в `src/jira_hub/web/app/`.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Вход по токену
|
||
Приложение SHALL работать с API хаба того же хоста под клиентским токеном: хранить его в `localStorage` под ключом
|
||
`jira-hub-token` (и в памяти, если хранилище закрыто) и передавать в заголовке `Authorization: Bearer` каждого запроса.
|
||
Если токена нет или любой запрос ответил 401, приложение SHALL показывать экран входа вместо страниц: поле токена с
|
||
подсказкой `uv run jira-hub tokens`, без панелей страниц и без фоновых запросов, которые могли бы изменить поле ввода.
|
||
Введённый токен SHALL проверяться запросом `GET /settings` и сохраняться только при успехе. Действие «Сменить токен»
|
||
SHALL очищать токен и показывать экран входа. Ответ 401 SHALL выводить на экран входа, только если запрос ушёл с текущим токеном: запоздавший 401 на
|
||
запрос с прежним токеном после входа с новым не учитывается. Если проверка токена получила ответ 4xx, кроме 401,
|
||
экран входа SHALL показывать текст ошибки хаба, а не «хаб не отвечает».
|
||
|
||
#### Scenario: Первый вход
|
||
- **WHEN** приложение открыто впервые, токена нет
|
||
- **THEN** показан экран входа; после ввода верного токена открывается запрошенная страница, токен сохранён
|
||
|
||
#### Scenario: Устаревший токен
|
||
- **WHEN** сохранённый токен не подходит и запрос ответил 401
|
||
- **THEN** показан экран входа с пояснением, что токен не подошёл; панели доски и задач не видны
|
||
|
||
#### Scenario: Токен от прежней доски
|
||
- **WHEN** в `localStorage` есть `jira-hub-token`, сохранённый страницей `links.html`
|
||
- **THEN** приложение входит с ним без запроса токена
|
||
|
||
#### Scenario: Запоздавший 401 от прежнего токена
|
||
- **WHEN** запрос ушёл с прежним токеном, пользователь вошёл с новым, и после этого запрос ответил 401
|
||
- **THEN** приложение остаётся на странице, новый токен сохранён
|
||
|
||
#### Scenario: Хаб отверг проверку токена
|
||
- **WHEN** проверка `GET /settings` ответила 400 с `detail`
|
||
- **THEN** экран входа показывает текст `detail`, а не «хаб не отвечает»
|
||
|
||
### Requirement: Оболочка и адреса
|
||
Приложение SHALL открываться под префиксом `/app/` и иметь страницы `/app/board` (доска связей), `/app/issues` (список
|
||
задач), `/app/feed` (лента изменений, требование «Лента изменений») и `/app/issues/{key}` (карточка задачи — окно поверх страницы, требование «Карточка задачи»); `/app` и `/app/` SHALL вести на `/app/board`, неизвестный путь —
|
||
на страницу «не найдено» со ссылками на разделы. Боковая панель SHALL быть закреплена у левого края окна на всю его
|
||
высоту и MUST NOT прокручиваться вместе со страницей; страница занимает остальную ширину. Сверху вниз панель SHALL
|
||
содержать: название «Хаб Jira» и кнопку «Свернуть панель»; выпадающий список проектов («<метка> — <название>», текущий
|
||
отмечен, в конце «+ Новый проект…»; требование «Проект в приложении»); разделы «Доска связей», «Задачи» и «Лента» — вертикальным
|
||
списком ссылок со значками (текущий отмечен `aria-current="page"`); внизу — блок данных Jira с действием «Обновить из
|
||
Jira» (требование «Обновление из Jira»), переключатель темы (требование «Тема») и меню с действием «Сменить токен».
|
||
Свёрнутая панель SHALL быть узкой полосой с теми же действиями значками: «Развернуть панель», метка текущего проекта,
|
||
разделы, «Обновить из Jira» с точкой состояния агента, тема, меню; названия — в подсказках и `aria-label`. Свёртка
|
||
SHALL храниться в `localStorage` под ключом `jira-hub-sidebar` (`collapsed`; без записи — развёрнута). На экранах уже
|
||
640 px панели в потоке нет: сверху SHALL быть закреплена строка с кнопкой «Меню», названием и кнопкой «Обновить из
|
||
Jira»; «Меню» открывает развёрнутую панель поверх страницы, панель закрывается выбором раздела, клавишей Escape или
|
||
щелчком мимо неё. Горизонтальной прокрутки страницы на любой ширине нет.
|
||
Неожиданная ошибка отрисовки страницы SHALL показываться сообщением с кнопкой «Перезагрузить», не пустым экраном.
|
||
|
||
#### Scenario: Переход между разделами
|
||
- **WHEN** на доске в боковой панели выбран раздел «Задачи»
|
||
- **THEN** адрес — `/app/issues` с текущим проектом, показан список задач; «назад» возвращает на доску
|
||
|
||
#### Scenario: Раздел «Лента»
|
||
- **WHEN** на доске в боковой панели выбран раздел «Лента»
|
||
- **THEN** адрес — `/app/feed` с текущим проектом, показана лента изменений за сегодня; раздел отмечен в панели
|
||
|
||
#### Scenario: Сменить токен
|
||
- **WHEN** в меню боковой панели выбрано «Сменить токен»
|
||
- **THEN** токен очищен, показан экран входа
|
||
|
||
#### Scenario: Панель на месте при прокрутке
|
||
- **WHEN** доска прокручена до самого низа
|
||
- **THEN** боковая панель видна целиком на прежнем месте: разделы и «Обновить из Jira» доступны без прокрутки вверх
|
||
|
||
#### Scenario: Свёрнутая панель
|
||
- **WHEN** нажата «Свернуть панель», страница перезагружена
|
||
- **THEN** панель свёрнута в полосу значков, доска шире; значок «Задачи» открывает список задач; «Развернуть панель»
|
||
возвращает полную панель
|
||
|
||
#### Scenario: Узкий экран
|
||
- **WHEN** ширина окна 390 px, нажата «Меню», в панели выбран раздел «Задачи»
|
||
- **THEN** панель открылась поверх страницы, после выбора закрыта, показан список задач; горизонтальной прокрутки нет
|
||
|
||
### 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` под ключом `jira-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)» — новые сверху, автор, время, кнопка «исходник» / «текст» у каждого комментария, текст,
|
||
отрендеренный по требованию «Разметка Jira в карточке»; кнопка «Скрыть» / «Показать» сворачивает раздел, выбор 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/jira_hub/web/app/`; каталог сборки не хранится в git. Шрифты (Mulish — текст интерфейса, IBM Plex Mono — ключи
|
||
задач и исходники), иконки и прочие ресурсы страницы SHALL входить в сборку: приложение не обращается к внешним
|
||
адресам. `make check` SHALL запускать `uv run pytest`, проверку типов и линтер фронта и тесты фронта; зелёным считается
|
||
только весь набор. `npm run dev` SHALL поднимать сервер разработки с прокси маршрутов API на хаб из `JIRA_HUB_URL` (по
|
||
умолчанию тестовый `http://127.0.0.1:8791`).
|
||
|
||
#### Scenario: Сборка с нуля
|
||
- **WHEN** в чистом checkout выполнен `make web`
|
||
- **THEN** в `src/jira_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` и ключи других проектов — текст);
|
||
`[~логин]` — «@логин».
|
||
Всё, что разборщик не распознал (`{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** текст показан как есть, без курсива, зачёркивания, жирного и ссылок
|
||
|
||
### Requirement: Проект в приложении
|
||
Доска и список задач SHALL показывать один проект — текущий. Текущий проект SHALL задаваться параметром `project` в
|
||
адресе `/app/board` и `/app/issues`; без параметра — последний выбранный в этом браузере (`localStorage`), если он есть
|
||
в `GET /projects`, иначе первый из `GET /projects`; выбранный проект записывается в адрес без новой записи истории.
|
||
Проекта из адреса нет в `GET /projects` — страница SHALL показывать «проект <метка> не найден» и выпадающий список
|
||
проектов вместо данных. Проектов нет — доска и список SHALL показывать приглашение создать проект («+ Новый проект…»).
|
||
Переходы «Доска связей» / «Задачи» SHALL сохранять текущий проект. Карточка задачи (`/app/issues/{key}`) от проекта не зависит.
|
||
|
||
Доска SHALL загружать `GET /board?project=<текущий>`; «Обновить из Jira» — `POST /refresh {full: true}`, обновляет все
|
||
проекты. Раскладка, дорожки и фильтры доски в `localStorage` SHALL храниться отдельно для каждого проекта;
|
||
у проекта `AWPCA` — на прежних ключах (раскладка, сделанная до проектов, сохраняется). Фильтр спринтов SHALL
|
||
показываться, только если хотя бы у одной своей задачи снимка (`neighbor = false`) есть спринт; иначе — «колонки
|
||
«Спринт» нет — показаны все задачи».
|
||
|
||
Форма «Новый проект» (пункт «+ Новый проект…» выпадающего списка) SHALL открываться окном поверх страницы: метка,
|
||
название, номер доски Jira (необязательно; подсказка «scrum или kanban определится сам»). «Создать» — `POST /projects`
|
||
с автором `viewer`; ошибка хаба 409/422 — текст `detail` рядом с кнопкой, окно остаётся открытым; успех — окно
|
||
закрывается, текущим становится новый проект, боковая панель показывает ход пакета обновления из ответа (`batch_id`).
|
||
|
||
#### Scenario: Первый вход после выкатки
|
||
- **WHEN** открыт `/app/board` без `project`, в `localStorage` выбора нет, проекты `AWPCA` и `STOCKS_DRRK`
|
||
- **THEN** адрес — `/app/board?project=AWPCA`, доска — `GET /board?project=AWPCA` с прежней раскладкой карточек
|
||
|
||
#### Scenario: Переключение проекта
|
||
- **WHEN** на доске `AWPCA` в выпадающем списке выбран `STOCKS_DRRK`, затем открыт раздел «Задачи»
|
||
- **THEN** доска — `GET /board?project=STOCKS_DRRK` со своей раскладкой; адрес «Задач» — `/app/issues?project=STOCKS_DRRK`
|
||
|
||
#### Scenario: Неизвестный проект в адресе
|
||
- **WHEN** открыт `/app/issues?project=NOPE`
|
||
- **THEN** «проект NOPE не найден» и список проектов; запроса `GET /issues` нет
|
||
|
||
#### Scenario: Создание проекта из формы
|
||
- **WHEN** в форме введены `STOCKS_DRRK`, «Запасы ДРРК», доска пусто; нажато «Создать»
|
||
- **THEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", author: "viewer"}`; текущий проект — `STOCKS_DRRK`;
|
||
боковая панель показывает «пакет #N выполняется»
|
||
|
||
#### Scenario: Метка занята
|
||
- **WHEN** в форме введена метка `awpca`, хаб ответил 409
|
||
- **THEN** окно открыто, рядом с «Создать» — текст ошибки хаба
|
||
|
||
#### Scenario: Канбан-проект
|
||
- **WHEN** текущий проект — `STOCKS_DRRK`, ни у одной своей задачи снимка нет спринта (у соседа из `AWPCA` спринт есть)
|
||
- **THEN** вместо фильтра спринтов — «колонки «Спринт» нет — показаны все задачи»
|
||
|
||
### 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: Обновление из Jira
|
||
Действие «Обновить из Jira» SHALL быть в боковой панели приложения и доступно с любой страницы. Оно SHALL проверять
|
||
агента (`GET /agent`; офлайн — предупреждение, пакет всё равно ставится), ставить `POST /refresh {full: true, author:
|
||
"viewer"}` и опрашивать `GET /batches/{id}` каждые 3 с одним опросом до терминального статуса пакета. Кнопка SHALL быть
|
||
недоступна (с признаком загрузки), пока запрос `/refresh` в пути или пакет выполняется. Незавершённый пакет автора
|
||
`viewer`, первая команда которого — `refresh`, SHALL подхватываться при входе в приложение и после каждой успешной
|
||
загрузки снимка доски, кроме перечитывания снимка после завершения собственного пакета; пакеты `viewer` с другими
|
||
командами не подхватываются. Опрос SHALL прекращаться при ответе 4xx (404 — «пакета нет в хабе») и после 5 ошибок
|
||
«хаб не отвечает» подряд, с сообщением и снова доступной кнопкой. По завершении пакета приложение SHALL перечитывать
|
||
снимки досок, списки задач, открытую карточку и список проектов; доска, показывающая снимок из файла, его не заменяет.
|
||
Пакет, поставленный формой «Новый проект», SHALL опрашиваться так же сразу после создания.
|
||
|
||
Боковая панель SHALL показывать блок данных Jira: состояние агента («агент на связи» / «агент офлайн», по `GET /agent`
|
||
при входе и раз в 60 с), время последнего полного обновления («данные на ДД.ММ ЧЧ:ММ» по `last_sync` снимка доски
|
||
текущего проекта, «полного обновления не было», если его нет) и одно текущее событие пакета: в очереди / выполняется (с
|
||
оставшимся временем, с пометкой «агент офлайн» и «хаб не отвечает, повтор»), выполнен, не выполнен с текстом ошибки,
|
||
отменён, опрос остановлен с причиной, хаб не ответил на постановку; новое событие заменяет предыдущее, «выполнен»
|
||
исчезает через 10 с.
|
||
|
||
#### Scenario: Обновление со списка задач
|
||
- **WHEN** открыт `/app/issues`, нажато «Обновить из Jira», пакет выполнен
|
||
- **THEN** в хабе один пакет `refresh` автора `viewer`; список задач перезапрошен; боковая панель показывала «пакет #N
|
||
выполняется», затем «пакет #N выполнен»
|
||
|
||
#### Scenario: Один прогон на всё приложение
|
||
- **WHEN** нажата «Обновить из Jira» и, пока пакет выполняется, выполнен переход между разделами и «Перечитать» на доске
|
||
- **THEN** кнопка остаётся недоступной, в хабе один пакет, его опрашивает один опрос
|
||
|
||
#### Scenario: Опрос получил 404
|
||
- **WHEN** опрос пакета получил 404
|
||
- **THEN** опрос остановлен, боковая панель показывает «пакета нет в хабе», кнопка «Обновить из Jira» снова доступна
|
||
|
||
#### Scenario: Пакет с другой вкладки
|
||
- **WHEN** пакет `viewer` с командой `refresh` поставлен с другой вкладки и не завершён, приложение открыто
|
||
- **THEN** боковая панель показывает «пакет #N выполняется» и по завершении данные перечитываются
|
||
|
||
#### Scenario: Пакет с другими командами
|
||
- **WHEN** незавершённый пакет автора `viewer` состоит из команд `ensure_fields`
|
||
- **THEN** приложение его не опрашивает, кнопка «Обновить из Jira» доступна
|
||
|
||
#### Scenario: Агент офлайн
|
||
- **WHEN** `GET /agent` отвечает `online: false` и нажато «Обновить из Jira»
|
||
- **THEN** пакет поставлен, событие — «агент офлайн — пакет выполнится, когда он выйдет на связь; пакет #N в очереди…»
|
||
|
||
### Requirement: Тема
|
||
Переключатель темы SHALL быть в боковой панели приложения: два положения — светлая и тёмная. Выбор SHALL храниться в
|
||
`localStorage` под ключом `graph-theme` (`light` / `dark`), как у прежней доски; без сохранённого выбора или со
|
||
значением `""` тема SHALL следовать настройке системы, и переключатель показывает её. Тема применяется до первой
|
||
отрисовки, без мигания.
|
||
|
||
#### Scenario: Выбор темы сохраняется
|
||
- **WHEN** в системе светлая тема, переключатель переведён в «тёмная», страница перезагружена
|
||
- **THEN** приложение в тёмной теме, в `localStorage` `graph-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
|