## 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 | дельты поверх новых имён | правка дельт при архиве | | 15 | 24.10.2026 | `last_full_sync(conn, base_jql=None)` в `modules/jira/mirror.py`: базовая JQL необязательна (по умолчанию `projects.base_jql`), `board()` передаёт свою; импорт в `project_fields.view` отложенный | интерфейс плана `(conn)` сохранён, `board()` не меняет сигнатуру; прямой импорт даёт цикл mirror → history → projects → project_fields | вернуть явный параметр | | 16 | 24.10.2026 | Фильтр `module` в `list_batches` применяется до `limit` перебором пакетов (по типу первой команды), без SQL-колонки | у пакета нет колонки модуля, схему не менять; объём пакетов невелик | добавить колонку `batch.module` | | 17 | 24.10.2026 | Тесты Task 1 лежат в `test_wiki_projects.py`/`test_project_fields.py` (нужны оба модуля), а не в `test_queue_core.py`/`test_api.py`; правлены старые ожидания в `test_db.py` (`jira_`-поля) | в `test_queue_core` модули подменены заглушкой | перенести тесты | | 18 | 24.10.2026 | R-1: трейлер коммитов ветки — `Co-Authored-By: Claude Sonnet 5.5`, а не `Opus 5.5` из плана | системная подсказка сессии об атрибуции приоритетнее текста плана | переписать трейлеры | | 19 | 24.10.2026 | R-2: предусловие плана («после слияния #97 в main») считается выполненным базированием на `feat/97-sdlc-rename` | указание владельца; PR #99 ещё не слит | ребейз на main после слияния #99 | | 20 | 24.10.2026 | R-3: параметр `q` страницы «Вики» реализует Task 5, Task 7 только добавляет тест | в плане требование задвоено между Task 5 и Task 7 | мелкая правка |