docs: план доработок экранов доски (#112)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-25 20:08:15 +04:00
parent 5c7a845a6e
commit ef9abfa8bd
2 changed files with 328 additions and 0 deletions

View File

@ -0,0 +1,323 @@
# Доработки «Бэклога» и «Спринта» (#112) — план реализации
> **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:** фильтры, свёртка, панель эпиков, выбор спринтов, скрытие колонок, значки типов и доска во всю высоту окна на
страницах «Бэклог», «Спринт», «Kanban» веб-приложения sdlc-hub — только просмотр.
**Architecture:** только фронт (`web/src/modules/jira/agile/`), данные — прежний `GET /agile`. Фильтры — чистая
модель `filters.ts` + запоминание `agilePref.ts` + хук `useAgileView`; страницы и компоненты только рисуют. Общие
части — панель фильтров `AgileToolbar`, меню `ColumnsMenu`, `BoardColumns`.
**Tech Stack:** React 19 + TypeScript + Vite, TanStack Query, Vitest + Testing Library, Playwright; UI-кит
`web/src/ui`, токены дизайн-системы AURA.
**Spec:** `openspec/changes/agile-web-polish/` (требование «Спринты и бэклог» в `specs/web-app/spec.md`, решения —
`design.md`, рулинги дизайна R-1…R-13; рулинги контроллера — с R-14).
## Global Constraints
- Веб только смотрит: MUST NOT появляться действий, меняющих Jira; ни одна кнопка не ставит команд.
- Сервер, API, схема, агент, MCP — не меняются (никаких правок вне `web/` и `openspec/`/`docs/`, кроме сида
`web/scripts/seed-hub.py`).
- Ключ хранилища — ровно `sdlc-hub-agile`; JSON `{ "<метка>": { "backlog"|"sprint"|"kanban": AgileView } }`; поиск
(`q`) и выбор спринтов не хранятся; любая ошибка хранилища — значения по умолчанию, без исключения наружу.
- Тексты интерфейса — дословно из спеки: «Ключ или тема», «Исполнитель», «все», «Без исполнителя», «Скрыть
выполненные», «Сбросить фильтры», «Эпики», «Все задачи», «Без эпика», «Эпик», «Колонки», «Спринты», «показано K»,
«Нет задач», «Нет задач по фильтрам», «Нет активного спринта», «Без колонки».
- Стили — только токены дизайн-системы (как в `agile.css`), без новых цветов-литералов.
- Работа только в worktree `/Users/andrey.epifancev/projects/jira-bridge/sdlc-hub/.wt/agile-polish`: никаких записей
в основной checkout и в другие репозитории (O-6 #108); никаких команд, ждущих stdin (`cat > файл` без heredoc и т.п.).
- Зелёный = `make check` целиком (pytest + `npm run lint` + `npm test`) и `cd web && npm run e2e` целиком.
## Review Focus
1. **Задача в двух выбранных спринтах** (перенос из активного в будущий) — на доске одна карточка, не две → тест в Task 2
(`sprintIssues`) и Task 6.
2. **Сохранённое значение, которого больше нет** (исполнитель ушёл, эпик закрыт и пропал, колонку переименовали в
Jira) — значение остаётся видимым и снимаемым, страница не пустеет молча → тесты в Task 2 (`assigneeOptions`), Task 5,
Task 7.
3. **Испорченное хранилище** (`sdlc-hub-agile` = `"oops"`, `{"AWPCA": 5}`, поле `collapsed: "x"`, `setItem` бросает)
— страница открывается со значениями по умолчанию → тесты в Task 2.
4. **Проект сменён на открытой странице** — фильтры другого проекта не протекают (страница пересоздаётся по `key`
проекта, состояние читается по метке) → тест в Task 4.
5. **Длинная колонка и узкое окно** — 900 px: страница без горизонтальной прокрутки, доска прокручивается внутри,
полоса прокрутки доски в окне; 2560 px — колонки используют ширину окна → e2e в Task 8.
---
### Task 0: Окружение worktree (без коммита)
- [ ] `cd /Users/andrey.epifancev/projects/jira-bridge/sdlc-hub/.wt/agile-polish && uv sync && (cd web && npm ci)`
- [ ] `make check` — зелёный (база до правок; запомнить число тестов в ledger).
### Task 1: Формат SP и значки типов
**Files:**
- Modify: `web/src/modules/jira/agile/model.ts` (`fmtSp`, новое `typeKind`)
- Modify: `web/src/modules/jira/agile/TypeIcon.tsx`, `web/src/modules/jira/agile/agile.css`
- Modify: `web/src/ui/icons.tsx` (новые символы)
- Test: `web/src/modules/jira/agile/model.test.ts`, новый `web/src/modules/jira/agile/TypeIcon.test.tsx`
**Interfaces:**
- Produces: `fmtSp(n: number | null): string`; `type TypeKind = "epic" | "story" | "task" | "bug" | "subtask" | "other"`;
`typeKind(type: string | null): TypeKind`; `isEpic(i: { issue_type: string | null }): boolean` — всё в `model.ts`.
Значки: `IconBookmark`, `IconCircle`, `IconSquares`, `IconDot` в `web/src/ui/icons.tsx`.
- [ ] **Step 1: тесты.** `model.test.ts`: `fmtSp(null) === "—"`, `fmtSp(3) === "3"`, `fmtSp(0.1 + 0.2) === "0.3"`,
`fmtSp(2.5) === "2.5"`, `fmtSp(1/3) === "0.33"`, `fmtSp(0) === "0"`. `typeKind`: «Эпик», «epic», «Фича» → `epic`; «История», «Story» → `story`; «Задача», «Task» →
`task`; «Ошибка», «BUG» → `bug`; «Подзадача», «Sub-task», «Subtask» → `subtask`; `null`, `""`, «Риск» → `other`.
`TypeIcon.test.tsx`: для каждого вида — `role="img"` с `aria-label` = исходное имя типа (для `null` — «Задача» не
подставлять: `aria-label="Тип не указан"`), класс `agile-type--<kind>`.
- [ ] **Step 2:** `cd web && npx vitest run src/modules/jira/agile/model.test.ts src/modules/jira/agile/TypeIcon.test.tsx` — FAIL.
- [ ] **Step 3: реализация.** `fmtSp`: `Number(n.toFixed(2))` → `String` (M-6). `typeKind` — сравнение без учёта
регистра со списками имён из спеки. Символы в `icons.tsx` тем же `make(...)`: закладка, круг, два квадрата, точка;
эпик — существующий `IconCritical` (молния), задача — `IconCheck`. `TypeIcon`: `<span className="agile-type
agile-type--<kind>">` — квадрат 16 px с `border-radius:var(--radius-xs)`, фон — токен вида (эпик `--k-ui`, история
`--color-status-success`, задача и подзадача `--color-action-primary`, ошибка `--color-status-error`, прочее —
`--color-text-secondary`), символ 12 px цвета `--color-text-on-primary`. Контраст символа проверить в светлой и
тёмной теме; не хватает — рулинг с выбранным токеном, не литерал.
- [ ] **Step 4:** тот же vitest — PASS; `npm run lint` — PASS.
- [ ] **Step 5:** коммит `feat(web): значки типов по образцу Jira и формат SP (#112, M-6)`.
### Task 2: Модель фильтров и запоминание
**Files:**
- Create: `web/src/modules/jira/agile/filters.ts`, `web/src/modules/jira/agile/agilePref.ts`
- Test: `web/src/modules/jira/agile/filters.test.ts`, `web/src/modules/jira/agile/agilePref.test.ts`
**Interfaces:**
- Consumes: `isEpic`, `typeKind` (Task 1); типы `AgileIssue`, `AgileSprint`, `AgileResponse` (`../api/types.ts`).
- Produces (`filters.ts`):
- `type AgilePage = "backlog" | "sprint" | "kanban"`
- `interface AgileView { assignee: string; epic: string; hideDone: boolean; collapsed: string[]; hiddenColumns: string[]; epicsPanel: boolean }`
- `const DEFAULT_VIEW: AgileView` = `{ assignee: "", epic: "", hideDone: false, collapsed: [], hiddenColumns: [], epicsPanel: true }`
- `const NONE = "-"` — значение «Без исполнителя» / «Без эпика»
- `interface IssueFilter { q: string; assignee: string; epic: string; hideDone: boolean }`
- `matches(i: AgileIssue, f: IssueFilter): boolean` — И всех условий; `q` — подстрока без учёта регистра в `key` или `summary` (пробелы по краям `q` отбрасываются)
- `hasFilters(f: IssueFilter): boolean`
- `type Opt = { value: string; label: string }`
- `assigneeOptions(issues: AgileIssue[], current: string): Opt[]` — `["", "все"]`, `[NONE, "Без исполнителя"]`, затем уникальные исполнители по `localeCompare("ru")`; `current`, которого нет, добавляется в конец с подписью = значению
- `interface EpicEntry { key: string; title: string; count: number; done: boolean }`
- `epicEntries(d: AgileResponse): EpicEntry[]` — по спеке: строки-эпики из всех спринтов и бэклога по `rank` (без повторов), затем ключи из `epic_key`, строк которых нет, по ключу (`title` = ключ, `done` = false); `count` — задачи-не-эпики с этим `epic_key` в активных, будущих спринтах и бэклоге (без повторов по ключу)
- `byRank(a: AgileIssue, b: AgileIssue): number` — `rank` строкой, `null` в конце, затем по `key`
- `sprintIssues(sprints: AgileSprint[]): AgileIssue[]` — объединение без повторов по `key`, сортировка `byRank`
- `selectableSprints(sprints: AgileSprint[]): AgileSprint[]` — активные (в порядке входа), затем будущие по `start_date` (через `jiraDate`), без даты — в конце
- `parseSprintIds(param: string | null, sprints: AgileSprint[]): number[]` — id из `"101,102"`, только среди `selectableSprints`, порядок `selectableSprints`; пусто → id всех активных
- Produces (`agilePref.ts`): `const AGILE_KEY = "sdlc-hub-agile"`; `readView(label: string, page: AgilePage): AgileView`;
`saveView(label: string, page: AgilePage, v: AgileView): void` (не трогает другие страницы и проекты в JSON).
- [ ] **Step 1: тесты `filters.test.ts`** (фабрики `iss`/`sprint` — как в `BacklogPage.test.tsx`): `matches` по каждому
условию и их И (`q: " aurora-1 "` находит `AURORA-1` и `AURORA-10`; `q: "тема"` — по теме; `assignee: NONE` — только
`null`; `epic: NONE` — только `epic_key === null`; `hideDone`); `hasFilters` для пустого и каждого поля;
`assigneeOptions` — порядок, «Без исполнителя», ушедший `current` в конце; `epicEntries` на данных сценария «Фильтр
по эпику» спеки (`AURORA-10`: 3 в бэклоге + 2 в активном → `count` 5), эпик, на который ссылаются, но строки нет,
— в конце с `title` = ключ, задачи прошлых спринтов не считаются; `sprintIssues` — задача в двух спринтах одна,
`rank: null` в конце; `selectableSprints` — закрытые отброшены, будущий без даты последним; `parseSprintIds` —
`"102,999"` → `[102]`, `"abc"` → все активные, `null` → все активные, будущий `"103"` → `[103]`.
- [ ] **Step 2: тесты `agilePref.test.ts`:** нет ключа → `DEFAULT_VIEW`; запись `AWPCA/backlog` не меняет
`AWPCA/sprint` и `STOCKS/backlog`; мусор: `"oops"`, `"[1]"`, `{"AWPCA": 5}`, `{"AWPCA":{"backlog":{"collapsed":"x","hideDone":"yes"}}}`
→ по полю значения по умолчанию (`collapsed: []`, `hideDone: false`), годные поля сохраняются; `localStorage.setItem`
бросает (`vi.spyOn(Storage.prototype, "setItem")`) — `saveView` не бросает; `getItem` бросает — `readView` →
`DEFAULT_VIEW`.
- [ ] **Step 3:** `npx vitest run src/modules/jira/agile/filters.test.ts src/modules/jira/agile/agilePref.test.ts` — FAIL.
- [ ] **Step 4: реализация** по Interfaces; разбор хранилища — поле за полем с проверкой типа (строка, булево, массив строк).
- [ ] **Step 5:** тот же vitest — PASS; `npm run lint` — PASS.
- [ ] **Step 6:** коммит `feat(web): модель фильтров экранов доски и их запоминание (#112)`.
### Task 3: Хук состояния и панель фильтров
**Files:**
- Create: `web/src/modules/jira/agile/useAgileView.ts`, `web/src/modules/jira/agile/AgileToolbar.tsx`
- Modify: `web/src/modules/jira/agile/agile.css`
- Test: `web/src/modules/jira/agile/AgileToolbar.test.tsx`
**Interfaces:**
- Consumes: Task 2 целиком.
- Produces:
- `useAgileView(label: string, page: AgilePage): { view: AgileView; set(patch: Partial<AgileView>): void; q: string; setQ(v: string): void; filter: IssueFilter; reset(): void }`
— `set` пишет в хранилище (`saveView`) вне функции-обновителя (как T8.1 #69); `reset` очищает `q`, `assignee`,
`epic`, `hideDone` и сохраняет остальное.
- `AgileToolbar({ filter, setQ, set, onReset, assignees, showHideDone, children }: { filter: IssueFilter; setQ(v: string): void; set(patch: Partial<AgileView>): void; onReset(): void; assignees: Opt[]; showHideDone: boolean; children?: ReactNode })`
— `Panel` с `SearchInput label="Поиск по ключу или теме" placeholder="Ключ или тема"`, `Select aria-label="Исполнитель"`,
при `showHideDone` — `Checkbox label="Скрыть выполненные"`, слот `children` (кнопка «Эпики», выбор спринтов, меню
«Колонки»), кнопка «Сбросить фильтры» только при `hasFilters(filter)`. Без кнопок, ставящих команды.
- [ ] **Step 1: тесты `AgileToolbar.test.tsx`** (рендер с хуком в обёртке): ввод в поиск меняет `filter.q`; выбор
исполнителя сохраняется в `localStorage["sdlc-hub-agile"]` по метке и странице, `q` — нет; «Сбросить фильтры»
появляется после ввода и снимает всё; `showHideDone={false}` — флажка нет; повторный монтаж с той же меткой и
страницей — исполнитель и флажок восстановлены.
- [ ] **Step 2:** `npx vitest run src/modules/jira/agile/AgileToolbar.test.tsx` — FAIL.
- [ ] **Step 3: реализация**; стили `.agile-toolbar` (flex-wrap, gap `--space-3`, поиск растягивается) в `agile.css`.
- [ ] **Step 4:** тот же vitest — PASS; `npm run lint` — PASS.
- [ ] **Step 5:** коммит `feat(web): панель фильтров экранов доски (#112)`.
### Task 4: «Бэклог»: фильтры, свёртка секций, без строк эпиков
**Files:**
- Modify: `web/src/modules/jira/agile/BacklogPage.tsx`, `web/src/modules/jira/agile/agile.css`
- Test: `web/src/modules/jira/agile/BacklogPage.test.tsx`
**Interfaces:**
- Consumes: `useAgileView(label, "backlog")`, `AgileToolbar`, `matches`, `isEpic`, `assigneeOptions` (Tasks 1–3).
- Produces: `SprintSection`/секция «Бэклог» принимают `collapsed: boolean; onToggle(): void; shown: AgileIssue[]` —
Task 5 добавляет фильтр эпика через тот же `filter`.
- [ ] **Step 1: тесты** (дописать в `BacklogPage.test.tsx`, существующие оставить зелёными):
- заголовок секции — кнопка с `aria-expanded="true"`; щелчок — `false`, задач секции нет, итоги видны; после
повторного монтажа (`unmount` + `open`) секция свёрнута (сценарий «Свёрнутый активный спринт»); свернуть можно и
будущий, и «Бэклог»;
- эпик (`issue_type: "Фича"`) в бэклоге и в спринте — строкой не показан; «N задач» секции «Бэклог» его не считает;
- «Скрыть выполненные» на спринте из 10 задач (4 выполнены, `count: 10`): 6 строк, «10 задач» и «показано 6»;
прошлый спринт раскрыт — все его задачи (сценарий «Скрыть выполненные»);
- поиск «AURORA-50» — в «Бэклоге» одна строка, в секции спринта «Нет задач по фильтрам»;
- смена проекта (Review Focus 4): исполнитель выбран у `AWPCA`, открыт тот же адрес с другим scrum-проектом —
исполнитель «все».
- «Бэклог только смотрит»: на странице нет элементов с `draggable`, все кнопки — свёртка, панель фильтров или
раскрытие (перечислить по `getAllByRole("button")` и их именам).
- [ ] **Step 2:** `npx vitest run src/modules/jira/agile/BacklogPage.test.tsx` — новые FAIL.
- [ ] **Step 3: реализация.** Видимые задачи секции = `issues.filter(i => !isEpic(i) && matches(i, f))`; для прошлых
спринтов `hideDone` не применяется (`{...f, hideDone: false}`). «показано K» — когда `shown.length` меньше числа
задач-не-эпиков секции. Заголовок — `<button className="agile-section__toggle" aria-expanded aria-controls>` с
`IconChevronDown`/`IconChevronUp`; id секции в `collapsed` — `String(sprint.id)` и `"backlog"`.
- [ ] **Step 4:** тот же vitest — PASS; `npm run lint` — PASS.
- [ ] **Step 5:** коммит `feat(web): «Бэклог» — фильтры, свёртка секций, эпики не строками (#112)`.
### Task 5: Панель «Эпики» на «Бэклоге»
**Files:**
- Create: `web/src/modules/jira/agile/EpicsPanel.tsx`
- Modify: `web/src/modules/jira/agile/BacklogPage.tsx`, `web/src/modules/jira/agile/agile.css`
- Test: `web/src/modules/jira/agile/BacklogPage.test.tsx`
**Interfaces:**
- Consumes: `epicEntries`, `EpicEntry`, `NONE`, `useAgileView` (Task 2–3), страница из Task 4.
- Produces: `EpicsPanel({ entries, value, onChange, hideDone }: { entries: EpicEntry[]; value: string; onChange(v: string): void; hideDone: boolean })`
и `EpicSelect` с теми же пропсами (`Select aria-label="Эпик"`: «Все задачи», эпики, «Без эпика»).
- [ ] **Step 1: тесты:**
- сценарий «Фильтр по эпику» спеки дословно (3 + 5 в бэклоге, 2 в спринте; выбран `AURORA-10` → в спринте 2 и
«показано 2», в «Бэклоге» 3, строки эпика нет, у пункта в панели 5);
- пункт — кнопка с `aria-pressed`; повторный щелчок по выбранному — «Все задачи» (`aria-pressed="true"`);
- «Без эпика» — только задачи с `epic_key: null`;
- эпик, на который ссылаются, но строки нет, — пункт с ключом; фильтр по нему работает;
- «Скрыть выполненные»: выполненный эпик пропадает из панели, кроме выбранного (Review Focus 2);
- сохранённый эпик, которого нет в данных, остаётся выбранным пунктом (по ключу), секции — «Нет задач по фильтрам»;
- кнопка «Эпики» в панели фильтров (`aria-pressed`) скрывает и показывает панель, состояние переживает повторный монтаж;
- фильтр эпика применяется и к задачам прошлого спринта.
- [ ] **Step 2:** vitest файла — новые FAIL.
- [ ] **Step 3: реализация.** Раскладка `.agile-backlog`: сетка `240px minmax(0,1fr)` (без панели — одна колонка);
при `@container page (max-width: 899px)` панель скрыта, в панели фильтров показан `EpicSelect` (на широком —
скрыт); кнопка «Эпики» на узком скрыта. Пункт: тема (или ключ), ключ моноширинным, число.
- [ ] **Step 4:** vitest файла — PASS; `npm run lint` — PASS.
- [ ] **Step 5:** коммит `feat(web): панель «Эпики» на «Бэклоге» (#112)`.
### Task 6: «Спринт»: выбор спринтов и фильтры
**Files:**
- Create: `web/src/modules/jira/agile/SprintPicker.tsx`
- Modify: `web/src/modules/jira/agile/SprintPage.tsx`
- Test: `web/src/modules/jira/agile/SprintPage.test.tsx`
**Interfaces:**
- Consumes: `selectableSprints`, `parseSprintIds`, `sprintIssues`, `matches`, `useAgileView(label, "sprint")`, `AgileToolbar`.
- Produces: `SprintPicker({ sprints, selected, onChange }: { sprints: AgileSprint[]; selected: number[]; onChange(ids: number[]): void })`
— `Dropdown` с кнопкой «Спринты» (на кнопке — имя единственного выбранного или «Спринты: N»), внутри `Checkbox` по
спринту (имя, «активный»/«будущий»); снять последнюю отметку нельзя (`disabled`). `BoardColumns` получает
отфильтрованные задачи (Task 7 добавит `hidden`).
- [ ] **Step 1: тесты** (переписать «два активных: переключатель…», «один активный: переключателя нет», «устаревший
?sprint=», «мусорный ?sprint=abc» под новую спеку; остальные — оставить):
- по умолчанию выбраны все активные; два активных → два заголовка и задачи обоих;
- сценарий «Будущий спринт на доске»: отметить будущий 102 при активном 101 → адрес `?sprint=101,102` без новой
записи истории (`router.state.historyAction === "REPLACE"`), два заголовка, «осталось N дн.» только у 101;
- задача в обоих выбранных спринтах — одна карточка (Review Focus 1);
- `?sprint=999` и `?sprint=abc` → все активные; `?sprint=103` (будущий) → только он;
- нет активного, есть будущий → «Нет активного спринта», ссылка «Бэклог» и выбор спринтов доступен; выбор будущего
показывает доску;
- сценарий «Исполнитель на доске спринта»: выбран исполнитель → только его карточки, числа колонок — по видимым; на
«Бэклоге» (`open("/jira/backlog?…")`) исполнитель «все»;
- «Скрыть выполненные» убирает выполненные карточки.
- [ ] **Step 2:** vitest файла — новые FAIL.
- [ ] **Step 3: реализация** по Interfaces; заголовки — `SprintHead` на каждый выбранный, компактно (`agile-section__head`),
«осталось…»/«срок прошёл» — только у `state === "active"`. Прежний `Segmented` убрать.
- [ ] **Step 4:** vitest файла — PASS; `npm run lint` — PASS.
- [ ] **Step 5:** коммит `feat(web): «Спринт» — выбор активных и будущих спринтов, фильтры (#112)`.
### Task 7: Колонки доски: скрытие, одна высота, прокрутка; «Kanban»
**Files:**
- Create: `web/src/modules/jira/agile/ColumnsMenu.tsx`
- Modify: `web/src/modules/jira/agile/BoardColumns.tsx`, `SprintPage.tsx`, `KanbanPage.tsx`, `agile.css`
- Test: `web/src/modules/jira/agile/SprintPage.test.tsx`, `KanbanPage.test.tsx`, `columns.test.ts` при необходимости
**Interfaces:**
- Consumes: `ColumnView`/`columnsOf` (без изменений), `useAgileView(label, "sprint" | "kanban")`, `AgileToolbar`.
- Produces: `ColumnsMenu({ names, hidden, onChange }: { names: string[]; hidden: string[]; onChange(hidden: string[]): void })`
— `Dropdown` с кнопкой «Колонки» (+ `Badge` с числом скрытых, если > 0), отметки по именам; последняя видимая —
`disabled`; имя из `hidden`, которого нет в `names`, в меню не показывается и на `Badge` не считается.
`BoardColumns({ columns, lastCaption, hidden }: { columns: ColumnView[]; lastCaption?: string; hidden?: string[] })`.
- [ ] **Step 1: воспроизвести баг п. 10** (superpowers:systematic-debugging): тестовый хаб (`bash web/scripts/test-hub.sh`
в фоне + `npm run dev` или собранное приложение) с широкой доской из Task 8 Step 1 (если её ещё нет — временно в
своём сиде), Playwright-скрипт в scratchpad: окно 2560 × 1300, замерить у `.page`, `.agile-columns` ширину и
`getBoundingClientRect().bottom` против `innerHeight`. Записать причину в ledger. Ожидаемая по design #9 и R-12:
полоса прокрутки ниже окна и ширина страницы ограничена `max-width:1440px` у `.page`. Если причина другая — чинить
её, требование спеки то же.
- [ ] **Step 2: тесты:** сценарий «Скрытая колонка» (снять «Готово» → колонки нет, на «Колонки» — 1, после повторного
монтажа так же); последнюю видимую снять нельзя; заголовки колонок — `getAllByRole("heading", { level: 3 })` (T10.3);
«Kanban»: поиск и исполнитель фильтруют колонки и «Бэклог (N)», флажка «Скрыть выполненные» нет, меню «Колонки» есть;
скрытое имя колонки, которой больше нет, не ломает меню.
- [ ] **Step 3:** vitest файлов — новые FAIL.
- [ ] **Step 4: реализация.** Скрытие — фильтр `columns` по имени в `BoardColumns`. Раскладка (design #9, R-12):
страница доски (`.agile--wide`) без ограничения `max-width` у `.page` (`.page:has(> .agile--wide)` или класс от
страницы — выбрать и записать рулинг), доска занимает высоту окна под шапкой и панелью фильтров (`min-height:360px`;
на экране < 640 px учесть закреплённую строку «Меню»), `.agile-columns{align-items:stretch}`, у колонки
прокручивается `.agile-col__list` (`overflow-y:auto; min-height:0`). Горизонтальная прокрутка — у `.agile-columns`.
- [ ] **Step 5:** vitest файлов — PASS; `npm run lint` — PASS; скрипт из Step 1 — полоса прокрутки в окне, последняя
колонка достижима, колонки одной высоты.
- [ ] **Step 6:** коммит `fix(web): колонки доски — одна высота, прокрутка в окне, скрытие колонок (#112, T10.3)`.
### Task 8: e2e, сид и попутные тесты hub#111
**Files:**
- Modify: `web/scripts/seed-hub.py`, `web/e2e/agile.spec.ts`
- Test: `web/src/modules/jira/agile/AgileGate.test.tsx` (новый), `web/src/shell/nav.test.ts` или `KanbanPage.test.tsx`
**Interfaces:**
- Consumes: всё выше; сид — прежний формат `boards` в `mirror.apply_state`.
- [ ] **Step 1: сид.** Проект `WIDE_E2E` (scrum-доска 6100): 11 колонок по своим статусам + задачи со статусом вне
колонок («Без колонки»), активный спринт, 40 задач в одной колонке, по 1–3 в остальных; эпики для AWPCA уже есть в
фикстуре (`AURORA-9100` «Фича», ссылки на отсутствующие `AURORA-1359`, `AURORA-9999`). Фиксированный `jira_updated`
у задач канбана вместо «сегодня 12:00», если от него не зависит «за 14 дней» (T12.2; если зависит — дата
`today - 1 day` и комментарий). Проверить, что прочие e2e (`projects.spec.ts`, `overview.spec.ts`) не считают проекты
жёстко; если считают — поправить ожидание.
- [ ] **Step 2: e2e** (`agile.spec.ts`):
- «Широкая доска» по спеке: окно 2560 × 1300, `WIDE_E2E` — высоты всех `.agile-col` равны (±1 px), нижний край
`.agile-columns` ≤ `innerHeight`, `scrollWidth > clientWidth` у доски, после `scrollLeft = scrollWidth` колонка
«Без колонки» целиком в окне, `document.documentElement.scrollWidth ≤ clientWidth`, ширина `.agile-columns` больше
1440 px;
- 900 px — прежний тест плюс нижний край доски ≤ `innerHeight`;
- «Бэклог» AWPCA: панель «Эпики» с `AURORA-9100`, выбор — строки только его задач; свёртка секции переживает
перезагрузку;
- «Спринт» AWPCA: отметить «AWPCA_Спринт 3» → адрес `sprint=102,103`; скрыть «Готово» → после перезагрузки скрыта;
- T12.3/T12.4: в существующих тестах файла — локаторы по роли и точному тексту вместо нестрогих `getByText` и
`.last()`/`.first()` по порядку DOM.
- [ ] **Step 3: T9.4** — vitest `AgileGate.test.tsx`: «Загрузка…» пока `/agile` висит; ошибка `/agile` → текст ошибки и
«Повторить», щелчок повторяет запрос; меню kanban-проекта: «Kanban» есть, «Бэклог»/«Спринт» нет.
- [ ] **Step 4:** `cd web && npm run e2e` — весь набор PASS; `make check` (из корня worktree) — PASS.
- [ ] **Step 5:** коммит `test(web): e2e широкой доски и фильтров, сид WIDE_E2E, тесты AgileGate (#112, T9.4, T12.2–T12.4)`.
### Task 9: Документы
**Files:**
- Modify: `openspec/changes/agile-web-polish/design.md` (рулинги контроллера R-13… в «Decisions made without discussion»),
`CLAUDE.md` / `ARCHITECTURE.md` — только если упоминают состав `agile/` или ключи `localStorage` (проверить grep).
- [ ] `grep -n "agile\|sdlc-hub-legend" CLAUDE.md ARCHITECTURE.md` — дописать `sdlc-hub-agile` и новые файлы, где
перечислены соседние; `openspec validate agile-web-polish --strict` — valid.
- [ ] коммит `docs: доработки экранов доски — рулинги и карта (#112)`.
**После плана (сессия, не SDD):** перенести новые значки и стиль `agile-type` в kit скилла `aura-design`
(вне worktree — только руками сессии), PR в Gitea, выкатка `make web` + kickstart — за владельцем.

View File

@ -121,3 +121,8 @@ discussion»).
merge остаётся открытой с комментарием о закрытых маркерах. Цена ошибки — взять/отложить другие пункты.
- R-11 (spec): узкий экран — панель эпиков превращается в выбор при контейнере уже 900 px. Цена ошибки — подобрать
порог.
- R-12 (plan, Task 7): на страницах доски («Спринт», «Kanban») страница не ограничена `max-width: 1440px` у `.page` —
на 2560 px колонки используют ширину окна (комментарий в `agile.css` уже называл «Спринт» страницей во всю ширину, а
ограничение оставалось). Обоснование: замечание владельца о 2560 px. Цена ошибки — вернуть ограничение одной строкой.
- R-13 (plan, Task 1): значок задачи без типа подписан «Тип не указан» (сейчас — «Задача»), вид — нейтральный.
Цена ошибки — вернуть прежнюю подпись.