From 76b60e5397f7d9e95d5a054a2b4acfbe5dae050d Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 15:18:37 +0400 Subject: [PATCH] =?UTF-8?q?docs(web-modules):=20T8.3,=20M-5=20README,=20?= =?UTF-8?q?=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=2056=E2=80=9365=20?= =?UTF-8?q?=D0=B2=20design.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- README.md | 6 +++--- openspec/changes/web-modules/design.md | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 52b9e04..841a706 100644 --- a/README.md +++ b/README.md @@ -212,14 +212,14 @@ uv run sdlc-hub agent publish --module wiki ../eva-agent/src/agent.py | `GET /agent/known-keys` | агент | ключи зеркала | | `GET /agent/release` | агент | опубликованная версия агента `{version, sha256, content}`; 404 `no_release` | | `GET /agent/files/{sha256}` | агент | файл вложения `{file, size, content}` (`content` — base64); 404 `no_file` | -| `GET /projects`, `POST /projects` | клиент | проекты `{projects: […]}` (метка, название, `board_id`, `board_type`, число задач) / завести `{label, name, board_id?, author?}` → 201 `{project, batch_id}` и пакет «Новый проект <метка>» (`refresh` по общей JQL); 409 `project_exists`, 422 `bad_project`, 422 `bad_batch` (постановка `refresh` отказана) | +| `GET /projects`, `POST /projects`, `PATCH /projects/{метка}` | клиент | проекты `{projects: […]}`: метка, название, `created_at` и поля модулей — jira: `board_id`, `board_type`, `issues_count`, `jira_synced_at` (время последнего полного обновления из Jira; `null` — ещё не было), wiki: `wiki_root`, `wiki_synced_at`, `wiki_sync_error`. `POST` заводит `{label, name, поля модулей?, author?}` → 201 `{project, batch_id, batches}`: пакеты, поставленные модулями (`batches: [{module, id}]`; у jira — «Новый проект <метка>», `refresh` по общей JQL; `batch_id` — пакет первого модуля); 409 `project_exists`, 422 `bad_project`, 422 `bad_batch` (постановка отказана). `PATCH` меняет изменяемые поля модулей (иные — 422 `bad_project`) → 200 `{project, batch_ids, batches}` | | `GET /issues` | клиент | фильтры `project` (метка; неизвестная — 404), `sprint`, `assignee`, `work_kind`, `fact_status`, `status`, `label`, `epic`, `neighbor`, `q`, `hide_closed` (`1` — без задач в статусах из настройки `closed_statuses`, по умолчанию «Готово», «Снято»), `limit`, `offset` | | `GET /issues/{key}` | клиент | задача целиком, с комментариями и журналом изменений `changes` (новые сверху) | | `GET /changes` | клиент | лента изменений: `since`, `until` (ISO со смещением или `Z`, иначе 422; по умолчанию — последние 24 ч), `project` (только свои задачи), `key`, `kinds`, `limit` (до 2000) → `{since, until, truncated, changes}` | | `GET /board` | клиент | `?project=<метка>` (неизвестная — 404): снимок для доски — свои задачи проекта, соседи (связи в обе стороны) и эпики своих задач, с `fact_status`/`work_kind`; связи — те, у которых хотя бы один конец — своя задача проекта (без `project` — все задачи и связи), `last_sync` — последний успешный полный прогон | | `GET /`, `GET /links` | — | перенаправление на `/app/` («Обзор») | | `PUT /issues/{key}/note` | клиент | заметки (частично) | -| `POST /batches`, `GET /batches`, `GET /batches/{id}` | клиент | пакеты команд; `project` в теле `POST /batches` — пакет проекта (проверки метки, ограничение задач проектом), `?project=` у `GET /batches` — только пакеты проекта | +| `POST /batches`, `GET /batches`, `GET /batches/{id}` | клиент | пакеты команд; `project` в теле `POST /batches` — пакет проекта (проверки метки, ограничение задач проектом), `?project=` у `GET /batches` — только пакеты проекта, `?module=<имя>` — только пакеты модуля (по типу первой команды; неизвестный модуль — 404), `limit`; в каждом пакете поле `module` (`null` — не определён) | | `POST /batches/{id}/cancel` | клиент | отмена пакета | | `POST /commands/{id}/retry`, `POST /commands/{id}/cancel` | клиент | повтор (новый пакет; `project` в теле — отказ, если команда не из этого проекта) / отмена (`force` для взятой) | | `POST /refresh` | клиент | `{keys}`, `{jql}` или `{full: true}` (общая JQL по меткам всех проектов; проектов нет — 422 «нет проектов») | @@ -234,7 +234,7 @@ curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8790/issues?sprint= ## Веб-приложение -Адреса: `http://127.0.0.1:8790/app/` — «Обзор» `/app/?project=<метка>` (карточки модулей Jira и «Вики» с состоянием агента, свежестью данных и обновлением, последние пакеты проекта), доска `/app/jira/board?project=<метка>`, задачи `/app/jira/issues?project=<метка>`, лента изменений `/app/jira/feed?project=<метка>`, вики `/app/wiki?project=<метка>` (период и виды событий, история — и в карточке задачи), карточка `/app/jira/issues/{key}` — окно поверх списка или доски (закрытие — ✕, Esc, щелчок по фону; прямой адрес открывает окно поверх списка задач); `/` и +Адреса: `http://127.0.0.1:8790/app/` — «Обзор» `/app/?project=<метка>` (карточки модулей Jira и «Вики» с состоянием агента, свежестью данных и обновлением, последние пакеты проекта), доска `/app/jira/board?project=<метка>`, задачи `/app/jira/issues?project=<метка>`, лента изменений `/app/jira/feed?project=<метка>` (период и виды событий, история — и в карточке задачи), вики `/app/wiki?project=<метка>`, карточка `/app/jira/issues/{key}` — окно поверх списка или доски (закрытие — ✕, Esc, щелчок по фону; прямой адрес открывает окно поверх списка задач); `/` и `/links` перенаправляют на «Обзор»; прежние адреса `/app/board`, `/app/issues`, `/app/feed` ведут на страницы модуля Jira. Вход по клиентскому токену (`uv run sdlc-hub tokens`), токен хранится в браузере; `localhost` и `127.0.0.1` — разные сайты для браузера, токен и раскладка у каждого свои. Выкатка на Mac mini: diff --git a/openspec/changes/web-modules/design.md b/openspec/changes/web-modules/design.md index eb406bb..09312d3 100644 --- a/openspec/changes/web-modules/design.md +++ b/openspec/changes/web-modules/design.md @@ -130,7 +130,7 @@ type HubModule = { | 26 | 24.10.2026 | Task 3: состояние на модуль — хук-трекер на модуль (`useTracker`, вызывается по константному списку `TRACKED`) и `useQuery` пакета в каждом, а не общий `useQueries` | перенос логики остановки опроса без изменений; `useQueries` потребовал бы переписать эффект завершения | заменить на `useQueries` | | 27 | 24.10.2026 | Task 3: `start` берёт агента свежим `fetchQuery(["agents"], staleTime 0)`, а не из кэша; ошибка этого запроса — событие `hub-down`/`error`, как у прежнего `GET /agent` | сохраняет поведение прежних сценариев (hub-down, 401 на предпроверке) | брать из кэша `useAgents()` | | 28 | 24.10.2026 | Task 3: wiki `refresh.start` приводит ответ `{batch_id}` к `{id}`; `track()` перехватывает отслеживание, если текущий пакет не «свой» (раньше — только если ничего не отслеживалось) | контракт ядра единый `{id}`; пакет нового проекта важнее чужого | отдельная форма ответа / прежняя проверка | -| 29 | 24.10.2026 | Task 3: сводка `useActivitySummary`: error > offline > busy > online | ошибка и офлайн агента важнее, чем «идёт пакет» | поменять порядок | +| 29 | 24.10.2026 | ~~Task 3: сводка `useActivitySummary`: error > offline > busy > online~~ — заменено решением 56 | ошибка и офлайн агента важнее, чем «идёт пакет» | поменять порядок | | 30 | 24.10.2026 | Task 3: временный `shell/activity/StatusBlock.tsx` (строки модулей и строка событий узкого экрана) вместо `SidebarStatus`/`TopbarNotice` модулей; Task 4 заменяет его; страница вики берёт действие из трекера, уведомление об ошибке постановки (`syncError`) убрано — ошибка видна событием модуля | оболочка должна работать и набор быть зелёным до Task 4 | вернуть Notice на странице вики | | 31 | 24.10.2026 | Task 3, fix T3.I1: подхват после 409 (`pickup(true)`) заменяет отслеживаемый чужой пакет первым (самым старым) другим незавершённым пакетом модуля; если другого нет — чужой остаётся | детали 409 (`batch_id`) клиент не разбирает, а список без команд не различает типы | брать `batch_id` из `ApiError.detail` | | 32 | 24.10.2026 | Task 4: «Обзор» (`/`) пока рисует `shell/OverviewPage.tsx` — заглушка с заголовком под `ProjectGate`; Task 5 заменяет её страницей со сводками | пункт «Обзор» и адрес `/` нужны панели и перенаправлениям уже сейчас | оставить `/` → `/jira/board` до Task 5 | @@ -157,3 +157,13 @@ type HubModule = { | 53 | 24.10.2026 | Task 8: проверка «без горизонтальной прокрутки» на 1440 px — `document.documentElement.scrollWidth <= window.innerWidth`, а не `===` | при классической полосе прокрутки Chromium `scrollWidth` равен ширине без полосы (1425 против 1440), равенство невозможно; смысл проверки — отсутствие переполнения — сохранён | ложное «зелёное» только если переполнение скрыто тем же зазором; на 390 px (оверлейные полосы) проверка прежняя и строгая | | 54 | 24.10.2026 | Task 8, дефект: у `.issue-table-wrap` добавлен `position: relative` | скрытый текст «, просрочено» (`.visually-hidden`, `position:absolute`) не имел позиционированного предка внутри обёртки и выходил за её прокрутку: на 390 px `scrollWidth` страницы 778 при окне 390 | без правки на узком экране у «Задач» появлялась горизонтальная прокрутка всей страницы | | 55 | 24.10.2026 | Task 8: «Последние пакеты проекта» проверяются пакетом «Обновить вики» (пакет проекта), а не «Обновить из Jira»; для событий пакета Jira в e2e — хелпер `jiraEvent` (строка модуля в панели / полоса на узком экране) | пакет «Обновить из Jira» не привязан к проекту (охват «все проекты») и в список проекта не попадает; на странице несколько `role=status`, прежний `getByRole("status")` неоднозначен | только тесты; список пакетов проекта и так фильтруется хабом по `project` | +| 56 | 24.10.2026 | Final fix wave, I-2: сводная точка — error > busy > offline > online (заменяет решение 29) | спека «Оболочка и адреса» — источник истины: ошибка, затем «пакет в работе», затем агенты на связи или нет | перепутанный порядок красит точку «офлайн» при идущем пакете; спека и код расходятся | +| 57 | 24.10.2026 | Final fix wave, M-6/T2.3: офлайн-агент модуля сводку не красит, если `freshness(project).available === false` (вики без корня); проект для проверки провайдер берёт из `useProjects()` (тот же ключ запроса, без нового запроса); пока проекты не загружены — агент считается значимым | ядру нечего показывать и обновлять у модуля без данных проекта; `freshness(p: Project)` не менялась | кратковременно (до загрузки проектов) возможна «офлайн» у проекта без корня вики | +| 58 | 24.10.2026 | Final fix wave, T2.3: `jira_synced_at` убрано из ядрового `Project` и перенесено в `JiraProject` (`modules/jira/projectFields.ts`); `jira/status.ts` читает поле через type guard `hasJiraSync(p)` без `as`; `boundary.test.ts` ловит имена полей модулей в ядре (`MODULE_TERMS` расширен) | ядро не должно знать поля модулей; контракт `freshness(p: Project)` сохранён — минимальное решение без обобщения `ModuleStatus` | проект без поля (старый ответ хаба) показывается как «обновления ещё не было» | +| 59 | 24.10.2026 | Final fix wave, M-11: в `agentText.ts` предупреждение/`update_hint` важнее офлайна (офлайн-агент с предупреждением показывается как «обновите агента») | предупреждение требует действия человека и объясняет, почему агент не работает, а «офлайн» может пройти сам | человек увидит «обновите агента» у агента, который ещё и офлайн; после обновления текст сменится на «офлайн» | +| 60 | 24.10.2026 | Final fix wave, M-9: принято как известное ограничение — `jira_synced_at` сбрасывается после создания проекта: `last_full_sync` сравнивает JQL прогона с текущей базовой JQL, а при новом проекте она меняется; до нового полного прогона у всех проектов «обновления ещё не было». Кодом не чинится, follow-up | правка — в хабе (Python), вне объёма веб-change | у всех проектов после создания нового до следующего «Обновить из Jira» нет даты свежести | +| 61 | 24.10.2026 | Final fix wave, #68 M-2: меню доски названо «Ещё — доска»; меню панели остаётся «Ещё» | однозначные доступные имена двух меню на одной странице; тесты больше не зависят от гонки | пользовательские сценарии, искавшие «Ещё» на доске, ищут новое имя | +| 62 | 24.10.2026 | Final fix wave, #73 M-2: выезжающая панель обёрнута в `div.sidebar-modal[role=dialog][aria-modal][aria-label=Меню]` (`display: contents`), `