From d3db88708bc8dd7d6dda7aa4a4005e58b9967ab2 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 22 Oct 2026 09:13:51 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=D1=8B=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B2=D1=8C=D1=8E:=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=86=D0=B0=20=D0=BD=D0=B5=20=D1=88=D0=B8=D1=80=D0=B5=20=D0=BE?= =?UTF-8?q?=D0=BA=D0=BD=D0=B0=20(C-1),=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=82=D0=B5=D0=BB=D0=B5=D1=84=D0=BE=D0=BD=D0=B5=20(I-?= =?UTF-8?q?1)=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- openspec/changes/sidebar/design.md | 6 ++++++ web/e2e/layout.spec.ts | 16 ++++++++++++++++ web/src/board/board.css | 2 +- web/src/issues/issues.css | 8 ++++---- web/src/shell/Sidebar.test.tsx | 11 +++++++++++ web/src/shell/Sidebar.tsx | 5 +++++ web/src/shell/shell.css | 6 ++++++ web/src/styles/base.css | 3 ++- 8 files changed, 51 insertions(+), 6 deletions(-) diff --git a/openspec/changes/sidebar/design.md b/openspec/changes/sidebar/design.md index 4e226e1..46f7576 100644 --- a/openspec/changes/sidebar/design.md +++ b/openspec/changes/sidebar/design.md @@ -83,3 +83,9 @@ | 4 | 22.10.2026 | Свёртка панели в полосу 64 px с запоминанием (решение 3) | доске нужна ширина; раз в спринт граф — тогда удобно свернуть | низкая | | 5 | 22.10.2026 | На узком экране — строка с «Меню» и выезжающая панель (решение 4) | закреплённая панель 4–5 строк (M-12 #65) съела бы экран | низкая: узкий экран — только «без поломок» | | 6 | 22.10.2026 | Развёрнута по умолчанию, ширина 240 px | на 1920 место есть; название проекта видно целиком | низкая | +| 7 | 22.10.2026 | R-1: панель без `overflow-y:auto` (вопреки решению 1) | прокрутка панели обрезала бы меню «Ещё» свёрнутой панели, выходящее за 64 px | низкая: на окне ниже ~470 px низ панели обрезан (M-5) | +| 8 | 22.10.2026 | R-2: в выезжающей панели «Закрыть меню» вместо «Свернуть панель» | свёртка внутри выезжающей панели бессмысленна (решение 4) | нет | +| 9 | 22.10.2026 | R-3: e2e M-3 переходит по разделам обычным щелчком, не `dispatchEvent` | панель закреплена, щелчок больше не прокручивает доску; тест заодно проверяет закрепление | нет | +| 10 | 22.10.2026 | R-4 (C-1): `.shell>.page{width:100%}` и `.page` — контейнер (`container: page/inline-size`); пороги раскладки страниц (фильтры списка, прокрутка таблицы, колонка ящика «Проблемы») — запросами к контейнеру с порогами «прежний порог окна минус поля страницы 48 px» | рядом панель 240/64 px: ширина страницы больше не равна ширине окна, и пороги по окну давали таблицу шире страницы | низкая: пороги — числа в CSS | +| 11 | 22.10.2026 | R-5 (I-1): на узком экране событие пакета — строкой под закреплённой строкой (`aria-hidden`, озвучивает `role=status` кнопки) | на телефоне подсказок нет, результат «Обновить из Jira» был не виден | низкая: строка занимает 1–2 строки по высоте, пока есть событие | + diff --git a/web/e2e/layout.spec.ts b/web/e2e/layout.spec.ts index 9e6e4b4..27ef928 100644 --- a/web/e2e/layout.spec.ts +++ b/web/e2e/layout.spec.ts @@ -42,3 +42,19 @@ test("390 px: «Меню» открывает панель, «Задачи» з await expect(page.getByTestId("page-issues")).toBeVisible(); expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true); }); + +test("C-1: на средних ширинах страница не шире окна — «Задачи» на 1280, доска на 1000 прокручивается сама", async ({ page }) => { + await page.setViewportSize({ width: 1280, height: 800 }); + await login(page, "/app/issues"); + await expect(page.locator("table.issue-table tbody tr").first()).toBeVisible(); + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(1280); + await page.setViewportSize({ width: 1000, height: 800 }); + await page.getByRole("link", { name: "Доска связей" }).click(); + await expect(page.locator(".board .card[data-key]").first()).toBeVisible(); + await page.getByRole("button", { name: "По датам" }).click(); + await expect(page.locator(".dcol").first()).toBeVisible(); + await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(1000); + await expect(page.getByRole("button", { name: /Проблемы/ })).toBeInViewport(); + const wrap = page.locator(".wrap"); + expect(await wrap.evaluate((el) => el.scrollWidth > el.clientWidth)).toBe(true); // доска шире своей рамки — прокрутка внутри +}); diff --git a/web/src/board/board.css b/web/src/board/board.css index fdd7ca1..1138a1f 100644 --- a/web/src/board/board.css +++ b/web/src/board/board.css @@ -167,7 +167,7 @@ td.k{font-family:var(--font-mono);font-size:12px;white-space:nowrap;color:var(-- /* 9. Движение и узкие экраны */ @media (prefers-reduced-motion:no-preference){.card{transition:border-color .15s}} @media (prefers-reduced-motion:reduce){.card .grip,.fold svg{transition:none}} -@media (max-width:900px){.stage{flex-direction:column}.drawer{flex:none;max-width:100%;max-height:none;position:static}} +@container page (max-width:852px){.stage{flex-direction:column}.drawer{flex:none;max-width:100%;max-height:none;position:static}} @media (max-width:640px){.sticky{position:static}.dates-bar{position:static}} @media (max-width:640px){.board-controls__sep,.legend .sep{display:none}} diff --git a/web/src/issues/issues.css b/web/src/issues/issues.css index 529ccf9..c77f6c0 100644 --- a/web/src/issues/issues.css +++ b/web/src/issues/issues.css @@ -7,10 +7,10 @@ .issue-filters__row .ui-btn[aria-pressed="true"],.issue-filters__row .ui-btn[aria-expanded="true"]{border-color:var(--color-border-selected);color:var(--color-action-primary)} .issue-filters__chips{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2)} .issue-filters__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-3) var(--space-4)} -@media (max-width:899.98px){.issue-filters__grid{grid-template-columns:repeat(2,minmax(0,1fr))}} -@media (max-width:639.98px){.issue-filters__grid{grid-template-columns:minmax(0,1fr)}.issue-filters__search{max-width:none}} -/* прокрутка внутри панели — только на узких экранах: overflow на предке отключает липкую шапку страницы */ -@media (max-width:1099.98px){.issue-table-wrap{overflow-x:auto}} +@container page (max-width:851.98px){.issue-filters__grid{grid-template-columns:repeat(2,minmax(0,1fr))}} +@container page (max-width:607.98px){.issue-filters__grid{grid-template-columns:minmax(0,1fr)}.issue-filters__search{max-width:none}} +/* прокрутка внутри панели — только при узкой странице: overflow на предке отключает липкую шапку страницы; пороги контейнера = прежние пороги окна минус поля страницы */ +@container page (max-width:1051.98px){.issue-table-wrap{overflow-x:auto}} .issue-table th:first-child{border-top-left-radius:calc(var(--radius-xl) - 1px)} .issue-table th:last-child{border-top-right-radius:calc(var(--radius-xl) - 1px)} .issue-table tbody tr:last-child td:first-child{border-bottom-left-radius:calc(var(--radius-xl) - 1px)} diff --git a/web/src/shell/Sidebar.test.tsx b/web/src/shell/Sidebar.test.tsx index a213fef..8c5abed 100644 --- a/web/src/shell/Sidebar.test.tsx +++ b/web/src/shell/Sidebar.test.tsx @@ -228,4 +228,15 @@ describe("боковая панель на узком экране", () => { await userEvent.click(within(bar()).getByRole("button", { name: "Закрыть меню" })); expect(screen.queryByRole("complementary", { name: "Панель приложения" })).toBeNull(); }); + + it("I-1: событие пакета видно под строкой, не только в подсказке", async () => { + hub(); + open("/app/board?project=AWPCA"); + const top = await screen.findByRole("banner", { name: "Строка приложения" }); + await userEvent.click(within(top).getByRole("button", { name: "Обновить из Jira" })); + const strip = await screen.findByTestId("topbar-event"); + await waitFor(() => expect(strip).toHaveTextContent(/пакет #7 выполняется/)); + expect(strip).not.toHaveClass("visually-hidden"); + expect(strip).toHaveAttribute("aria-hidden", "true"); // озвучивает role=status кнопки, второй раз не нужно + }); }); diff --git a/web/src/shell/Sidebar.tsx b/web/src/shell/Sidebar.tsx index ec405e6..b72139a 100644 --- a/web/src/shell/Sidebar.tsx +++ b/web/src/shell/Sidebar.tsx @@ -6,6 +6,8 @@ import { withProject } from "../projects/current.ts"; import { ProjectPicker } from "../projects/ProjectPicker.tsx"; import { IconBoard, IconButton, IconClose, IconList, IconMenu, IconSidebar, Menu, MenuItem, ThemeSwitch } from "../ui/index.ts"; import { DataStatus } from "./DataStatus.tsx"; +import { useJiraRefresh } from "./refresh.ts"; +import { refreshText } from "./refreshStatus.ts"; import { readSidebar, saveSidebar } from "./sidebarPref.ts"; export type Section = "/board" | "/issues"; @@ -37,6 +39,8 @@ function NarrowShell(p: Props) { const [open, setOpen] = useState(false); const menuBtn = useRef(null); // объект стабилен, пока панель открыта: эффект фокуса и Escape в Panel не перезапускается на каждой отрисовке + const refresh = useJiraRefresh(); + const rt = refresh.event ? refreshText(refresh.event) : null; // I-1: на телефоне подсказок нет — событие пакета строкой под панелью const drawer = useMemo(() => ({ close: () => { setOpen(false); menuBtn.current?.focus(); }, navigated: () => setOpen(false) }), []); return ( <> @@ -47,6 +51,7 @@ function NarrowShell(p: Props) { + {rt && } {open && <>
diff --git a/web/src/shell/shell.css b/web/src/shell/shell.css index b8c5751..4706ae1 100644 --- a/web/src/shell/shell.css +++ b/web/src/shell/shell.css @@ -1,5 +1,7 @@ /* оболочка: боковая панель и страница (#69, design 1–3); прокрутка — у окна, панель липнет к его верху */ .shell{display:grid;grid-template-columns:auto minmax(0,1fr);min-height:100vh} +/* C-1: у элемента сетки с margin-inline:auto ширина — по содержимому; явная 100% держит страницу в своей дорожке */ +.shell>.page{width:100%;box-sizing:border-box} .sidebar{ position:sticky;top:0;height:100vh;height:100dvh;width:240px;box-sizing:border-box;z-index:20; display:flex;flex-direction:column;gap:var(--space-4);padding:var(--space-4); @@ -58,3 +60,7 @@ .shell{display:block} :root{--topbar-h:56px} } +.topbar-event{ + position:sticky;top:56px;z-index:19;padding:var(--space-2) var(--space-4);border-bottom:1px solid var(--color-border-default); + background:var(--color-bg-subtle);font-size:var(--fs-caption);line-height:var(--lh-caption);font-weight:600;overflow-wrap:anywhere; +} diff --git a/web/src/styles/base.css b/web/src/styles/base.css index 202ce5c..8dc7e1c 100644 --- a/web/src/styles/base.css +++ b/web/src/styles/base.css @@ -3,7 +3,8 @@ html,body{min-height:100%} body{font:var(--fs-body)/var(--lh-body) var(--font-ui);background:var(--color-bg-canvas);color:var(--color-text-primary);margin:0} h1{font-size:var(--fs-section);line-height:var(--lh-section);font-weight:700;margin:0} [hidden]{display:none!important} -.page{padding-inline:24px;padding-block:0 32px;max-width:1440px;margin-inline:auto} +/* страница — контейнер: раскладка страниц зависит от её ширины, а не окна (рядом боковая панель, #69) */ +.page{padding-inline:24px;padding-block:0 32px;max-width:1440px;margin-inline:auto;container:page/inline-size} .page--board{max-width:none} @media (max-width:639px){.page{padding-inline:16px}} :focus-visible{outline:2px solid var(--color-focus-ring);outline-offset:2px}