From fbfbd555e6307c9bb5e236d3b29ef6f47f94aa5a Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 14:25:33 +0400 Subject: [PATCH] =?UTF-8?q?fix(web-modules):=20=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=86=D1=8B=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9=20=E2=80=94=20=D1=88=D0=B0=D0=BF=D0=BA=D0=B8,=20=C2=AB?= =?UTF-8?q?=D0=92=D0=B8=D0=BA=D0=B8=C2=BB=20=D1=81=20=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=BD=D0=B5=D0=BC,=20=D0=BF=D1=80=D0=BE=D1=81=D1=80=D0=BE?= =?UTF-8?q?=D1=87=D0=BA=D0=B0,=20=D0=BF=D1=80=D0=BE=D0=BA=D1=80=D1=83?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=C2=AB=D0=97=D0=B0=D0=B4=D0=B0=D1=87=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- openspec/changes/web-modules/design.md | 5 + web/src/modules/jira/RefreshButton.tsx | 9 ++ web/src/modules/jira/board/BoardPage.test.tsx | 13 ++- web/src/modules/jira/board/BoardPage.tsx | 2 +- web/src/modules/jira/board/board.css | 2 - .../jira/board/components/BoardHeader.tsx | 24 +++-- web/src/modules/jira/feed/FeedPage.test.tsx | 8 ++ web/src/modules/jira/feed/FeedPage.tsx | 3 +- .../modules/jira/issues/IssueCard.test.tsx | 4 +- web/src/modules/jira/issues/IssueCard.tsx | 4 +- web/src/modules/jira/issues/IssueTable.tsx | 11 ++- .../modules/jira/issues/IssuesPage.test.tsx | 22 +++++ web/src/modules/jira/issues/IssuesPage.tsx | 3 +- web/src/modules/jira/issues/issues.css | 4 +- web/src/modules/wiki/PageView.tsx | 7 ++ web/src/modules/wiki/Summary.test.tsx | 20 ++++ web/src/modules/wiki/Summary.tsx | 4 +- web/src/modules/wiki/Tree.test.tsx | 9 ++ web/src/modules/wiki/Tree.tsx | 9 +- web/src/modules/wiki/WikiPage.test.tsx | 98 ++++++++++++++++++- web/src/modules/wiki/WikiPage.tsx | 50 +++++++--- web/src/modules/wiki/wiki.css | 5 +- web/src/projects/ProjectGate.tsx | 2 +- web/src/projects/projects.css | 2 - web/src/projects/projects.test.tsx | 2 +- web/src/shell/ModuleStatusBlock.test.tsx | 8 ++ web/src/shell/OverviewPage.test.tsx | 3 +- web/src/shell/Sidebar.test.tsx | 5 +- web/src/shell/activity/activity.ts | 1 - web/src/styles/base.css | 2 + 30 files changed, 287 insertions(+), 54 deletions(-) create mode 100644 web/src/modules/jira/RefreshButton.tsx create mode 100644 web/src/modules/wiki/Summary.test.tsx 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 ( +