170 lines
40 KiB
Markdown
170 lines
40 KiB
Markdown
## 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 | мелкая правка |
|
||
| 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~~ — заменено решением 56 | ошибка и офлайн агента важнее, чем «идёт пакет» | поменять порядок |
|
||
| 30 | 24.10.2026 | Task 3: временный `shell/activity/StatusBlock.tsx` (строки модулей и строка событий узкого экрана) вместо `SidebarStatus`/`TopbarNotice` модулей; Task 4 заменяет его; страница вики берёт действие из трекера, уведомление об ошибке постановки (`syncError`) убрано — ошибка видна событием модуля | оболочка должна работать и набор быть зелёным до Task 4 | вернуть Notice на странице вики |
|
||
| 31 | 24.10.2026 | Task 3, fix T3.I1: подхват после 409 (`pickup(true)`) заменяет отслеживаемый чужой пакет первым (самым старым) другим незавершённым пакетом модуля; если другого нет — чужой остаётся | детали 409 (`batch_id`) клиент не разбирает, а список без команд не различает типы | брать `batch_id` из `ApiError.detail` |
|
||
| 32 | 24.10.2026 | Task 4: «Обзор» (`/`) пока рисует `shell/OverviewPage.tsx` — заглушка с заголовком под `ProjectGate`; Task 5 заменяет её страницей со сводками | пункт «Обзор» и адрес `/` нужны панели и перенаправлениям уже сейчас | оставить `/` → `/jira/board` до Task 5 |
|
||
| 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 не обязан знать модули | пакет неизвестного модуля молча не отслеживается |
|
||
| 45 | 24.10.2026 | Task 7: узкий экран «Вики» (страница выше дерева) — через `matchMedia("(max-width: 759.98px)")` и порядок в разметке (`WikiPage`), а не CSS `order`; дерево при поиске скрыто (`hidden`), а не размонтировано | порядок Tab совпадает с видимым; раскрытые вручную узлы переживают поиск (T10.2, T10.6); порог окна — приближение порога контейнера страницы (заменено решением 51: мерится ширина контейнера) | CSS `order` и размонтирование дерева |
|
||
| 46 | 24.10.2026 | Task 7: заголовки текста страницы вики сдвинуты ниже заголовка страницы: `#`/`##` → h3, `###` → h4 … не глубже h6 | порядок h1 → h2 → h3 без скачков и без второго h1 (T10.3) | оставить h1–h4 как есть |
|
||
| 47 | 24.10.2026 | Task 7: `.issue-table-wrap{overflow-x:auto}` на любой ширине (раньше — только `@container page < 1052px`) | на 1440 px девять столбцов шире панели; цена указана неверно: обёртка-скроллер выключает липкость шапки совсем — см. решение 50 | вернуть условие и сжать столбцы |
|
||
| 48 | 24.10.2026 | Task 7: проверка T5.2 «ядро не грузит /board и /wiki/status» идёт на странице вне модулей (`/app/nowhere`), а не на `/wiki?project=…` | страница «Вики» сама запрашивает `/wiki/status` (это модуль, не ядро) | проверять на странице вики с исключением статуса |
|
||
| 49 | 24.10.2026 | Task 7: `scrollbar-gutter: stable` — на `html` (прокручивается окно, отдельной области страницы нет); «Обновить из Jira» в шапках — общий `modules/jira/RefreshButton.tsx` | страницы разной высоты не двигаются по ширине; одна реализация кнопки на три страницы | прокручиваемый контейнер страницы; кнопка в каждой странице |
|
||
| 50 | 24.10.2026 | Task 7, fix T7.I1: обёртка таблицы «Задач» при высоте окна ≥ 700 px получает `max-height: calc(100vh - 220px)` и `overflow-y:auto`, шапка липнет к обёртке; на низких окнах ограничения нет — шапка уходит со страницей | при `overflow-x:auto` обёртка — скроллер, и `position:sticky` шапки внутри неё без собственной вертикальной прокрутки не работает (решение 47 ошибочно считало, что шапка липнет к панели); на длинных списках шапка должна оставаться на виду; цена — вложенная вертикальная прокрутка (на телефонах высотой < 700 px её нет) | снять sticky/z-index и принять уходящую шапку |
|
||
| 51 | 24.10.2026 | Task 7, fix T7.I2: порядок «страница/дерево» в `WikiPage` определяется `ResizeObserver` на `.wiki__body` с порогом `≤ 759.98` px — тем же, что `@container page` в `wiki.css`; `matchMedia` убран | окно и контейнер страницы (рядом боковая панель) расходятся, на окне ≈ 760–1050 px CSS уже в одну колонку, а DOM ещё нет; до первого замера (и без `ResizeObserver`) — широкая раскладка; ключи панелей сохраняют состояние дерева | вернуть `matchMedia` с запасом по ширине панели |
|
||
| 52 | 24.10.2026 | Task 7, fix T7.I2: `useWikiDownload` возвращает объект через `useMemo` | перерисовка `WikiPage` (замер ширины) пересоздавала `{start,status}` → набор компонентов Markdown → ссылки-вложения перемонтировались (тест M-15 обнаружил); так же ломали бы их ввод в поиск и опрос статуса | не трогать хук и убрать состояние замера |
|
||
| 53 | 24.10.2026 | Task 8: проверка «без горизонтальной прокрутки» на 1440 px — `document.documentElement.scrollWidth <= window.innerWidth`, а не `===` | при классической полосе прокрутки Chromium `scrollWidth` равен ширине без полосы (1425 против 1440), равенство невозможно; смысл проверки — отсутствие переполнения — сохранён | ложное «зелёное» только если переполнение скрыто тем же зазором; на 390 px (оверлейные полосы) проверка прежняя и строгая |
|
||
| 54 | 24.10.2026 | Task 8, дефект: у `.issue-table-wrap` добавлен `position: relative` | скрытый текст «, просрочено» (`.visually-hidden`, `position:absolute`) не имел позиционированного предка внутри обёртки и выходил за её прокрутку: на 390 px `scrollWidth` страницы 778 при окне 390 | без правки на узком экране у «Задач» появлялась горизонтальная прокрутка всей страницы |
|
||
| 55 | 24.10.2026 | Task 8: «Последние пакеты проекта» проверяются пакетом «Обновить вики» (пакет проекта), а не «Обновить из Jira»; для событий пакета Jira в e2e — хелпер `jiraEvent` (строка модуля в панели / полоса на узком экране) | пакет «Обновить из Jira» не привязан к проекту (охват «все проекты») и в список проекта не попадает; на странице несколько `role=status`, прежний `getByRole("status")` неоднозначен | только тесты; список пакетов проекта и так фильтруется хабом по `project` |
|
||
| 56 | 24.10.2026 | Final fix wave, I-2: сводная точка — error > busy > offline > online (заменяет решение 29) | спека «Оболочка и адреса» — источник истины: ошибка, затем «пакет в работе», затем агенты на связи или нет | перепутанный порядок красит точку «офлайн» при идущем пакете; спека и код расходятся |
|
||
| 57 | 24.10.2026 | Final fix wave, M-6/T2.3: офлайн-агент модуля сводку не красит, если `freshness(project).available === false` (вики без корня); проект для проверки провайдер берёт из `useProjects()` (тот же ключ запроса, без нового запроса); пока проекты не загружены — агент считается значимым | ядру нечего показывать и обновлять у модуля без данных проекта; `freshness(p: Project)` не менялась | кратковременно (до загрузки проектов) возможна «офлайн» у проекта без корня вики |
|
||
| 58 | 24.10.2026 | Final fix wave, T2.3: `jira_synced_at` убрано из ядрового `Project` и перенесено в `JiraProject` (`modules/jira/projectFields.ts`); `jira/status.ts` читает поле через type guard `hasJiraSync(p)` без `as`; `boundary.test.ts` ловит имена полей модулей в ядре (`MODULE_TERMS` расширен) | ядро не должно знать поля модулей; контракт `freshness(p: Project)` сохранён — минимальное решение без обобщения `ModuleStatus` | проект без поля (старый ответ хаба) показывается как «обновления ещё не было» |
|
||
| 59 | 24.10.2026 | Final fix wave, M-11: в `agentText.ts` предупреждение/`update_hint` важнее офлайна (офлайн-агент с предупреждением показывается как «обновите агента») | предупреждение требует действия человека и объясняет, почему агент не работает, а «офлайн» может пройти сам | человек увидит «обновите агента» у агента, который ещё и офлайн; после обновления текст сменится на «офлайн» |
|
||
| 60 | 24.10.2026 | Final fix wave, M-9: принято как известное ограничение — `jira_synced_at` сбрасывается после создания проекта: `last_full_sync` сравнивает JQL прогона с текущей базовой JQL, а при новом проекте она меняется; до нового полного прогона у всех проектов «обновления ещё не было». Кодом не чинится, follow-up | правка — в хабе (Python), вне объёма веб-change | у всех проектов после создания нового до следующего «Обновить из Jira» нет даты свежести |
|
||
| 61 | 24.10.2026 | Final fix wave, #68 M-2: меню доски названо «Ещё — доска»; меню панели остаётся «Ещё» | однозначные доступные имена двух меню на одной странице; тесты больше не зависят от гонки | пользовательские сценарии, искавшие «Ещё» на доске, ищут новое имя |
|
||
| 62 | 24.10.2026 | Final fix wave, #73 M-2: выезжающая панель обёрнута в `div.sidebar-modal[role=dialog][aria-modal][aria-label=Меню]` (`display: contents`), `<aside>` «Панель приложения» остаётся внутри; прокрутка страницы блокируется `overflow:hidden` на `<html>` пока панель открыта | роль dialog на самой `aside` стёрла бы роль complementary и имя «Панель приложения» (тесты, e2e, читалки экрана); `scrollbar-gutter: stable` исключает сдвиг | обёртка не должна менять раскладку; при сбое очистки эффекта прокрутка осталась бы заблокированной (очистка возвращает прежнее значение) |
|
||
| 63 | 24.10.2026 | Final fix wave, M-2: `Active` получает `project`; `track(id, project?)` (по умолчанию — текущий проект); смена проекта сбрасывает отслеживаемый пакет проектного модуля, только если он принадлежит другому проекту; «Новый проект» передаёт метку нового проекта | пакет вики нового проекта ставится до смены адреса и раньше сбрасывался эффектом смены проекта | пакет без указанного проекта считается пакетом текущего |
|
||
| 64 | 24.10.2026 | Final fix wave, T10.6: `Tree.selectedKnown` истинно, только когда полная цепочка предков выбранного узла до корня есть в `nodes` | узел мог прийти раньше родителя, и предки не раскрывались | при разорванной цепочке (родителя в дереве нет) предки не раскрываются — раскрывать нечего |
|
||
| 65 | 24.10.2026 | Final fix wave, M-1/M-7: `start()` фиксирует `generation` вместе с проектом до `await`; признак «агент офлайн» подхваченного пакета вычисляется при отрисовке (`active.agentOffline \|\| agent.online === false`) | пакет старого проекта не должен появляться в строке нового; снимок на момент подхвата устаревает | признак «офлайн» не снимается, пока пакет отслеживается, если был поставлен при офлайн-агенте |
|