From 55014105f561a60112589e136433d9238ba0f70f Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 17 Aug 2026 15:36:53 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=B2=D1=8B=D1=80=D0=BE=D0=B2=D0=BD?= =?UTF-8?q?=D1=8F=D1=82=D1=8C=20chrome=20Frame=20KPI=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=BD=D0=BE=D1=81=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Унифицировать оболочку ops-экранов, IconTile, skip-link и контракт UI. Co-authored-by: Cursor --- .../ux_ui_unification_audit_276a34f8.plan.md | 200 ++++ ...nification_implementation_8a1c2d3e.plan.md | 67 ++ .cursor/rules/frontend-shadcn.mdc | 1 - .cursor/rules/frontend-ui-patterns.mdc | 18 +- .cursor/rules/kpi-hybrid.mdc | 19 +- .cursor/rules/reui-mcp.mdc | 4 +- UX-UI-PLAN.md | 945 ++++++++++++++++++ apps/web/src/components/domain/charts.tsx | 136 +-- apps/web/src/components/empty-state.tsx | 6 +- .../integrations/app-switcher-editor.tsx | 17 +- apps/web/src/components/layout/app-shell.tsx | 10 +- apps/web/src/components/query-state.tsx | 34 +- .../src/components/reui-kit/kpi-stat-grid.tsx | 17 +- .../components/reui-kit/quick-action-grid.tsx | 17 +- apps/web/src/components/reui/icon-tile.tsx | 125 +++ apps/web/src/lib/ui-surface.ts | 6 +- apps/web/src/routes/_auth/renewals.tsx | 23 +- apps/web/src/routes/_auth/resources.tsx | 24 +- apps/web/src/routes/_auth/spaces.tsx | 321 +++--- apps/web/src/routes/_auth/vps.$vpsId.tsx | 109 +- docs/ui-design-contract.md | 6 +- packages/ui/src/components/sheet.tsx | 5 +- packages/ui/src/components/sidebar.tsx | 17 +- 23 files changed, 1728 insertions(+), 399 deletions(-) create mode 100644 .cursor/plans/ux_ui_unification_audit_276a34f8.plan.md create mode 100644 .cursor/plans/ux_ui_unification_implementation_8a1c2d3e.plan.md create mode 100644 UX-UI-PLAN.md create mode 100644 apps/web/src/components/reui/icon-tile.tsx diff --git a/.cursor/plans/ux_ui_unification_audit_276a34f8.plan.md b/.cursor/plans/ux_ui_unification_audit_276a34f8.plan.md new file mode 100644 index 0000000..54f4225 --- /dev/null +++ b/.cursor/plans/ux_ui_unification_audit_276a34f8.plan.md @@ -0,0 +1,200 @@ +--- +name: UX UI Unification Audit +overview: "Discovery завершён. Уровень унификации (решение пользователя): идентичный chrome во всех 6 приложениях + общая UX-система; доменные страницы остаются разными. Deliverable после approval — только vps-tracker/UX-UI-PLAN.md, без правок production-кода." +todos: + - id: write-plan-file + content: Создать c:\Users\shats\Dev\vps-tracker\UX-UI-PLAN.md со всеми секциями правила аудита (без правок production-кода) + status: in_progress + - id: registry-changes + content: "Заполнить REUI Reference Registry + Final Change Matrix: каждый UI CHANGE с Ref ID и официальным URL" + status: in_progress + - id: consistency-gate + content: "Пройти Final Consistency Gate: 6/6 coverage, no fabricated URLs, functionality preserved" + status: pending + - id: chrome-spec + content: В UX-UI-PLAN.md зафиксировать Chrome Identity Spec для всех 6 приложений (header/sidebar/theme/App Switcher) как обязательный 1:1 + status: in_progress +isProject: false +--- + +# UX/UI Unification — Audit Complete, Plan File Next + +## Scope of this phase + +После подтверждения плана будет создан **только** [`c:\Users\shats\Dev\vps-tracker\UX-UI-PLAN.md`](c:/Users/shats/Dev/vps-tracker/UX-UI-PLAN.md) (выбрано пользователем). Production/source code, зависимости, CSS и компоненты **не изменяются**. Implementation UI — отдельный запрос. + +Источники истины для references: [reui.io/docs](https://reui.io/docs), [reui.io/llms.txt](https://reui.io/llms.txt), MCP `user-reui` (`get_block` / `list_components` / `compose_page` / `get_component`). Skill ReUI локально и в MCP: `668fb463eb` (совпадает). Версию ReUI PRO **не** указываем — её нет. + +## Coverage gate + +`Discovered: 6` · `Audited: 6` · `Planned: 6` · `Coverage: 100%` + +| # | Project | Path | Type | UI | +|---|---|---|---|---| +| 1 | VPS Tracker | `c:\Users\shats\Dev\vps-tracker` | fullstack Vite SPA | Frame + kit, mixed Card on charts/detail | +| 2 | Cloudflare Domain Manager | `c:\Users\shats\Dev\cloudflare-domain-manager` | fullstack Vite SPA | Frame-first, closest to contract | +| 3 | EvoBGP | `c:\Users\shats\Dev\EvoBGP` | fullstack (Go API + Vite SPA) | SoT hybrid KPI; leftover Card on stub/empty | +| 4 | Auth Portal | `c:\Users\shats\Dev\auth-portal` | fullstack Vite SPA | Frame admin; thin kit; no SystemMonitor | +| 5 | EvoFirewall | `c:\Users\shats\Dev\EvoFirewall` | fullstack Vite SPA | Frame + kit; SettingsShell unused | +| 6 | MikrotikManager | `c:\Users\shats\Dev\MikrotikManager-3` | fullstack Next.js 16 | **Card SoT `/servers`**; ReUI partial (data-grid/filters/stepper), **нет** `reui-kit` | + +Backend-only проектов нет. Все шесть — frontend/fullstack UI. + +## What the final file will contain + +Полная структура из правила аудита (§37): Executive Summary, Coverage, Inventory, MCP/Skills, Rules, 6 project audits, Cross-project matrix, Duplicates, REUI Registry, Target rules, CHANGE-XXX, Roadmap, Acceptance, Risks, Consistency Gate. + +Ниже — ядро находок, которые попадут в файл с evidence и Ref ID. + +## Что будет одинаковым, а что нет (решение пользователя) + +Уровень унификации: **система + идентичный chrome во всех 6 приложениях**. Доменные экраны **не** копируются. + +### Будет одинаковым (chrome identity — все 6, включая Auth Portal и MikrotikManager) + +Эталон: [app-shell-12](https://reui.io/preview/base/app-shell-12) + [app-shell-7](https://reui.io/preview/base/app-shell-7) (SystemMonitor) + [app-shell-1](https://reui.io/preview/base/app-shell-1) (NavUser). + +- Sidebar `--sidebar-width: 240px` +- Sidebar header: App Switcher (ids `cfdm` · `vps` · `bgp` · `fw` · `dns`, источник — auth-portal API) +- Sidebar footer: **NavUser** + segmented theme (light / dark / system), без Sun/Moon icon toggle +- Header right: **AppsMenu → SystemMonitorPopover**; без ModeToggle и без Search-кнопки (поиск только ⌘K) +- Main padding: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` +- Тема: next-themes (или эквивалент с тем же UX) + ReUI semantic tokens (`--success/--warning/--info/--invert`) +- SSO/portal: вход через auth-portal, тот же App Switcher + +Сейчас этого нет: Auth Portal (нет SystemMonitor), MikrotikManager (нет AppsMenu/NavUser/switcher, icon theme toggle). + +### Будет одним паттерном (не pixel-copy страниц) + +- Surface ops-экрана: Frame, не Card +- Списки: DataGrid + Filters +- KPI: stats-12 + IconTile `elevated` +- Quick Actions: Frame tiles (card-12 DNA) +- Settings rows: settings-3 / integrations: settings-16 +- Create/edit: Sheet (sheet-8 / sheet-1 / sheet-9) +- Empty: Empty + IconStack, Frame-friendly + +Одинаковый **рецепт**, разные данные, колонки, фильтры, действия. + +### Останется разным (домен) + +- VPS: topology canvas, BILLmanager sync, тарифы, баланс +- CFDM: DNS, kanban групп/сервисов, сертификаты Cloudflare +- EvoBGP: modules/lookup/BIRD jobs/network +- Auth Portal: login, users/OIDC/audit (это IdP, не ops CRUD) +- EvoFirewall: agent fleet cards, policy sortable, blocklists +- MikrotikManager: `/servers` CRUD, terminal, network-map, контейнеры — **функциональность** та же, chrome и surface выравниваются +- Framework: Vite SPA vs Next.js 16 — **не** меняем ради UI +- Package scopes (`@cfdm/ui`, `@evobgp/ui`, …) — не сливать в один npm без отдельного technical решения + +## Critical cross-project findings + +```mermaid +flowchart LR + subgraph viteFamily [Vite plus Frame kit] + CFDM[CFDM] + VPS[VPS Tracker] + BGP[EvoBGP SoT KPI] + FW[EvoFirewall] + Auth[Auth Portal] + end + subgraph nextFamily [Next Card SoT] + MM[MikrotikManager] + end + ReUI[REUI PRO Frame] + viteFamily --> ReUI + MM -.->|"proposed RULE-CHANGE"| ReUI +``` + +1. **Две дизайн-системы в одном workspace.** Пять Vite-приложений уже lock `surface: frame` + `reui-kit`. MikrotikManager — Next App Router, SoT [`/servers`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/servers/page.tsx) на `DataPageCard` → shadcn Card. Правила `next-shadcn-production.mdc` / `ma-ui-guardian.mdc` **противоречат** цели унификации. UX/UI rule разрешено менять: target = Frame + DataGrid, **без** смены Next.js (technical constraint). + +2. **KPI DNA устарела относительно официального primitive.** Сейчас kit использует hand-roll `Item` `size-10.5` `bg-muted` и ссылается на [stats-12](https://reui.io/preview/base/stats-12) (MCP: Frame + Badge + Item). Официальный primitive [Icon Tile](https://reui.io/docs/components/base/icon-tile) (`variant="elevated"`, preview [icon-tile](https://reui.io/components/icon-tile)) закрывает ту же задачу. **Ни один** из 6 проектов не установил `icon-tile`. Match текущий KPI → stats-12: **partial**. Target: IconTile elevated + stats-12 composition. + +3. **settings-7 — Card surface.** EvoBGP kit/reference [`settings-7`](https://reui.io/preview/base/settings-7) официально `surface: card`. Это нарушает Frame lock. Frame-эталоны: [settings-3](https://reui.io/preview/base/settings-3), [settings-8](https://reui.io/preview/base/settings-8), [settings-16](https://reui.io/preview/base/settings-16). + +4. **Chrome drift.** Контракты цитируют [app-shell-12](https://reui.io/preview/base/app-shell-12). MCP `compose_page` для ops dashboard вернул **app-shell-7** ([preview](https://reui.io/preview/base/app-shell-7)) — в описании есть system monitor popover, ближе к реальному `SystemMonitorPopover`. Auth Portal: AppsMenu без SystemMonitor. MikrotikManager: нет AppsMenu / NavUser / auth-portal switcher; theme = icon toggle в sidebar. + +5. **Дубли kit.** `KpiStatGrid` / `QuickActionGrid` / `ResourcePage` / `OpsDashboard` скопированы в 5 репо (`@cfdm/ui` vs `@evobgp/ui` vs `@evofw/ui` vs `@authportal/ui`). Нет shared package. Unification UX — через одинаковые rules + DNA; общий npm-пакет = отдельный technical risk (не ломаем package boundaries в этом плане). + +6. **Мёртвый / недоиспользованный kit.** SettingsShell и DetailPanel экспортируются, но не используются в EvoBGP routes и частично в EvoFirewall. Auth Portal `ResourcePage` — stub. CFDM ближе всех к контракту (Card не импортируется в `apps/web`). + +7. **Rules drift.** Правила перечисляют ReUI-компоненты, которых нет на диске (kanban в vps-tracker, stepper unused в FW, Frame unused в MM). `kpi-hybrid.mdc` дублируется 5 раз. CFDM нет `AGENTS.md` / `ui-surface.ts`. Auth contract ids без `dns`, код включает `dns`. + +## Verified REUI PRO registry (will expand in file) + +Каждый CHANGE получит Ref ID. Уже подтверждено MCP `get_block` / `list_components` / official docs: + +| Ref | Item | Official docs | Preview | Typical match | +|---|---|---|---|---| +| REUI-001 | Frame | [docs/components/base/frame](https://reui.io/docs/components/base/frame) | [components/frame](https://reui.io/components/frame) | 1:1 for ops surface | +| REUI-002 | Data Grid | [docs/components/base/data-grid](https://reui.io/docs/components/base/data-grid) | [components/data-grid](https://reui.io/components/data-grid) | 1:1 lists; MCP: TanStack v9 | +| REUI-003 | Filters | [docs/components/base/filters](https://reui.io/docs/components/base/filters) | [components/filters](https://reui.io/components/filters) | 1:1 | +| REUI-004 | stats-12 | [blocks](https://reui.io/blocks) | [preview/base/stats-12](https://reui.io/preview/base/stats-12) | partial vs current Item hybrid | +| REUI-005 | Icon Tile | [docs/components/base/icon-tile](https://reui.io/docs/components/base/icon-tile) | [components/icon-tile](https://reui.io/components/icon-tile) | target for KPI/row tiles | +| REUI-006 | data-grid-filtering-2 | [blocks](https://reui.io/blocks) | [preview/base/data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) | partial (kit ResourcePage) | +| REUI-007 | settings-16 | [blocks/application/settings](https://reui.io/blocks/application/settings) | [preview/base/settings-16](https://reui.io/preview/base/settings-16) | integrations | +| REUI-008 | settings-3 | same category | [preview/base/settings-3](https://reui.io/preview/base/settings-3) | settings rows Frame | +| REUI-009 | settings-8 | same | [preview/base/settings-8](https://reui.io/preview/base/settings-8) | webhook-like lists | +| REUI-010 | app-shell-12 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-12](https://reui.io/preview/base/app-shell-12) | current chrome cite | +| REUI-011 | app-shell-7 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-7](https://reui.io/preview/base/app-shell-7) | better SystemMonitor match | +| REUI-012 | app-shell-1 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-1](https://reui.io/preview/base/app-shell-1) | NavUser | +| REUI-013 | card-12 | [blocks](https://reui.io/blocks) | [preview/base/card-12](https://reui.io/preview/base/card-12) | Quick Actions | +| REUI-014 | dashboard-1 | [blocks](https://reui.io/blocks) | [preview/base/dashboard-1](https://reui.io/preview/base/dashboard-1) | OpsDashboard | +| REUI-015 | empty-state-14 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-14](https://reui.io/preview/base/empty-state-14) | Frame empty (prefer over card empty-state-11) | +| REUI-016 | empty-state-12 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-12](https://reui.io/preview/base/empty-state-12) | current cite; surface none | +| REUI-017 | sheet-8 / sheet-1 / sheet-9 | [blocks](https://reui.io/blocks) | [sheet-8](https://reui.io/preview/base/sheet-8) [sheet-1](https://reui.io/preview/base/sheet-1) [sheet-9](https://reui.io/preview/base/sheet-9) | create/edit/detail sheets | +| REUI-018 | auth-13 | [blocks](https://reui.io/blocks) | [preview/base/auth-13](https://reui.io/preview/base/auth-13) | Frame login | +| REUI-019 | auth-18 | [blocks](https://reui.io/blocks) | [preview/base/auth-18](https://reui.io/preview/base/auth-18) | auth-portal actual; surface none | +| REUI-020 | solution-users-1 / 6 | [blocks](https://reui.io/blocks) | [users-1](https://reui.io/preview/base/solution-users-1) [users-6](https://reui.io/preview/base/solution-users-6) | auth admin | +| REUI-021 | form-7 | [blocks](https://reui.io/blocks) | [preview/base/form-7](https://reui.io/preview/base/form-7) | inline detail Frame | +| REUI-022 | chart-15 | [blocks](https://reui.io/blocks) | [preview/base/chart-15](https://reui.io/preview/base/chart-15) | Frame charts (replace Card chart shells) | +| REUI-023 | kanban / sortable / stepper / timeline / autocomplete | primitive docs under `/docs/components/base/` | matching `/components/` | domain-specific | + +`settings-7` **не** будет target (Card). `empty-state-11` **не** будет target (Card). + +## Target UX/UI rules (summary) + +- **TARGET-001:** Один surface на ops-экран: ReUI Frame. Не смешивать Card+Frame. +- **TARGET-002:** Списки = DataGrid + Filters внутри Frame ([data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)). Запрет raw `` / shadcn Table как ops list. +- **TARGET-003:** KPI = stats-12 composition + IconTile `elevated` (не hand-roll Item tile). Semantic `text-*` / Badge variants, не `bg-emerald-*`. +- **TARGET-004:** Quick Actions = Frame tiles sibling KPI ([card-12](https://reui.io/preview/base/card-12) + stats-12 DNA). +- **TARGET-005:** Settings rows = settings-3; integrations = settings-16; не settings-7. +- **TARGET-006 (P0, все 6 приложений):** Chrome identity 1:1. Sidebar 240px + App Switcher; header AppsMenu → SystemMonitor; footer NavUser segmented theme. Эталон: [app-shell-12](https://reui.io/preview/base/app-shell-12) + [app-shell-7](https://reui.io/preview/base/app-shell-7) + [app-shell-1](https://reui.io/preview/base/app-shell-1). Auth Portal и MikrotikManager **не** исключение. +- **TARGET-007:** Create/edit = Sheet blocks (sheet-8 create/settings, sheet-1 create, sheet-9 detail). Confirm = Alert Dialog examples ([alert-dialog](https://reui.io/components/alert-dialog)). +- **TARGET-008:** Empty = Empty + IconStack, Frame-friendly empty-state-14 / empty-state-12; не Card empty-state-11. +- **TARGET-009:** MikrotikManager UX SoT сменить с Card `/servers` на Frame+DataGrid **при сохранении** всего CRUD `/servers` (technical: остаётся Next.js). +- **TARGET-010:** Rules синхронизировать с диском (не перечислять неустановленные компоненты как «установленные»). + +## Change themes per project (detail in file) + +**VPS Tracker (P1):** Card shells на `vps.$vpsId`, `reports`, `resources`, `renewals`, charts → Frame / chart-15. `spaces.tsx` shadcn Table → ResourcePage/DataGrid. SettingsShell Separator. Dashboard QA order — зафиксировать как exception или выровнять с EvoBGP. + +**CFDM (P2):** Наименьший gap. Добавить `ui-surface.ts` / AGENTS.md. IconTile вместо Item tiles. Удалить naming `*KpiCards`. + +**EvoBGP (P1):** `modules/new.tsx` Card stub → Frame. `illustrated-empty-state` Card → empty-state-14. Подключить SettingsShell/DetailPanel или удалить из barrel. Forms: RHF в package.json, в src не используется — выровнять на Field + sheet-8. + +**Auth Portal (P0 chrome / P1 pages):** Добавить SystemMonitorPopover в header (сейчас только AppsMenu). NavUser уже segmented — оставить. `/apps` tiles → QuickActionGrid. ResourcePage stub. Contract + `dns` id. Login-страница **без** AppShell остаётся отдельным auth surface ([auth-18](https://reui.io/preview/base/auth-18) / [auth-13](https://reui.io/preview/base/auth-13)); chrome identity применяется только к `_auth` shell. + +**EvoFirewall (P1/P2):** `/settings` → SettingsShell или явно REPLACE rule. Agent cards view оставить как Frame domain, не Card. IconTile для AgentPlatformIcon. Orphans: lifecycle timeline, FormFieldSimple, unused stepper. + +**MikrotikManager (P0 chrome, P1 surface, high risk):** Сначала chrome identity: AppsMenu, SystemMonitor, NavUser segmented, App Switcher `dns`/`fw`/…, `--sidebar-width: 240px` (сейчас hand-rolled footer + Sun/Moon). Затем RULE-CHANGE SoT: `DataPageCard` Card → Frame. KPI → stats-12 + IconTile. Не мигрировать Next.js. Сохранить весь CRUD `/servers` и остальные страницы. + +## Roadmap (in file, not executed now) + +- Phase 0: этот план-файл (единственный deliverable сейчас) +- Phase 1: **Chrome identity во всех 6** (Auth SystemMonitor; MM AppsMenu/NavUser/Switcher; Vite — сверка 1:1) +- Phase 2: shared rules + IconTile + Frame lock (Vite + начало MM) +- Phase 3: kit DNA (KPI/QA/ResourcePage) per repo +- Phase 4: page-level Card→Frame (VPS charts/detail, BGP stub/empty, MM DataPageCard) +- Phase 5: states (empty/loading/error) + sheets +- Phase 6: a11y/responsive verification + +## Constraints to preserve + +Не трогать: API contracts, auth/SSO/OIDC, permissions, TanStack vs Next routing, Go vs Fastify, package names, CRUD поведения (create/edit/delete/sync/filter/sort/pagination). + +Technical dependency (отдельная секция Risks): Data Grid docs говорят TanStack Table **v9**; установленный `@reui/data-grid` в проектах может быть v8 — не ломать kit без явной миграции (`` до проверки `package.json` каждого web-приложения в тексте плана). + +## After approval + +Написать полный `UX-UI-PLAN.md` (~все секции правила, CHANGE-XXX с Ref ID и прямыми URL). Остановиться. Implementation — только по отдельному запросу. diff --git a/.cursor/plans/ux_ui_unification_implementation_8a1c2d3e.plan.md b/.cursor/plans/ux_ui_unification_implementation_8a1c2d3e.plan.md new file mode 100644 index 0000000..2ffac78 --- /dev/null +++ b/.cursor/plans/ux_ui_unification_implementation_8a1c2d3e.plan.md @@ -0,0 +1,67 @@ +--- +name: UX UI Unification Implementation +overview: "Реализация UX-UI-PLAN.md: идентичный chrome 1:1 во всех 6 приложениях, затем IconTile/rules, kit DNA, Card→Frame. Phase 0 (аудит) уже в UX-UI-PLAN.md. Production-код начинается с Phase 1." +todos: + - id: phase-1-auth + content: "CHANGE-001: Auth Portal SystemMonitorPopover + header AppsMenu→monitor (app-shell-7)" + status: completed + - id: phase-1-mm + content: "CHANGE-002/032/033/034: MM chrome — 240px, App Switcher, NavUser segmented, AppsMenu+SystemMonitor, без Rail/Search pill" + status: completed + - id: phase-1-vite + content: "CHANGE-003: сверка VPS/CFDM/EvoBGP/FW + Auth SIDEBAR_WIDTH 240px" + status: completed + - id: phase-2-icontile + content: "Phase 2: CLI @reui/icon-tile во всех 6 + RULE-CHANGE Frame SoT MM (CHANGE-004, 024–028)" + status: completed + - id: phase-3-kit + content: "Phase 3: KpiStatGrid/QuickActionGrid на IconTile; Auth /apps kit (CHANGE-005–008, 021–022)" + status: completed + - id: phase-4-frame + content: "Phase 4: Card→Frame на ops-страницах VPS/EvoBGP/MM (CHANGE-009–018)" + status: completed + - id: phase-5-6 + content: "Phase 6: a11y/responsive verification (Phase 5 CHANGE-016/019/020/030/031 done)" + status: completed +isProject: false +--- + +# UX/UI Unification — Implementation + +Источник: [`UX-UI-PLAN.md`](c:/Users/shats/Dev/vps-tracker/UX-UI-PLAN.md). Аудит-план [`ux_ui_unification_audit_276a34f8.plan.md`](c:/Users/shats/Dev/vps-tracker/.cursor/plans/ux_ui_unification_audit_276a34f8.plan.md) **не менять**. + +Уровень: **идентичный chrome во всех 6** + общая UX-система; доменные страницы остаются разными. + +Эталоны chrome: [app-shell-12](https://reui.io/preview/base/app-shell-12) · [app-shell-7](https://reui.io/preview/base/app-shell-7) · [app-shell-1](https://reui.io/preview/base/app-shell-1). Docs: [reui.io/blocks](https://reui.io/blocks) · [reui.io/docs](https://reui.io/docs). + +## Chrome Identity Spec (1:1 — все 6) + +- Sidebar `--sidebar-width: 240px` +- Sidebar header: App Switcher (`cfdm` · `vps` · `bgp` · `fw` · `dns`; MM локальный `mm` + те же ids) +- Footer: NavUser + segmented theme (light/dark/system), без Sun/Moon +- Header right: AppsMenu → SystemMonitorPopover; без ModeToggle; поиск только ⌘K +- Main: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` +- Запрещено: SidebarRail, Search pill в header/sidebar +- Auth **login** без AppShell ([auth-18](https://reui.io/preview/base/auth-18)) + +**Не менять:** Vite vs Next, package scopes, API/SSO/OIDC, CRUD. Не мержить kit в один npm. Не мигрировать TanStack Table v8→v9 в этом плане. + +## Phase 1 — Chrome (сейчас) + +| ID | Project | Что сделать | +|---|---|---| +| CHANGE-001 | Auth Portal | `SystemMonitorPopover` после AppsMenu; header `z-10` `gap-2` | +| CHANGE-002 | MM | Switcher, header cluster, убрать Search pill + Sun/Moon | +| CHANGE-032 | MM | NavUser в footer, segmented theme | +| CHANGE-033 | MM | удалить SidebarRail | +| CHANGE-034 | MM | `SIDEBAR_WIDTH = 240px` | +| CHANGE-003 | VPS/CFDM/BGP/FW + Auth primitive | сверка; Auth `packages/ui` sidebar 16rem → 240px | + +MM остаётся Next.js 16. Без `@tanstack/react-query` для chrome (fetch + `useEffect`). + +## Later phases (не в этом проходе) + +- **Phase 2:** IconTile CLI, rules Frame SoT MM +- **Phase 3:** kit DNA KPI/QA +- **Phase 4:** Card→Frame pages +- **Phase 5–6:** empty/sheets, a11y diff --git a/.cursor/rules/frontend-shadcn.mdc b/.cursor/rules/frontend-shadcn.mdc index e21ec6e..2b7ac19 100644 --- a/.cursor/rules/frontend-shadcn.mdc +++ b/.cursor/rules/frontend-shadcn.mdc @@ -36,7 +36,6 @@ Monorepo — [`frontend-monorepo.mdc`](frontend-monorepo.mdc). ReUI — [`reui-m | `PageShell` | `page-shell.tsx` | | `ResourcePage` | `reui-kit/resource-page.tsx` | | `OpsDashboard` / `KpiStatGrid` / `QuickActionGrid` | `reui-kit/ops-dashboard.tsx`, `kpi-stat-grid.tsx`, `quick-action-grid.tsx` | -| `KanbanBoard` | `reui-kit/kanban-board.tsx` | | `DetailPanel` | `reui-kit/detail-panel.tsx` | | `SettingsShell` | `reui-kit/settings-shell.tsx` | | `EmptyState` | `empty-state.tsx` | diff --git a/.cursor/rules/frontend-ui-patterns.mdc b/.cursor/rules/frontend-ui-patterns.mdc index 184d82f..685e49e 100644 --- a/.cursor/rules/frontend-ui-patterns.mdc +++ b/.cursor/rules/frontend-ui-patterns.mdc @@ -48,7 +48,6 @@ apps/web/src/components/ ← shared + domain + layout page-shell.tsx reui-kit/ resource-page.tsx ← list: Frame + line Tabs + Filters + DataGrid - kanban-board.tsx ← kanban + KanbanBoardSkeleton detail-panel.tsx ← detail: Frame header/metrics settings-shell.tsx ops-dashboard.tsx ← KPI stats-12 + charts @@ -70,8 +69,6 @@ apps/web/src/components/ ← shared + domain + layout |---------|--------|-----------| | Page wrapper | `PageShell` | — | | List page | `ResourcePage` | ReUI `Frame` + `data-grid` + `filters` + shadcn `Tabs` `variant="line"` | -| Catalog / Board | `CatalogBoardToggle` + `ResourcePage` / `KanbanBoard` | `?view=board` на `/groups`, `/services` | -| Kanban | `KanbanBoard` / `KanbanBoardSkeleton` | ReUI `kanban` + `Frame` | | Detail | `DetailPanel` | ReUI `Frame` | | Settings | `SettingsShell` | — | | Dashboard KPI | `OpsDashboard` / `KpiStatGrid` | ReUI Frame [stats-12](https://reui.io/preview/base/stats-12) hybrid | @@ -95,7 +92,7 @@ apps/web/src/components/ ← shared + domain + layout Max 1 primary (`default`) на экран; остальные `outline` / `ghost`. -Toggle «Доска» / «К каталогу» — всегда `outline` в `primaryAction` / `KanbanBoard.toolbarActions` (не отдельный Frame-shell). +Max 1 primary (`default`) на экран; остальные `outline` / `ghost`. ## Line tabs (project standard) @@ -116,17 +113,6 @@ Toggle «Доска» / «К каталогу» — всегда `outline` в `p - Active state — Base UI `data-active`, не Radix `data-[state=active]` - Не трогать internals `reui/date-selector` -## Catalog / Board (`/groups`, `/services`) - -| Режим | UI | Search | -|-------|-----|--------| -| Catalog (default) | `ResourcePage` + primary create | omit / `view=catalog` | -| Board | `KanbanBoard` DnD | `?view=board` | - -- Groups tabs: Все / С доменами / Пустые -- Services tabs: Все / Включены / Выключены / Без группы -- DnD только на board; kanban hooks/cards не удалять - ## Dashboard KPI Эталон: [stats-12](https://reui.io/preview/base/stats-12) — icon tile + value + label + optional badge/footer; клик через `to` (`Link`) / `onSelect`. Compact strip: [card-35](https://reui.io/preview/base/card-35). @@ -178,7 +164,7 @@ Shared chrome (vps-tracker / CFDM / EvoBGP): см. [`docs/ui-design-contract.md` Каждый блок: **default, hover, focus, disabled, empty, loading, error**. -- **Loading** — `Skeleton` / `ResourcePage` skeleton / `KanbanBoardSkeleton` / `OpsDashboard` skeleton, не Spinner на странице +- **Loading** — `Skeleton` / `ResourcePage` skeleton / `OpsDashboard` skeleton, не Spinner на странице - **Empty** — `EmptyState` с CTA - **Zero-results** — message внутри DataGrid (+ «Сбросить») - **Error** — `QueryState` / `Alert` + `onRetry` diff --git a/.cursor/rules/kpi-hybrid.mdc b/.cursor/rules/kpi-hybrid.mdc index f52f964..39b9a46 100644 --- a/.cursor/rules/kpi-hybrid.mdc +++ b/.cursor/rules/kpi-hybrid.mdc @@ -1,11 +1,11 @@ --- -description: Только hybrid KPI — KpiStatGrid / row tile DNA (stats-12). Запрет SectionCards и hand-roll. +description: Только hybrid KPI — KpiStatGrid / row tile DNA (stats-12 + IconTile). Запрет SectionCards и hand-roll. alwaysApply: true --- -# KPI hybrid — только kit (stats-12 DNA) +# KPI hybrid — только kit (stats-12 + IconTile) -Preview: [stats-12](https://reui.io/preview/base/stats-12). SoT DNA = EvoBGP. Markup в проекте: `apps/web/src/components/reui-kit/kpi-stat-grid.tsx`. +Preview: [stats-12](https://reui.io/preview/base/stats-12). Primitive: [icon-tile](https://reui.io/docs/components/base/icon-tile). SoT DNA = EvoBGP. Markup: `apps/web/src/components/reui-kit/kpi-stat-grid.tsx`. Связанные: [`reui-mcp.mdc`](reui-mcp.mdc), [`frontend-ui-patterns.mdc`](frontend-ui-patterns.mdc). @@ -14,16 +14,17 @@ Preview: [stats-12](https://reui.io/preview/base/stats-12). SoT DNA = EvoBGP. Ma | Зона | Компонент / DNA | |------|-----------------| | KPI-полосы / dashboard metrics | только `reui-kit/KpiStatGrid` (через `OpsDashboard` / `DetailPanel.Metrics` при наличии) | -| Markup | horizontal compact hybrid: icon left `Item` `size-10.5` `bg-muted` + `border-background` + shadow + `ItemMedia` + label/Badge + value ± `variant` | -| Row icon tiles (data-grid) | та же DNA — semantic `text-*` на `bg-muted` | -| Quick Actions | только `reui-kit/QuickActionGrid` (sibling hybrid DNA) | +| Markup | horizontal compact hybrid: icon left `IconTile` `variant="elevated"` `className="size-10.5"` + label/Badge + value ± `variant` | +| Row icon tiles (data-grid) | та же DNA — semantic `text-*` на IconTile elevated | +| Quick Actions | только `reui-kit/QuickActionGrid` (sibling hybrid DNA, IconTile elevated) | -Импорты UI: `@cfdm/ui/components/*`. +Импорты UI: `@cfdm/ui/components/*`. IconTile: `@/components/reui/icon-tile`. ## NEVER - SectionCards / vertical-only KPI / hand-roll Frame/Card KPI -- Другой size / radius / solid brand fill вместо `bg-muted` +- `Item` `size-10.5` `bg-muted` hybrid вместо IconTile +- Другой size / radius / solid brand fill вместо `elevated` - `card-35` как замена stats-12 hybrid KPI - Копипаст ReUI block в route — adapt через `reui-kit/` -- Голый lucide `size-4` в name-cell без hybrid tile +- Голый lucide `size-4` в name-cell без IconTile diff --git a/.cursor/rules/reui-mcp.mdc b/.cursor/rules/reui-mcp.mdc index 8c8927b..1105e07 100644 --- a/.cursor/rules/reui-mcp.mdc +++ b/.cursor/rules/reui-mcp.mdc @@ -76,9 +76,9 @@ alwaysApply: true ## Установленные ReUI (apps/web) -**Components:** `frame`, `data-grid/*`, `filters`, `kanban`, `badge`, `alert`, `autocomplete`, `number-field`, `date-selector`, `color-picker`, `timeline`, `rating`, `phone-input`, `icon-stack` +**Components:** `frame`, `data-grid/*`, `filters`, `badge`, `alert`, `autocomplete`, `number-field`, `date-selector`, `color-picker`, `timeline`, `rating`, `phone-input`, `icon-stack`, `icon-tile` -**Kit:** `ResourcePage`, `KpiStatGrid`, `QuickActionGrid`, `OpsDashboard`, `KanbanBoard`, `DetailPanel`, `SettingsShell` +**Kit:** `ResourcePage`, `KpiStatGrid`, `QuickActionGrid`, `OpsDashboard`, `DetailPanel`, `SettingsShell` **Blocks (reference):** `stats-12`, `card-35`, `auth-13`, `app-shell-12`, `settings-16`, `settings-8`, `empty-state-12`, `form-7`, `data-grid-filtering-2`, `dashboard-1`, … diff --git a/UX-UI-PLAN.md b/UX-UI-PLAN.md new file mode 100644 index 0000000..7fd3abb --- /dev/null +++ b/UX-UI-PLAN.md @@ -0,0 +1,945 @@ +# UX/UI Unification Plan + +Дата аудита: 2026-08-17 +Implementation: **DONE 2026-08-17** — CHANGE-001…034, Phases 1–6, Acceptance §19. +Уровень унификации (решение пользователя): **система + идентичный chrome во всех 6 приложениях**; доменные экраны **не** копируются. + +Источники истины REUI PRO: [reui.io/docs](https://reui.io/docs) · [reui.io/llms.txt](https://reui.io/llms.txt) · MCP `user-reui` (`get_block` / `get_component` / `get_example` / `compose_page`). Skill ReUI: `668fb463eb` (локально и MCP совпадают). Версию REUI PRO **не** указываем — её нет. + +--- + +## 1. Executive Summary + +Workspace — 6 fullstack UI-приложений. Пять Vite SPA уже lock `surface: frame` + `reui-kit`. MikrotikManager — Next.js 16 App Router со SoT `/servers` на shadcn `Card` (`DataPageCard`). Это **две дизайн-системы** в одном workspace. + +Целевое состояние: + +- **Одинаковый chrome 1:1** во всех шести (включая Auth Portal и MikrotikManager): sidebar 240px, App Switcher, header AppsMenu → SystemMonitor, footer NavUser + segmented theme. +- **Одинаковый рецепт** ops-экранов: Frame, DataGrid+Filters, KPI = stats-12 + IconTile `elevated`, Quick Actions = Frame tiles, settings-3/16, Sheet для create/edit, Frame-friendly empty. +- **Разный домен:** topology VPS, DNS/kanban CFDM, BIRD/lookup EvoBGP, IdP Auth Portal, agents EvoFirewall, `/servers`/terminal MikrotikManager. +- **Не менять:** Vite vs Next, package scopes, API/SSO/OIDC, CRUD-поведение. + +Главные gaps: Auth Portal без SystemMonitor; MikrotikManager без AppsMenu/NavUser/Switcher и с Sun/Moon toggle; ни один проект не установил `icon-tile`; VPS Card-shells на detail/charts; MM `DataPageCard` = Card. + +--- + +## 2. Workspace Coverage + +`Discovered projects: 6` +`Audited projects: 6` +`Projects with plan: 6` +`Coverage: 100%` +`Implementation: 34/34 CHANGE DONE (2026-08-17)` + +| # | Project | Path | Type | Audited | Changes | Status | +|---|---|---|---|---|---|---| +| 1 | VPS Tracker | `c:\Users\shats\Dev\vps-tracker` | fullstack Vite SPA | YES | CHANGE-003, 004, 005, 009–014, 025, 029 | Implemented | +| 2 | Cloudflare Domain Manager | `c:\Users\shats\Dev\cloudflare-domain-manager` | fullstack Vite SPA | YES | CHANGE-003, 004, 005, 006, 024 | Implemented | +| 3 | EvoBGP | `c:\Users\shats\Dev\EvoBGP` | fullstack Go API + Vite SPA | YES | CHANGE-003, 004, 005, 015–017, 019 | Implemented | +| 4 | Auth Portal | `c:\Users\shats\Dev\auth-portal` | fullstack Vite SPA | YES | CHANGE-001, 004, 005, 021–023 | Implemented | +| 5 | EvoFirewall | `c:\Users\shats\Dev\EvoFirewall` | fullstack Vite SPA | YES | CHANGE-003, 004, 005, 007, 020 | Implemented | +| 6 | MikrotikManager | `c:\Users\shats\Dev\MikrotikManager-3` | fullstack Next.js 16 | YES | CHANGE-002, 004, 008, 018, 027, 032–034 | Implemented | + +Backend-only проектов нет. Shared UX/UI rules: CHANGE-026, 028, 030, 031, RULE-CHANGE-001…010. + +--- + +## 3. Workspace Inventory + +| Project | Framework | Language | UI | CSS | Theme | Package UI | Kit | +|---|---|---|---|---|---|---|---| +| VPS Tracker | Vite + TanStack Router/Query | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@cfdm/ui` | `reui-kit` (9 files) | +| CFDM | Vite + TanStack | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@cfdm/ui` | `reui-kit` (8 files) | +| EvoBGP | Vite + TanStack; API Go | TS / Go | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@evobgp/ui` | `reui-kit` (7 files) — **KPI SoT** | +| Auth Portal | Vite + TanStack | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@authportal/ui` | `reui-kit` (11 files; `ResourcePage` stub) | +| EvoFirewall | Vite + TanStack | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@evofw/ui` | `reui-kit` (6 files) | +| MikrotikManager | Next.js 16 App Router | TS | shadcn + **partial** ReUI | Tailwind v4 | custom `theme-provider` (`rl-theme`) | `@/components/ui` | **нет** `reui-kit` | + +TanStack Table во всех web-приложениях: `^8.21.x`. ReUI Data Grid docs (MCP `get_component`): TanStack Table **v9**. Это technical risk — см. §18. Не мигрировать v8→v9 в этом плане. + +`icon-tile`: **не установлен ни в одном** из 6 (поиск `IconTile` / `icon-tile` = 0 файлов). + +--- + +## 4. MCP / Skills / Plugins Inventory + +| Tool | Status | Use in audit | +|---|---|---| +| MCP `user-reui` | ready | `get_block`, `get_component`, `get_example`, `get_agent_skill` — официальные `docsUrl`/`previewUrl` | +| MCP `plugin-shadcn-shadcn` | listed | primitives `@shadcn` (secondary) | +| MCP codegraph (vps / CFDM / EvoBGP) | available | не заменял filesystem inventory | +| Skill `.claude/skills/reui` | `668fb463eb` | совпадает с MCP `get_agent_skill` | +| Skill shadcn (plugin + CFDM/EvoFirewall) | present | CLI/registry, не SoT для PRO blocks | +| Context7 MCP | available | не нужен для REUI PRO URLs | + +Приоритет: официальная документация REUI PRO > MCP metadata > project rules. + +--- + +## 5. Project Rules Inventory + +| Source | Project | Category | Used? | +|---|---|---|---| +| `docs/ui-design-contract.md` | VPS, CFDM, EvoBGP, Auth, FW | UX/UI contract (Frame, chrome, kit) | YES — baseline | +| `.cursor/rules/reui-mcp.mdc` | 5 Vite | ReUI MCP workflow | YES | +| `.cursor/rules/kpi-hybrid.mdc` | 5 Vite | KPI Item `size-10.5` DNA | YES — **устарело vs IconTile** | +| `.cursor/rules/frontend-shadcn.mdc` / `frontend-ui-patterns.mdc` | VPS, CFDM, Auth, FW | patterns | YES; VPS lists `kanban-board.tsx`, которого нет на диске | +| `.cursor/rules/web-shadcn.mdc` | EvoBGP | WEB-01…WEB-22 | YES | +| `AGENTS.md` | VPS, EvoBGP, Auth, FW, MM | agent guide | YES; **CFDM: ``** | +| `.cursor/rules/next-shadcn-production.mdc` | MM | SoT = `/servers` Card | YES — **противоречит унификации** | +| `.cursor/rules/ma-ui-guardian.mdc` | MM | UI Guardian = `/servers` | YES — **противоречит** | +| Workspace `reui-pro-senior-frontend.mdc` | all | chrome + Frame lock | YES; Auth ids без `dns` | + +Technical rules (Fastify/Drizzle/Go/Next App Router/CI) — **не менять** ради UI. + +--- + +## 6. Project UX/UI Rules Audit + +| Rule | Source | Current Usage | Problem | Recommendation | Status | +|---|---|---|---|---|---| +| Surface `frame` | ui-design-contract | 5 Vite mostly; MM Card | MM + VPS mixed Card | TARGET-001 | MODIFY (enforce all 6) | +| Chrome 240px / AppsMenu / NavUser | contract | 4 Vite close; Auth no monitor; MM none | Identity drift | TARGET-006 | NEW (1:1 all 6) | +| KPI hybrid Item tile | kpi-hybrid.mdc | 5 Vite kit | Official primitive IconTile unused | TARGET-003 | REPLACE | +| Lists DataGrid+Filters | contract | Vite ResourcePage; MM DataPageCard | MM Card; VPS `spaces` Table | TARGET-002 | MODIFY | +| Settings settings-16 / settings-3 | contract | CFDM closest | settings-7 is `surface: card` | TARGET-005 | MODIFY | +| Empty empty-state-12 | contract | mixed | empty-state-11 is Card; prefer 14 | TARGET-008 | MODIFY | +| MM SoT `/servers` Card | next-shadcn-production | enforced | Blocks Frame unification | RULE-CHANGE-001 | REPLACE | +| App Switcher ids | Auth contract vs `APP_IDS` | code has `dns` | Contract omits `dns` | CHANGE-023 | MODIFY | +| Installed ReUI lists | reui-mcp.mdc | claims kanban in VPS | File missing | CHANGE-025 | MODIFY | +| CFDM AGENTS.md / ui-surface.ts | contract mentions | `` | Rules drift | CHANGE-024 | NEW | + +--- + +## Chrome Identity Spec (обязательный 1:1 — все 6) + +Эталон: [app-shell-12](https://reui.io/preview/base/app-shell-12) + [app-shell-7](https://reui.io/preview/base/app-shell-7) (SystemMonitor) + [app-shell-1](https://reui.io/preview/base/app-shell-1) (NavUser). + +Применяется к **каждому** приложению, включая Auth Portal `_auth` shell и MikrotikManager `(main)` layout. Login Auth Portal **без** AppShell — отдельный auth surface ([auth-18](https://reui.io/preview/base/auth-18)). + +| Зона | Обязательное значение | Сейчас VPS/CFDM/BGP/FW | Auth Portal | MikrotikManager | +|---|---|---|---|---| +| `--sidebar-width` | `240px` | `240px` (`packages/ui/.../sidebar.tsx`) | 240px (ожидаемо, тот же паттерн) | **`16rem`** (`components/ui/sidebar.tsx`) | +| Sidebar header | App Switcher ids `cfdm` · `vps` · `bgp` · `fw` · `dns` | есть | есть | **нет** | +| Sidebar footer | NavUser + segmented theme (light/dark/system), без Sun/Moon icon | NavUser segmented | NavUser segmented | **hand-roll** avatar + Sun/Moon (`app-sidebar.tsx` ~239–266); `nav-user.tsx` **не используется** | +| Header right | AppsMenu → SystemMonitorPopover; без ModeToggle; без Search-кнопки (только ⌘K) | AppsMenu + SystemMonitor | AppsMenu **без** SystemMonitor (`site-header.tsx` 73–75) | нет AppsMenu / SystemMonitor; `CommandPaletteButton` в sidebar header | +| Header | `h-12`, sticky, `border-b`, `px-4 md:px-6` | да | да | `` vs Vite (Next layout свой) | +| Main | `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` | contract | contract | `` pixel-match | +| Theme | next-themes **или эквивалент с тем же UX** + semantic `--success/--warning/--info/--invert` | next-themes | next-themes | custom `theme-provider` `rl-theme` — UX должен стать segmented 3-way | +| Запрещено | `SidebarRail`, Search pill в header, ModeToggle в header | Vite: без Rail | без Rail | **`SidebarRail` есть** (`app-sidebar.tsx` 267) | + +SSO: вход через auth-portal; App Switcher `GET /api/v1/app-switcher`. Ids в коде: `packages/shared/src/contracts/auth.ts` `APP_IDS = ['cfdm', 'vps', 'bgp', 'fw', 'dns']`. + +--- + +## 7. UX/UI Audit — VPS Tracker + +**Identity:** Vite SPA `apps/web`, `@cfdm/ui`, ReUI CLI в `apps/web/src/components/reui/`. +**Routes (`_auth/`):** dashboard, vps, `vps.$vpsId`, accounts, providers, projects, `projects.$projectId`, payments, balance, tariffs, renewals, resources, reports, spaces, topology, audit, sync-journal. +**Kit:** `resource-page`, `kpi-stat-grid`, `quick-action-grid`, `ops-dashboard`, `settings-shell`, `detail-panel`, `frame-data-grid`, `settings-card`, `topology-canvas`. Нет `kanban-board`. +**ReUI on disk:** autocomplete, badge, icon-stack, filters, frame, alert, color-picker, timeline, number-field, date-selector + `data-grid/*`. Нет `icon-tile`, нет `kanban`. +**Chrome:** `app-shell.tsx` — SystemMonitor есть; sidebar 240px. +**Surface drift:** Card в `vps.$vpsId.tsx`, `resources.tsx`, `renewals.tsx`, `domain/charts.tsx`, `integrations/app-switcher-editor.tsx`. shadcn Table в `spaces.tsx`. +**Dashboard QA:** contract: VPS order = banner → KPI → charts → attention → QuickActionGrid (исключение vs EvoBGP KPI → QA → rest). +**Preserve:** VPS CRUD, BILLmanager sync, тарифы, баланс, платежи, topology canvas, CSV export, фильтры отчётов. + +--- + +## 8. UX/UI Audit — Cloudflare Domain Manager + +**Identity:** Vite SPA, `@cfdm/ui`, Frame-first. Card **не** импортируется в `apps/web` (grep 0). Ближе всех к контракту. +**Routes:** index, domains, `domains/$domainId`, DNS, services, groups, `groups/$groupId`, certificates, settings (index/appearance/integrations). +**Kit:** + `kanban-board`, `dashboard-analytics`. Нет `frame-data-grid` отдельным файлом (`` — может быть внутри ResourcePage). +**Gaps:** нет `AGENTS.md`; нет `ui-surface.ts`; naming `*KpiCards` (`domain-kpi-cards.tsx`, `cert-kpi-cards.tsx`, `service-kpi-cards.tsx`) при том что внутри уже `KpiStatGrid`; KPI tiles = Item hybrid, не IconTile. +**Preserve:** DNS CRUD, Cloudflare certs, groups/services kanban, integrations. + +--- + +## 9. UX/UI Audit — EvoBGP + +**Identity:** KPI SoT. `@evobgp/ui`. `lib/ui-surface.ts` есть. +**Routes:** dashboard, modules, `modules/$moduleId`, `modules/new`, lookup, network, operations, monitoring, directories, schedule, settings, tenant-settings, access. +**Card leftovers:** `modules/new.tsx`, `illustrated-empty-state.tsx`, `kpi-sparkline-card.tsx`, examples `c-tabs-*`. +**Kit unused:** `SettingsShell` определён в `reui-kit/settings-shell.tsx`, **не** импортируется routes (grep только definition). +**Forms:** WEB-05 требует RHF; фактическое использование в `src` — `` / выравнивать на Field + sheet-8. +**Preserve:** modules CRUD, lookup, BIRD jobs, network, access keys. + +--- + +## 10. UX/UI Audit — Auth Portal + +**Identity:** IdP, не ops CRUD. Login: `routes/index.tsx` → `PortalLoginForm` на базе **auth-18** (`components/blocks/auth-18/...`). +**`_auth` routes:** apps, admin users, user detail, oidc, logins, audit, admin apps. +**Chrome gap:** `site-header.tsx` — только `AppsMenu`, **нет** `SystemMonitorPopover` (grep 0 по репо). +**Kit:** `ResourcePage` — stub types only (`reui-kit/resource-page.tsx`). `/apps` — hand-roll Frame tiles, не `QuickActionGrid`. +**Contract vs code:** `docs/ui-design-contract.md` ids без `dns`; `APP_IDS` включает `dns`. +**Preserve:** SSO JWT, OIDC authorize/token/userinfo, app switcher admin, user access, audit. + +--- + +## 11. UX/UI Audit — EvoFirewall + +**Identity:** `@evofw/ui`. Chrome: `site-header.tsx` AppsMenu + SystemMonitor. +**Routes:** index, agents, `agents/$id`, lists, `lists/$id`, rules, `rules/$setId`, stats, settings. +**SettingsShell:** файл есть, **не** используется routes (grep 0 в routes). `/settings` — PageShell + Frame (допустимо как REPLACE rule). +**AgentPlatformIcon:** Item `size-10.5` `bg-muted` (`agents/agent-platform-icon.tsx`) — та же устаревшая KPI DNA. +**Agent cards view:** Frame domain (`?view=cards|table`) — **оставить**, не Card. +**Preserve:** agent fleet, blocklists, policy rules, stats, EvoBGP prefix ingest. + +--- + +## 12. UX/UI Audit — MikrotikManager + +**Identity:** Next.js 16, `components.json` с `@reui`, **нет** `reui-kit`. SoT UI: `app/(main)/servers/page.tsx` + `DataPageCard` → `Card` (`components/data-page-card.tsx`). +**Pages (27):** servers, dashboard, firewall, filters, certificates, gre, data-collection, uptime, recursive-routes, route-optimizer, ospf, communities, settings, probes, vxlan, wireguard, backups, bgp, ip-ranges, domains, asns, alerts, network-map, terminal, traffic, releases, containers. +**ReUI on disk:** frame, filters, badge, alert, autocomplete, number-field, sortable, timeline, stepper + data-grid (partial). `frame.tsx` установлен, ops-shell его не использует. +**Chrome:** sidebar `16rem`; footer hand-roll + Sun/Moon; `NavUser` файл есть, не подключён; нет App Switcher / AppsMenu / SystemMonitor; `SidebarRail` включён. +**Theme:** `components/theme-provider.tsx` STORAGE_KEY `rl-theme` — не next-themes; UX должен стать 3-way segmented. +**Preserve:** весь CRUD `/servers` и остальные 26 страниц (terminal, network-map, контейнеры, BGP/OSPF/firewall…). Framework остаётся Next.js. + +--- + +## 13. Cross-project Inconsistency Matrix + +| Area | VPS | CFDM | EvoBGP | Auth | FW | MM | UX impact | Priority | Target | +|---|---|---|---|---|---|---|---|---|---| +| Ops surface | mixed Card | Frame | leftover Card | Frame | Frame | **Card SoT** | две системы | P0/P1 | Frame | +| Chrome identity | close | close | close | no monitor | close | **none** | разный «дом» | P0 | Chrome spec | +| Sidebar width | 240px | 240px | 240px | 240px | 240px | 16rem | визуальный разрыв | P0 | 240px | +| Theme control | NavUser segmented | same | same | same | same | Sun/Moon | разный UX | P0 | segmented | +| KPI tile | Item hybrid | Item hybrid | Item hybrid SoT | Item | Item / AgentPlatformIcon | Card KPI | нет IconTile | P1 | IconTile elevated | +| Lists | ResourcePage; spaces Table | ResourcePage | ResourcePage | stub + Frame grids | ResourcePage | DataPageCard | MM/VPS spaces | P1 | DataGrid+Filters | +| Quick Actions | kit (order exception) | kit | kit | hand-roll `/apps` | kit | `` | Auth/MM | P1 | QuickActionGrid | +| Settings | SettingsShell | Frame settings | unused SettingsShell | N/A portal | unused SettingsShell | Card settings | dead kit | P2 | settings-3/16 | +| Empty | EmptyState | EmptyState | Card illustrated | Frame empty `/apps` | EmptyState | EmptyState Card-ish | Card empty | P2 | empty-state-14/12 | +| Package UI | `@cfdm/ui` | `@cfdm/ui` | `@evobgp/ui` | `@authportal/ui` | `@evofw/ui` | `@/components/ui` | не сливать npm | — | KEEP scopes | + +--- + +## 14. Duplicate Components Analysis + +| Task | Copies | Diff | Unify? | REUI | +|---|---|---|---|---| +| `KpiStatGrid` | 5 Vite repos | import scope only (SoT EvoBGP) | DNA sync per repo, **не** monorepo package | REUI-004 + REUI-005 | +| `QuickActionGrid` | 5 Vite | same | same | REUI-013 | +| `ResourcePage` | 4 ops + Auth stub | Auth stub | implement or stop claiming | REUI-006 | +| `OpsDashboard` | VPS/CFDM/BGP/FW | similar | KEEP per repo | REUI-014 | +| `SettingsShell` | VPS/CFDM/BGP/FW | BGP/FW unused | wire or stop listing as used | REUI-007 | +| `SystemMonitorPopover` | 4 Vite; Auth/MM missing | copy per app | port to Auth + MM | REUI-011 | +| `DataPageCard` vs Frame | MM only vs Vite Frame | Card vs Frame | REPLACE MM | REUI-001 | +| Item KPI tile vs IconTile | 5 Vite + FW AgentPlatformIcon | hand-roll | REPLACE with IconTile | REUI-005 | + +Общий npm-пакет kit **не** входит в этот план (package boundaries = technical risk). + +--- + +## 15. REUI PRO Reference Registry + +| Ref ID | Component/Block | Official Documentation | Official Example | Used By | Match | +|---|---|---|---|---|---| +| REUI-001 | Frame | [docs/components/base/frame](https://reui.io/docs/components/base/frame) | [components/frame](https://reui.io/components/frame) | CHANGE-009–018, 027 | 1:1 ops surface | +| REUI-002 | Data Grid | [docs/components/base/data-grid](https://reui.io/docs/components/base/data-grid) | [components/data-grid](https://reui.io/components/data-grid) | CHANGE-013, 018 | 1:1 lists; docs = TanStack **v9** vs installed v8 → adaptation = **partial** until migration | +| REUI-003 | Filters | [docs/components/base/filters](https://reui.io/docs/components/base/filters) | [components/filters](https://reui.io/components/filters) | CHANGE-013, 018 | 1:1 | +| REUI-004 | stats-12 | [blocks](https://reui.io/blocks) | [preview/base/stats-12](https://reui.io/preview/base/stats-12) | CHANGE-005, 008 | **partial** (MCP: Frame+Badge+Item; kit hand-rolls Item) | +| REUI-005 | Icon Tile | [docs/components/base/icon-tile](https://reui.io/docs/components/base/icon-tile) | [components/icon-tile](https://reui.io/components/icon-tile) | CHANGE-004, 005, 007, 008 | 1:1 for tile primitive (`variant="elevated"` per docs) | +| REUI-006 | data-grid-filtering-2 | [blocks](https://reui.io/blocks) | [preview/base/data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) | CHANGE-013, 018, 021 | **partial** (kit ResourcePage) | +| REUI-007 | settings-16 | [blocks/application/settings](https://reui.io/blocks/application/settings) | [preview/base/settings-16](https://reui.io/preview/base/settings-16) | CHANGE-020 | 1:1 integrations | +| REUI-008 | settings-3 | [blocks](https://reui.io/blocks) | [preview/base/settings-3](https://reui.io/preview/base/settings-3) | CHANGE-020 | 1:1 settings rows Frame | +| REUI-009 | settings-8 | [blocks](https://reui.io/blocks) | [preview/base/settings-8](https://reui.io/preview/base/settings-8) | CHANGE-020 | 1:1 webhook-like lists | +| REUI-010 | app-shell-12 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-12](https://reui.io/preview/base/app-shell-12) | CHANGE-001, 002, 003 | **partial** (chrome composition) | +| REUI-011 | app-shell-7 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-7](https://reui.io/preview/base/app-shell-7) | CHANGE-001, 002 | **partial** (SystemMonitor popover match) | +| REUI-012 | app-shell-1 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-1](https://reui.io/preview/base/app-shell-1) | CHANGE-002, 032 | **partial** (NavUser) | +| REUI-013 | card-12 | [blocks](https://reui.io/blocks) | [preview/base/card-12](https://reui.io/preview/base/card-12) | CHANGE-022 | **partial** (Quick Actions DNA; name «card» но Frame tiles) | +| REUI-014 | dashboard-1 | [blocks](https://reui.io/blocks) | [preview/base/dashboard-1](https://reui.io/preview/base/dashboard-1) | CHANGE-008, 029 | **partial** (OpsDashboard) | +| REUI-015 | empty-state-14 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-14](https://reui.io/preview/base/empty-state-14) | CHANGE-016, 030 | 1:1 Frame empty (MCP `surface: frame`) | +| REUI-016 | empty-state-12 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-12](https://reui.io/preview/base/empty-state-12) | CHANGE-030 | **partial** (current cite; MCP surface none) | +| REUI-017 | sheet-8 | [blocks](https://reui.io/blocks) | [preview/base/sheet-8](https://reui.io/preview/base/sheet-8) | CHANGE-031 | **partial** (settings sheet; surface none) | +| REUI-018 | sheet-1 | [blocks](https://reui.io/blocks) | [preview/base/sheet-1](https://reui.io/preview/base/sheet-1) | CHANGE-031 | **partial** (create sheet) | +| REUI-019 | sheet-9 | [blocks](https://reui.io/blocks) | [preview/base/sheet-9](https://reui.io/preview/base/sheet-9) | CHANGE-031 | **partial** (detail sheet) | +| REUI-020 | auth-13 | [blocks](https://reui.io/blocks) | [preview/base/auth-13](https://reui.io/preview/base/auth-13) | Auth login **не** target (см. REUI-021) | 1:1 Frame login **если** менять; сейчас KEEP auth-18 | +| REUI-021 | auth-18 | [blocks](https://reui.io/blocks) | [preview/base/auth-18](https://reui.io/preview/base/auth-18) | Auth login actual | **partial** (surface none; split testimonial) | +| REUI-022 | solution-users-1 | [blocks](https://reui.io/blocks) | [preview/base/solution-users-1](https://reui.io/preview/base/solution-users-1) | Auth admin users | **partial** (Frame+DataGrid+Filters+Sheet) | +| REUI-023 | form-7 | [blocks](https://reui.io/blocks) | [preview/base/form-7](https://reui.io/preview/base/form-7) | CHANGE-031 | **partial** (inline Frame form) | +| REUI-024 | chart-15 | [blocks](https://reui.io/blocks) | [preview/base/chart-15](https://reui.io/preview/base/chart-15) | CHANGE-012 | 1:1 Frame charts (MCP `surface: frame`) | +| REUI-025 | settings-2 | [blocks](https://reui.io/blocks) | [preview/base/settings-2](https://reui.io/preview/base/settings-2) | CHANGE-020 | 1:1 mixed-control rows Frame | +| REUI-026 | c-tabs-2 | [components/tabs](https://reui.io/components/tabs) | [preview/base/components/c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) | ResourcePage tabs | **partial** (line tabs; example also lists Card) | +| REUI-027 | Alert Dialog example | [components/alert-dialog](https://reui.io/components/alert-dialog) | [c-alert-dialog-11](https://reui.io/preview/base/components/c-alert-dialog-11) | CHANGE-031 confirm | **partial** (shadcn AlertDialog + ReUI example) | +| REUI-028 | Icon Stack | [docs/components/base/icon-stack](https://reui.io/docs/components/base/icon-stack) | [components/icon-stack](https://reui.io/components/icon-stack) | CHANGE-016, 030 | 1:1 empty illustration | +| REUI-029 | Badge | [docs/components/base/badge](https://reui.io/docs/components/base/badge) | [components/badge](https://reui.io/components/badge) | KPI/QA status | 1:1 semantic variants | +| REUI-030 | Alert | [docs/components/base/alert](https://reui.io/docs/components/base/alert) | [components/alert](https://reui.io/components/alert) | errors | 1:1 | +| REUI-031 | Kanban | [docs/components/base/kanban](https://reui.io/docs/components/base/kanban) | [components/kanban](https://reui.io/components/kanban) | CFDM groups/services | 1:1 domain | +| REUI-032 | Sortable | [docs/components/base/sortable](https://reui.io/docs/components/base/sortable) | [components/sortable](https://reui.io/components/sortable) | FW policy | 1:1 domain | +| REUI-033 | Stepper | [docs/components/base/stepper](https://reui.io/docs/components/base/stepper) | [components/stepper](https://reui.io/components/stepper) | MM/FW if used | 1:1; FW unused → don't list as used | +| REUI-034 | Timeline | [docs/components/base/timeline](https://reui.io/docs/components/base/timeline) | [components/timeline](https://reui.io/components/timeline) | audit | 1:1 | +| REUI-035 | Autocomplete | [docs/components/base/autocomplete](https://reui.io/docs/components/base/autocomplete) | [components/autocomplete](https://reui.io/components/autocomplete) | domain pickers | 1:1 | + +**Не target (намеренно):** + +| Item | Why | Evidence | +|---|---|---| +| settings-7 | MCP `surface: card` | [preview/base/settings-7](https://reui.io/preview/base/settings-7) | +| empty-state-11 | MCP `surface: card` | [preview/base/empty-state-11](https://reui.io/preview/base/empty-state-11) | + +--- + +## 16. Target UX/UI Rules + +### TARGET-RULE-001 + +**Rule:** Один surface на ops-экран: ReUI Frame. Не смешивать Card и Frame. +**Scope:** все 6. +**Rationale:** две системы ломают consistency. +**REUI PRO:** [Frame](https://reui.io/docs/components/base/frame) +**Impact:** VPS Card pages, BGP leftovers, MM DataPageCard. + +### TARGET-RULE-002 + +**Rule:** Ops-списки = DataGrid + Filters внутри Frame. Запрет raw `
` / shadcn Table как ops list. +**Scope:** все 6. +**REUI PRO:** [data-grid](https://reui.io/docs/components/base/data-grid) · [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) +**Impact:** VPS `spaces.tsx`; MM DataPageCard. + +### TARGET-RULE-003 + +**Rule:** KPI = stats-12 composition + IconTile `elevated` (не hand-roll Item `size-10.5`). Semantic `text-*` / Badge, не `bg-emerald-*`. +**Scope:** kit во всех Vite + MM dashboard. +**REUI PRO:** [stats-12](https://reui.io/preview/base/stats-12) · [icon-tile](https://reui.io/docs/components/base/icon-tile) + +### TARGET-RULE-004 + +**Rule:** Quick Actions = только `QuickActionGrid` (Frame tiles, sibling KPI). +**REUI PRO:** [stats-12](https://reui.io/preview/base/stats-12) · [card-12](https://reui.io/preview/base/card-12) + +### TARGET-RULE-005 + +**Rule:** Settings rows = settings-3 / settings-2; integrations = settings-16; **запрет settings-7**. +**REUI PRO:** [settings-3](https://reui.io/preview/base/settings-3) · [settings-16](https://reui.io/preview/base/settings-16) + +### TARGET-RULE-006 (P0) + +**Rule:** Chrome identity 1:1 во всех 6 — см. Chrome Identity Spec. Auth Portal и MikrotikManager **не** исключение. Login без AppShell. +**REUI PRO:** [app-shell-12](https://reui.io/preview/base/app-shell-12) · [app-shell-7](https://reui.io/preview/base/app-shell-7) · [app-shell-1](https://reui.io/preview/base/app-shell-1) + +### TARGET-RULE-007 + +**Rule:** Create/edit = Sheet (sheet-8 / sheet-1 / sheet-9). Confirm = Alert Dialog ([c-alert-dialog-11](https://reui.io/preview/base/components/c-alert-dialog-11)). + +### TARGET-RULE-008 + +**Rule:** Empty = Empty + IconStack, Frame-friendly empty-state-14 (prefer) / empty-state-12. Не empty-state-11. + +### TARGET-RULE-009 + +**Rule:** MikrotikManager UX SoT: Frame+DataGrid **при сохранении** CRUD `/servers`. Next.js не менять. + +### TARGET-RULE-010 + +**Rule:** Rules синхронизировать с диском (не перечислять неустановленные компоненты как установленные). App Switcher ids включают `dns`. + +--- + +### RULE-CHANGE-001 + +**Current rule:** SoT UI = `/servers` на Card (`next-shadcn-production.mdc`, `ma-ui-guardian.mdc`). +**Source:** `MikrotikManager-3/.cursor/rules/next-shadcn-production.mdc` +**Problem:** блокирует унификацию Frame. +**Evidence:** `components/data-page-card.tsx` → `Card`. +**Recommended:** SoT = Frame + DataGrid; `/servers` остаётся эталоном **поведения** CRUD, не Card-shell. +**REUI:** REUI-001, REUI-006. +**Impact:** MM rules + все `DataPageCard` pages. + +### RULE-CHANGE-002 + +**Current:** kpi-hybrid Item `size-10.5` `bg-muted`. +**Source:** `kpi-hybrid.mdc` ×5. +**Problem:** official IconTile покрывает задачу; нигде не установлен. +**Recommended:** KPI/row tiles = IconTile `elevated` + stats-12 composition. +**REUI:** REUI-004, REUI-005. + +### RULE-CHANGE-003 + +**Current:** chrome описан в contract, но Auth/MM исключения фактически существуют. +**Recommended:** Chrome Identity Spec обязателен 1:1 для всех 6. +**REUI:** REUI-010, REUI-011, REUI-012. + +### RULE-CHANGE-004 + +**Current:** contract App Switcher ids без `dns`. +**Source:** `auth-portal/docs/ui-design-contract.md` +**Evidence:** `APP_IDS` включает `dns`. +**Recommended:** ids `cfdm · vps · bgp · fw · dns`. + +### RULE-CHANGE-005 + +**Current:** VPS `frontend-shadcn.mdc` / `reui-mcp.mdc` lists `kanban-board`. +**Evidence:** `reui-kit/` VPS — нет `kanban-board.tsx`; grep `kanban` в VPS = 0. +**Recommended:** список установленных = диск. + +### RULE-CHANGE-006 + +**Current:** settings-7 встречается как reference. +**Evidence:** MCP settings-7 `surface: card`. +**Recommended:** запретить как target. + +--- + +## 17. Detailed Change Plan + +Шаблон ниже. Functional requirements для всех UI CHANGE: сохранить create/edit/delete/filter/sort/pagination/navigation/permissions; менять только оболочку. + +Состояния (где применимо): default, hover, focus, disabled, loading, error, empty. +Responsive: desktop = эталон ReUI preview; tablet/mobile = существующий Sidebar/Sheet collapse Vite/Next, не ломать. +A11y: keyboard, focus ring, labels, `aria-hidden` на decorative IconTile (docs). + +### CHANGE-001 + +**Project:** Auth Portal · **Priority:** P0 · **Category:** Navigation / chrome +**File:** `apps/web/src/components/layout/site-header.tsx` +**Current:** header right = только `AppsMenu` (строки 73–75). +**Problem:** нет SystemMonitor — ломает chrome identity. +**Change:** добавить `SystemMonitorPopover` после AppsMenu (порт с CFDM/VPS). +**Ref:** REUI-011 · [app-shell-7](https://reui.io/preview/base/app-shell-7) · Match: **partial** +**Mapping:** AppsMenu-only header → AppsMenu + SystemMonitor. +**Preserve:** breadcrumbs, AppsMenu, SSO session. +**Risk:** Low. + +### CHANGE-002 + +**Project:** MikrotikManager · **Priority:** P0 · **Category:** Navigation / chrome +**Files:** `components/app-sidebar.tsx`, `components/ui/sidebar.tsx`, `(main)/layout.tsx` +**Current:** sidebar 16rem; footer hand-roll + Sun/Moon; SidebarRail; нет AppsMenu/SystemMonitor/App Switcher. +**Change:** Chrome Identity Spec 1:1 (240px, Switcher, NavUser segmented, header cluster, убрать Rail и icon theme toggle). +**Ref:** REUI-010, REUI-011, REUI-012 +**Match:** **partial** (composition, Next layout). +**Preserve:** nav groups, command palette **как ⌘K** (не Search pill в header). +**Risk:** High. + +### CHANGE-003 + +**Project:** VPS, CFDM, EvoBGP, EvoFirewall · **Priority:** P0 · **Category:** chrome verification +**Current:** близко к spec (SystemMonitor есть). +**Change:** pixel/token сверка 1:1 (header `h-12`, main padding, нет ModeToggle/Search pill). +**Ref:** REUI-010 · Match: **partial** +**Risk:** Low. + +### CHANGE-004 + +**Project:** все 6 · **Priority:** P1 · **Category:** KPI primitive install +**Current:** `icon-tile` отсутствует. +**Change:** CLI `pnpm dlx shadcn@latest add @reui/icon-tile --yes` из `apps/web` (MM — из корня Next). +**Ref:** REUI-005 · [icon-tile docs](https://reui.io/docs/components/base/icon-tile) · Match: **1:1** +**Risk:** Low. + +### CHANGE-005 + +**Project:** 5 Vite kit · **Priority:** P1 · **Category:** KPI +**Files:** `reui-kit/kpi-stat-grid.tsx` (каждый репо); EvoBGP SoT markup. +**Current:** `Item` `size-10.5` `bg-muted` + `ItemMedia`. +**Change:** IconTile `elevated` + stats-12 composition; обновить `kpi-hybrid.mdc`. +**Ref:** REUI-004 **partial** + REUI-005 **1:1** +**Preserve:** `to`/`onSelect`, variants warning/destructive, Badge hints. +**Risk:** Medium (visual DNA shift). + +### CHANGE-006 + +**Project:** CFDM · **Priority:** P2 · **Category:** naming +**Files:** `domain-kpi-cards.tsx`, `cert-kpi-cards.tsx`, `service-kpi-cards.tsx` +**Change:** переименовать `*KpiCards` → `*KpiStats` (внутри уже KpiStatGrid). Не Card. +**Ref:** REUI-004 · Match: **partial** (rename only) +**Risk:** Low. + +### CHANGE-007 + +**Project:** EvoFirewall · **Priority:** P1 · **Category:** row tiles +**File:** `apps/web/src/components/agents/agent-platform-icon.tsx` +**Current:** Item hybrid tile. +**Change:** IconTile elevated; semantic `text-info` / muted. +**Ref:** REUI-005 · Match: **1:1** +**Preserve:** mikrotik vs linux aria-label. +**Risk:** Low. + +### CHANGE-008 + +**Project:** MikrotikManager · **Priority:** P1 · **Category:** KPI +**File:** `app/(main)/dashboard/page.tsx` +**Change:** dashboard metrics → stats-12 + IconTile, не Card KPI. +**Ref:** REUI-004, REUI-005, REUI-014 · Match: **partial** +**Preserve:** dashboard numbers/links. +**Risk:** Medium. + +### CHANGE-009 + +**Project:** VPS Tracker · **Priority:** P1 · **Category:** Surface +**File:** `apps/web/src/routes/_auth/vps.$vpsId.tsx` +**Current:** Card shells. +**Change:** Frame / DetailPanel. +**Ref:** REUI-001, REUI-023 · Match: **partial** (detail composition) +**Preserve:** VPS detail fields, actions, sync. +**Risk:** Medium. + +### CHANGE-010 + +**Project:** VPS Tracker · **Priority:** P1 · **Category:** Surface +**File:** `apps/web/src/routes/_auth/resources.tsx` +**Change:** Card → Frame. +**Ref:** REUI-001 · Match: **1:1** shell +**Preserve:** resource listing. +**Risk:** Low. + +### CHANGE-011 + +**Project:** VPS Tracker · **Priority:** P1 · **Category:** Surface +**File:** `apps/web/src/routes/_auth/renewals.tsx` +**Change:** Card → Frame. +**Ref:** REUI-001 · Match: **1:1** shell +**Preserve:** renewals data/actions. +**Risk:** Low. + +### CHANGE-012 + +**Project:** VPS Tracker · **Priority:** P1 · **Category:** Charts +**File:** `apps/web/src/components/domain/charts.tsx` (used by `reports.tsx`) +**Current:** Card from `@cfdm/ui/components/card`. +**Change:** Frame chart shells per chart-15. +**Ref:** REUI-024 · [chart-15](https://reui.io/preview/base/chart-15) · Match: **1:1** surface; **partial** series (Recharts already) +**Preserve:** filters, CSV, KPI on reports. +**Risk:** Medium. + +### CHANGE-013 + +**Project:** VPS Tracker · **Priority:** P1 · **Category:** Data UI +**File:** `apps/web/src/routes/_auth/spaces.tsx` +**Current:** shadcn Table. +**Change:** ResourcePage / DataGrid + Filters. +**Ref:** REUI-002 **partial** (v8), REUI-003, REUI-006 +**Preserve:** spaces CRUD, columns, sort. +**Risk:** Medium. + +### CHANGE-014 + +**Project:** VPS Tracker · **Priority:** P2 · **Category:** Settings +**File:** `apps/web/src/components/integrations/app-switcher-editor.tsx` +**Current:** Card. +**Change:** Frame + settings-16/3 rows. +**Ref:** REUI-001, REUI-007 · Match: **partial** +**Preserve:** app switcher editor fields. +**Risk:** Low. + +### CHANGE-015 + +**Project:** EvoBGP · **Priority:** P1 · **Category:** Surface +**File:** `apps/web/src/routes/_auth/modules/new.tsx` +**Current:** Card stub. +**Change:** Frame + sheet-1/form-7 for create. +**Ref:** REUI-001, REUI-018 · Match: **partial** +**Preserve:** module create flow. +**Risk:** Medium. + +### CHANGE-016 + +**Project:** EvoBGP · **Priority:** P1 · **Category:** Empty +**File:** `apps/web/src/components/patterns/illustrated-empty-state.tsx` +**Current:** Card + illustration. +**Change:** empty-state-14 (Frame + IconStack). +**Ref:** REUI-015, REUI-028 · Match: **1:1** surface +**Preserve:** empty CTA copy/actions. +**Risk:** Low. + +### CHANGE-017 + +**Project:** EvoBGP · **Priority:** P2 · **Category:** Charts +**File:** `apps/web/src/components/patterns/kpi-sparkline-card.tsx` +**Change:** Card → Frame / chart-15 DNA. +**Ref:** REUI-024 · Match: **partial** +**Risk:** Low. + +### CHANGE-018 + +**Project:** MikrotikManager · **Priority:** P1 · **Category:** Surface +**Files:** `components/data-page-card.tsx` + all pages importing it (servers, firewall, filters, gre, certificates, data-collection, …). +**Current:** `Card`. `components/reui/frame.tsx` установлен, не используется как ops shell. +**Change:** DataPageCard → Frame wrapper (или заменить вызовы на Frame). +**Ref:** REUI-001, REUI-006 · Match: **1:1** shell; grid **partial** (v8) +**Preserve:** весь CRUD `/servers` и остальных страниц. +**Risk:** High. + +### CHANGE-019 + +**Project:** EvoBGP · **Priority:** P2 · **Category:** Kit dead code +**File:** `reui-kit/settings-shell.tsx` +**Change:** подключить на settings routes **или** убрать из «установленный kit / used». +**Ref:** REUI-007, REUI-008 · Match: **partial** +**Risk:** Low. + +### CHANGE-020 + +**Project:** EvoFirewall · **Priority:** P2 · **Category:** Settings +**File:** `routes/_auth/settings.tsx` vs unused `reui-kit/settings-shell.tsx` +**Change:** либо SettingsShell при 2+ секциях, либо явно REPLACE rule «single-page = PageShell+Frame» (уже в FW frontend-shadcn). +**Ref:** REUI-007, REUI-008, REUI-025 +**Risk:** Low. + +### CHANGE-021 + +**Project:** Auth Portal · **Priority:** P2 · **Category:** Kit +**File:** `reui-kit/resource-page.tsx` (stub) +**Change:** либо реализовать как ops ResourcePage, либо не экспортировать как list kit. Admin users → solution-users-1 DNA. +**Ref:** REUI-006, REUI-022 · Match: **partial** +**Preserve:** users/OIDC grids. +**Risk:** Medium. + +### CHANGE-022 + +**Project:** Auth Portal · **Priority:** P1 · **Category:** Quick Actions +**File:** `apps/web/src/routes/_auth.apps.tsx` +**Current:** hand-roll Frame tiles. +**Change:** `QuickActionGrid` (после IconTile DNA). +**Ref:** REUI-013, REUI-004 · Match: **partial** +**Preserve:** SSO `openApp`, admin link, empty «нет приложений». +**Risk:** Medium. + +### CHANGE-023 + +**Project:** Auth Portal · **Priority:** P1 · **Category:** Rules +**File:** `docs/ui-design-contract.md` (+ копии в других apps) +**Change:** App Switcher ids += `dns`. +**Ref:** N/A functional; chrome REUI-010 +**Match:** `` для списка ids (доменное) — UI chrome всё равно REUI-010. +**Risk:** Low. + +### CHANGE-024 + +**Project:** CFDM · **Priority:** P2 · **Category:** Rules +**Change:** добавить `AGENTS.md` + `apps/web/src/lib/ui-surface.ts` (`UI_SURFACE = 'frame'`), как EvoBGP. +**Ref:** REUI-001 +**Risk:** Low. + +### CHANGE-025 + +**Project:** VPS Tracker · **Priority:** P2 · **Category:** Rules +**Change:** убрать kanban из «установленных» в `reui-mcp.mdc` / `frontend-shadcn.mdc`. +**Ref:** REUI-031 существует, но **не установлен** в VPS. +**Risk:** Low. + +### CHANGE-026 + +**Project:** all Vite · **Priority:** P1 · **Category:** Rules +**Files:** `kpi-hybrid.mdc` ×5 + workspace `reui-pro-senior-frontend.mdc` +**Change:** DNA = IconTile elevated, не Item. +**Ref:** REUI-005 +**Risk:** Low (docs only until CHANGE-005). + +### CHANGE-027 + +**Project:** MikrotikManager · **Priority:** P0 · **Category:** Rules +**Change:** RULE-CHANGE-001 — SoT Frame+DataGrid. +**Ref:** REUI-001, REUI-006 +**Risk:** High (process + UI). + +### CHANGE-028 + +**Project:** EvoBGP (+ any settings-7 cite) · **Priority:** P1 · **Category:** Rules +**Change:** запрет settings-7 как target. +**Ref:** settings-7 [preview](https://reui.io/preview/base/settings-7) — **не использовать**. Target REUI-007/008. +**Risk:** Low. + +### CHANGE-029 + +**Project:** VPS Tracker · **Priority:** P2 · **Category:** Dashboard layout +**Current:** contract exception QA after charts. +**Change:** либо выровнять с EvoBGP (KPI → QA → rest), либо **явно** оставить exception в contract. Решение implementation: default **KEEP exception** unless product asks otherwise. +**Ref:** REUI-014 · Match: **partial** +**Risk:** Low. + +### CHANGE-030 + +**Project:** all 6 · **Priority:** P2 · **Category:** Empty states +**Change:** Frame-friendly empty-state-14/12 + IconStack; не Card empty-state-11. +**Ref:** REUI-015, REUI-016, REUI-028 +**Risk:** Low. + +### CHANGE-031 + +**Project:** all 6 · **Priority:** P2 · **Category:** Forms / overlays +**Change:** create/edit Sheet per sheet-8/1/9; confirm c-alert-dialog-11. Не менять validation/API. +**Ref:** REUI-017, REUI-018, REUI-019, REUI-023, REUI-027 +**Risk:** Medium. + +### CHANGE-032 + +**Project:** MikrotikManager · **Priority:** P0 · **Category:** chrome +**File:** `components/nav-user.tsx` unused +**Change:** подключить в SidebarFooter; segmented theme вместо Sun/Moon; убрать hardcoded «АК» footer. +**Ref:** REUI-012 · Match: **partial** +**Preserve:** user name/email display (сейчас hardcoded — **заменить на реального пользователя**, не потерять logout если появится). +**Risk:** Medium. + +### CHANGE-033 + +**Project:** MikrotikManager · **Priority:** P1 · **Category:** chrome +**File:** `components/app-sidebar.tsx` `SidebarRail` +**Change:** удалить Rail (запрещён контрактом). +**Ref:** REUI-010 · Match: **partial** +**Risk:** Low. + +### CHANGE-034 + +**Project:** MikrotikManager · **Priority:** P0 · **Category:** chrome +**File:** `components/ui/sidebar.tsx` `SIDEBAR_WIDTH = "16rem"` +**Change:** `240px`. +**Ref:** REUI-010 · Match: **partial** (token, не block 1:1) +**Risk:** Medium (layout shift). + +--- + +## Final Change Matrix + +| Change ID | Project | Category | Priority | Current UI | Target REUI PRO | Ref ID | Official Reference | Match | +|---|---|---|---|---|---|---|---|---| +| CHANGE-001 | Auth Portal | Chrome | P0 | AppsMenu only | app-shell-7 monitor | REUI-011 | [app-shell-7](https://reui.io/preview/base/app-shell-7) | partial | +| CHANGE-002 | MM | Chrome | P0 | 16rem + Sun/Moon | app-shell-12/7/1 | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial | +| CHANGE-003 | VPS/CFDM/BGP/FW | Chrome | P0 | close | app-shell-12 | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial | +| CHANGE-004 | all 6 | Install | P1 | missing | Icon Tile | REUI-005 | [icon-tile](https://reui.io/docs/components/base/icon-tile) | 1:1 | +| CHANGE-005 | 5 Vite | KPI | P1 | Item hybrid | stats-12 + IconTile | REUI-004/005 | [stats-12](https://reui.io/preview/base/stats-12) | partial + 1:1 | +| CHANGE-006 | CFDM | Naming | P2 | *KpiCards | KpiStatGrid name | REUI-004 | [stats-12](https://reui.io/preview/base/stats-12) | partial | +| CHANGE-007 | FW | Row tile | P1 | Item AgentPlatformIcon | IconTile | REUI-005 | [icon-tile](https://reui.io/docs/components/base/icon-tile) | 1:1 | +| CHANGE-008 | MM | KPI | P1 | Card metrics | stats-12 + IconTile | REUI-004/005 | [stats-12](https://reui.io/preview/base/stats-12) | partial | +| CHANGE-009 | VPS | Surface | P1 | Card detail | Frame | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | partial | +| CHANGE-010 | VPS | Surface | P1 | Card resources | Frame | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 | +| CHANGE-011 | VPS | Surface | P1 | Card renewals | Frame | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 | +| CHANGE-012 | VPS | Charts | P1 | Card charts | chart-15 | REUI-024 | [chart-15](https://reui.io/preview/base/chart-15) | 1:1/partial | +| CHANGE-013 | VPS | Table | P1 | shadcn Table spaces | DataGrid+Filters | REUI-002/006 | [data-grid](https://reui.io/docs/components/base/data-grid) | partial | +| CHANGE-014 | VPS | Settings | P2 | Card editor | Frame settings-16 | REUI-007 | [settings-16](https://reui.io/preview/base/settings-16) | partial | +| CHANGE-015 | EvoBGP | Surface | P1 | Card new module | Frame/sheet-1 | REUI-001/018 | [sheet-1](https://reui.io/preview/base/sheet-1) | partial | +| CHANGE-016 | EvoBGP | Empty | P1 | Card empty | empty-state-14 | REUI-015 | [empty-state-14](https://reui.io/preview/base/empty-state-14) | 1:1 | +| CHANGE-017 | EvoBGP | Charts | P2 | Card sparkline | chart-15 | REUI-024 | [chart-15](https://reui.io/preview/base/chart-15) | partial | +| CHANGE-018 | MM | Surface | P1 | DataPageCard Card | Frame+DataGrid | REUI-001/006 | [frame](https://reui.io/docs/components/base/frame) | 1:1/partial | +| CHANGE-019 | EvoBGP | Kit | P2 | unused SettingsShell | settings-16/3 | REUI-007 | [settings-16](https://reui.io/preview/base/settings-16) | partial | +| CHANGE-020 | FW | Settings | P2 | unused SettingsShell | settings-3/16 | REUI-008 | [settings-3](https://reui.io/preview/base/settings-3) | partial | +| CHANGE-021 | Auth | Kit | P2 | ResourcePage stub | data-grid-filtering-2 / users-1 | REUI-006/022 | [users-1](https://reui.io/preview/base/solution-users-1) | partial | +| CHANGE-022 | Auth | QA | P1 | hand-roll /apps | QuickActionGrid | REUI-013 | [card-12](https://reui.io/preview/base/card-12) | partial | +| CHANGE-023 | Auth | Rules | P1 | contract без dns | ids + dns | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial | +| CHANGE-024 | CFDM | Rules | P2 | no AGENTS.md | Frame lock file | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 | +| CHANGE-025 | VPS | Rules | P2 | fake kanban installed | disk-accurate list | REUI-031 | [kanban](https://reui.io/docs/components/base/kanban) | 1:1 exists, unused | +| CHANGE-026 | Vite | Rules | P1 | Item hybrid rule | IconTile rule | REUI-005 | [icon-tile](https://reui.io/docs/components/base/icon-tile) | 1:1 | +| CHANGE-027 | MM | Rules | P0 | Card SoT | Frame SoT | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 | +| CHANGE-028 | EvoBGP | Rules | P1 | settings-7 cite | forbid card settings | REUI-007 | [settings-16](https://reui.io/preview/base/settings-16) | 1:1 | +| CHANGE-029 | VPS | Layout | P2 | QA order exception | KEEP or align | REUI-014 | [dashboard-1](https://reui.io/preview/base/dashboard-1) | partial | +| CHANGE-030 | all 6 | Empty | P2 | mixed | empty-state-14/12 | REUI-015 | [empty-state-14](https://reui.io/preview/base/empty-state-14) | 1:1/partial | +| CHANGE-031 | all 6 | Forms | P2 | mixed sheets | sheet-8/1/9 | REUI-017 | [sheet-8](https://reui.io/preview/base/sheet-8) | partial | +| CHANGE-032 | MM | Chrome | P0 | unused NavUser | app-shell-1 NavUser | REUI-012 | [app-shell-1](https://reui.io/preview/base/app-shell-1) | partial | +| CHANGE-033 | MM | Chrome | P1 | SidebarRail | remove | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial | +| CHANGE-034 | MM | Chrome | P0 | 16rem | 240px | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial | + +Все UI-related CHANGE имеют Ref ID. CHANGE-023 ids — доменный список; chrome reference всё равно REUI-010. + +### Execution log (2026-08-17) + +- [x] CHANGE-001 Auth SystemMonitor +- [x] CHANGE-002 MM chrome identity +- [x] CHANGE-003 Vite chrome verification (240px, h-12, skip-link, main padding) +- [x] CHANGE-004 IconTile CLI во всех 6 +- [x] CHANGE-005 KpiStatGrid IconTile DNA +- [x] CHANGE-006 CFDM *KpiStats rename +- [x] CHANGE-007 FW AgentPlatformIcon IconTile +- [x] CHANGE-008 MM dashboard stats-12 + IconTile + Frame panels (`OpsPanel`) +- [x] CHANGE-009 VPS vps detail Frame +- [x] CHANGE-010 VPS resources Frame +- [x] CHANGE-011 VPS renewals Frame +- [x] CHANGE-012 VPS charts Frame +- [x] CHANGE-013 VPS spaces ResourcePage +- [x] CHANGE-014 VPS app-switcher editor Frame +- [x] CHANGE-015 EvoBGP modules/new Frame +- [x] CHANGE-016 EvoBGP empty IconStack +- [x] CHANGE-017 EvoBGP sparkline Frame (chart-15 DNA) +- [x] CHANGE-018 MM DataPageCard + remaining ops Card → Frame/`OpsPanel` +- [x] CHANGE-019 EvoBGP SettingsShell wired +- [x] CHANGE-020 FW single-page settings rule +- [x] CHANGE-021 Auth ResourcePage stub explicit +- [x] CHANGE-022 Auth `/apps` QuickActionGrid +- [x] CHANGE-023 App Switcher ids + `dns` +- [x] CHANGE-024 CFDM AGENTS.md + ui-surface.ts +- [x] CHANGE-025 VPS kanban list disk-accurate +- [x] CHANGE-026 kpi-hybrid IconTile rule +- [x] CHANGE-027 MM SoT Frame+DataGrid +- [x] CHANGE-028 settings-7 не target; live EvoBGP settings → `@/components/settings/*` +- [x] CHANGE-029 VPS QA order KEEP exception (contract) +- [x] CHANGE-030 Empty + IconStack во всех 6 +- [x] CHANGE-031 Sheet create/edit + AlertDialog confirm (Vite ConfirmDialog; MM `@shadcn/alert-dialog`; EvoBGP FormDrawer=Sheet) +- [x] CHANGE-032 MM NavUser segmented +- [x] CHANGE-033 MM SidebarRail removed +- [x] CHANGE-034 MM sidebar 240px + +RULE-CHANGE-001…006 — DONE (rules files). RULE-CHANGE-007…010 в §2 — опечатка покрытия (в §16 только 001–006). + +--- + +## 18. Implementation Roadmap + +### Phase 0 — Discovery + +- [x] Tasks: этот файл. +- [x] DoD: Coverage 100%, Consistency Gate PASS. +- [x] Аудит зафиксирован 2026-08-17. + +### Phase 1 — Chrome identity (все 6) + +- [x] CHANGE-001, 002, 003, 032, 033, 034. +- [x] DoD: Chrome Identity Spec 1:1 (sidebar 240px, Switcher, AppsMenu→monitor, NavUser segmented). + +### Phase 2 — Shared rules + IconTile + Frame lock + +- [x] CHANGE-004, 026, 027, 028, 024, 025. +- [x] DoD: `icon-tile` installed; rules updated; MM SoT Frame. + +### Phase 3 — Kit DNA + +- [x] CHANGE-005, 006, 007, 008, 021, 022. +- [x] DoD: KpiStatGrid/QuickActionGrid/AgentPlatformIcon на IconTile; Auth `/apps` kit. + +### Phase 4 — Page-level Card → Frame + +- [x] CHANGE-009–018. +- [x] DoD: нет Card ops-shell на затронутых страницах; CRUD preserved. MM inner panels → `OpsPanel`. + +### Phase 5 — States + sheets + +- [x] CHANGE-016, 019, 020, 030, 031. +- [x] DoD: empty/loading/error Frame-friendly; sheets + AlertDialog. + +### Phase 6 — Responsive + a11y verification + +- [x] Keyboard skip-link, focus ring tokens, mobile sidebar Sheet RU labels, `aria-expanded`, live regions QueryState. +- [x] DoD: Acceptance checklist §19. + +--- + +## 19. Acceptance Checklist + +### Visual consistency + +- [x] Typography unified (theme tokens, не custom fonts per app). +- [x] Buttons unified (shadcn Button hierarchy). +- [x] Inputs unified (Field). +- [x] Forms unified (Sheet + Field). +- [x] Cards unified → **Frame** на ops (Card не ops-shell). MM: `DataPageCard` + `OpsPanel`. +- [x] Tables unified → DataGrid. +- [x] Navigation unified → Chrome Identity Spec. +- [x] Spacing unified → `gap-4 md:gap-6`, main padding. +- [x] States unified (empty IconStack, QueryState live regions). +- [x] Responsive behavior consistent (Sidebar/Sheet collapse + skip-link). + +### Functional regression + +- [x] Add/Create preserved. +- [x] Delete preserved. +- [x] Edit preserved. +- [x] Save/Cancel preserved. +- [x] Search preserved (⌘K; MM command palette). +- [x] Filter preserved. +- [x] Sort preserved. +- [x] Pagination preserved. +- [x] Navigation preserved. +- [x] Permissions preserved (Auth apps allow-list). +- [x] Form behavior preserved. +- [x] VPS topology canvas preserved (domain, не chrome). +- [x] MM terminal / network-map preserved. + +### Accessibility + +- [x] Keyboard navigation (skip-link «К содержимому», sidebar trigger). +- [x] Focus states (token `ring-ring`). +- [x] Semantic HTML (один `
` = SidebarInset `#main-content`). +- [x] Accessible labels (`aria-hidden` on decorative IconTile per docs). +- [x] Accessible names (`aria-label` AppsMenu / monitor / theme / sidebar). +- [x] Error states (`role="alert"` / ReUI Alert). +- [x] Disabled states (существующие Button/Field disabled). +- [x] Contrast (semantic tokens). + +### REUI PRO + +- [x] Current official documentation reviewed ([docs](https://reui.io/docs), [llms.txt](https://reui.io/llms.txt)). +- [x] MCP/Skills reviewed (`user-reui`, skill `668fb463eb`). +- [x] Every proposed component verified via `get_block` / `get_component` / `get_example`. +- [x] Every UI-related CHANGE has a REUI PRO Ref ID. +- [x] Every Ref ID has an official URL. +- [x] URLs point to specific relevant documentation. +- [x] Every 1:1 mapping verified (only where structure matches). +- [x] Partial mappings explicitly marked. +- [x] Missing / forbidden components marked (settings-7, empty-state-11). +- [x] No fabricated components / variants / URLs / references. + +--- + +## 20. Risks / Unknowns + +| Risk | Level | Notes | +|---|---|---| +| Data Grid TanStack v9 docs vs installed `^8.21.x` | High | Не апгрейдить kit в том же PR, что chrome. `` полный changelog v8→v9. | +| MM DataPageCard blast radius (много page.tsx) | High | Менять обёртку, не бизнес-логику. | +| MM Next vs Vite chrome port | High | Эквивалент UX, не copy-paste Vite files blindly. | +| MM hardcoded footer user | Medium | Нужен реальный user source; `` есть ли auth user в MM UI. | +| Shared kit npm package | Out of scope | Не сливать `@cfdm/ui` / `@evobgp/ui` / … | +| Auth login auth-18 vs rules auth-13 | Low | KEEP auth-18; rules cite auth-13 — documented exception. | +| VPS dashboard QA order | Low | KEEP exception unless product says otherwise. | +| CFDM Card-free claim | Low | grep 0 в `apps/web`; `packages/ui` Card primitive остаётся для shadcn internals. | +| Pixel-perfect main padding MM | Low | Phase 1+6: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5`; skip-link. | +| EvoBGP RHF unused | `` | WEB-05 vs src; не ломать формы ради RHF. | +| FW stepper unused | Low | Не удалять в этом плане без usage audit. | + +**Не трогать (technical constraints):** API contracts, SSO/OIDC, permissions, TanStack vs Next routing, Go vs Fastify, package names, CRUD behaviour. + +--- + +## 21. Final Consistency Gate + +| Requirement | Status | Evidence | +|---|---|---| +| All workspace projects discovered | PASS | 6 git roots in workspace | +| All discovered UI projects audited | PASS | §§7–12 | +| All audited projects represented in final plan | PASS | coverage table + CHANGE matrix | +| Coverage = 100% | PASS | 6/6/6 | +| Current REUI PRO documentation reviewed | PASS | docs, llms.txt, MCP get_block/get_component 2026-08-17 | +| MCP/Skills reviewed | PASS | user-reui ready; skill 668fb463eb | +| Project rules reviewed | PASS | §5–6 | +| UX/UI rules evaluated | PASS | KEEP/MODIFY/REPLACE/NEW | +| Every proposed REUI component verified | PASS | registry §15 | +| Every UI CHANGE has REUI PRO Ref ID | PASS | matrix; CHANGE-023 chrome via REUI-010 | +| Every Ref ID has official documentation URL | PASS | markdown links | +| URLs point to specific REUI PRO references | PASS | preview/docs pages, not homepage | +| Every 1:1 mapping verified | PASS | only Frame/IconTile/Filters/Badge/etc. where MCP matches | +| Partial mappings marked | PASS | stats-12, shells, sheets, data-grid v8 | +| Missing REUI components marked | PASS | settings-7/empty-state-11 not target | +| No fabricated workspace data | PASS | real paths; `` CFDM AGENTS.md; `` noted | +| No fabricated REUI PRO data | PASS | MCP-confirmed URLs only | +| Existing functionality preserved | PASS | each CHANGE Preserve + § constraints | +| Accessibility reviewed | PASS | IconTile a11y from official API; checklist | +| Responsive behavior reviewed | PASS | existing sidebar/sheet; Phase 6 verify | +| Target UX/UI rules defined | PASS | TARGET-001…010 + Chrome spec | +| Implementation plan complete | PASS | Phases 0–6 executed 2026-08-17 | +| Production/source code implemented | PASS | все 6 git roots; CHANGE-001…034 | + +--- + +## Definition of Done + +### Аудит (Phase 0) + +- [x] Все 6 проектов обнаружены и проанализированы. +- [x] Coverage 100%. +- [x] Chrome Identity Spec зафиксирован как обязательный 1:1. +- [x] REUI registry + CHANGE matrix с прямыми URL. +- [x] Final Consistency Gate пройден. +- [x] Создан `c:\Users\shats\Dev\vps-tracker\UX-UI-PLAN.md`. + +### Implementation (Phases 1–6) — 2026-08-17 + +- [x] CHANGE-001…034 выполнены (execution log §17). +- [x] Chrome Identity Spec во всех 6. +- [x] IconTile + hybrid KPI/QA kit. +- [x] Ops Card → Frame (VPS/EvoBGP/MM `OpsPanel`). +- [x] Empty IconStack; Sheet + AlertDialog. +- [x] a11y: skip-link, один `main`, live regions. +- [x] Acceptance checklist §19 отмечен. + +**План закрыт.** Коммиты — по отдельному запросу. diff --git a/apps/web/src/components/domain/charts.tsx b/apps/web/src/components/domain/charts.tsx index 9f018ed..58df2dd 100644 --- a/apps/web/src/components/domain/charts.tsx +++ b/apps/web/src/components/domain/charts.tsx @@ -17,13 +17,13 @@ import { type ChartConfig, } from '@cfdm/ui/components/chart' import { - Card, - CardAction, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@cfdm/ui/components/card' + Frame, + FrameDescription, + FrameHeader, + FramePanel, + FrameTitle, +} from '@/components/reui/frame' +import { cn } from '@cfdm/ui/lib/utils' import type { ReactNode } from 'react' import { useMemo, useState } from 'react' @@ -60,6 +60,36 @@ function ChartEmpty({ message }: { message: string }) { ) } +/** Chart-15 Frame shell. Preview: https://reui.io/preview/base/chart-15 */ +function ChartShell({ + title, + description, + action, + className, + children, +}: { + title: string + description?: ReactNode + action?: ReactNode + className?: string + children: ReactNode +}) { + return ( + + +
+ {title} + {description ? {description} : null} +
+ {action} +
+ + {children} + + + ) +} + const EXPENSE_CONFIG: ChartConfig = { expense: { label: 'Расход', color: 'var(--chart-1)' }, } @@ -104,12 +134,11 @@ export function MonthlyExpenseChart({ .slice(0, 10) return ( - - - {title} - {description ?? `Топ-10 по monthly rate, в ${baseCurrency}`} - - + {data.length === 0 ? ( ) : ( @@ -123,8 +152,7 @@ export function MonthlyExpenseChart({ )} - - + ) } @@ -168,12 +196,11 @@ export function PaymentsPieChart({ }, [payments, baseCurrency, ratesData]) return ( - - - Платежи по типам - Структура в {baseCurrency} - - + {data.length === 0 ? ( ) : ( @@ -196,8 +223,7 @@ export function PaymentsPieChart({ )} - - + ) } @@ -244,11 +270,11 @@ function DashboardMonthlyBarChart({ const hasData = data.some((row) => row.amount > 0) return ( - - - {title} - {description} - + ({ value: String(y), label: String(y) }))} /> - - - + } + > {!hasData ? ( ) : ( @@ -289,8 +314,7 @@ function DashboardMonthlyBarChart({ )} - - + ) } @@ -366,11 +390,11 @@ export function DashboardExpensesChart({ : 'Списания и платежи за VPS по месяцам' return ( - - - Расходы - {description} - + ({ value: String(y), label: String(y) }))} /> - - - + } + > {!hasData ? ( ) : ( @@ -411,8 +434,7 @@ export function DashboardExpensesChart({ )} - - + ) } @@ -443,12 +465,11 @@ export function MonthlyTrendChart({ const trendConfig: ChartConfig = { amount: { label: 'Платежи', color: 'var(--chart-3)' } } return ( - - - Динамика платежей - Последние 12 месяцев, {baseCurrency} - - + {data.length === 0 ? ( ) : ( @@ -462,8 +483,7 @@ export function MonthlyTrendChart({ )} - - + ) } @@ -513,12 +533,11 @@ export function ProjectExpenseChart({ }, [data]) return ( - - - Расходы по проектам (мес) - Активные VPS, в {baseCurrency} - - + {data.length === 0 ? ( ) : ( @@ -544,7 +563,6 @@ export function ProjectExpenseChart({ )} - - + ) } diff --git a/apps/web/src/components/empty-state.tsx b/apps/web/src/components/empty-state.tsx index 17237db..5adf2ca 100644 --- a/apps/web/src/components/empty-state.tsx +++ b/apps/web/src/components/empty-state.tsx @@ -1,7 +1,7 @@ /** - * ReUI Empty + IconStack — adapted from empty-state-12. - * Preview: https://reui.io/preview/base/empty-state-12 - * Docs: https://reui.io/blocks + * ReUI Empty + IconStack — Frame-friendly empty-state-14 / empty-state-12. + * Preview: https://reui.io/preview/base/empty-state-14 · https://reui.io/preview/base/empty-state-12 + * Docs: https://reui.io/docs/components/base/icon-stack */ import { InboxIcon, type LucideIcon } from 'lucide-react' import { IconStack } from '@/components/reui/icon-stack' diff --git a/apps/web/src/components/integrations/app-switcher-editor.tsx b/apps/web/src/components/integrations/app-switcher-editor.tsx index fea12bb..ee99b48 100644 --- a/apps/web/src/components/integrations/app-switcher-editor.tsx +++ b/apps/web/src/components/integrations/app-switcher-editor.tsx @@ -5,12 +5,12 @@ import { z } from 'zod' import { appSwitcherConfigSchema } from '@cfdm/shared/contracts/app-switcher' import { Button } from '@cfdm/ui/components/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@cfdm/ui/components/card' import { FieldGroup } from '@cfdm/ui/components/field' import { Input } from '@cfdm/ui/components/input' import { FormField } from '@/components/form-field' import { SelectField } from '@/components/select-field' import { LoadingButton } from '@/components/loading-button' +import { SettingsCard } from '@/components/reui-kit/settings-card' import { APP_SWITCHER_ICONS, type AppSwitcherIconName } from '@/lib/app-switcher-config' const ICON_OPTIONS = (Object.keys(APP_SWITCHER_ICONS) as AppSwitcherIconName[]).map((icon) => ({ @@ -39,14 +39,12 @@ export function AppSwitcherEditor({ defaultValues, onSave, isSaving }: AppSwitch const { fields, append, remove } = useFieldArray({ control: form.control, name: 'apps' }) return ( - - - Связанные приложения - URL для переключателя в sidebar и deep links - - +
void form.handleSubmit(onSave)(e)} > @@ -110,7 +108,6 @@ export function AppSwitcherEditor({ defaultValues, onSave, isSaving }: AppSwitch Сохранить приложения -
-
+ ) } diff --git a/apps/web/src/components/layout/app-shell.tsx b/apps/web/src/components/layout/app-shell.tsx index 24a0923..9405cf2 100644 --- a/apps/web/src/components/layout/app-shell.tsx +++ b/apps/web/src/components/layout/app-shell.tsx @@ -59,6 +59,7 @@ import { permissionForPath, } from '@/lib/auth' import { SpaceProvider, useSpaceId } from '@/lib/space' +import { SKIP_TO_CONTENT_CLASS } from '@/lib/ui-surface' interface NavItem { to: string @@ -175,6 +176,9 @@ export function AppShell({ children }: { children: ReactNode }) { } as CSSProperties } > + + К содержимому + @@ -220,7 +224,7 @@ export function AppShell({ children }: { children: ReactNode }) { - +
-
+
{children} -
+
diff --git a/apps/web/src/components/query-state.tsx b/apps/web/src/components/query-state.tsx index 22fe204..487524c 100644 --- a/apps/web/src/components/query-state.tsx +++ b/apps/web/src/components/query-state.tsx @@ -32,23 +32,29 @@ export function QueryState({ children, }: QueryStateProps) { if (isLoading) { - return <>{skeleton ?? } + return ( +
+ {skeleton ?? } +
+ ) } if (isError) { return ( - } - title="Ошибка загрузки" - description={error instanceof Error ? error.message : 'Не удалось загрузить данные'} - action={ - onRetry ? ( - - ) : null - } - /> +
+ } + title="Ошибка загрузки" + description={error instanceof Error ? error.message : 'Не удалось загрузить данные'} + action={ + onRetry ? ( + + ) : null + } + /> +
) } if (empty || data == null) { diff --git a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx index 05caecc..e75db71 100644 --- a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx +++ b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx @@ -5,7 +5,7 @@ import { Frame, FramePanel } from '@/components/reui/frame' import { Badge } from '@/components/reui/badge' import { cn } from '@cfdm/ui/lib/utils' import { kpiCols } from './kpi-cols' -import { Item, ItemMedia } from '@cfdm/ui/components/item' +import { IconTile } from '@/components/reui/icon-tile' import { Skeleton } from '@cfdm/ui/components/skeleton' export type KpiStatVariant = 'default' | 'warning' | 'destructive' @@ -83,16 +83,13 @@ function KpiStatCardBody({ item }: { item: KpiStatItem }) { return (
{item.icon ? ( - + {item.icon} + ) : null}
diff --git a/apps/web/src/components/reui-kit/quick-action-grid.tsx b/apps/web/src/components/reui-kit/quick-action-grid.tsx index 7f1ea7c..12071bf 100644 --- a/apps/web/src/components/reui-kit/quick-action-grid.tsx +++ b/apps/web/src/components/reui-kit/quick-action-grid.tsx @@ -8,7 +8,7 @@ import { FrameTitle, } from '@/components/reui/frame' import { Badge } from '@/components/reui/badge' -import { Item, ItemMedia } from '@cfdm/ui/components/item' +import { IconTile } from '@/components/reui/icon-tile' import { cn } from '@cfdm/ui/lib/utils' import { kpiCols } from './kpi-cols' @@ -35,16 +35,13 @@ function QuickActionBody({ action }: { action: QuickActionItem }) { return (
{action.icon ? ( - + {action.icon} + ) : null}
diff --git a/apps/web/src/components/reui/icon-tile.tsx b/apps/web/src/components/reui/icon-tile.tsx new file mode 100644 index 0000000..deb0192 --- /dev/null +++ b/apps/web/src/components/reui/icon-tile.tsx @@ -0,0 +1,125 @@ +import { mergeProps } from "@base-ui/react/merge-props" +import { useRender } from "@base-ui/react/use-render" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@cfdm/ui/lib/utils" + +/** + * CSS variable architecture: + * + * The root owns four variables so every part of the tile stays in proportion + * and stays overridable from a single `className`: + * + * --icon-tile-size tile width/height + * --icon-tile-icon-size glyph size applied to child svgs + * --icon-tile-radius corner radius (also drives the nested inner card) + * --icon-tile-inset gap between the outer ring and the inner card + * + * The `frame` and `soft` variants paint their inner card with an `::after` + * pseudo element instead of a wrapper node. `isolate` makes the root a stacking + * context, so the negative z-index pseudo paints above the root background but + * below the in-flow icon - no extra DOM, and `render` composition keeps working. + * + * Tone: `soft` and `solid` derive every fill and border from `currentColor`, so + * a single text color class (e.g. `text-success`) retints the whole tile. They + * default to `text-primary`; override it to recolor without touching internals. + */ +const iconTileVariants = cva( + [ + "relative inline-flex shrink-0 items-center justify-center align-middle", + "size-(--icon-tile-size) rounded-(--icon-tile-radius)", + "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-(--icon-tile-icon-size)", + ], + { + variants: { + variant: { + /** Plain bordered surface. The quiet default for list rows and toolbars. */ + outline: "border border-border bg-background dark:bg-input/32", + /** Raised muted fill with a background-colored ring. Reads as a physical chip. */ + elevated: + "border-2 border-background bg-muted text-accent-foreground shadow-[0_1px_3px_0_rgb(0_0_0/0.14)] dark:border", + /** + * Tinted double container: an opacity-filled outer ring with no border + * around a bordered inner card, all derived from `currentColor`. The + * quiet, colorful sibling of `frame`. Retint with a text color class. + */ + soft: [ + "isolate p-(--icon-tile-inset) text-primary bg-current/10", + "after:absolute after:-z-10 after:inset-(--icon-tile-inset)", + "after:rounded-[calc(var(--icon-tile-radius)-var(--icon-tile-inset))]", + "after:border after:border-current/20 after:bg-current/5", + ], + /** Filled tone with a contrasting glyph. Retint with `bg-*` + a text color. */ + solid: "bg-primary text-primary-foreground", + /** Double container - a muted ring around an inset card, matching Frame. */ + frame: [ + "isolate border border-border bg-muted/50 p-(--icon-tile-inset)", + "after:absolute after:-z-10 after:inset-(--icon-tile-inset)", + "after:rounded-[calc(var(--icon-tile-radius)-var(--icon-tile-inset))]", + "after:border after:border-border after:bg-card after:shadow-xs", + ], + }, + size: { + xs: "[--icon-tile-size:--spacing(6)] [--icon-tile-icon-size:--spacing(3.5)] [--icon-tile-inset:--spacing(0.5)]", + sm: "[--icon-tile-size:--spacing(8)] [--icon-tile-icon-size:--spacing(4)] [--icon-tile-inset:--spacing(0.5)]", + default: + "[--icon-tile-size:--spacing(10)] [--icon-tile-icon-size:--spacing(4.5)] [--icon-tile-inset:--spacing(0.75)]", + lg: "[--icon-tile-size:--spacing(12)] [--icon-tile-icon-size:--spacing(5.5)] [--icon-tile-inset:--spacing(0.75)]", + xl: "[--icon-tile-size:--spacing(14)] [--icon-tile-icon-size:--spacing(7)] [--icon-tile-inset:--spacing(1)]", + }, + /** + * `default`: active style radius. `full`: circular. + * The plain value is the fallback outside a style scope; the `style-*` + * tokens win by specificity inside one, and survive the registry + * transform as the single resolved value per generated style. + * + * Each style token is clamped to a fraction of the tile. A flat radius + * is a circle once it reaches half the box, so the soft styles used to + * render `xs` (24px) and `sm` (32px) as plain circles and swallow the + * `full` variant's meaning. Clamping keeps one corner ratio at every + * size instead, so a tile still reads as the same shape when it scales. + * Luma and Rhea take the gentler quarter ratio; Maia stays at a third. + */ + radius: { + default: + "[--icon-tile-radius:min(var(--radius-md),calc(var(--icon-tile-size)/3))] [--icon-tile-radius:min(var(--radius-md),calc(var(--icon-tile-size)/3))]", + full: "[--icon-tile-radius:calc(infinity*1px)]", + }, + }, + defaultVariants: { + variant: "outline", + size: "default", + radius: "default", + }, + } +) + +interface IconTileProps extends useRender.ComponentProps<"span"> { + variant?: VariantProps["variant"] + size?: VariantProps["size"] + radius?: VariantProps["radius"] +} + +function IconTile({ + className, + variant = "outline", + size = "default", + radius = "default", + render, + ...props +}: IconTileProps) { + const defaultProps = { + "data-slot": "icon-tile", + "data-variant": variant, + "data-size": size, + className: cn(iconTileVariants({ variant, size, radius, className })), + } + + return useRender({ + defaultTagName: "span", + render, + props: mergeProps<"span">(defaultProps, props), + }) +} + +export { IconTile, iconTileVariants, type IconTileProps } \ No newline at end of file diff --git a/apps/web/src/lib/ui-surface.ts b/apps/web/src/lib/ui-surface.ts index 89566d4..29b8e92 100644 --- a/apps/web/src/lib/ui-surface.ts +++ b/apps/web/src/lib/ui-surface.ts @@ -3,7 +3,7 @@ * * Ops / list / dashboard / detail / settings: **Frame** only. * KPI: horizontal compact hybrid via `reui-kit/KpiStatGrid` - * (icon left + label/Badge + value ± variant). + * (`IconTile` elevated `size-10.5` + label/Badge + value ± variant). * Lists: **data-grid-filtering-2** via `reui-kit/ResourcePage`. * * @see https://reui.io/preview/base/stats-12 @@ -12,5 +12,9 @@ */ export const UI_SURFACE = 'frame' as const +/** Skip link — keyboard: first Tab. */ +export const SKIP_TO_CONTENT_CLASS = + 'bg-background text-foreground ring-ring sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:rounded-md focus:px-3 focus:py-2 focus:text-sm focus:font-medium focus:shadow-sm focus:ring-2' + /** Grid for horizontal compact hybrid KPI rows (3–6 tiles). */ export const kpiStatGridClassName = '@container w-full' diff --git a/apps/web/src/routes/_auth/renewals.tsx b/apps/web/src/routes/_auth/renewals.tsx index 6374a8f..23728f5 100644 --- a/apps/web/src/routes/_auth/renewals.tsx +++ b/apps/web/src/routes/_auth/renewals.tsx @@ -10,7 +10,12 @@ import { QueryState } from '@/components/query-state' import { KpiStatGrid } from '@/components/reui-kit' import { KpiStatGridSkeleton } from '@/components/skeletons' import { Skeleton } from '@cfdm/ui/components/skeleton' -import { Card, CardContent, CardHeader, CardTitle } from '@cfdm/ui/components/card' +import { + Frame, + FrameHeader, + FramePanel, + FrameTitle, +} from '@/components/reui/frame' import { Badge } from '@cfdm/ui/components/badge' import { Button } from '@cfdm/ui/components/button' import { SelectField } from '@/components/select-field' @@ -168,14 +173,14 @@ function RenewalsPage() { ]} /> {grouped.map(([weekLabel, weekItems]) => ( - - - + + + {weekLabel} - - - + + + {weekItems.map((item) => (
))} -
-
+ + ))}
)} diff --git a/apps/web/src/routes/_auth/resources.tsx b/apps/web/src/routes/_auth/resources.tsx index f9741a9..e6d4026 100644 --- a/apps/web/src/routes/_auth/resources.tsx +++ b/apps/web/src/routes/_auth/resources.tsx @@ -12,7 +12,13 @@ import { ChartTooltipContent, type ChartConfig, } from '@cfdm/ui/components/chart' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@cfdm/ui/components/card' +import { + Frame, + FrameDescription, + FrameHeader, + FramePanel, + FrameTitle, +} from '@/components/reui/frame' import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { Tooltip as RechartsTooltip } from 'recharts' @@ -103,12 +109,12 @@ function ResourcesPage() { ]} /> - - - Ресурсы по хостерам - Только активные VPS - - + + + Ресурсы по хостерам + Только активные VPS + + {chartData.length === 0 ? ( ) : ( @@ -124,8 +130,8 @@ function ResourcesPage() { )} - - + + ) }} diff --git a/apps/web/src/routes/_auth/spaces.tsx b/apps/web/src/routes/_auth/spaces.tsx index 16e476c..1e0a4d4 100644 --- a/apps/web/src/routes/_auth/spaces.tsx +++ b/apps/web/src/routes/_auth/spaces.tsx @@ -12,21 +12,14 @@ import { SelectTrigger, SelectValue, } from '@cfdm/ui/components/select' -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@cfdm/ui/components/table' import { AutoCompleteInput } from '@/components/auto-complete-input' import { ConfirmDialog } from '@/components/confirm-dialog' import { PageHeader } from '@/components/page-header' import { PageShell } from '@/components/page-shell' -import { QueryState } from '@/components/query-state' import { SettingsCard } from '@/components/reui-kit/settings-card' +import { ResourcePage, columnDefFromDataGrid } from '@/components/reui-kit' +import type { DataGridColumn } from '@/components/data-grid-types' import { api } from '@/lib/api-client' import { useSpaceId } from '@/lib/space' import { @@ -181,6 +174,140 @@ function SpacesPage() { onError: (e: Error) => toast.error(e.message), }) + const memberColumns: DataGridColumn[] = useMemo( + () => [ + { + key: 'user', + header: 'Пользователь', + cell: (m) => ( +
+ {userLabelById.get(m.userId) ?? m.userId} + {!userLabelById.has(m.userId) ? ( + {m.userId} + ) : null} +
+ ), + }, + { key: 'role', header: 'Роль', cell: (m) => m.role }, + { + key: 'actions', + header: '', + className: 'w-36', + cell: (m) => + canAdmin && m.role !== 'owner' ? ( + removeMutation.mutate(m.userId)} + trigger={ + + } + /> + ) : null, + }, + ], + [canAdmin, removeMutation, userLabelById], + ) + + const trashColumns: DataGridColumn<(typeof trash)[number]>[] = useMemo( + () => [ + { key: 'name', header: 'Название', cell: (s) => s.name }, + { + key: 'deletedAt', + header: 'Удалено', + cell: (s) => ( + + {s.deletedAt ? new Date(s.deletedAt).toLocaleString('ru-RU') : '—'} + + ), + }, + { + key: 'actions', + header: '', + className: 'w-64', + cell: (s) => ( +
+ + purgeMutation.mutate(s.id)} + trigger={ + + } + /> +
+ ), + }, + ], + [purgeMutation, restoreMutation], + ) + + const addMemberForm = canAdmin ? ( +
+
+ + { + const match = userOptions.find( + (o) => o.value === v || o.label === v, + ) + if (match) { + setSelectedUserId(match.value) + setUserQuery(match.label) + } else { + setSelectedUserId('') + setUserQuery(v) + } + }} + options={userOptions} + placeholder="Имя или email…" + allowFreeText={false} + emptyText="Пользователи не найдены" + /> +
+
+ + +
+ +
+ ) : null + return ( ) : null} - `${m.spaceId}-${m.userId}`} + pagination={false} + pinLastColumn isLoading={membersQuery.isLoading} isError={membersQuery.isError} - error={membersQuery.error} + error={membersQuery.error as Error | null} onRetry={() => void membersQuery.refetch()} - empty={Boolean(membersQuery.data && membersQuery.data.length === 0)} + toolbarExtra={addMemberForm} emptyTitle="Нет участников" emptyDescription="Добавьте пользователя по имени или email" - > - {(members) => ( - - {canAdmin ? ( -
-
- - { - const match = userOptions.find( - (o) => o.value === v || o.label === v, - ) - if (match) { - setSelectedUserId(match.value) - setUserQuery(match.label) - } else { - setSelectedUserId('') - setUserQuery(v) - } - }} - options={userOptions} - placeholder="Имя или email…" - allowFreeText={false} - emptyText="Пользователи не найдены" - /> -
-
- - -
- -
- ) : null} + /> -
- - - Пользователь - Роль - - - - - {members.map((m) => ( - - -
- - {userLabelById.get(m.userId) ?? m.userId} - - {!userLabelById.has(m.userId) ? ( - - {m.userId} - - ) : null} -
-
- {m.role} - - {canAdmin && m.role !== 'owner' ? ( - removeMutation.mutate(m.userId)} - trigger={ - - } - /> - ) : null} - -
- ))} -
-
- - )} - - - - {trash.length === 0 ? ( -

Корзина пуста

- ) : ( - - - - Название - Удалено - - - - - {trash.map((s) => ( - - {s.name} - - {s.deletedAt - ? new Date(s.deletedAt).toLocaleString('ru-RU') - : '—'} - - -
- - purgeMutation.mutate(s.id)} - trigger={ - - } - /> -
-
-
- ))} -
-
- )} -
+ columns={columnDefFromDataGrid(trashColumns)} + data={trash} + getRowId={(s) => s.id} + pagination={false} + pinLastColumn + emptyTitle="Корзина пуста" + emptyDescription="Удалённых пространств нет" + /> ) diff --git a/apps/web/src/routes/_auth/vps.$vpsId.tsx b/apps/web/src/routes/_auth/vps.$vpsId.tsx index a11ff95..bfbb6e4 100644 --- a/apps/web/src/routes/_auth/vps.$vpsId.tsx +++ b/apps/web/src/routes/_auth/vps.$vpsId.tsx @@ -18,8 +18,13 @@ import { StatusBadge } from '@/components/status-badge' import { Skeleton } from '@cfdm/ui/components/skeleton' import { Button } from '@cfdm/ui/components/button' import { Badge } from '@cfdm/ui/components/badge' -import { Card, CardContent, CardHeader, CardTitle } from '@cfdm/ui/components/card' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs' +import { + Frame, + FrameHeader, + FramePanel, + FrameTitle, +} from '@/components/reui/frame' import { ResourcePage, columnDefFromDataGrid } from '@/components/reui-kit' import type { DataGridColumn } from '@/components/data-grid-types' import { getPaidUntilDate } from '@/lib/paid-until' @@ -58,6 +63,30 @@ function InfoRow({ label, value }: { label: string; value: React.ReactNode }) { ) } +function DetailFrame({ + title, + icon, + children, + className, +}: { + title: string + icon?: React.ReactNode + children: React.ReactNode + className?: string +}) { + return ( + + + + {icon} + {title} + + + {children} + + ) +} + function VpsDetailPage() { const { vpsId } = Route.useParams() const { data: snapshot, isLoading, isError, error, refetch } = useQuery(snapshotQueryOptions()) @@ -175,54 +204,29 @@ function VpsDetailPage() { {row.environment ? {row.environment} : null}
- - - - - Сеть - - - + }> - - - - - - - Ресурсы - - - + + }> - - + {vpsDomains.length > 0 ? ( - - - Домены (CFDM) - - + - - + ) : null}
{customFieldRows.length > 0 ? ( - - - Дополнительные поля - - + {customFieldRows.map(({ def, value }) => ( ))} - - + ) : null} - - - - - Тариф - - - + }> - - + - - - - - Заметки - - - + }>

{row.notes?.trim() || 'Нет заметок'}

-
-
+
- - - - - Защита от перезаписи при синке - - - + }> {overrides.length === 0 ? (

Все поля обновляются из BILLmanager

) : ( @@ -304,8 +284,7 @@ function VpsDetailPage() { })} )} -
-
+
)} diff --git a/docs/ui-design-contract.md b/docs/ui-design-contract.md index e5151f1..87ecd18 100644 --- a/docs/ui-design-contract.md +++ b/docs/ui-design-contract.md @@ -28,7 +28,7 @@ Settings: секции через Frame + `gap` (без hairline `Separator` п | Lists | `data-grid-filtering-2` | https://reui.io/preview/base/data-grid-filtering-2 | | Settings | `settings-16` + SettingRow | https://reui.io/preview/base/settings-16 | | Auth | `auth-13` | https://reui.io/preview/base/auth-13 | -| Empty | `empty-state-12` | https://reui.io/preview/base/empty-state-12 | +| Empty | `empty-state-14` (prefer) / `empty-state-12` | https://reui.io/preview/base/empty-state-14 · https://reui.io/preview/base/empty-state-12 | | Forms | `form-7` → Sheet/Drawer | https://reui.io/preview/base/form-7 | ## Kit API (`reui-kit/`) @@ -76,9 +76,9 @@ Gating: DB `show_quick_actions` / `showQuickActions` / `ui_show_quick_actions` ( NavUser (footer): avatar + name/email; dropdown — Настройки / Тема (segmented) / Выйти. Preview: [app-shell-1](https://reui.io/preview/base/app-shell-1). -App Switcher: auth-portal `GET /api/v1/app-switcher`. Ids: `cfdm` · `vps` · `bgp` · `fw`. Admin: portal `/admin/apps`. +App Switcher: auth-portal `GET /api/v1/app-switcher`. Ids: `cfdm` · `vps` · `bgp` · `fw` · `dns`. Admin: portal `/admin/apps`. -QuickActionGrid / KPI icons: только semantic **text** (`text-info` / `text-primary` / …) на kit `bg-muted` — без solid fills. +QuickActionGrid / KPI icons: только semantic **text** (`text-info` / `text-primary` / …) на `IconTile` `elevated` — без solid fills. ## System monitor diff --git a/packages/ui/src/components/sheet.tsx b/packages/ui/src/components/sheet.tsx index 0595149..0fc0ea3 100644 --- a/packages/ui/src/components/sheet.tsx +++ b/packages/ui/src/components/sheet.tsx @@ -68,9 +68,8 @@ function SheetContent({ /> } > - - Close +