diff --git a/openspec/changes/web-modules/design.md b/openspec/changes/web-modules/design.md index 772ee03..9f0ebd1 100644 --- a/openspec/changes/web-modules/design.md +++ b/openspec/changes/web-modules/design.md @@ -146,3 +146,8 @@ type HubModule = { | 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); порог окна — приближение порога контейнера страницы | 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 девять столбцов шире панели; цена — липкая шапка таблицы липнет к панели, а не к окну | вернуть условие и сжать столбцы | +| 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` | страницы разной высоты не двигаются по ширине; одна реализация кнопки на три страницы | прокручиваемый контейнер страницы; кнопка в каждой странице | diff --git a/web/src/modules/jira/RefreshButton.tsx b/web/src/modules/jira/RefreshButton.tsx new file mode 100644 index 0000000..be9bf6a --- /dev/null +++ b/web/src/modules/jira/RefreshButton.tsx @@ -0,0 +1,9 @@ +import { Button, IconRefresh } from "../../ui/index.ts"; +import { useModuleActivity } from "../../shell/activity/activity.ts"; +import { jiraStatus } from "./status.ts"; + +// «Обновить из Jira» в шапке страницы модуля: вторичная кнопка; ход пакета показывает блок состояния боковой панели +export function RefreshButton() { + const act = useModuleActivity("jira"); + return ; +} diff --git a/web/src/modules/jira/board/BoardPage.test.tsx b/web/src/modules/jira/board/BoardPage.test.tsx index 79826e5..41ed29a 100644 --- a/web/src/modules/jira/board/BoardPage.test.tsx +++ b/web/src/modules/jira/board/BoardPage.test.tsx @@ -36,6 +36,15 @@ const NO_SPRINT_TEXT = "колонки «Спринт» нет — показа describe("страница доски", () => { beforeEach(() => setToken("tok")); + it("шапка: вторичная кнопка «Обновить из Jira», primary нет; пустого блока .board-src под шапкой нет", async () => { + hub(); + const { container } = page(); + await screen.findByText(/задач из/); + expect(screen.getByRole("button", { name: "Обновить из Jira" })).toHaveClass("ui-btn--secondary"); + expect(container.querySelector(".ui-btn--primary")).toBeNull(); + expect(container.querySelector(".board-src")).toBeNull(); + }); + it("строит доску из хаба: источник, статистика, выбор самого наполненного спринта", async () => { hub(); page(); @@ -156,11 +165,11 @@ describe("страница доски", () => { expect(screen.getByText("скрыто: 1")).toBeInTheDocument(); }); - it("доска не рисует «Обновить из Jira» и ход пакета: они в верхней панели", async () => { + it("кнопка «Обновить из Jira» в шапке есть, хода пакета и «Тема» на доске нет", async () => { hub(); page(); await screen.findByText(/задач из 9/); - expect(screen.queryByRole("button", { name: "Обновить из Jira" })).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Обновить из Jira" })).toBeInTheDocument(); await userEvent.click(screen.getByRole("button", { name: "Ещё" })); expect(screen.getByRole("button", { name: "Ещё" })).toHaveAttribute("aria-expanded", "true"); expect(screen.getByRole("button", { name: "Перечитать" })).toBeInTheDocument(); // меню действительно открыто diff --git a/web/src/modules/jira/board/BoardPage.tsx b/web/src/modules/jira/board/BoardPage.tsx index 803e000..c6c8d46 100644 --- a/web/src/modules/jira/board/BoardPage.tsx +++ b/web/src/modules/jira/board/BoardPage.tsx @@ -70,7 +70,7 @@ export function BoardPage({ project }: { project: string }) { return (
- prefs.toggle("problems")} onExport={onExport} onTable={() => prefs.toggle("table")} diff --git a/web/src/modules/jira/board/board.css b/web/src/modules/jira/board/board.css index 93a296e..f859d08 100644 --- a/web/src/modules/jira/board/board.css +++ b/web/src/modules/jira/board/board.css @@ -4,8 +4,6 @@ .stats{font-variant-numeric:tabular-nums} .stats b{color:var(--color-text-primary);font-weight:600} .board-src{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-2) var(--space-3);min-height:var(--lh-body)} -.board-src:empty{display:none} -.board-src__label{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--color-text-secondary);max-width:420px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .board-event{font-size:var(--fs-body);line-height:var(--lh-body);color:var(--color-text-secondary)} .board-event--warn{color:color-mix(in srgb, var(--color-status-warning) 55%, var(--color-text-primary))} .menu__label{display:inline-flex;align-items:center;gap:var(--space-2)} diff --git a/web/src/modules/jira/board/components/BoardHeader.tsx b/web/src/modules/jira/board/components/BoardHeader.tsx index 189f88a..73d8a29 100644 --- a/web/src/modules/jira/board/components/BoardHeader.tsx +++ b/web/src/modules/jira/board/components/BoardHeader.tsx @@ -2,10 +2,11 @@ import { useEffect, useRef, useState } from "react"; import { statusText, type BoardStatus } from "../status.ts"; import type { BoardModel, Visible } from "../types.ts"; import { NOT_IN_HUB } from "../constants.ts"; +import { RefreshButton } from "../../RefreshButton.tsx"; import { Badge, Button, IconButton, IconRefresh, IconUpload, IconWarning, Menu, MenuItem, MenuSeparator, Notice, PageHeader } from "../../../../ui/index.ts"; interface BoardHeaderProps { - label: string; title: string; source: "hub" | "file"; fileName: string; status: BoardStatus | null; byDate: boolean; snap: boolean; + source: "hub" | "file"; fileName: string; status: BoardStatus | null; byDate: boolean; snap: boolean; model: BoardModel; vis: Visible; problemCount: number; problemsOpen: boolean; tableOpen: boolean; onReload(): void; onFile(name: string, text: string): void; onProblems(): void; onExport(): void; onTable(): void; onSnap(): void; onReset(): void; @@ -29,6 +30,7 @@ export function BoardHeader(p: BoardHeaderProps) { return ( +