diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8b8fe4d..5dfab78 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -22,8 +22,7 @@ | `cli` | `init`, `serve`, `import`, `mcp`, `tokens`, `agent publish` | `api`, `mcp_server`, `importer`, `release`, `settings`, `db`, `paths` | `web/` — веб-приложение хаба (React + TypeScript + Vite): вход по клиентскому токену, страницы «Доска» (`/app/board`) -и «Задачи» -(`/app/issues`); карточка `/app/issues/{key}` — окно поверх страницы, с которой её открыли: `shell/Layout.tsx` рисует +и «Задачи» (`/app/issues`); карточка `/app/issues/{key}` — окно поверх страницы, с которой её открыли: `shell/Layout.tsx` рисует страницу по адресу из `location.state.background` (`useRoutes` с явным адресом) и `issues/IssueModal.tsx` поверх неё. Собирается `make web` в `src/jira_hub/web/app/`; `api` отдаёт сборку без токена (`/app/assets/*` — файлы, прочие `/app…` — `index.html`, 503 без сборки), `/` и `/links` перенаправляет на `/app/board`. Данные — только через HTTP API с клиентским токеном; `mirror.board` отдаёт зеркало без преобразований, diff --git a/web/e2e/board.spec.ts b/web/e2e/board.spec.ts index bdd1976..ca5f2da 100644 --- a/web/e2e/board.spec.ts +++ b/web/e2e/board.spec.ts @@ -89,10 +89,12 @@ test("M-3: прокрутка и данные доски после окна к await expect.poll(() => page.evaluate(() => window.scrollY)).toBeGreaterThan(y - 2); }); -test("заметка из окна уходит в хаб и доска под ним не пересоздаётся", async ({ page }) => { +test("заметка из окна уходит в хаб, карточка на доске остаётся на месте, прокрутка сохраняется", async ({ page }) => { // доска показывает вид работы по меткам Jira (fromHub), заметка work_kind в карточку доски не попадает: проверяем снимок /board + await page.locator('.board .card[data-key="AURORA-9002"]').evaluate((e) => { (e as HTMLElement).dataset.marker = "keep"; }); await page.locator('.board .card[data-key="AURORA-9002"] a.open-card').click(); const d = page.getByRole("dialog"); + const y = await page.evaluate(() => window.scrollY); await d.getByRole("button", { name: "Править" }).click(); await d.getByLabel("Вид работы").selectOption("тестирование"); const snap = page.waitForResponse((r) => new URL(r.url()).pathname === "/board" && r.request().method() === "GET"); @@ -103,4 +105,6 @@ test("заметка из окна уходит в хаб и доска под await page.keyboard.press("Escape"); await expect(page.getByTestId("page-board")).toBeAttached(); await expect(page.locator('.board .card[data-key="AURORA-9002"]')).toBeVisible(); + await expect(page.locator('.board .card[data-key="AURORA-9002"]')).toHaveAttribute("data-marker", "keep"); // узел не пересоздан + expect(await page.evaluate(() => window.scrollY)).toBe(y); }); diff --git a/web/e2e/issues.spec.ts b/web/e2e/issues.spec.ts index 13e0b18..fb56137 100644 --- a/web/e2e/issues.spec.ts +++ b/web/e2e/issues.spec.ts @@ -76,8 +76,11 @@ test("карточка: описание свёрнуто, комментари await expect(d.getByTestId("comments").getByRole("listitem").first()).toContainText("[мост] служебный комментарий"); await expect(d.getByRole("button", { name: "развернуть" })).toBeVisible(); await expect(d.locator("pre.description")).toHaveClass(/clamped/); // свёрнуто построчным обрезанием (текст в DOM остаётся) + const hClamped = (await d.locator("pre.description").boundingBox())!.height; + expect(await d.locator("pre.description").evaluate((e) => e.scrollHeight > e.clientHeight)).toBe(true); await d.getByRole("button", { name: "развернуть" }).click(); await expect(d.locator("pre.description")).not.toHaveClass(/clamped/); + expect((await d.locator("pre.description").boundingBox())!.height).toBeGreaterThan(hClamped); await expect(d.getByText("Конец описания.")).toBeVisible(); await expect(d.getByRole("cell", { name: "ensure_fields" })).toBeHidden(); await d.getByText("Команды моста (2)").click();