diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 4292119..a65633a 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -30,6 +30,11 @@
`/app/board`. Данные — только через HTTP API с клиентским токеном; `mirror.board` отдаёт зеркало без преобразований,
перевод в модель доски — в `web/src/board/model`. Логика доски — чистые модули `web/src/board/{model,analysis,layout}`
и `storage.ts` (ключи `localStorage` прежней страницы; у проекта AWPCA — прежние, у остальных с суффиксом `@<метка>`; набор ключей — состояние модуля, выставляется при монтировании страницы), отрисовка — `web/src/board/components`.
+Вид — дизайн-система AURA: токены цвета, шрифта, отступов и теней (светлая и тёмная темы) — `web/src/styles/tokens.css`,
+единственное место с литералами цвета; базовые компоненты (`Button`, `Notice`, `Dialog`, `Field`, `Panel`, меню и др.) —
+`web/src/ui/`, остальные стили подключаются к токенам по семантическим именам. Обновление данных из Jira (кнопка в
+верхней панели, отслеживание пакета) — `web/src/shell/refresh.ts` (провайдер в `App.tsx`); доска отдаёт наружу только
+источник и события снимка.
Цепочки: `api → queue → commands/mirror/settings/projects → db`; `db → projects` (только миграция, ленивый импорт); `projects → settings`; `db → backup → paths`, `db → paths`; `files → paths`; `api → relay → settings`; `mcp_server → client → HTTP API`; `importer → mirror/settings`.
diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx
index 611e12c..6e08d5b 100644
--- a/web/src/App.test.tsx
+++ b/web/src/App.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen, waitFor } from "@testing-library/react";
+import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { createMemoryRouter } from "react-router";
import { beforeEach, describe, expect, it } from "vitest";
@@ -36,8 +36,9 @@ describe("оболочка", () => {
it("неизвестный путь — «не найдено» со ссылками", async () => {
render();
expect(await screen.findByRole("heading", { name: "Страница не найдена" })).toBeInTheDocument();
- expect(screen.getByRole("link", { name: "доска" })).toHaveAttribute("href", "/app/board");
- expect(screen.getByRole("link", { name: "задачи" })).toHaveAttribute("href", "/app/issues");
+ const main = within(screen.getByRole("main"));
+ expect(main.getByRole("link", { name: "Доска связей" })).toHaveAttribute("href", "/app/board");
+ expect(main.getByRole("link", { name: "Задачи" })).toHaveAttribute("href", "/app/issues");
});
it("«Сменить токен» показывает экран входа", async () => {
@@ -57,6 +58,7 @@ describe("оболочка", () => {
render();
expect(await screen.findByText(/сломалось/)).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Перезагрузить" })).toBeInTheDocument();
+ expect(screen.getAllByRole("alert")).toHaveLength(1);
expect(screen.getByRole("link", { name: "Доска связей" })).toBeInTheDocument();
} finally { PAGE_ROUTES.splice(PAGE_ROUTES.indexOf(boom), 1); }
});
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 34645c9..cb4bce8 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -7,7 +7,6 @@ import { RefreshProvider } from "./shell/RefreshProvider.tsx";
import { routes } from "./routes.tsx";
import "./styles/tokens.css";
import "./styles/base.css";
-import "./styles/controls.css";
type Router = ReturnType;
diff --git a/web/src/auth/LoginScreen.test.tsx b/web/src/auth/LoginScreen.test.tsx
index edec09a..f082226 100644
--- a/web/src/auth/LoginScreen.test.tsx
+++ b/web/src/auth/LoginScreen.test.tsx
@@ -19,6 +19,7 @@ describe("вход", () => {
expect(screen.getByRole("heading", { name: "Вход в хаб" })).toBeInTheDocument();
expect(screen.queryByText("страница приложения")).not.toBeInTheDocument();
expect(screen.getByText(/uv run jira-hub tokens/)).toBeInTheDocument();
+ expect(screen.getByLabelText("Клиентский токен хаба")).toHaveAttribute("type", "password");
});
it("верный токен сохраняется и открывает приложение", async () => {
@@ -37,7 +38,7 @@ describe("вход", () => {
const input = screen.getByLabelText("Клиентский токен хаба");
await userEvent.type(input, "wrong");
await userEvent.click(screen.getByRole("button", { name: "Войти" }));
- expect(await screen.findByText(/Токен не подошёл/)).toBeInTheDocument();
+ expect(await screen.findByRole("alert")).toHaveTextContent(/Токен не подошёл/);
expect(input).toHaveValue("wrong");
expect(localStorage.getItem("jira-hub-token")).toBeNull();
});
diff --git a/web/src/auth/LoginScreen.tsx b/web/src/auth/LoginScreen.tsx
index c8c3553..eb1c29a 100644
--- a/web/src/auth/LoginScreen.tsx
+++ b/web/src/auth/LoginScreen.tsx
@@ -1,8 +1,10 @@
import { useQueryClient } from "@tanstack/react-query";
import { useState, type FormEvent } from "react";
import { ApiError, AuthError, checkToken } from "../api/client.ts";
+import { Button, Field, Notice, Panel, TextInput } from "../ui/index.ts";
import { setToken, TOKEN_RE } from "./token.ts";
import { useAuth } from "./useAuth.ts";
+import "./login.css";
const BAD_TOKEN = "Токен не подошёл. Проверьте его по uv run jira-hub tokens: токен могли пересоздать.";
const BAD_CHARS = "В токене недопустимые символы: скопируйте строку после «клиентский токен:» целиком, без пробелов и кириллицы.";
@@ -36,15 +38,19 @@ export function LoginScreen() {
return (
-
Вход в хаб
-
Приложению нужен клиентский токен хаба. Его показывает команда uv run jira-hub tokens в каталоге
- jira-hub на этом Mac. Токен сохранится в этом браузере.
- {error &&
{error}
}
-
+
+
Вход в хаб
+
Приложению нужен клиентский токен хаба. Его показывает команда uv run jira-hub tokens в каталоге
+ jira-hub на этом Mac. Токен сохранится в этом браузере.
- JSON доски: все задачи, связи, раскладка, дорожки и фильтры. Загружается обратно пунктом «Загрузить файл…» в меню «Ещё». Если файл не скачался, скопируйте текст.
+
+ JSON доски: все задачи, связи, раскладка, дорожки и фильтры. Загружается обратно пунктом «Загрузить файл…» в меню «Ещё». Если файл не скачался, скопируйте текст.
diff --git a/web/src/board/components/Lane.tsx b/web/src/board/components/Lane.tsx
index 40d5895..e9711f8 100644
--- a/web/src/board/components/Lane.tsx
+++ b/web/src/board/components/Lane.tsx
@@ -1,5 +1,5 @@
import { useRef, useState, type PointerEvent } from "react";
-import { IconChevronDown } from "../../ui/icons.tsx";
+import { IconChevronDown, IconChevronUp } from "../../ui/icons.tsx";
import type { LaneGeom } from "../layout/layout.ts";
import { plural } from "../model/util.ts";
@@ -44,7 +44,7 @@ export function Lane({ g, index, last, count, lanes, boardTop, targetClass, onTo
{g.name}{plural(count, "задача", "задачи", "задач")}
-
+