Andrey Epifancev fd1799ac4a docs(openspec): архив web-app (#21)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:20:58 +04:00

18 KiB
Raw Blame History

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 очищать токен и показывать экран входа.

Scenario: Первый вход

  • WHEN приложение открыто впервые, токена нет
  • THEN показан экран входа; после ввода верного токена открывается запрошенная страница, токен сохранён

Scenario: Устаревший токен

  • WHEN сохранённый токен не подходит и запрос ответил 401
  • THEN показан экран входа с пояснением, что токен не подошёл; панели доски и задач не видны

Scenario: Токен от прежней доски

  • WHEN в localStorage есть jira-hub-token, сохранённый страницей links.html
  • THEN приложение входит с ним без запроса токена

Requirement: Оболочка и адреса

Приложение SHALL открываться под префиксом /app/ и иметь страницы /app/board (доска связей), /app/issues (список задач) и /app/issues/{key} (карточка задачи); /app и /app/ SHALL вести на /app/board, неизвестный путь — на страницу «не найдено» со ссылками на разделы. Верхняя панель SHALL содержать переходы «Доска», «Задачи» и действие «Сменить токен». Неожиданная ошибка отрисовки страницы SHALL показываться сообщением с кнопкой «Перезагрузить», не пустым экраном.

Scenario: Переход между разделами

  • WHEN на доске выбран раздел «Задачи»
  • THEN адрес — /app/issues, показан список задач; «назад» возвращает на доску

Requirement: Ошибки хаба

Приложение SHALL различать ошибки запросов к хабу: 401 — экран входа; хаб не отвечает (сеть, 5xx) — сообщение «хаб не отвечает» с возможностью повторить, ранее загруженные данные остаются на экране; прочие 4xx — текст detail ответа рядом с действием, которое его вызвало.

Scenario: Хаб остановлен

  • WHEN доска показана, хаб остановлен, нажато «Перечитать»
  • THEN на доске прежние данные и сообщение «хаб не отвечает — показаны прежние данные»

Requirement: Справочники из настроек хаба

Приложение SHALL брать перевод меток видов работ (kind_labels) и типов связей Jira (link_types) из GET /settings и не содержать собственных копий этих словарей. Если нужного словаря в настройках нет, страница SHALL сообщать, какого словаря не хватает, вместо пустой доски или списка без подписей.

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 быть значок «Открыть карточку», ведущий на /app/issues/{key}; ключ карточки по-прежнему открывает задачу в Jira. Действия «Сменить токен» в меню доски нет — оно в верхней панели приложения. Ещё два отличия от links.html (R-10, R-18): двойной щелчок по уголку карточки меряет высоту по содержимому при новой ширине, а не при прежней; указатель при перетаскивании и растяжке захватывается на нажатом элементе, а не на карточке, поэтому двойной щелчок по уголку сбрасывает размер. Загрузка файла CSV/JSON доступна только после входа, с экрана входа её нет.

Scenario: Раскладка переживает переход

  • WHEN раскладка карточек сохранена страницей links.html, открыта /app/board с теми же данными
  • THEN карточки стоят на тех же местах, дорожки в том же порядке и свёртке, фильтры те же

Scenario: Совпадение со старой доской

  • WHEN на одной фикстуре GET /board и одном наборе фильтров построены модель, видимая выборка, критический путь и проблемы старой страницей и приложением
  • THEN результаты совпадают

Scenario: Открыть карточку

  • WHEN на карточке AURORA-1620 нажат значок «Открыть карточку»
  • THEN открыта /app/issues/AURORA-1620 внутри приложения

Requirement: Источник данных доски

Доска SHALL хранить явный источник данных — хаб или файл. Пока показан снимок из файла (CSV или JSON-снимок), ответы хаба SHALL его не заменять; «Перечитать» SHALL возвращать источник «хаб» и загружать GET /board заново без обращения к Jira. Импорт JSON в режиме хаба SHALL применять раскладку, дорожки и фильтры и перечитывать хаб. «Обновить из Jira» SHALL проверять агента (GET /agent; офлайн — предупреждение, пакет всё равно ставится), ставить POST /refresh {full: true, author: "viewer"}, опрашивать GET /batches/{id} каждые 3 с одним опросом до терминального статуса пакета и затем перечитывать GET /board. Кнопка «Обновить из Jira» SHALL быть недоступна, пока запрос /refresh в пути или пакет выполняется. Незавершённый пакет refresh с автором viewer SHALL подхватываться при каждой успешной загрузке из хаба. Опрос пакета SHALL прекращаться при ответе 404 и после 5 ошибок «хаб не отвечает» подряд, с сообщением и снова доступной кнопкой. Шапка доски SHALL показывать источник данных (данные хаба на время последнего полного прогона; «из файла») и одно текущее событие (загрузка; пакет выполняется / выполнен / ошибка с текстом; хаб не отвечает — прежние данные; агент офлайн — пакет поставлен; ошибка разбора файла — «файл не разобран: <причина>»); новое событие заменяет предыдущее целиком.

Scenario: Поздний ответ хаба не затирает файл

  • WHEN запрос GET /board в пути, и в это время загружен CSV
  • THEN после ответа хаба на доске по-прежнему данные CSV с пометкой «из файла»

Scenario: Нет второго полного прогона

  • WHEN нажата «Обновить из Jira» и, пока пакет выполняется, доска перерисована (фильтр, «Перечитать»)
  • THEN кнопка остаётся недоступной, в хабе один пакет, его опрашивает один опрос

Scenario: Пакет пропал

  • WHEN опрос пакета получил 404
  • THEN опрос остановлен, показано сообщение, кнопка «Обновить из Jira» снова доступна

Scenario: Пакет после «Перечитать»

  • WHEN пакет viewer поставлен с другой вкладки и не завершён, нажато «Перечитать»
  • THEN доска показывает «пакет #N выполняется» и по завершении перечитывает данные

Requirement: Список задач

Страница /app/issues SHALL показывать задачи зеркала из GET /issues таблицей: ключ (ссылка на карточку), тема, тип, статус Jira, исполнитель, спринт, срок (planned_end, иначе duedate), эпик (имя эпика, иначе ключ), вид работы, статус по факту, признак «сосед». Фильтры SHALL соответствовать параметрам GET /issues (спринт, исполнитель, вид работы, статус по факту, статус Jira, метка, эпик, только соседи, поиск по ключу или теме), храниться в query-строке адреса и применяться сервером; варианты выпадающих списков SHALL браться из снимка GET /board. Задачи SHALL загружаться порциями по 50 строк; «Показать ещё» SHALL быть доступна, пока последняя порция полная.

Scenario: Фильтр в адресе

  • WHEN выбран спринт «AWPCA_Спринт 2» и исполнитель
  • THEN адрес содержит sprint= и assignee=, таблица — результат GET /issues с этими параметрами; открытие этого адреса в новой вкладке показывает ту же выборку

Scenario: Конец списка

  • WHEN вторая порция вернула 17 задач
  • THEN в таблице 67 задач, «Показать ещё» не показана

Requirement: Карточка задачи

Страница /app/issues/{key} SHALL показывать задачу из GET /issues/{key}: поля зеркала, ссылку на задачу в Jira, описание как текст с сохранёнными переносами строк (без разбора разметки Jira), исходящие и входящие связи с подписью типа (логический тип из link_types, иначе имя типа Jira) и ссылками на карточки связанных задач, 10 последних команд по задаче (id, пакет, тип, статус, время завершения, ошибка). Если задачи нет в зеркале (404), страница SHALL сообщать об этом со ссылкой на список.

Scenario: Связанная задача

  • WHEN у AURORA-1620 исходящая связь ASUPR_Блокер_Начала на AURORA-1621
  • THEN в карточке «блокирует начало → AURORA-1621», ключ ведёт на /app/issues/AURORA-1621

Requirement: Служебные заметки

Карточка задачи SHALL давать править служебные заметки: «вид работы» — выбор из значений kind_labels настроек (и текущего значения, если его там нет, и пустого), «статус по факту» — текст с подсказками из ключей status_labels, «сделано», «осталось», «заметка владельца» — многострочный текст. Сохранение SHALL отправлять PUT /issues/{key}/note только с изменёнными полями и updated_by: "web"; очищенное поле отправляется как null. После успеха SHALL показываться сохранённая заметка с временем и автором; при 422 — текст ошибки у формы без потери введённого. Уход со страницы с несохранёнными правками SHALL требовать подтверждения. Заметки пишутся только в хаб, в Jira приложение ничего не отправляет.

Scenario: Только изменённые поля

  • WHEN в заметке изменено только «осталось» и нажато «Сохранить»
  • THEN тело PUT /issues/{key}/note — {rest_text: "…", updated_by: "web"}

Scenario: Уход с несохранёнными правками

  • WHEN в форме заметки есть несохранённая правка и выбран раздел «Доска»
  • THEN приложение спрашивает подтверждение; при отказе остаётся в карточке с введённым текстом

Requirement: Сборка и разработка

Приложение SHALL быть npm-проектом в web/ (React, TypeScript, Vite) с точными версиями зависимостей и package-lock.json. make web SHALL устанавливать зависимости (npm ci) и собирать приложение в src/jira_hub/web/app/; каталог сборки не хранится в git. 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 не показывает файлов сборки