spec(web-modules): веб под модули — состояние модулей, «Обзор», адреса модулей
Change web-modules по аудиту веба 24.10.2026 (#98). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eb86563e2a
commit
58d8891d06
2
openspec/changes/web-modules/.openspec.yaml
Normal file
2
openspec/changes/web-modules/.openspec.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-24
|
||||
118
openspec/changes/web-modules/design.md
Normal file
118
openspec/changes/web-modules/design.md
Normal file
@ -0,0 +1,118 @@
|
||||
## Context
|
||||
|
||||
Основа — `main` после слияния sdlc-rename (#97): пакет `sdlc_hub`, бренд «SDLC Hub», ключи `sdlc-hub-*`. Ветка
|
||||
`feat/98-web-modules` перебазируется на него до исполнения плана.
|
||||
|
||||
Аудит веба 24.10.2026 (сабагент, код + живой хаб, скриншоты в scratchpad сессии) — главное:
|
||||
- контракт `HubModule` (`web/src/modules/types.ts`): `name`, `pages`, `nav`, `Provider`, `SidebarStatus`
|
||||
(компонент), `TopbarNotice`, `useOverlay`, `projectFields`, `useOnProjectCreated(batchId)`;
|
||||
- `RefreshProvider` (модуль jira, 174 строки) — единственный трекер пакетов: подхват пакетов `viewer` с первой
|
||||
командой `refresh` (по `GET /batches/{id}` на каждого кандидата), один опрос, остановка, перечитывание; агент — `GET
|
||||
/agent` (только jira); время — `last_sync` из снимка `/board` (грузится на каждой странице);
|
||||
- wiki: `SyncStatus` — текст из `GET /projects` + `GET /wiki/status`, кнопка только в шапке страницы;
|
||||
- ядро знает jira: `/` → `/board` в модуле jira, типы jira и `fact_status` в `api/types.ts`, `api/closed.ts`, бренд;
|
||||
модуль wiki импортирует `fmtStamp` из модуля jira;
|
||||
- `modules/jira/issues/wiki/wiki.css` (`div.wiki h1…`) перебивает заголовки страницы «Вики» (корень `div.wiki`);
|
||||
- `POST /projects` → один `batch_id`, оболочка раздаёт его всем модулям;
|
||||
- API: `GET /agents` уже есть (все поля агента), `GET /projects` уже несёт свежесть wiki, у пакета нет модуля.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** оболочка рисует состояние модулей по их описанию; пакеты и агенты отслеживает ядро; навигация и главная
|
||||
страница — модульные; граница ядра и модулей в вебе проверяется тестами; найденные аудитом дефекты страниц исправлены.
|
||||
|
||||
**Non-Goals и Anti-Goals:** см. proposal.md.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Навигация — группы по модулям + «Обзор»** (вариант «А + Б» аудита). Группы оставляют все разделы на виду (их
|
||||
четыре), «Обзор» даёт модульную главную вместо `/board` и место для сводки. Переключатель модулей (вариант «В»)
|
||||
отклонён: лишний щелчок между доской и вики ради масштаба, которого нет.
|
||||
2. **Адреса с префиксом модуля** (`/app/jira/…`, `/app/wiki`) и перенаправления со старых. Делается сейчас, пока
|
||||
ломается всё сразу (переименование); пути модулей не пересекаются по построению. Перенаправления объявляет модуль
|
||||
jira (это его прежние адреса). `GET /` хаба — на `/app/`.
|
||||
3. **Состояние модуля — данные (`ModuleStatus`), рисует ядро.** Компонент `SidebarStatus` убирается: из-за него блок и
|
||||
был разнобойным. Свежесть берётся из уже загруженного `GET /projects` чистой функцией от проекта — без запросов
|
||||
модуля: jira — `jira_synced_at` (новое поле, общее для проектов), wiki — `wiki_synced_at`/`wiki_sync_error`.
|
||||
4. **Один трекер пакетов в ядре** (`shell/activity/`): обобщение `RefreshProvider` — состояние на модуль
|
||||
(`event`, `active`, `busy`), опрос `GET /batches/{id}`, правила остановки прежние. Подхват — по полю `module` из
|
||||
`GET /batches?module=` (новое), любого автора: автообновления хаба и пакеты из MCP теперь видны (С1/В1 аудита), а
|
||||
подхват не делает запрос на каждого кандидата. Прежнее правило «только viewer + refresh» отменено (сценарий «Пакет с
|
||||
другими командами» меняется: пакет показывается, но не блокирует кнопку).
|
||||
5. **Кнопка недоступна только на время пакета своего действия обновления** (jira — первая команда `refresh`, wiki —
|
||||
`wiki_sync`; тип виден в `GET /batches/{id}`, который трекер и так опрашивает). Иначе долгое скачивание вложения
|
||||
блокировало бы «Обновить из Jira».
|
||||
6. **Обновление: значок в строке модуля + вторичная кнопка в шапке страниц модуля**, primary для обновления нет. Сейчас
|
||||
на «Вики» две зелёные кнопки (в панели и в шапке) — против правила AURA «одна primary на экран».
|
||||
7. **Агенты — один опрос `GET /agents`** раз в 60 с; точка агента: на связи / офлайн / «обновите агента» (`warning`
|
||||
или `update_hint`), всегда с текстом.
|
||||
8. **Узкий экран**: в верхней строке «Меню», метка проекта и сводная точка; отдельная кнопка «Обновить из Jira» и
|
||||
`TopbarNotice` из контракта убираются (событие пакета показывает ядро строкой под верхней строкой). Выезжающая
|
||||
панель — модальная (#73 M-1…M-3).
|
||||
9. **Пакеты нового проекта по модулям**: `POST`/`PATCH /projects` отдают `batches: [{module, id}]`; `batch_id` и
|
||||
`batch_ids` остаются для MCP. Оболочка передаёт каждому модулю его пакет (снимает M-14/T5.3 #68 и потерю `wiki_sync`).
|
||||
10. **Граница в вебе — тестами**: импорты (ядро → только `modules/index.ts`; модуль → не другой модуль), уникальность
|
||||
имён модулей и `nav.to` (T7.5/T7.6 #90), пересечение имён классов CSS между модулями и с ядром. Префиксы классов
|
||||
не вводятся: тест на пересечение ловит именно случившийся дефект и не требует переименовывать сотни классов.
|
||||
`div.wiki` разметки Jira переименовывается в `.jira-markup`.
|
||||
11. **Типы jira — в модуль jira** (`modules/jira/api/types.ts`), `closed.ts` — туда же; `fact_status` удаляется из веба
|
||||
(веб-часть #60). В `api/types.ts` ядра остаются `Batch`, `AgentView` (полный вид из `GET /agents`), `Project`,
|
||||
`CreatedProject`, `UpdatedProject`, `HubSettings` без словарей jira.
|
||||
12. **Форматтеры дат — ядро** (`ui/format.ts`): `fmtStamp` («ДД.ММ ЧЧ:ММ»), `fmtDate` («ДД.ММ», не текущий год —
|
||||
«ДД.ММ.ГГГГ»). Списки и карточка перестают выводить ISO.
|
||||
13. **«Обзор» берёт сводку у модуля** (`Summary({project})`); счётчики jira — из снимка доски проекта (запрос только на
|
||||
«Обзоре»); вики — `wiki_pages` из проекта.
|
||||
14. **Порядок архива**: sdlc-rename архивируется раньше web-modules: дельты `web-app`/`hub-api` этого change написаны
|
||||
поверх текстов после переименования.
|
||||
|
||||
## Контракт веб-модуля (после change)
|
||||
|
||||
```ts
|
||||
type ModuleStatus = {
|
||||
agent?: string; // имя модуля агента в GET /agents
|
||||
scope: "all" | "project"; // охват свежести и обновления
|
||||
freshness(p: Project): { available: boolean; at: string | null; error: string | null };
|
||||
refresh?: {
|
||||
label: string; // «Обновить из Jira», «Обновить вики»
|
||||
start(project: string): Promise<{ id: number; estimate_seconds?: number }>; // 409 → ApiError, ядро подхватит идущий
|
||||
isOwn(b: Batch): boolean; // пакет этого действия (по commands[0].type)
|
||||
};
|
||||
invalidate(qc: QueryClient, project: string | null): void; // что перечитать после пакета модуля
|
||||
};
|
||||
type HubModule = {
|
||||
name: string; title: string; // «jira» / «Jira»
|
||||
pages: RouteObject[]; nav: NavItem[]; // nav.to — с префиксом модуля
|
||||
status?: ModuleStatus;
|
||||
Summary?: ComponentType<{ project: Project }>; // карточка «Обзора»
|
||||
Provider?, useOverlay?, projectFields?; // как было; SidebarStatus, TopbarNotice, useOnProjectCreated — удалены
|
||||
};
|
||||
```
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Подхват пакетов любого автора** покажет и пакеты, поставленные из чата (`claude`), — это желаемо, но строка «Jira»
|
||||
будет чаще «занята». Кнопка при этом не блокируется (решение 5).
|
||||
- **Счётчики на «Обзоре»** грузят снимок доски — тяжёлый запрос; только на этой странице и с тем же ключом кэша, что у
|
||||
доски.
|
||||
- **Смена адресов** ломает закладки — их держат перенаправления; внешних ссылок на веб нет (MCP веб-адресов не отдаёт).
|
||||
|
||||
## Decisions made without discussion
|
||||
|
||||
Владелец 24.10.2026 ответил «делай» на вопросы brainstorming — решения 1–14 выше приняты агентом по аудиту.
|
||||
|
||||
| # | Дата | Решение | Обоснование | Цена ошибки |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 24.10.2026 | Навигация — группы + «Обзор» | 4 раздела, 2 модуля; нужна модульная главная | переделка панели |
|
||||
| 2 | 24.10.2026 | Адреса `/app/<модуль>/…` с перенаправлениями | заодно с переименованием, пути не пересекаются | вернуть пути, перенаправления наоборот |
|
||||
| 3 | 24.10.2026 | Состояние модуля — данные, рисует ядро | разнобой блоков возник из компонента | — |
|
||||
| 4 | 24.10.2026 | Трекер в ядре, подхват любого автора по `module` | видны автообновления и пакеты MCP | вернуть фильтр по автору |
|
||||
| 5 | 24.10.2026 | Блокирует только пакет своего обновления | долгие пакеты не мешают обновлению | — |
|
||||
| 6 | 24.10.2026 | Обновление — значок в панели + secondary в шапке | одна primary на экран | перекраска кнопок |
|
||||
| 7 | 24.10.2026 | Один опрос `/agents` | агент wiki не виден сейчас | — |
|
||||
| 8 | 24.10.2026 | Узкий экран: метка проекта + сводная точка | проект не виден, две кнопки обновления | вернуть кнопку |
|
||||
| 9 | 24.10.2026 | `batches` по модулям, старые поля остаются | пакет wiki терялся; MCP не ломать | — |
|
||||
| 10 | 24.10.2026 | Тест на пересечение классов CSS вместо префиксов | ловит дефект без массового переименования | ввести префиксы позже |
|
||||
| 11 | 24.10.2026 | Типы jira — в модуль, `fact_status` из веба удалён | граница ядра; #60 | вернуть поле в тип |
|
||||
| 12 | 24.10.2026 | Форматтеры дат в ядре | три формата дат, импорт wiki → jira | — |
|
||||
| 13 | 24.10.2026 | Сводка «Обзора» от модуля | ядро не знает данных модулей | — |
|
||||
| 14 | 24.10.2026 | Архив после sdlc-rename | дельты поверх новых имён | правка дельт при архиве |
|
||||
58
openspec/changes/web-modules/proposal.md
Normal file
58
openspec/changes/web-modules/proposal.md
Normal file
@ -0,0 +1,58 @@
|
||||
## Why
|
||||
|
||||
Веб хаба собирался, когда модуль был один. После hub-modules (#87) и wiki-module (#91) у хаба два модуля со своими
|
||||
агентами, а оболочка по-прежнему говорит на языке Jira. Владелец 24.10.2026 заметил, что блок в боковой панели вводит в
|
||||
заблуждение: «агент на связи» — это только агент jira, у вики нет ни агента, ни кнопки, неясно, к чему относится «данные
|
||||
на …». Владелец попросил пересмотреть весь веб и организовать его под модули в новом имени sdlc-hub (#97).
|
||||
|
||||
Аудит 24.10.2026 (код, спеки, живой хаб в двух темах и на 390 px) показал, что это не одна подпись:
|
||||
- в контракте веб-модуля нет понятия «состояние модуля» (агент, свежесть данных и её охват, обновление, пакет в
|
||||
работе): каждый модуль рисует свой блок по-своему, оболочка не может ни свести их, ни показать сводку;
|
||||
- блок jira грузит полный снимок доски на каждой странице ради одного времени `last_sync`;
|
||||
- ход пакетов отслеживает только jira; автообновления хаба и пакеты из MCP не видны, время данных отстаёт;
|
||||
- оболочка привязана к jira: `/` ведёт на доску, типы jira и `closed.ts` лежат в ядре, модуль wiki импортирует
|
||||
форматтер из модуля jira;
|
||||
- CSS модулей не разделён: правило разметки Jira в карточке (`div.wiki h1`) ломает заголовки страницы «Вики»;
|
||||
- пакет синхронизации вики нового проекта теряется: `POST /projects` отдаёт один `batch_id`;
|
||||
- страница «Задачи» прокручивается по горизонтали на 1440 px, просрочка в таблице показана только цветом.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Состояние модуля — данные, а не компонент.** Модуль описывает: название для показа, агента, свежесть (время, охват
|
||||
«все проекты» / «проект», ошибка), действие обновления и правило «чей пакет». Ядро рисует блок одинаково в боковой
|
||||
панели, свёрнутой панели, на узком экране и на «Обзоре».
|
||||
- **Ядро отслеживает пакеты и агентов.** Один опрос `GET /agents` для всех модулей; один трекер пакетов (вынос
|
||||
`RefreshProvider` из модуля jira) — подхват незавершённого пакета модуля любого автора, тексты событий, 409, остановка
|
||||
опроса, перечитывание данных модуля по завершении.
|
||||
- **Навигация по модулям и «Обзор».** Боковая панель группирует разделы под заголовками модулей; новая страница ядра
|
||||
«Обзор» (`/app/?project=`) — главная: карточка каждого модуля (состояние + сводка от модуля) и последние пакеты
|
||||
проекта.
|
||||
- **Адреса модулей с префиксом модуля:** `/app/jira/board`, `/app/jira/issues`, `/app/jira/issues/{key}`,
|
||||
`/app/jira/feed`, `/app/wiki`; прежние адреса перенаправляют с сохранением параметров; `/` и `/links` хаба — на `/app/`.
|
||||
- **API для блока состояния:** `GET /projects` — время последнего полного обновления Jira; `GET /batches` — модуль
|
||||
пакета; `POST`/`PATCH /projects` — пакеты по модулям.
|
||||
- **Граница ядра и модулей в вебе:** типы jira, `closed.ts` и словари jira уходят в модуль jira; форматтеры дат — в
|
||||
ядро; классы CSS модуля не совпадают с классами другого модуля и ядра (тест-страж); `fact_status` удаляется из веба.
|
||||
- **Доработки по аудиту:** заголовок вкладки «<страница> · <проект> · SDLC Hub»; окно «Новый проект» с группами полей
|
||||
по модулям и подсказками под полем; узкий экран с меткой проекта и сводной точкой состояния, модальная выезжающая
|
||||
панель; «Задачи» без горизонтальной прокрутки страницы, просрочка текстом; «Вики» открывает корень и показывает
|
||||
понятную шапку; мелкие тексты («Команды хаба», подсказка выбора проекта).
|
||||
- **Закрываемые follow-ups:** #94 (веб: T4.1, T4.2, T4.4, T4.5, T10.1–T10.7, T9.1), #90 (T7.5, T7.6), #73 (M-1…M-7),
|
||||
#68 (N-1, N-2, M-2, M-12, M-14, M-17, M-18, T5.3), #35 (M-9, O-16; T3.1/M-10 — отметить сделанным).
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Переименование (#97) — делается раньше; этот change строится на `sdlc_hub`, «SDLC Hub», ключах `sdlc-hub-*`.
|
||||
- Страница «Агенты и очередь», настройки хаба в вебе.
|
||||
- Полное удаление «статуса по факту» из базы, API, MCP и агента (#60); здесь — только из типов веба.
|
||||
- Разметка Jira в ленте (С6 аудита), сворачивание фильтров доски на телефоне (С12), 409 `no_root` у `/wiki/status`
|
||||
(С10), доступность компонентов `ui/` (`Menu`, `Segmented`, `ThemeSwitch`), доработки окна карточки из #42, служебные
|
||||
метки `fact-*`/`kind-*` в карточке — в follow-up.
|
||||
- Изменения логики доски, списка задач, ленты и вики сверх перечисленного.
|
||||
|
||||
## Anti-Goals
|
||||
|
||||
- Блок состояния, который снова надо читать как загадку: у каждой строки должно быть видно, к какому модулю она
|
||||
относится и что именно обновит кнопка (все проекты или текущий).
|
||||
- Ядро веба, знающее про jira или wiki по имени: всё, что оболочка показывает о модуле, приходит из описания модуля.
|
||||
- Две основные (primary) кнопки на одном экране.
|
||||
112
openspec/changes/web-modules/specs/hub-api/spec.md
Normal file
112
openspec/changes/web-modules/specs/hub-api/spec.md
Normal file
@ -0,0 +1,112 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Раздача веб-приложения
|
||||
Хаб SHALL отдавать собранное веб-приложение (спека `web-app`) из каталога пакета `src/sdlc_hub/web/app/`, читая файлы
|
||||
с диска на каждый запрос:
|
||||
- `GET /app/assets/<путь>` — файл из `assets/` сборки; путь, который после разрешения выходит за `assets/`, не
|
||||
существует или содержит нулевой байт, — 404; `Content-Type` по расширению (`.js` — `text/javascript; charset=utf-8`, `.css` —
|
||||
`text/css; charset=utf-8`, `.svg` — `image/svg+xml`, `.png` — `image/png`, `.ico` — `image/x-icon`, `.woff2` —
|
||||
`font/woff2`, `.map` — `application/json`, прочее — `application/octet-stream`);
|
||||
`Cache-Control: public, max-age=31536000, immutable`;
|
||||
- любой другой `GET /app` или `GET /app/…` — `index.html` сборки, `text/html; charset=utf-8`, `Cache-Control: no-cache`;
|
||||
- если `index.html` сборки нет — на любой `GET /app…` 503 `text/html; charset=utf-8` с текстом, что приложение не
|
||||
собрано и нужно выполнить `make web` в каталоге sdlc-hub;
|
||||
- `GET /` и `GET /links` — 302 с `Location: /app/` (главная страница приложения — «Обзор»).
|
||||
Сборка приложения не хранится в git; её создаёт `make web`.
|
||||
|
||||
#### Scenario: Файл сборки
|
||||
- **WHEN** в сборке есть `assets/index-abc123.js`, запрошен `GET /app/assets/index-abc123.js`
|
||||
- **THEN** 200, `Content-Type: text/javascript; charset=utf-8`, `Cache-Control` с `immutable`, тело — содержимое файла
|
||||
|
||||
#### Scenario: Путь приложения
|
||||
- **WHEN** запрошен `GET /app/issues/AURORA-1620`
|
||||
- **THEN** 200, тело — `index.html` сборки, `Cache-Control: no-cache`
|
||||
|
||||
#### Scenario: Выход за каталог сборки
|
||||
- **WHEN** запрошен `GET /app/assets/../../api.py` (в том числе в виде `%2e%2e`)
|
||||
- **THEN** 404, содержимое файлов вне `assets/` не отдаётся
|
||||
|
||||
#### Scenario: Приложение не собрано
|
||||
- **WHEN** каталога сборки нет, запрошен `GET /app/board`
|
||||
- **THEN** 503, тело — HTML с подсказкой `make web`
|
||||
|
||||
#### Scenario: Старый адрес доски
|
||||
- **WHEN** запрошен `GET /links` или `GET /`
|
||||
- **THEN** 302, `Location: /app/`
|
||||
|
||||
#### Scenario: Нулевой байт в пути
|
||||
- **WHEN** запрошен `GET /app/assets/%00` или `GET /app/assets/a%00.js`
|
||||
- **THEN** 404 `not_found`, а не 500
|
||||
|
||||
### Requirement: Маршруты проектов
|
||||
Хаб SHALL предоставлять под клиентским токеном:
|
||||
- `GET /projects` → список проектов по `created_at`, затем `label`; каждый — плоский объект из полей ядра (`label`,
|
||||
`name`, `created_at`) и полей всех модулей (`modules`, «Поля проекта от модулей»). Модуль jira даёт `board_id`,
|
||||
`board_type`, `issues_count` (задачи зеркала с меткой проекта), `jira_synced_at` (время завершения последнего полного
|
||||
обновления зеркала, общее для всех проектов; `null`, если его не было), модуль wiki — `wiki_root`, `wiki_root_title`,
|
||||
`wiki_pages`, `wiki_synced_at`, `wiki_sync_error`.
|
||||
- `POST /projects` `{label, name, author?, <поля модулей>}` → 201 `{project, batch_id, batches}`: в одной транзакции
|
||||
проект записывается, модули записывают свои поля и ставят свои пакеты; `batches` — список `{module, id}` всех
|
||||
поставленных пакетов в порядке модулей; `batch_id` — пакет первого модуля, вернувшего пакет (у jira — «Новый проект <метка>» из одного `refresh` по базовой JQL, уже включающей новую метку). Автор пакетов —
|
||||
`author` (по умолчанию `api`). Неизвестное поле, неверные поля ядра (см. `data-model`, «Проекты») или модуля — 422 с
|
||||
указанием поля; метка, совпадающая с существующей без учёта регистра, — 409. При ошибке ни проекта, ни пакетов нет.
|
||||
- `PATCH /projects/{label}` `{<изменяемые поля модулей>, author?}` → 200 `{project, batch_ids, batches}`: модули правят
|
||||
свои поля и ставят пакеты в одной транзакции; `batches` — список `{module, id}`. Неизменяемое поле (метка, название, `board_id`) — 422 «<поле> после создания не
|
||||
меняется»; неизвестное поле или неверное значение — 422 с указанием поля; проекта нет — 404.
|
||||
|
||||
#### Scenario: Новый проект
|
||||
- **WHEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", board_id: 5120, author: "viewer"}`, есть проект `AWPCA`
|
||||
- **THEN** 201; `GET /projects` отдаёт два проекта, у `STOCKS_DRRK` `board_type = null`, `wiki_root = null`; в очереди
|
||||
пакет автора `viewer` с `refresh {jql: 'project = AURORA AND labels in ("AWPCA", "STOCKS_DRRK")'}`
|
||||
|
||||
#### Scenario: Проект без доски
|
||||
- **WHEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК"}`
|
||||
- **THEN** 201, `board_id = null`
|
||||
|
||||
#### Scenario: Проект с корнем вики
|
||||
- **WHEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", wiki_root: "DOC-001234"}`
|
||||
- **THEN** 201; в очереди пакет `refresh` и пакет `wiki_sync {label: "STOCKS_DRRK", root: "DOC-001234"}`; `batch_id` —
|
||||
пакет `refresh`; `batches` — `[{module: "jira", id: <refresh>}, {module: "wiki", id: <wiki_sync>}]`
|
||||
|
||||
#### Scenario: Повтор метки
|
||||
- **WHEN** `POST /projects {label: "AWPCA", name: "…"}`, проект `AWPCA` есть
|
||||
- **THEN** 409, пакет не поставлен
|
||||
|
||||
#### Scenario: Без названия
|
||||
- **WHEN** `POST /projects {label: "STOCKS_DRRK", name: " "}`
|
||||
- **THEN** 422 с указанием поля `name`
|
||||
|
||||
#### Scenario: Неизвестное поле
|
||||
- **WHEN** `POST /projects {label: "X", name: "X", board: 5}`
|
||||
- **THEN** 422 с указанием поля `board`, проекта нет
|
||||
|
||||
#### Scenario: Корень вики у существующего проекта
|
||||
- **WHEN** `PATCH /projects/AWPCA {wiki_root: "DOC-000777"}`
|
||||
- **THEN** 200, `batch_ids` — один пакет `wiki_sync`, `batches` — `[{module: "wiki", id: <он же>}]`; `GET /projects` отдаёт `AWPCA` с `wiki_root = DOC-000777`
|
||||
|
||||
#### Scenario: Доску не меняют
|
||||
- **WHEN** `PATCH /projects/AWPCA {board_id: 1}`
|
||||
- **THEN** 422 «board_id после создания не меняется», доска прежняя
|
||||
|
||||
#### Scenario: Свежесть Jira в проекте
|
||||
- **WHEN** последний полный пакет `refresh` завершён в 11:07, `GET /projects`
|
||||
- **THEN** у каждого проекта `jira_synced_at` — это время; до первого полного обновления — `null`
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Модуль пакета
|
||||
`GET /batches` и `GET /batches/{id}` SHALL отдавать у пакета поле `module` — имя модуля, которому принадлежит тип первой
|
||||
команды пакета (по описаниям модулей в реестре); пакет без команд или с типом, которого не знает ни один модуль, —
|
||||
`module = null`. Фильтр `GET /batches?module=<имя>` SHALL отдавать только пакеты модуля; неизвестный модуль — 404.
|
||||
|
||||
#### Scenario: Модуль у пакетов
|
||||
- **WHEN** в очереди пакет «Автообновление» (`refresh`, автор `hub`) и пакет `wiki_sync` проекта `AWPCA`; `GET /batches`
|
||||
- **THEN** у первого `module = "jira"`, у второго `module = "wiki"`
|
||||
|
||||
#### Scenario: Фильтр по модулю
|
||||
- **WHEN** `GET /batches?module=wiki&project=AWPCA`
|
||||
- **THEN** только пакеты модуля wiki проекта `AWPCA`
|
||||
|
||||
#### Scenario: Неизвестный модуль
|
||||
- **WHEN** `GET /batches?module=nope`
|
||||
- **THEN** 404
|
||||
299
openspec/changes/web-modules/specs/web-app/spec.md
Normal file
299
openspec/changes/web-modules/specs/web-app/spec.md
Normal file
@ -0,0 +1,299 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Оболочка и адреса
|
||||
Приложение SHALL открываться под префиксом `/app/`. Главная страница — «Обзор» ядра (`/app/?project=<метка>`,
|
||||
требование «Обзор проекта»). Страницы модулей SHALL лежать под префиксом имени модуля: модуль jira — `/app/jira/board`
|
||||
(доска связей), `/app/jira/issues` (список задач), `/app/jira/feed` (лента изменений, требование «Лента изменений») и
|
||||
`/app/jira/issues/{key}` (карточка задачи — окно поверх страницы, требование «Карточка задачи»); модуль wiki —
|
||||
`/app/wiki` (требование «Вики проекта»). Прежние адреса `/app/board`, `/app/issues`, `/app/issues/{key}`, `/app/feed`
|
||||
SHALL перенаправлять на новые с сохранением строки запроса, без новой записи истории. Неизвестный путь — страница «не
|
||||
найдено» со ссылками на «Обзор» и разделы модулей.
|
||||
|
||||
Боковая панель SHALL быть закреплена у левого края окна на всю его высоту и MUST NOT прокручиваться вместе со
|
||||
страницей; если её содержимое не помещается по высоте, прокручивается сама панель. Сверху вниз панель SHALL содержать:
|
||||
название «SDLC Hub» и кнопку «Свернуть панель»; выпадающий список проектов («<метка> — <название>», текущий отмечен, в
|
||||
конце «+ Новый проект…»; требование «Проект в приложении»); пункт «Обзор»; разделы модулей группами — заголовок группы
|
||||
— название модуля («Jira», «Вики»), под ним ссылки разделов модуля со значками (текущий отмечен
|
||||
`aria-current="page"`); внизу — блок состояния модулей (требование «Состояние и обновление модулей»), переключатель
|
||||
темы (требование «Тема») и меню с действием «Сменить токен». Свёрнутая панель SHALL быть узкой полосой с теми же
|
||||
действиями значками: «Развернуть панель», метка текущего проекта (полностью — в подсказке), «Обзор», разделы
|
||||
(группы разделены линией), по значку состояния на модуль, тема, меню; названия — в подсказках и `aria-label`. Свёртка
|
||||
SHALL храниться в `localStorage` под ключом `sdlc-hub-sidebar` (`collapsed`; без записи — развёрнута).
|
||||
|
||||
На экранах уже 640 px панели в потоке нет: сверху SHALL быть закреплена строка с кнопкой «Меню», меткой текущего
|
||||
проекта и сводной точкой состояния модулей (ошибка, если она есть у какого-нибудь модуля; иначе — пакет в работе;
|
||||
иначе — все агенты на связи или нет), которая открывает панель. «Меню» открывает развёрнутую панель поверх страницы
|
||||
модальным окном: фокус переходит в панель и остаётся в ней, страница под ней недоступна; панель закрывается выбором
|
||||
раздела, клавишей Escape (если не открыто вложенное меню — тогда Escape закрывает только его) или щелчком мимо неё; фокус
|
||||
возвращается на «Меню». Горизонтальной прокрутки страницы на любой ширине нет.
|
||||
|
||||
Заголовок вкладки браузера SHALL быть «<название страницы> · <метка проекта> · SDLC Hub» (без проекта — без средней
|
||||
части); название страницы даёт оболочка («Обзор») или модуль. Неожиданная ошибка отрисовки страницы SHALL показываться
|
||||
сообщением с кнопкой «Перезагрузить», не пустым экраном.
|
||||
|
||||
#### Scenario: Переход между разделами
|
||||
- **WHEN** на доске в боковой панели выбран раздел «Задачи» группы «Jira»
|
||||
- **THEN** адрес — `/app/jira/issues` с текущим проектом, показан список задач; «назад» возвращает на доску
|
||||
|
||||
#### Scenario: Раздел «Лента»
|
||||
- **WHEN** на доске в боковой панели выбран раздел «Лента»
|
||||
- **THEN** адрес — `/app/jira/feed` с текущим проектом, показана лента изменений за сегодня; раздел отмечен в панели
|
||||
|
||||
#### Scenario: Сменить токен
|
||||
- **WHEN** в меню боковой панели выбрано «Сменить токен»
|
||||
- **THEN** токен очищен, показан экран входа
|
||||
|
||||
#### Scenario: Панель на месте при прокрутке
|
||||
- **WHEN** доска прокручена до самого низа
|
||||
- **THEN** боковая панель видна целиком на прежнем месте: разделы и блок состояния модулей доступны без прокрутки вверх
|
||||
|
||||
#### Scenario: Свёрнутая панель
|
||||
- **WHEN** нажата «Свернуть панель», страница перезагружена
|
||||
- **THEN** панель свёрнута в полосу значков, доска шире; значок «Задачи» открывает список задач; видны два значка
|
||||
состояния — Jira и Вики; «Развернуть панель» возвращает полную панель
|
||||
|
||||
#### Scenario: Узкий экран
|
||||
- **WHEN** ширина окна 390 px, нажата «Меню», в панели выбран раздел «Задачи»
|
||||
- **THEN** панель открылась поверх страницы, фокус в панели; после выбора панель закрыта, фокус на «Меню», показан список
|
||||
задач; горизонтальной прокрутки нет
|
||||
|
||||
#### Scenario: Прежний адрес
|
||||
- **WHEN** открыт `/app/issues/AURORA-1536?project=AWPCA`
|
||||
- **THEN** адрес заменён на `/app/jira/issues/AURORA-1536?project=AWPCA`, карточка открыта поверх списка задач
|
||||
|
||||
#### Scenario: Заголовок вкладки
|
||||
- **WHEN** открыта страница «Вики» проекта `AWPCA`
|
||||
- **THEN** заголовок вкладки — «Вики · AWPCA · SDLC Hub»
|
||||
|
||||
### Requirement: Проект в приложении
|
||||
«Обзор», доска, список задач, лента и вики SHALL показывать один проект — текущий. Текущий проект SHALL задаваться
|
||||
параметром `project` в адресе страниц; без параметра — последний выбранный в этом браузере (`localStorage`), если он
|
||||
есть в `GET /projects`, иначе первый из `GET /projects`; выбранный проект записывается в адрес без новой записи истории.
|
||||
Проекта из адреса нет в `GET /projects` — страница SHALL показывать «проект <метка> не найден» и подсказку выбрать
|
||||
проект в списке «Проект» боковой панели (на узком экране — в «Меню») вместо данных. Проектов нет — страницы SHALL
|
||||
показывать приглашение создать проект («+ Новый проект…»). Переходы между страницами SHALL сохранять текущий проект;
|
||||
смена проекта на странице, которой нет у нового проекта, открывает «Обзор». Карточка задачи
|
||||
(`/app/jira/issues/{key}`) от проекта не зависит.
|
||||
|
||||
Доска SHALL загружать `GET /board?project=<текущий>`. Раскладка, дорожки и фильтры доски в `localStorage` SHALL
|
||||
храниться отдельно для каждого проекта; у проекта `AWPCA` — на прежних ключах (раскладка, сделанная до проектов,
|
||||
сохраняется). Фильтр спринтов SHALL показываться, только если хотя бы у одной своей задачи снимка (`neighbor = false`)
|
||||
есть спринт; иначе — «колонки «Спринт» нет — показаны все задачи».
|
||||
|
||||
Форма «Новый проект» (пункт «+ Новый проект…» выпадающего списка) SHALL открываться окном поверх страницы: метка (с
|
||||
подсказкой «метка задач в Jira и имя проекта в хабе») и название, затем поля каждого модуля в своей группе с
|
||||
заголовком — названием модуля: «Jira» — номер доски Jira (необязательно; подсказка «scrum или kanban определится
|
||||
сам»), «Вики» — корень вики (необязательно; подсказка «DOC-… или ссылка на страницу Eva»). Подсказка — текстом под
|
||||
полем, не в placeholder. Пустое необязательное поле в запрос не попадает; числовое поле с нецелым значением — ошибка у
|
||||
поля, запрос не уходит. «Создать» — `POST /projects` с автором `viewer`; ошибка хаба 409/422 — текст `detail` рядом с
|
||||
кнопкой, окно остаётся открытым; успех — окно закрывается, текущим становится новый проект, каждый пакет из ответа
|
||||
(`batches`) отслеживается своим модулем (требование «Состояние и обновление модулей»).
|
||||
|
||||
#### Scenario: Первый вход после выкатки
|
||||
- **WHEN** открыт `/app/jira/board` без `project`, в `localStorage` выбора нет, проекты `AWPCA` и `STOCKS_DRRK`
|
||||
- **THEN** адрес — `/app/jira/board?project=AWPCA`, доска — `GET /board?project=AWPCA` с прежней раскладкой карточек
|
||||
|
||||
#### Scenario: Переключение проекта
|
||||
- **WHEN** на доске `AWPCA` в выпадающем списке выбран `STOCKS_DRRK`, затем открыт раздел «Задачи»
|
||||
- **THEN** доска — `GET /board?project=STOCKS_DRRK` со своей раскладкой; адрес «Задач» —
|
||||
`/app/jira/issues?project=STOCKS_DRRK`
|
||||
|
||||
#### Scenario: Неизвестный проект в адресе
|
||||
- **WHEN** открыт `/app/jira/issues?project=NOPE`
|
||||
- **THEN** «проект NOPE не найден» и подсказка выбрать проект в боковой панели; запроса `GET /issues` нет
|
||||
|
||||
#### Scenario: Создание проекта из формы
|
||||
- **WHEN** в форме введены `STOCKS_DRRK`, «Запасы ДРРК», доска и корень вики пусто; нажато «Создать»
|
||||
- **THEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", author: "viewer"}`; текущий проект — `STOCKS_DRRK`;
|
||||
строка «Jira» блока состояния показывает «пакет #N выполняется»
|
||||
|
||||
#### Scenario: Проект с корнем вики из формы
|
||||
- **WHEN** в форме введены `STOCKS_DRRK`, «Запасы ДРРК», корень `DOC-001234`; нажато «Создать»
|
||||
- **THEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", wiki_root: "DOC-001234", author: "viewer"}`; строка
|
||||
«Jira» показывает пакет обновления, строка «Вики» — пакет синхронизации
|
||||
|
||||
#### Scenario: Метка занята
|
||||
- **WHEN** в форме введена метка `awpca`, хаб ответил 409
|
||||
- **THEN** окно открыто, рядом с «Создать» — текст ошибки хаба
|
||||
|
||||
#### Scenario: Канбан-проект
|
||||
- **WHEN** текущий проект — `STOCKS_DRRK`, ни у одной своей задачи снимка нет спринта (у соседа из `AWPCA` спринт есть)
|
||||
- **THEN** вместо фильтра спринтов — «колонки «Спринт» нет — показаны все задачи»
|
||||
|
||||
#### Scenario: Нецелый номер доски
|
||||
- **WHEN** в поле «Номер доски Jira» введено `51.5`, нажато «Создать»
|
||||
- **THEN** у поля — «нужно целое число», запроса `POST /projects` нет
|
||||
|
||||
### Requirement: Вики проекта
|
||||
Приложение SHALL иметь страницу «Вики» (`/app/wiki?project=<метка>`, раздел группы «Вики» боковой панели; текущий
|
||||
проект — как у остальных страниц, «Проект в приложении»). Шапка страницы: заголовок «Вики», подзаголовок «<название
|
||||
корня> · <N> страниц · обновлено ДД.ММ ЧЧ:ММ» и действие «Обновить вики» (вторичная кнопка; требование «Состояние и
|
||||
обновление модулей»). Страница SHALL показывать:
|
||||
- дерево страниц проекта (`GET /wiki/tree`) слева, свёрнутое до первого уровня; узел раскрывается по клику; узел
|
||||
выбранной страницы и его предки раскрыты;
|
||||
- выбранную страницу справа (`GET /wiki/pages/{code}`): заголовок, дата синхронизации, ссылка «открыть в Eva»,
|
||||
Markdown с таблицами, без сырого HTML; без `page` в адресе — корневая страница; ссылки `page:<код>` открывают страницу
|
||||
вики (код в адресе `&page=<код>`), ссылки `attachment:<id>` скачивают вложение (запрос, опрос состояния, сохранение
|
||||
файла браузером), состояние `failed` — текст ошибки рядом со ссылкой;
|
||||
- на экранах уже 640 px выбранная страница — над деревом;
|
||||
- поиск над деревом (`GET /wiki/search`): список результатов с фрагментами вместо дерева, пока строка поиска не пуста;
|
||||
после очистки поиска раскрытие дерева прежнее;
|
||||
- проекта без корня — форму «Корень вики (DOC-… или ссылка)» с кнопкой «Привязать» (`PATCH /projects/{label}`); 422 —
|
||||
текст ошибки у поля;
|
||||
- корень есть, синхронизаций нет — «вики ещё не синхронизирована» и действие «Обновить вики»;
|
||||
- последняя синхронизация с ошибкой — плашку с текстом ошибки над страницей.
|
||||
|
||||
#### Scenario: Открыть страницу по ссылке
|
||||
- **WHEN** на странице DOC-3 нажата ссылка `page:DOC-2`
|
||||
- **THEN** адрес `/app/wiki?project=AWPCA&page=DOC-2`, справа DOC-2, в дереве DOC-2 выделен
|
||||
|
||||
#### Scenario: Проект без корня
|
||||
- **WHEN** открыта `/app/wiki?project=sfm_kedr`, корня нет; введено `DOC-001234`, нажато «Привязать»
|
||||
- **THEN** `PATCH /projects/sfm_kedr {wiki_root: "DOC-001234"}`; строка «Вики» блока состояния показывает ход пакета
|
||||
синхронизации
|
||||
|
||||
#### Scenario: Поиск
|
||||
- **WHEN** в строке поиска «согласов»
|
||||
- **THEN** вместо дерева — результаты `GET /wiki/search?project=AWPCA&q=согласов`; очистка строки возвращает дерево
|
||||
|
||||
#### Scenario: Без выбранной страницы
|
||||
- **WHEN** открыта `/app/wiki?project=AWPCA` без `page`
|
||||
- **THEN** справа корневая страница проекта, в дереве она выделена
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Обновление из Jira
|
||||
**Reason**: обновление и ход пакетов стали общими для модулей.
|
||||
**Migration**: требование «Состояние и обновление модулей»; поведение «Обновить из Jira» (одна кнопка на приложение,
|
||||
`POST /refresh {full: true, author: "viewer"}`, один опрос, подхват, остановка, перечитывание) сохраняется в нём для
|
||||
модуля jira.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Состояние и обновление модулей
|
||||
Каждый веб-модуль SHALL описывать своё состояние данными, а рисовать их SHALL ядро. Описание модуля: название для
|
||||
показа («Jira», «Вики»); агент (имя модуля в `GET /agents`) или его отсутствие; свежесть данных для текущего проекта —
|
||||
время, охват («все проекты» или «проект») и ошибка последнего обновления; действие обновления (название, охват,
|
||||
постановка пакета) или его отсутствие; правило, какие пакеты принадлежат модулю; данные, которые надо перечитать после
|
||||
пакета модуля; есть ли у модуля данные для проекта (у вики без корня — нет).
|
||||
|
||||
Ядро SHALL:
|
||||
- опрашивать `GET /agents` при входе и раз в 60 с — один опрос на приложение; агент модуля: «на связи», «офлайн» или
|
||||
«обновите агента» (есть `warning` или `update_hint`, текст — в подсказке);
|
||||
- вести один трекер пакетов на приложение: на модуль — не больше одного отслеживаемого пакета; пакет подхватывается при
|
||||
входе, раз в 60 с и после действия обновления, если он не завершён, принадлежит модулю (поле `module` в `GET
|
||||
/batches`) и текущему проекту (для модулей с охватом «проект») — любого автора, в том числе автообновления хаба и
|
||||
MCP; опрос `GET /batches/{id}` каждые 3 с до терминального статуса; 4xx (404 — «пакета нет в хабе») и 5 ошибок «хаб
|
||||
не отвечает» подряд — опрос остановлен с сообщением; по завершении — перечитать данные модуля и список проектов;
|
||||
- ответ 409 на постановку пакета — отслеживать уже идущий пакет модуля, без сообщения об ошибке;
|
||||
- агент модуля офлайн при обновлении — пакет всё равно ставится, событие «агент офлайн — пакет выполнится, когда он
|
||||
выйдет на связь; пакет #N в очереди…».
|
||||
|
||||
Блок состояния в развёрнутой боковой панели — по строке на модуль, у которого есть данные для проекта: название модуля,
|
||||
точка агента (цвет и текст «агент на связи» / «агент офлайн» / «обновите агента» — не только цвет), кнопка-значок
|
||||
обновления с подписью действия в подсказке и `aria-label` («Обновить из Jira — все проекты», «Обновить вики — проект
|
||||
AWPCA»); под ними — «данные на ДД.ММ ЧЧ:ММ» с охватом («все проекты» / «проект»), «обновления ещё не было» или ошибка
|
||||
последнего обновления; под ними — одно текущее событие пакета модуля: в очереди / выполняется (с оставшимся временем,
|
||||
если хаб его дал, и пометками «агент офлайн», «хаб не отвечает, повтор»), выполнен (исчезает через 10 с), не выполнен
|
||||
с текстом ошибки, отменён, опрос остановлен с причиной. Кнопка обновления модуля недоступна (с признаком загрузки), пока
|
||||
запрос постановки в пути или выполняется пакет действия обновления модуля (у jira — пакет с первой командой `refresh`, у
|
||||
wiki — `wiki_sync`); прочие пакеты модуля показываются, но обновление не блокируют: нажатие ставит пакет обновления, и
|
||||
трекер переключается на него. Свёрнутая панель — по значку модуля с точкой агента, подробности в
|
||||
подсказке, нажатие — обновление. На узком экране — сводная точка в верхней строке (требование «Оболочка и адреса»),
|
||||
событие пакета — строкой под верхней строкой.
|
||||
|
||||
Модуль jira: агент `jira`; свежесть — `jira_synced_at` из `GET /projects`, охват «все проекты»; действие «Обновить из
|
||||
Jira» — `POST /refresh {full: true, author: "viewer"}`; после пакета перечитываются снимки досок, списки задач, лента и
|
||||
открытая карточка; доска, показывающая снимок из файла, его не заменяет. Модуль wiki: агент `wiki`; свежесть —
|
||||
`wiki_synced_at`, `wiki_sync_error` из `GET /projects`, охват «проект»; данные для проекта есть, только если есть
|
||||
`wiki_root`; действие «Обновить вики» — `POST /wiki/sync {project, author: "viewer"}`; после пакета перечитываются дерево,
|
||||
страницы и поиск. Блок состояния MUST NOT загружать снимок доски или `GET /wiki/status`.
|
||||
|
||||
Страница модуля с действием обновления SHALL дублировать его вторичной кнопкой в шапке страницы; основной (primary)
|
||||
кнопки обновления в приложении нет.
|
||||
|
||||
#### Scenario: Обновление со списка задач
|
||||
- **WHEN** открыт `/app/jira/issues`, нажато «Обновить из Jira» в строке «Jira», пакет выполнен
|
||||
- **THEN** в хабе один пакет `refresh` автора `viewer`; список задач перезапрошен; строка «Jira» показывала «пакет #N
|
||||
выполняется», затем «пакет #N выполнен»; строка «Вики» не изменилась
|
||||
|
||||
#### Scenario: Один прогон на модуль
|
||||
- **WHEN** нажата «Обновить из Jira» и, пока пакет выполняется, выполнен переход между разделами и нажата «Обновить из
|
||||
Jira» в шапке доски
|
||||
- **THEN** кнопки «Обновить из Jira» недоступны, в хабе один пакет, его опрашивает один опрос
|
||||
|
||||
#### Scenario: Опрос получил 404
|
||||
- **WHEN** опрос пакета вики получил 404
|
||||
- **THEN** опрос остановлен, строка «Вики» показывает «пакета нет в хабе», «Обновить вики» снова доступна
|
||||
|
||||
#### Scenario: Автообновление хаба
|
||||
- **WHEN** хаб поставил пакет «Автообновление» (автор `hub`, модуль `jira`), приложение открыто на странице «Вики»
|
||||
- **THEN** не позже чем через 60 с строка «Jira» показывает «пакет #N выполняется», по завершении — новое «данные на …»
|
||||
|
||||
#### Scenario: Пакет другого проекта
|
||||
- **WHEN** идёт пакет `wiki_sync` проекта `STOCKS_DRRK`, текущий проект — `AWPCA`
|
||||
- **THEN** строка «Вики» его не показывает, «Обновить вики» доступна
|
||||
|
||||
#### Scenario: Пакет с другими командами
|
||||
- **WHEN** незавершённый пакет модуля jira автора `viewer` состоит из команд `ensure_fields`
|
||||
- **THEN** строка «Jira» показывает «пакет #N выполняется», кнопка «Обновить из Jira» доступна; её нажатие ставит пакет
|
||||
`refresh`, и строка показывает его
|
||||
|
||||
#### Scenario: Агент офлайн
|
||||
- **WHEN** агент `wiki` офлайн и нажато «Обновить вики»
|
||||
- **THEN** пакет поставлен; строка «Вики» — точка с текстом «агент офлайн» и событие «агент офлайн — пакет выполнится,
|
||||
когда он выйдет на связь; пакет #N в очереди…»
|
||||
|
||||
#### Scenario: Синхронизация уже идёт
|
||||
- **WHEN** нажато «Обновить вики», хаб ответил 409
|
||||
- **THEN** строка «Вики» показывает идущий пакет, сообщения об ошибке нет
|
||||
|
||||
#### Scenario: Проект без корня вики
|
||||
- **WHEN** текущий проект `sfm_kedr` без корня вики
|
||||
- **THEN** в блоке состояния только строка «Jira»; на свёрнутой панели — один значок состояния
|
||||
|
||||
#### Scenario: Агенту нужно обновление
|
||||
- **WHEN** `GET /agents` отдаёт агента `jira` с `warning`
|
||||
- **THEN** строка «Jira» — точка с текстом «обновите агента», текст `warning` в подсказке
|
||||
|
||||
### Requirement: Обзор проекта
|
||||
Страница ядра «Обзор» (`/app/?project=<метка>`, пункт «Обзор» боковой панели) SHALL показывать шапку «Обзор» с
|
||||
названием проекта и под ней карточку на каждый модуль, у которого есть данные для проекта: название модуля, агент (с
|
||||
версией из `GET /agents`), свежесть с охватом, текущее событие пакета, вторичная кнопка обновления и сводка, которую
|
||||
даёт модуль. Сводка jira: число своих задач проекта, из них открытых и просроченных (по снимку доски проекта), ссылки
|
||||
«Доска связей», «Задачи», «Лента». Сводка wiki: число страниц, поле поиска по вики (Enter открывает «Вики» с этим
|
||||
поиском), ссылка «Вики». Модуль без данных для проекта SHALL показывать карточку с объяснением и действием модуля (у
|
||||
вики — «Привязать корень вики» ведёт на страницу «Вики»). Под карточками — «Последние пакеты проекта»: до 10 из
|
||||
`GET /batches?project=<метка>`: номер, модуль, название, автор, статус, время создания.
|
||||
|
||||
#### Scenario: Главная
|
||||
- **WHEN** открыт `/app/` без проекта, последний выбранный — `AWPCA`
|
||||
- **THEN** адрес `/app/?project=AWPCA`, две карточки — «Jira» и «Вики», список последних пакетов
|
||||
|
||||
#### Scenario: Поиск по вики с обзора
|
||||
- **WHEN** на «Обзоре» `AWPCA` в поле поиска вики введено «согласов» и нажат Enter
|
||||
- **THEN** открыта «Вики» с результатами поиска «согласов»
|
||||
|
||||
#### Scenario: Проект без вики
|
||||
- **WHEN** «Обзор» проекта `sfm_kedr` без корня вики
|
||||
- **THEN** карточка «Вики» с объяснением и ссылкой «Привязать корень вики»
|
||||
|
||||
### Requirement: Граница модулей в вебе
|
||||
Ядро веба (`web/src/{shell,ui,api,projects,auth,styles}`) MUST NOT импортировать модули, кроме списка модулей
|
||||
`modules/index.ts`, и MUST NOT содержать типов, справочников и терминов модулей (типы задач Jira, закрытые статусы,
|
||||
словари видов работ и связей, «статус по факту»). Модуль MUST NOT импортировать другой модуль; общее (форматтеры дат
|
||||
«ДД.ММ», «ДД.ММ ЧЧ:ММ», «ДД.ММ.ГГГГ» для дат не текущего года, скачивание файла) — в ядре. Имена модулей и адреса
|
||||
разделов (`to`) SHALL быть уникальны. Имя класса CSS, объявленное в стилях модуля, MUST NOT объявляться в стилях
|
||||
другого модуля или ядра. Даты в списке задач и карточке SHALL выводиться форматтерами ядра, не строкой ISO.
|
||||
|
||||
#### Scenario: Пересечение классов
|
||||
- **WHEN** в `modules/jira/**.css` и `modules/wiki/**.css` объявлен класс `.wiki`
|
||||
- **THEN** тесты фронта падают с именем класса и файлами
|
||||
|
||||
#### Scenario: Импорт чужого модуля
|
||||
- **WHEN** файл в `modules/wiki/` импортирует из `modules/jira/`
|
||||
- **THEN** тесты фронта падают с именем файла
|
||||
|
||||
#### Scenario: Дубль адреса раздела
|
||||
- **WHEN** два модуля объявляют раздел с одинаковым `to`
|
||||
- **THEN** тесты фронта падают с адресом и модулями
|
||||
Loading…
x
Reference in New Issue
Block a user