27 KiB
27 KiB
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
- Навигация — группы по модулям + «Обзор» (вариант «А + Б» аудита). Группы оставляют все разделы на виду (их
четыре), «Обзор» даёт модульную главную вместо
/boardи место для сводки. Переключатель модулей (вариант «В») отклонён: лишний щелчок между доской и вики ради масштаба, которого нет. - Адреса с префиксом модуля (
/app/jira/…,/app/wiki) и перенаправления со старых. Делается сейчас, пока ломается всё сразу (переименование); пути модулей не пересекаются по построению. Перенаправления объявляет модуль jira (это его прежние адреса).GET /хаба — на/app/. - Состояние модуля — данные (
ModuleStatus), рисует ядро. КомпонентSidebarStatusубирается: из-за него блок и был разнобойным. Свежесть берётся из уже загруженногоGET /projectsчистой функцией от проекта — без запросов модуля: jira —jira_synced_at(новое поле, общее для проектов), wiki —wiki_synced_at/wiki_sync_error. - Один трекер пакетов в ядре (
shell/activity/): обобщениеRefreshProvider— состояние на модуль (event,active,busy), опросGET /batches/{id}, правила остановки прежние. Подхват — по полюmoduleизGET /batches?module=(новое), любого автора: автообновления хаба и пакеты из MCP теперь видны (С1/В1 аудита), а подхват не делает запрос на каждого кандидата. Прежнее правило «только viewer + refresh» отменено (сценарий «Пакет с другими командами» меняется: пакет показывается, но не блокирует кнопку). - Кнопка недоступна только на время пакета своего действия обновления (jira — первая команда
refresh, wiki —wiki_sync; тип виден вGET /batches/{id}, который трекер и так опрашивает). Иначе долгое скачивание вложения блокировало бы «Обновить из Jira». - Обновление: значок в строке модуля + вторичная кнопка в шапке страниц модуля, primary для обновления нет. Сейчас на «Вики» две зелёные кнопки (в панели и в шапке) — против правила AURA «одна primary на экран».
- Агенты — один опрос
GET /agentsраз в 60 с; точка агента: на связи / офлайн / «обновите агента» (warningилиupdate_hint), всегда с текстом. - Узкий экран: в верхней строке «Меню», метка проекта и сводная точка; отдельная кнопка «Обновить из Jira» и
TopbarNoticeиз контракта убираются (событие пакета показывает ядро строкой под верхней строкой). Выезжающая панель — модальная (#73 M-1…M-3). - Пакеты нового проекта по модулям:
POST/PATCH /projectsотдаютbatches: [{module, id}];batch_idиbatch_idsостаются для MCP. Оболочка передаёт каждому модулю его пакет (снимает M-14/T5.3 #68 и потерюwiki_sync). - Граница в вебе — тестами: импорты (ядро → только
modules/index.ts; модуль → не другой модуль), уникальность имён модулей иnav.to(T7.5/T7.6 #90), пересечение имён классов CSS между модулями и с ядром. Префиксы классов не вводятся: тест на пересечение ловит именно случившийся дефект и не требует переименовывать сотни классов.div.wikiразметки Jira переименовывается в.jira-markup. - Типы jira — в модуль jira (
modules/jira/api/types.ts),closed.ts— туда же;fact_statusудаляется из веба (веб-часть #60). Вapi/types.tsядра остаютсяBatch,AgentView(полный вид изGET /agents),Project,CreatedProject,UpdatedProject,HubSettingsбез словарей jira. - Форматтеры дат — ядро (
ui/format.ts):fmtStamp(«ДД.ММ ЧЧ:ММ»),fmtDate(«ДД.ММ», не текущий год — «ДД.ММ.ГГГГ»). Списки и карточка перестают выводить ISO. - «Обзор» берёт сводку у модуля (
Summary({project})); счётчики jira — из снимка доски проекта (запрос только на «Обзоре»); вики —wiki_pagesиз проекта. - Порядок архива: sdlc-rename архивируется раньше web-modules: дельты
web-app/hub-apiэтого change написаны поверх текстов после переименования.
Контракт веб-модуля (после change)
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 | мелкая правка |
| 21 | 24.10.2026 | Task 2: ModuleProviders.tsx перенесён из modules/ в shell/ (импортирует только modules/index.ts) |
тест-страж границы допускает из ядра лишь modules/index.ts и modules/types.ts; App.tsx импортировал modules/ModuleProviders.tsx |
вернуть файл и ослабить страж |
| 22 | 24.10.2026 | Task 2: fmtDate доски (board/model/util.ts, принимает не-ISO даты Jira, год не показывает) остаётся в модуле; в ядре ui/format.ts — свой fmtDate, таблица и карточка задач берут его (даты другого года — ДД.ММ.ГГГГ); fmtStamp(null) → «—» |
задача требует форматтеры в ядре; доска сравнивает строки дат Jira в разных локалях | свести две функции в одну |
| 23 | 24.10.2026 | Task 2: словари jira-настроек — тип JiraSettings extends HubSettings в modules/jira/api/types.ts; HubSettings ядра — только project_key и индексная подпись |
страж запрещает термины jira в ядре | вернуть словари в HubSettings |
| 24 | 24.10.2026 | Task 3: ActivityProvider стоит в Layout (внутри ProjectContext), а не в App.tsx; «проект создан» — trackBatch(batchId) ядра (модуль берётся из Batch.module, запасной путь — refresh.isOwn) вместо useOnProjectCreated модуля |
project провайдера берётся из ProjectContext, он создаётся в Layout; модуль не называется в ядре |
перенести провайдер выше и передавать проект иначе |
| 25 | 24.10.2026 | Task 3: подхват пакетов — только при монтировании, смене проекта, раз в 60 с и после 409; прежний подхват «после загрузки снимка доски» (skipPickup, T10.1/T4.1) убран |
ядро не знает ключей данных модуля; правила подхвата заданы спекой; счётчик skipPickup больше не нужен |
вернуть подписку на кэш по ключу, объявляемому модулем |
| 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 |
ошибка и офлайн агента важнее, чем «идёт пакет» | поменять порядок |
| 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 |
| 33 | 24.10.2026 | Task 4: пункты панели ядра — shell/nav.tsx (OVERVIEW, GROUPS, NAV); NotFound, Layout и Sidebar берут их оттуда; группа — role="group" с видимым названием модуля (в свёрнутой панели название скрыто) |
один источник списка разделов для панели, «не найдено» и заголовка вкладки | свести в MODULES.flatMap по месту |
| 34 | 24.10.2026 | Task 4: список под окном карточки по прямому/прежнему адресу берёт project из адреса карточки (listBackground(search)), закрытие — /jira/issues?project=… |
брифом: «закрытие окна — с текущим проектом»; константа LIST_BACKGROUND без проекта теряла выбор |
вернуть константу, проект подставит Layout |
| 35 | 24.10.2026 | Task 4: модальность выезжающей панели — атрибут inert на соседях aside внутри .shell (страница и верхняя строка), фокус на «Меню» возвращается эффектом после закрытия; Dropdown гасит Escape (preventDefault) только когда сам открыт, а панель закрывается по Escape лишь при !defaultPrevented |
ловушка Tab на списке фокусируемых хрупка; inert закрывает и чтение с экрана |
ловушка фокуса вручную |
| 36 | 24.10.2026 | Task 4: --topbar-h удалён из shell.css и из board.css (.drawer на ширине страницы < 852 px статичен, а строка приложения бывает только на < 640 px) |
M-6 #73: переменная мертва | вернуть переменную |
| 37 | 24.10.2026 | Task 4: прокручивается середина панели (.sidebar__scroll: разделы и блок состояния), низ с темой и меню «Ещё» остаётся вне прокрутки; поправка R-1 решения #69 (меню «Ещё» свёрнутой панели выходит за 64 px, при overflow на всей панели обрезалось бы) |
M-5 #73: на низком окне панель не должна обрезаться | прокрутка всей панели и обрезанное меню |
| 38 | 24.10.2026 | Task 4: сводная точка узкого экрана — role="img" с aria-label «Состояние модулей: ошибка / идёт обновление / агент на связи / агент офлайн»; «идёт обновление» — нейтральный цвет (токена «info» нет); событие пакета узкого экрана — постоянный role="status" под строкой (data-testid="topbar-events") |
цвет не единственный признак; T10.1 #94 | вернуть aria-hidden-строку |
| 39 | 24.10.2026 | Task 5: GET /board на / («Обзор» грузит снимок доски для сводки jira, решение 13) — тест no wiki row without root блока состояния проверяет только отсутствие GET /wiki/status |
open() теста открывает /, а он теперь тянет доску; суть теста — строка вики без корня |
тест строже, чем нужно; ослабление скрывает запрос статуса вики без корня |
| 40 | 24.10.2026 | Task 5: общий agentText(a) (shell/activity/agentText.ts) — текст, подсказка и признак «на связи» агента; им пользуются блок состояния и карточки «Обзора» |
текст «агент на связи/офлайн/обновите агента» не должен разойтись между двумя местами | рефакторинг блока состояния; тесты блока его держат |
| 41 | 24.10.2026 | Task 5: useRecentBatches(project) опрашивает GET /batches?project=&limit=10 раз в 15 с (refetchInterval) |
ход пакетов меняется без участия страницы; инвалидация из активности ядра усложнила бы контракт | лишние запросы раз в 15 с на «Обзоре» |
| 42 | 24.10.2026 | Task 5: поле поиска вики на «Обзоре» не ведёт на пустой запрос (Enter при пустой строке ничего не делает); q в «Вики» — только начальное значение поля, выбор страницы убирает q из адреса |
пустой поиск = страница «Вики» без q, для неё есть ссылка «Вики»; hrefOf страницы по контракту Task 4 не несёт поиск |
пользователь, вставший на ?q= и открывший страницу, теряет в адресе строку поиска (в поле она остаётся) |
| 43 | 24.10.2026 | Task 6: номер доски проверяется в окне сам (/^\d+$/, ≥ 1, текст «нужно целое число»), поле — type="text" с inputMode="numeric", форма noValidate |
type="number" даёт браузерную подсказку вместо нашего текста и пропускает 51.5/1e3 по-разному в браузерах; T4.1 |
поле без стрелок счётчика; невалидное значение ловится только по «Создать» |
| 44 | 24.10.2026 | Task 6: в ядре useTrackModuleBatch() (activity.ts) — отслеживание пакета по имени модуля; окно создания вынесено в shell/NewProject.tsx; useTrackBatch остаётся для пакетов без известного модуля |
пакеты нового проекта привязываются каждому своему модулю по batches[].module (M-14, T5.3 #68); Layout не обязан знать модули |
пакет неизвестного модуля молча не отслеживается |