10 KiB
Context
Просмотрщик «Спринт 2 линк» — один HTML-файл (~117 КБ, из них ~100 КБ — встроенная выгрузка CSV от 11.10.2026) без
зависимостей: разбор CSV фильтра Jira (fromJiraCsv) → модель {nodes, links, sprints} → доска (дорожки по эпикам,
карточки, стрелки блокировок, режим «по датам», критический путь, панель проблем, таблица), раскладка и фильтры —
в localStorage под ключами graph-<имя>:board, не зависящими от источника; экспорт/импорт JSON (arm-ka-links/1).
Модель узла: {key, title, kind, prio, due, status, epic, sprint, who}; связи {src, dst, type} с логическими типами
«блокирует начало / блокирует завершение / связанные» (LINK_TYPES переводит имена Jira ASUPR_*).
Хаб (main fc732eb): зеркало issue содержит все нужные поля (planned_end, epic_key, labels, sprint, assignee,
issue_type), связи — issue_link (source_key, target_key, link_type) с семантикой Jira, заметки — issue_note,
журнал синхронизаций — sync_run. HTTP — http.server, маршруты ROUTES с областью токена, ответы только JSON (_send).
GET /issues постраничный и без связей.
Goals / Non-Goals
Goals: доска на живых данных хаба на Mac mini без ручной выгрузки; обновление из Jira кнопкой; перенос раскладки старой страницы; API, пригодный будущему React-приложению.
Non-Goals: запись из доски (viewer-edit), React-приложение, доступ из сети, изменение логики доски.
Decisions
- Одна страница — статика хаба. Файл
src/jira_hub/web/links.htmlвходит в пакет (hatch включает файлы каталога пакета), читается при запросе черезPath(__file__).parent / "web" / "links.html". МаршрутыGET /иGET /linksбез области токена (вROUTES— областьNone,_authне вызывается); ответ —text/html; charset=utf-8,Cache-Control: no-cache(страница меняется вместе с хабом). Вapi— отправка не-JSON ответа (обработчик возвращаетRaw(content_type, bytes)). GET /board— сырой снимок, а не модель доски. Перевод в модель (вид работы по меткамkind-*, имена эпиков, логические типы связей) остаётся на странице — там он уже есть для CSV, и React-приложению нужен тот же нейтральный формат. Чтение — в одной транзакции (BEGINна чтение: задачи, связи иlast_syncиз одного момента). Функцияmirror.board(conn) -> dict;apiтолько оборачивает.last_sync— последний завершённый полный прогон:sync_runсkind = 'jql',query= текущаяbase_jql,finished_atне пуст, поstarted_atпоследний. Обновления по ключам (хвостовыеrefreshпакетов) в «данные на» не считаются: они обновляют часть задач.- Вход по токену в браузере. Токен — в
localStorage["jira-hub-token"], заголовокAuthorization: Bearerв каждомfetchк тому же хосту; 401 → форма токена с пояснением. Токен не попадает в HTML, URL и логи хаба. - «Обновить из Jira» через существующие маршруты.
GET /agent(офлайн → предупреждение, пакет всё равно ставится),POST /refresh {full: true, author: "viewer"}, опросGET /batches/{id}каждые 3 с до терминального статуса пакета, затемGET /board. Кнопка недоступна, пока пакет идёт; перезагрузка страницы во время пакета не теряет его — пакет выполнится, «Перечитать» покажет результат. - Модель доски из снимка (
fromHub). Эпики (issue_type«Фича»/Epic) — имена дорожек (epic_key→summary), сами карточками не становятся; остальные задачи, включая соседей, — карточки; вид работы — первая метка изKIND_LABELS, иначеissue_type; срок —planned_end, иначеduedate; связи — только типы изLINK_TYPES,src = source,dst = target; ключи из связей, которых нет в зеркале, — заглушки на дорожке «Нет в зеркале». Имя источника — «Хаб». - Импорт JSON в режиме хаба сохраняет раскладку, дорожки и фильтры (как сейчас) и вместо показа снимка из JSON
перечитывает
GET /board. Загрузка CSV — снимок файла с пометкой «из файла»; «Перечитать» возвращает данные хаба. - Встроенная выгрузка удаляется (
DEFAULT_CSV,DEFAULT_NAMEи их использование вmigrateStore): без хаба страница показывает причину и предлагает загрузить файл. КонстантаEPICSостаётся запасным словарём имён для CSV. - Проверка страницы — сценарий в браузере. Тестовой обвязки для JS в хабе нет; страница проверяется прогоном через
Playwright на хабе с тестовой базой (
JIRA_HUB_HOMEво временном каталоге, зеркало заполнено черезPOST /agent/state) и затем на живом хабе: вход, доска, «Обновить из Jira», «Перечитать», импорт JSON, неверный токен, остановленный хаб. Сторона хаба — pytest.
Risks / Trade-offs
- [Снимок всего зеркала на каждый запрос] → ~200 задач и сотни связей — десятки КБ; при росте — фильтр по спринту в параметрах (не сейчас).
- [Токен в
localStorageдоступен скриптам страницы] → страница своя, сторонних скриптов нет; доступ только с Mac mini. - [Расхождение
KIND_LABELS/LINK_TYPESстраницы с настройками хаба (kind_labels,link_types)] → словари совпадают на 14.10.2026; синхронизация из/settings— в React-приложении. - [Страница отдаётся без токена] → в ней нет данных; все данные — через API с токеном.
Migration Plan
- Merge →
git pullвjira-hub→launchctl kickstart -k gui/$(id -u)/ru.aepif.jira-hub. - Открыть
http://127.0.0.1:8790/, ввести клиентский токен (uv run jira-hub tokens). - В старой странице «Спринт 2 линк» — «экспорт JSON», в новой — загрузить этот JSON: раскладка применится к живым данным.
- Откат: предыдущий
mainи перезапуск; старая страница вadmin/работает как раньше.
Open Questions
Нет.
Decisions made without discussion
| Маркер | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| R-1 (design #2) | 2026-10-14 | GET /board отдаёт сырые данные зеркала, перевод в модель доски — на странице |
нейтральный формат нужен React-приложению; разбор уже есть на странице | лишний перевод на клиенте — дёшево переделать |
| R-2 (design #3) | 2026-10-14 | «Данные на» — время последнего полного прогона, обновления по ключам не учитываются | частичное обновление не делает всю доску свежей | владелец может видеть время старше последних изменений по отдельным задачам |
| R-3 (design #5) | 2026-10-14 | Пакет обновления — автор viewer, опрос каждые 3 с |
автор отличает пакеты доски от claude/hub; 3 с — ощутимо и не грузит хаб |
нет |
| R-4 (design #6) | 2026-10-14 | Соседи по связям — обычные карточки, заглушки только для ключей вне зеркала | у соседей в зеркале полные данные | соседей без спринта скрывает фильтр спринта, как и раньше |
| R-5 (design #8) | 2026-10-14 | Встроенная выгрузка от 11.10.2026 удаляется | устаревшие данные вводят в заблуждение; файл худеет со ~117 до ~17 КБ | без хаба доска пуста до загрузки файла |