Andrey Epifancev ad48db2929 docs(openspec): viewer-live — размер встроенной выгрузки (#17)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:01:11 +04:00

84 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Context
Просмотрщик «Спринт 2 линк» — один HTML-файл (~117 КБ, из них ~19 КБ — встроенная выгрузка 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
1. **Одна страница — статика хаба.** Файл `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)`).
2. **`GET /board` — сырой снимок, а не модель доски.** Перевод в модель (вид работы по меткам `kind-*`, имена эпиков,
логические типы связей) остаётся на странице — там он уже есть для CSV, и React-приложению нужен тот же нейтральный
формат. Чтение — в одной транзакции (`BEGIN` на чтение: задачи, связи и `last_sync` из одного момента). Функция
`mirror.board(conn) -> dict`; `api` только оборачивает.
3. **`last_sync` — последний завершённый полный прогон:** `sync_run` с `kind = 'jql'`, `query` = текущая `base_jql`,
`finished_at` не пуст, по `started_at` последний. Обновления по ключам (хвостовые `refresh` пакетов) в «данные на» не
считаются: они обновляют часть задач.
4. **Вход по токену в браузере.** Токен — в `localStorage["jira-hub-token"]`, заголовок `Authorization: Bearer` в каждом
`fetch` к тому же хосту; 401 → форма токена с пояснением. Токен не попадает в HTML, URL и логи хаба.
5. **«Обновить из Jira» через существующие маршруты.** `GET /agent` (офлайн → предупреждение, пакет всё равно ставится),
`POST /refresh {full: true, author: "viewer"}`, опрос `GET /batches/{id}` каждые 3 с до терминального статуса пакета,
затем `GET /board`. Кнопка недоступна, пока пакет идёт; перезагрузка страницы во время пакета не теряет его — пакет
выполнится, «Перечитать» покажет результат.
6. **Модель доски из снимка (`fromHub`).** Эпики (`issue_type` «Фича»/Epic) — имена дорожек (`epic_key` → `summary`), сами
карточками не становятся; остальные задачи, включая соседей, — карточки; вид работы — первая метка из `KIND_LABELS`,
иначе `issue_type`; срок — `planned_end`, иначе `duedate`; связи — только типы из `LINK_TYPES`, `src = source`,
`dst = target`; ключи из связей, которых нет в зеркале, — заглушки на дорожке «Нет в зеркале». Имя источника — «Хаб».
7. **Импорт JSON в режиме хаба** сохраняет раскладку, дорожки и фильтры (как сейчас) и вместо показа снимка из JSON
перечитывает `GET /board`. Загрузка CSV — снимок файла с пометкой «из файла»; «Перечитать» возвращает данные хаба.
8. **Встроенная выгрузка удаляется** (`DEFAULT_CSV`, `DEFAULT_NAME` и их использование в `migrateStore`): без хаба
страница показывает причину и предлагает загрузить файл. Константа `EPICS` остаётся запасным словарём имён для CSV.
9. **Проверка страницы — сценарий в браузере.** Тестовой обвязки для 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
1. Merge → `git pull` в `jira-hub` → `launchctl kickstart -k gui/$(id -u)/ru.aepif.jira-hub`.
2. Открыть `http://127.0.0.1:8790/`, ввести клиентский токен (`uv run jira-hub tokens`).
3. В старой странице «Спринт 2 линк» — «экспорт JSON», в новой — загрузить этот JSON: раскладка применится к живым данным.
4. Откат: предыдущий `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 удаляется | устаревшие данные вводят в заблуждение; файл худеет на ~19 КБ | без хаба доска пуста до загрузки файла |