From 52a6c93db8bca8c322d6bcd3d6fdee344e05fe8f Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 11:31:38 +0400 Subject: [PATCH] =?UTF-8?q?plan(web-modules):=20=D0=BF=D0=BB=D0=B0=D0=BD?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=B2=D0=B5=D0=B1=D0=B0=20=D0=BF=D0=BE=D0=B4=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D1=83=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../plans/2026-10-24-web-modules.md | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 docs/superpowers/plans/2026-10-24-web-modules.md diff --git a/docs/superpowers/plans/2026-10-24-web-modules.md b/docs/superpowers/plans/2026-10-24-web-modules.md new file mode 100644 index 0000000..6a6be1d --- /dev/null +++ b/docs/superpowers/plans/2026-10-24-web-modules.md @@ -0,0 +1,185 @@ +# Веб под модули — план реализации + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** оболочка веба рисует состояние модулей (агент, свежесть, обновление, пакет) по их описанию, навигация сгруппирована по модулям с главной страницей «Обзор», адреса модулей с префиксом модуля, граница ядра и модулей проверяется тестами, дефекты страниц из аудита исправлены. + +**Architecture:** контракт `HubModule` получает `title`, `status: ModuleStatus` (данные) и `Summary`; ядро (`web/src/shell/activity/`) держит один опрос `GET /agents` и один трекер пакетов на все модули (обобщение `RefreshProvider` модуля jira); блок состояния, «Обзор», верхняя строка узкого экрана — компоненты ядра. Бэкенд добавляет `jira_synced_at` в проект, `module` в пакет и `batches` в ответы `POST/PATCH /projects`. + +**Tech Stack:** React 19 + TypeScript + Vite, TanStack Query, react-router, Vitest + Testing Library, Playwright; Python 3.13 (uv, pytest). + +**Spec:** `openspec/changes/web-modules/` (proposal.md, design.md, specs/web-app, specs/hub-api) — issue jira-hub#98. Аудит и решения — design.md. + +## Global Constraints + +- **Предусловие:** ветка перебазирована на `main` после слияния sdlc-rename (#97): пакет `src/sdlc_hub/`, бренд «SDLC Hub», ключи `sdlc-hub-*`, `readMigrated` в `web/src/api/legacyStorage.ts`. Без этого план не начинать. +- Адреса: «Обзор» `/` (под `/app`), jira — `/jira/board`, `/jira/issues`, `/jira/issues/:key`, `/jira/feed`; wiki — `/wiki`; прежние `/board`, `/issues`, `/issues/:key`, `/feed` — перенаправление с сохранением `search`, `replace`. +- Тексты (точно): названия модулей «Jira», «Вики»; «Обзор»; агент — «агент на связи» / «агент офлайн» / «обновите агента»; свежесть — «данные на ДД.ММ ЧЧ:ММ · все проекты» / «данные на ДД.ММ ЧЧ:ММ · проект», «обновления ещё не было»; действия — «Обновить из Jira», «Обновить вики»; подсказки значков — «Обновить из Jira — все проекты», «Обновить вики — проект <метка>»; заголовок вкладки «<страница> · <метка> · SDLC Hub»; «Последние пакеты проекта»; «Команды хаба»; «Привязать корень вики»; «нужно целое число». +- События пакета — прежние тексты `refreshText` (`пакет #N в очереди/выполняется…`, `выполнен`, `не выполнен: …`, `отменён`, `…: <причина> — опрос остановлен, «<действие>» снова доступна`, `хаб не отвечает — пакет не поставлен`); `POLL_MS = 3000`, `MAX_POLL_ERRORS = 5`, `DONE_VISIBLE_MS = 10_000`; подхват и `GET /agents` — раз в `60_000` мс. +- Primary-кнопок обновления нет: значок в строке модуля, `variant="secondary"` в шапках страниц. +- Цвет не единственный признак: точка агента и просрочка — с текстом (`title`/скрытый текст). +- AURA (скилл `aura-design`): цвета только из `styles/tokens.css`, компоненты из `web/src/ui/`. +- Зелёный — весь набор: `uv run pytest -q`, `cd web && npm run lint && npm test`; перед финальным ревью ещё `cd web && npm run e2e`. +- Живой хаб (:8790) и его данные не трогать; e2e — только на тестовом хабе (`web/scripts/test-hub.sh`, :8791). +- Язык комментариев, текстов, коммитов — русский; коммиты заканчиваются `Co-Authored-By: Claude Opus 5.5 `. + +## Review Focus + +1. Пакет, поставленный из MCP или автообновлением, во время открытой страницы: строка модуля должна его показать не позже чем через 60 с и не блокировать кнопку обновления — Task 3, `picks up batch of any author` и `foreign batch does not block refresh`. +2. Смена проекта во время идущего пакета вики: строка «Вики» нового проекта не показывает чужой пакет, опрос старого не течёт в новую строку — Task 3, `project switch drops project-scoped batch`. +3. Проект без корня вики на всех поверхностях (панель развёрнута/свёрнута, телефон, «Обзор»): нет пустой строки «Вики», нет запроса `/wiki/status` из оболочки — Task 4 `no wiki row without root`, Task 5 `overview card without root`. +4. Прежний адрес карточки из истории браузера (`/app/issues/KEY` без проекта): перенаправление и окно поверх списка, закрытие окна возвращает на `/jira/issues` с текущим проектом — Task 4, `legacy card address`. +5. Пересечение классов CSS, которое уже ломало «Вики»: тест-страж должен падать на `div.wiki`-подобном конфликте и на дубле класса между модулем и ядром — Task 2, `css classes do not collide`. + +--- + +### Task 1: API для блока состояния + +**Files:** +- Modify: `src/sdlc_hub/modules/jira/project_fields.py` (`view`: `jira_synced_at`), `src/sdlc_hub/core/queue.py` (`batch_view`: `module`; `list_batches(conn, limit=20, project=None, module=None)`), `src/sdlc_hub/core/api.py` (`h_batches_get`: `?module=`, неизвестный — 404 `not_found`; `POST /projects` и `PATCH /projects/{label}`: `batches`; `GET /` и `GET /links` → `Location: /app/`) +- Test: `tests/test_project_fields.py`, `tests/test_queue_core.py`, `tests/test_api.py`, `tests/test_web_app.py`, `tests/test_wiki_projects.py` + +**Interfaces:** +- Produces: `GET /projects[].jira_synced_at: str | null` — `finished_at` последнего `sync_run` полного обновления (тот же запрос, что `last_sync` в `modules/jira/mirror.py` снимка доски — вынести в функцию `last_full_sync(conn) -> dict | None` модуля jira и использовать в обоих местах); `batch_view(...)["module"]: str | None` — `registry.module_of(<тип первой команды по seq>).name`; `POST /projects` → `{project, batch_id, batches: [{"module": str, "id": int}]}`; `PATCH` → `{project, batch_ids, batches}`. + +- [ ] **Step 1: Тесты** + - `test_projects_jira_synced_at`: до полного обновления `GET /projects` → у проекта `jira_synced_at is None`; после завершённого полного `refresh` с `sync_run.finished_at = "2026-10-24T11:07:00"` → это значение у каждого проекта. + - `test_batch_module`: пакет `refresh` → `module == "jira"`; пакет `wiki_sync` → `"wiki"`; пакет без команд → `None` (в `list_batches` и `batch_view`). + - `test_batches_module_filter`: `GET /batches?module=wiki&project=AWPCA` → только пакеты wiki проекта; `GET /batches?module=nope` → 404. + - `test_create_project_batches`: `POST /projects {label, name, wiki_root: "DOC-001234"}` → `batches == [{"module": "jira", "id": }, {"module": "wiki", "id": }]`, `batch_id == `; `PATCH /projects/AWPCA {wiki_root}` → `batches == [{"module": "wiki", "id": batch_ids[0]}]`. + - `test_root_redirect` (в `test_web_app.py`, заменить прежний): `GET /` и `GET /links` → 302, `Location: /app/`. +- [ ] **Step 2:** `uv run pytest tests/test_project_fields.py tests/test_queue_core.py tests/test_api.py tests/test_web_app.py tests/test_wiki_projects.py -q` — FAIL. +- [ ] **Step 3:** реализация по Interfaces. В ядре (`queue.py`, `api.py`) — без названий модулей (граница `tests/test_registry.py`): модуль берётся из `registry.module_of`. +- [ ] **Step 4:** `uv run pytest -q` — весь набор PASS (в том числе `test_registry.py`, эталоны MCP: `batch_id` в MCP не меняется). +- [ ] **Step 5: Commit** — `feat(web-modules): API — свежесть Jira в проекте, модуль пакета, пакеты нового проекта по модулям`. + +### Task 2: Граница ядра и модулей в вебе + +**Files:** +- Create: `web/src/ui/format.ts` (+ `format.test.ts`), `web/src/modules/jira/api/types.ts`, `web/src/boundary.test.ts` +- Move: `web/src/api/closed.ts` (+ тест) → `web/src/modules/jira/api/closed.ts` +- Modify: `web/src/api/types.ts` (остаются `Batch` (+ `module: string | null`), `BatchCommand`, `BatchStatus`, `AgentView`, `CreatedBatch`, `Project`, `CreatedProject`, `UpdatedProject`, `HubSettings` без словарей jira; `fact_status` удалён везде, в том числе `web/src/test/issueFixtures.ts` и комментарий `modules/jira/issues/card.ts`), импорты типов в модулях; `modules/jira/board/model/util.ts` (`fmtStamp` переезжает в `ui/format.ts`), `modules/wiki/PageView.tsx`, `modules/wiki/SyncStatus.tsx`; `modules/jira/issues/wiki/wiki.css` и разметка, использующая `className="wiki"` в модуле jira → `jira-markup`; `IssueTable.tsx` и `IssueCard.tsx` (даты через `fmtDate`); `web/src/modules/index.ts` или `modules.test.tsx` (уникальность) + +**Interfaces:** +- Produces: `fmtStamp(iso: string | null): string` — «ДД.ММ ЧЧ:ММ» (как сейчас в `board/model/util.ts`); `fmtDate(iso: string | null, now?: Date): string` — «ДД.ММ», год не текущий — «ДД.ММ.ГГГГ», `null` — «—»; `AgentView` = поля `GET /agents`: `{module: string; online: boolean; last_seen_at: string | null; agent_version: number | null; warning: string | null; update_hint: string | null; current_command_id: number | null; release: {version: number} | null}`. + +- [ ] **Step 1: Тесты** + - `format.test.ts`: `fmtDate("2026-10-19", new Date("2026-10-24"))` → `"19.10"`; `fmtDate("2025-12-31", new Date("2026-10-24"))` → `"31.12.2025"`; `fmtDate(null)` → `"—"`; `fmtStamp("2026-10-24T11:07:00")` → `"24.10 11:07"`. + - `boundary.test.ts` (читает файлы через `import.meta.glob(..., { query: "?raw", eager: true })`): + - `core does not import modules`: файлы `src/{shell,ui,api,projects,auth,styles}/**/*.{ts,tsx}` (кроме тестов) не импортируют `modules/` иначе как `modules/index.ts` и `modules/types.ts`; + - `module does not import other module`: файл `modules//**` не импортирует `modules//`; + - `css classes do not collide`: множество имён классов (`/\.([a-zA-Z_][\w-]*)/g` по селекторам, без значений свойств) в `modules//**/*.css` не пересекается с `modules//**/*.css` и с `src/{shell,ui,styles,projects,auth}/**/*.css`; сообщение — имя класса и файлы; + - `core has no module terms`: в файлах ядра нет `fact_status`, `closed_statuses`, `kind_labels`, `link_types`, `BoardIssue`, `IssueRow`. + - `modules.test.tsx`: `names unique` и `nav paths unique` (T7.6) — сообщение с дублем; `NotFound` — ключ ссылки уникален при дубле (T7.5). +- [ ] **Step 2:** `cd web && npx vitest run src/ui/format.test.ts src/boundary.test.ts src/modules/modules.test.tsx` — FAIL (пересечение `.wiki`, импорт wiki → jira, термины в `api/types.ts`). +- [ ] **Step 3:** перенос типов и `closed.ts`, `ui/format.ts`, `jira-markup`, даты в таблице и карточке, удаление `fact_status`. +- [ ] **Step 4:** `cd web && npm run lint && npm test` — PASS; на странице «Вики» тестового хаба заголовок h1 — 20 px (проверить в e2e Task 8). +- [ ] **Step 5: Commit** — `refactor(web-modules): граница ядра и модулей в вебе, форматтеры дат в ядре, без fact_status`. + +### Task 3: Контракт состояния модуля и трекер пакетов в ядре + +**Files:** +- Create: `web/src/shell/activity/ActivityProvider.tsx`, `web/src/shell/activity/activity.ts` (контекст и хуки), `web/src/shell/activity/events.ts` (перенос `refreshStatus.ts`: `ModuleEvent`, `eventText(e, actionLabel)`, константы), `web/src/shell/activity/activity.test.tsx` (перенос и обобщение `modules/jira/refresh/refresh.test.tsx`), `web/src/modules/jira/status.ts`, `web/src/modules/wiki/status.ts` +- Modify: `web/src/modules/types.ts` (`ModuleStatus`, `HubModule.title`, `status`, `Summary`; удалить `SidebarStatus`, `TopbarNotice`, `useOnProjectCreated`), `web/src/modules/index.ts`, `web/src/modules/jira/index.tsx`, `web/src/modules/wiki/index.tsx`, `web/src/App.tsx` (провайдер ядра), `web/src/modules/wiki/api/queries.ts` (`useWikiSync` не нужен оболочке; страница вики берёт действие из трекера) +- Delete: `web/src/modules/jira/refresh/` (`RefreshProvider.tsx`, `refresh.ts`, `refreshStatus.ts`, `DataStatus.tsx`, `SidebarStatus.tsx`, их тесты) после переноса тестов; `web/src/modules/wiki/SyncStatus.tsx` (+ тест) + +**Interfaces:** +- Consumes: `Batch.module`, `AgentView` (Task 2), `GET /batches?module=&project=&limit=`, `jira_synced_at` (Task 1). +- Produces: + - `ModuleStatus` — как в design.md («Контракт веб-модуля»): `agent?: string; scope: "all" | "project"; freshness(p: Project): {available: boolean; at: string | null; error: string | null}; refresh?: {label: string; start(project: string): Promise<{id: number; estimate_seconds?: number}>; isOwn(b: Batch): boolean}; invalidate(qc: QueryClient, project: string | null): void`. + - `useModuleActivity(name: string): {agent: AgentView | null; event: ModuleEvent | null; busy: boolean; running: Batch | null; start(): Promise; track(id: number): void}` — `busy`: постановка в пути или отслеживаемый пакет удовлетворяет `refresh.isOwn`. + - `useAgents(): AgentView[]`; `useActivitySummary(): "error" | "busy" | "online" | "offline"` (для сводной точки). + - `ActivityProvider({children, project, pollMs?})` — `project` — текущая метка (из `ProjectContext`). + - jira: `status = {agent: "jira", scope: "all", freshness: p => ({available: true, at: p.jira_synced_at ?? null, error: null}), refresh: {label: "Обновить из Jira", start: () => api.post("/refresh", {full: true, author: "viewer"}), isOwn: b => b.commands?.[0]?.type === "refresh"}, invalidate: board-все, issues, issue, changes}`; wiki: `{agent: "wiki", scope: "project", freshness: p => ({available: !!p.wiki_root, at: p.wiki_synced_at ?? null, error: p.wiki_sync_error ?? null}), refresh: {label: "Обновить вики", start: project => api.post("/wiki/sync", {project, author: "viewer"}), isOwn: b => b.commands?.[0]?.type === "wiki_sync"}, invalidate: ["wiki", project] tree/page/search}`. +- Правила трекера: подхват — при монтировании, при смене проекта, раз в 60 с и после `start`: `GET /batches?module=&limit=20` (+`&project=<метка>` для `scope: "project"`), первый незавершённый (`queued`/`running`, самый старый) — если ничего не отслеживается; при смене проекта отслеживаемый пакет модуля с `scope: "project"` сбрасывается без события. `start`: агент офлайн по `useAgents()` → `agentOffline: true` в событии; ответ 409 (`ApiError.status === 409`) — сразу подхват без события ошибки. Если отслеживается чужой пакет (не `isOwn`), `start` ставит пакет и переключает отслеживание на него. По завершении пакета — событие, `module.status.invalidate(qc, project)`, перечитывание `["projects"]`. + +- [ ] **Step 1: Тесты** `activity.test.tsx` (мок `fetch` как в прежнем `refresh.test.tsx`, фейковые таймеры) — перенести все сценарии прежнего файла на модуль jira (`M-2`, `M-3`, `M-4`, `T10.2`, `T4.1`, 401, `batch-done` гаснет, переход между страницами), заменив «подхват viewer+refresh» новыми: + - `picks up batch of any author`: `GET /batches?module=jira` отдаёт пакет автора `hub` в `running` → `event.kind === "batch"`, опрос `GET /batches/{id}`; + - `foreign batch does not block refresh`: отслеживается пакет jira с `commands[0].type = "ensure_fields"` → `busy === false`; `start()` → один `POST /refresh`, `event.id` — новый пакет; + - `project switch drops project-scoped batch`: отслеживается `wiki_sync` проекта `AWPCA`, `project` → `STOCKS_DRRK` → `event === null`, опрос пакета AWPCA остановлен; jira-пакет не сброшен; + - `wiki 409 tracks running batch`: `POST /wiki/sync` → 409 → подхват идущего пакета, события ошибки нет; + - `agent offline from /agents`: `GET /agents` → wiki `online: false`; `start()` для wiki → событие с `agentOffline: true`; + - `invalidates module data only`: завершение пакета wiki инвалидирует `["wiki", "AWPCA", …]` и `["projects"]`, не `["board"]`; + - `one agents poll`: два модуля → один запрос `GET /agents` за интервал. +- [ ] **Step 2:** `cd web && npx vitest run src/shell/activity` — FAIL. +- [ ] **Step 3:** реализация. Состояние — на модуль (`Record`), опросы пакетов — `useQueries` по активным; логика остановки — перенос из `RefreshProvider` без изменений. +- [ ] **Step 4:** `cd web && npm run lint && npm test` — PASS (временные заглушки оболочки, если Task 4 ещё не сделан: `Sidebar` может рендерить старый блок через `useModuleActivity` — главное, чтобы набор был зелёным). +- [ ] **Step 5: Commit** — `feat(web-modules): состояние модуля в контракте, трекер пакетов и агенты в ядре`. + +### Task 4: Оболочка — группы, блок состояния, узкий экран, адреса + +**Files:** +- Create: `web/src/shell/ModuleStatusBlock.tsx` (+ тест), `web/src/shell/useDocumentTitle.ts` +- Modify: `web/src/shell/Sidebar.tsx` (+ `Sidebar.test.tsx`), `web/src/shell/Layout.tsx`, `web/src/shell/NotFound.tsx`, `web/src/shell/shell.css`, `web/src/modules/jira/index.tsx` (пути `/jira/…`, перенаправления прежних путей), `web/src/modules/jira/issues/modal.ts` и `overlay.tsx` (`/jira/issues`, закрытие окна — с текущим проектом), все ссылки на `/board`, `/issues`, `/feed` внутри модулей +- Test: `web/src/shell/*.test.tsx`, `web/src/projects/projects.test.tsx` + +**Interfaces:** +- Consumes: `useModuleActivity`, `useAgents`, `useActivitySummary` (Task 3); `HubModule.title`, `status`. +- Produces: `NavItem.title?: string` (название страницы для вкладки; по умолчанию `label`); `useDocumentTitle(page: string | null, project: string | null)`; `ModuleStatusBlock({project, variant: "expanded" | "collapsed"})`; оболочка: пункт «Обзор» (`to: "/"`) — первым, затем группы `MODULES` с заголовком `title`; смена проекта вне раздела → `/`. + +- [ ] **Step 1: Тесты** + - `ModuleStatusBlock.test.tsx`: две строки «Jira» и «Вики» с точкой и текстом агента («агент на связи»/«агент офлайн»/«обновите агента», `warning` в `title`); «данные на 24.10 11:07 · все проекты» и «данные на 24.10 11:05 · проект»; значок с `aria-label` «Обновить из Jira — все проекты» и «Обновить вики — проект AWPCA»; `jira_synced_at: null` → «обновления ещё не было»; `wiki_sync_error: "403 Forbidden"` → текст ошибки в строке «Вики» (тон ошибки, с текстом); `no wiki row without root` — проект без `wiki_root` → одна строка, запросов `/wiki/status` и `/board` нет; `collapsed` — по значку на модуль, подробности в `title`. + - `Sidebar.test.tsx`: заголовки групп «Jira», «Вики»; «Обзор» первым; на 390 px верхняя строка — «Меню», метка проекта, сводная точка (`aria-label` с состоянием); `drawer is modal` — фокус в панели, Escape во вложенном меню закрывает только меню (M-1 #73), Escape после — панель, фокус на «Меню»; метка проекта свёрнутой панели целиком в `title` (M-4). + - `Layout`/`projects.test.tsx`: `legacy card address` — `/issues/AURORA-1?project=AWPCA` → `/jira/issues/AURORA-1?project=AWPCA`, окно поверх списка, закрытие → `/jira/issues?project=AWPCA`; `/board?project=X` → `/jira/board?project=X`; смена проекта на «Вики» у проекта без вики → остаётся «Вики» (страница с формой привязки), на неизвестном адресе → `/`; заголовок вкладки «Вики · AWPCA · SDLC Hub», «Обзор · AWPCA · SDLC Hub». +- [ ] **Step 2:** `cd web && npx vitest run src/shell src/projects` — FAIL. +- [ ] **Step 3:** реализация. Панель прокручивается сама, если не влезает (M-5 #73); удалить мёртвый `--topbar-h` (M-6), устаревшие комментарии про верхнюю панель (M-7 #73, С19 аудита); событие пакета на узком экране — строкой под верхней строкой (замена `TopbarNotice`), на развёрнутой панели — в строке модуля; `role="status"` — постоянный контейнер (T10.1 #94). +- [ ] **Step 4:** `cd web && npm run lint && npm test` — PASS. +- [ ] **Step 5: Commit** — `feat(web-modules): боковая панель по модулям, блок состояния, узкий экран, адреса модулей`. + +### Task 5: Страница «Обзор» + +**Files:** +- Create: `web/src/shell/OverviewPage.tsx` (+ `OverviewPage.test.tsx`), `web/src/modules/jira/Summary.tsx`, `web/src/modules/wiki/Summary.tsx` +- Modify: `web/src/shell/pages.tsx` (маршрут `/` → «Обзор» под `ProjectGate`), `web/src/modules/jira/index.tsx`, `web/src/modules/wiki/index.tsx` (`Summary`), `web/src/api/queries.ts` (`useRecentBatches(project)` → `GET /batches?project=&limit=10`) + +**Interfaces:** +- Consumes: `ModuleStatusBlock` нет — карточка модуля «Обзора» рисует ядро из `useModuleActivity` + `status.freshness` + `useAgents` (версия `agent_version`), кнопка `variant="secondary"`; `Summary({project: Project})`. +- Produces: jira `Summary`: « задач · <открытых> открытых · <просроченных> просрочено» (по `useBoard(project)`: свои задачи `neighbor = false`; открытые — не `closed` по `modules/jira/api/closed.ts`; просроченные — открытые с `duedate` < сегодня), ссылки «Доска связей», «Задачи», «Лента»; wiki `Summary`: « страниц», поле поиска (Enter → `/wiki?project=<метка>&q=<строка>`; страница «Вики» читает `q` в поле поиска), ссылка «Вики»; без корня — объяснение и ссылка «Привязать корень вики» на `/wiki?project=<метка>`. + +- [ ] **Step 1: Тесты** `OverviewPage.test.tsx`: `/` с проектом `AWPCA` (корень есть) — две карточки «Jira», «Вики» с агентом и версией («v14», «v1»), свежестью, кнопками «Обновить из Jira», «Обновить вики» (обе `secondary`); `overview card without root` — `sfm_kedr` → карточка «Вики» с «Привязать корень вики»; «Последние пакеты проекта» — строки `#`, модуль, название, автор, статус, время; поиск по вики Enter → адрес `/wiki?project=AWPCA&q=согласов`, на «Вики» строка поиска заполнена и показаны результаты. +- [ ] **Step 2:** `cd web && npx vitest run src/shell/OverviewPage.test.tsx` — FAIL. +- [ ] **Step 3:** реализация (сетка карточек: 2 колонки ≥ 1024 px, 1 колонка уже; компоненты `Panel`, `PageHeader`, `Button`). +- [ ] **Step 4:** `cd web && npm run lint && npm test` — PASS. +- [ ] **Step 5: Commit** — `feat(web-modules): страница «Обзор» со сводками модулей и последними пакетами`. + +### Task 6: Окно «Новый проект» по модулям + +**Files:** +- Modify: `web/src/projects/NewProjectDialog.tsx` (+ тест), `web/src/projects/projects.css`, `web/src/api/queries.ts` (`useCreateProject`, `useUpdateProject` — тип ответа с `batches`), `web/src/api/types.ts` (`CreatedProject.batches`, `UpdatedProject.batches: {module: string; id: number}[]`), `web/src/shell/Layout.tsx` (`onCreated` → `track` каждого пакета своим модулем), `web/src/modules/wiki/RootForm.tsx` (пакет из `batches` → `track` модуля wiki) + +**Interfaces:** +- Consumes: `useModuleActivity(name).track` (Task 3), `HubModule.title`, `projectFields`. + +- [ ] **Step 1: Тесты** `NewProjectDialog.test.tsx`: группы `fieldset` с `legend` «Jira» и «Вики»; подсказки — текст под полем (`aria-describedby`), `placeholder` пустой; подсказка метки «метка задач в Jira и имя проекта в хабе»; `51.5` в «Номер доски Jira» → «нужно целое число», запроса нет (T4.1); ответ `batches` с двумя пакетами → `track` jira и `track` wiki вызваны со своими id (M-14, T5.3 #68); `setExtra` — функциональная форма (T4.2: два быстрых ввода в разные поля не теряют первое); `FIELDS` собирается при отрисовке, не при импорте (T4.5); `useUpdateProject` и показ 422 у поля корня — тесты (T4.4). +- [ ] **Step 2:** `cd web && npx vitest run src/projects src/modules/wiki` — FAIL. +- [ ] **Step 3:** реализация. +- [ ] **Step 4:** `cd web && npm run lint && npm test` — PASS. +- [ ] **Step 5: Commit** — `feat(web-modules): «Новый проект» — поля по модулям, подсказки, пакеты каждому модулю`. + +### Task 7: Страницы модулей — исправления по аудиту + +**Files:** +- Modify: `web/src/modules/wiki/WikiPage.tsx`, `Tree.tsx`, `Search.tsx`, `wiki.css` (+ тесты); `web/src/modules/jira/board/components/BoardHeader.tsx`, `web/src/modules/jira/issues/IssuesPage.tsx`, `IssueTable.tsx`, `issues.css`, `IssueCard.tsx`, `web/src/modules/jira/feed/FeedPage.tsx`; `web/src/projects/ProjectGate.tsx`; `web/src/styles/` (глобальное `scrollbar-gutter: stable` для прокручиваемой области страницы) + +- [ ] **Step 1: Тесты** + - «Вики»: шапка — h1 «Вики», подзаголовок « · страниц · обновлено ДД.ММ ЧЧ:ММ», кнопка «Обновить вики» `secondary` из `useModuleActivity("wiki")`; без `page` — открыт корень и выделен в дереве; узлы-предки выбранной страницы раскрыты, после очистки поиска раскрытие прежнее (T10.2, T10.6); `aria-setsize`/`aria-posinset` у узлов (T10.2); порядок заголовков h1 → h2 (T10.3); на 390 px (`matchMedia` мок) страница выше дерева; параметр `q` заполняет поиск. + - Доска, «Задачи», «Лента»: в шапке вторичная кнопка «Обновить из Jira» (`useModuleActivity("jira")`), нет primary; под шапкой доски нет пустого блока `.board-src` без содержимого (N-1), мёртвые пропсы `label`/`title` удалены (N-2 #68). + - «Задачи»: просроченный срок — с текстом «просрочено» для экранного чтения (`visually-hidden`) и `title`; срок через `fmtDate`. + - Карточка: раздел «Команды хаба» вместо «Команды моста». + - `ProjectGate`: «проект NOPE не найден» и подсказка «выберите проект в списке «Проект» боковой панели (на узком экране — в «Меню»)». +- [ ] **Step 2:** `cd web && npx vitest run src/modules src/projects` — FAIL. +- [ ] **Step 3:** реализация. Горизонтальная прокрутка «Задач» на 1440 px: таблица прокручивается внутри своей панели (`overflow-x: auto` у обёртки таблицы, ширина обёртки не больше контейнера страницы). +- [ ] **Step 4:** `cd web && npm run lint && npm test` — PASS. +- [ ] **Step 5: Commit** — `fix(web-modules): страницы модулей — шапки, «Вики» с корнем, просрочка, прокрутка «Задач»`. + +### Task 8: e2e, документация, спека + +**Files:** +- Modify: `web/e2e/*.spec.ts` (новые адреса, «Обзор», блок состояния, перенаправления), `web/e2e/hub.ts` (фикстура пакетов, если нужна), `ARCHITECTURE.md` (раздел «Веб»: контракт `HubModule` с `status`/`Summary`, трекер в `shell/activity/`, адреса), `CLAUDE.md` (адреса веба, «Обзор»), `README.md` (адреса веба) +- Test: `web/e2e/layout.spec.ts`, `web/e2e/screens.spec.ts`, новый `web/e2e/overview.spec.ts` + +- [ ] **Step 1: Тесты e2e** (тестовый хаб): `overview.spec.ts` — `/app/` → `/app/?project=…`, две карточки, список пакетов; `layout.spec.ts` — на 1440 px у «Задач» `document.documentElement.scrollWidth === window.innerWidth`; на «Вики» `getComputedStyle(h1).fontSize === "20px"`; `/app/board` → `/app/jira/board`; на 390 px — метка проекта в верхней строке, «Меню» модально; `screens.spec.ts` — скриншоты «Обзора» и панели в обеих темах (как прочие экраны). +- [ ] **Step 2:** `cd web && npm run e2e` — новые тесты падают до правок e2e-хелперов, затем PASS. +- [ ] **Step 3:** документация. +- [ ] **Step 4:** `uv run pytest -q`, `cd web && npm run lint && npm test && npm run e2e`, `openspec validate web-modules --strict` — PASS. +- [ ] **Step 5: Commit** — `docs(web-modules): ARCHITECTURE, CLAUDE.md, README, e2e под модули`.