diff --git a/.agents/skills/reui/SKILL.md b/.agents/skills/reui/SKILL.md index 160e7e5..d76c1c2 100644 --- a/.agents/skills/reui/SKILL.md +++ b/.agents/skills/reui/SKILL.md @@ -5,7 +5,7 @@ user-invocable: false allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) --- -> **ReUI skill version `0e224b0281`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. +> **ReUI skill version `42d70dcc3d`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. # ReUI for Agents diff --git a/.agents/skills/reui/rules/cli.md b/.agents/skills/reui/rules/cli.md index e603164..7c40844 100644 --- a/.agents/skills/reui/rules/cli.md +++ b/.agents/skills/reui/rules/cli.md @@ -29,6 +29,8 @@ REUI_LICENSE_KEY=your-license-key } ``` +The shadcn CLI expands `${REUI_LICENSE_KEY}` from `.env.local` inside `components.json`, but an MCP client config never expands variables, so a ReUI MCP server config must carry the raw token instead (for example `reui_pat_your_token_here`). + The MCP `get_project_context` tool returns the right config. Full guide: https://reui.io/docs/registry ## Installing diff --git a/.agents/skills/reui/rules/components.md b/.agents/skills/reui/rules/components.md index 8767bbb..0c24578 100644 --- a/.agents/skills/reui/rules/components.md +++ b/.agents/skills/reui/rules/components.md @@ -1,6 +1,6 @@ # ReUI components -The 17 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `filters`, `frame`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. +The 19 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. **Rule one: never guess a component's API. Read it first.** Call **`get_component(name)`** for its inline `api` (props + usage, no web fetch), and **share the result's `docsUrl`** (the component's API documentation page) with the user whenever you work with that component's API, so they have the full reference (the `/llms.txt` index is a further fallback). Then call **`get_examples(name)`** to install a worked example and copy real composition. The contracts below are first-try orientation (required props, composition shape, the one gotcha); the inline `api` is the full reference. No single block fits? Compose: search the components you need, read each `get_component`, install a `get_examples` example per component, and adapt. @@ -32,6 +32,34 @@ Common mistakes: - **Incorrect:** a raw `` / hand-rolled pagination. **Correct:** use `data-grid`; read its API for sticky header, pagination, virtualization, row selection. - **Incorrect:** styling rows/cells with arbitrary classes. **Correct:** drive layout via `tableLayout` and the documented `ColumnMeta` (e.g. `cellClassName`, `headerTitle`). +## event-calendar + +**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** headless-first: `EventCalendarContent` renders the active view (month/week/day/days/agenda; a resource view activates when `resources` is passed) - there is no per-view JSX to compose. Events are `{ id, title, start, end (exclusive), allDay?, color?, recurrence?, resourceId? }`. Mutations flow through `onEventUpdate`/`canDropEvent` (return `false` to reject); the root needs an explicit height because it is a min-h-0 flex column. + +## gantt + +**Required:** `resources` (the left tree) plus bars via `events`/`defaultEvents` attached by `resourceId`. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** bars move along the time axis only (never across rows) and are all-day spans with exclusive `end`; `progress` is 0-100. Scales are `day | week | month | quarter | year`. Zoom control, infinite scroll, summary rollups, and row checkboxes are ON by default - turn off what you do not need. Same `onEventUpdate`/`canDropEvent` commit pipeline as `event-calendar`; the root needs an explicit height. + ## kanban **Required:** `value` (`Record`), `onValueChange`, `getItemValue` diff --git a/.claude/skills/reui/SKILL.md b/.claude/skills/reui/SKILL.md index 160e7e5..d76c1c2 100644 --- a/.claude/skills/reui/SKILL.md +++ b/.claude/skills/reui/SKILL.md @@ -5,7 +5,7 @@ user-invocable: false allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) --- -> **ReUI skill version `0e224b0281`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. +> **ReUI skill version `42d70dcc3d`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. # ReUI for Agents diff --git a/.claude/skills/reui/rules/cli.md b/.claude/skills/reui/rules/cli.md index e603164..7c40844 100644 --- a/.claude/skills/reui/rules/cli.md +++ b/.claude/skills/reui/rules/cli.md @@ -29,6 +29,8 @@ REUI_LICENSE_KEY=your-license-key } ``` +The shadcn CLI expands `${REUI_LICENSE_KEY}` from `.env.local` inside `components.json`, but an MCP client config never expands variables, so a ReUI MCP server config must carry the raw token instead (for example `reui_pat_your_token_here`). + The MCP `get_project_context` tool returns the right config. Full guide: https://reui.io/docs/registry ## Installing diff --git a/.claude/skills/reui/rules/components.md b/.claude/skills/reui/rules/components.md index 8767bbb..0c24578 100644 --- a/.claude/skills/reui/rules/components.md +++ b/.claude/skills/reui/rules/components.md @@ -1,6 +1,6 @@ # ReUI components -The 17 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `filters`, `frame`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. +The 19 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. **Rule one: never guess a component's API. Read it first.** Call **`get_component(name)`** for its inline `api` (props + usage, no web fetch), and **share the result's `docsUrl`** (the component's API documentation page) with the user whenever you work with that component's API, so they have the full reference (the `/llms.txt` index is a further fallback). Then call **`get_examples(name)`** to install a worked example and copy real composition. The contracts below are first-try orientation (required props, composition shape, the one gotcha); the inline `api` is the full reference. No single block fits? Compose: search the components you need, read each `get_component`, install a `get_examples` example per component, and adapt. @@ -32,6 +32,34 @@ Common mistakes: - **Incorrect:** a raw `
` / hand-rolled pagination. **Correct:** use `data-grid`; read its API for sticky header, pagination, virtualization, row selection. - **Incorrect:** styling rows/cells with arbitrary classes. **Correct:** drive layout via `tableLayout` and the documented `ColumnMeta` (e.g. `cellClassName`, `headerTitle`). +## event-calendar + +**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** headless-first: `EventCalendarContent` renders the active view (month/week/day/days/agenda; a resource view activates when `resources` is passed) - there is no per-view JSX to compose. Events are `{ id, title, start, end (exclusive), allDay?, color?, recurrence?, resourceId? }`. Mutations flow through `onEventUpdate`/`canDropEvent` (return `false` to reject); the root needs an explicit height because it is a min-h-0 flex column. + +## gantt + +**Required:** `resources` (the left tree) plus bars via `events`/`defaultEvents` attached by `resourceId`. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** bars move along the time axis only (never across rows) and are all-day spans with exclusive `end`; `progress` is 0-100. Scales are `day | week | month | quarter | year`. Zoom control, infinite scroll, summary rollups, and row checkboxes are ON by default - turn off what you do not need. Same `onEventUpdate`/`canDropEvent` commit pipeline as `event-calendar`; the root needs an explicit height. + ## kanban **Required:** `value` (`Record`), `onValueChange`, `getItemValue` diff --git a/.cursor/rules/reui-mcp.mdc b/.cursor/rules/reui-mcp.mdc new file mode 100644 index 0000000..5995edf --- /dev/null +++ b/.cursor/rules/reui-mcp.mdc @@ -0,0 +1,111 @@ +--- +description: ReUI PRO (@reui) — MCP user-reui, Frame surface, kit, license, матрица выбора +alwaysApply: true +--- + +# ReUI MCP — обязательно (PRO + free) + +Проект: **Base UI** (`style: base-nova`), surface lock **`frame`**. + +Связанные: [`shadcn-mcp.mdc`](shadcn-mcp.mdc), [`web-shadcn.mdc`](web-shadcn.mdc), [`web-shadcn.mdc`](web-shadcn.mdc), [`docs/ui-design-contract.md`](docs/ui-design-contract.md). + +| Документ | URL | +|----------|-----| +| **llms.txt** | https://reui.io/llms.txt | +| **Get Started** | https://reui.io/docs/get-started | +| **Styling** | https://reui.io/docs/styling | +| **MCP** | https://reui.io/docs/mcp | +| **Blocks** | https://reui.io/blocks | +| **Settings blocks** | https://reui.io/blocks/application/settings | +| **License** | https://reui.io/docs/license-setup | +| **Base UI components** | https://reui.io/docs/components/base/ | + +## Primary MCP + +1. **`user-reui`** — `search` / `compose_page` / `get_block` / `get_component` / `get_install_command` / `validate_usage` / `get_audit_checklist` +2. **`plugin-shadcn-shadcn`** — primitives `@shadcn`; для `@reui` — вторично + +**Обязательно** цитировать `previewUrl` + `docsUrl` для каждой UI-зоны. + +## Когда ReUI vs shadcn + +| Задача | Registry | Импорт | +|--------|----------|--------| +| Button, Sheet, Field, Sidebar, Tabs | `@shadcn` | `@evobgp/ui/components/*` | +| PRO pages/sections (settings, stats, auth, dashboard) | `@reui` blocks | adapt → `apps/web/src/components/` / `reui-kit/` | +| Data Grid | `@reui` | `@/components/reui/data-grid/*` → `ResourcePage` | +| Filters | `@reui` | `@/components/reui/filters` | +| Frame surface | `@reui` | `@/components/reui/frame` | +| KPI | block [stats-12](https://reui.io/preview/base/stats-12) | `reui-kit/KpiStatGrid` (EvoBGP hybrid SoT) | +| Quick Actions | Frame tiles sibling KPI | `reui-kit/QuickActionGrid` | +| Semantic badge / alert | `@reui` | `@/components/reui/badge`, `@/components/reui/alert` | +| Number / date / autocomplete / color / kanban | `@reui` | `@/components/reui/*` | + +**Сложные списки** — `ResourcePage` (Frame + data-grid + filters), не raw `
`, не DataGridCard. +**Quick Actions** — только `QuickActionGrid` (не Card / Button grid). + +## MCP workflow + +0. Codegraph / поиск существующих `reui-kit/*`, `@/components/reui/*` +1. `user-reui` `search` (`surface: "frame"`, `category` при известном) +2. Страница целиком → `compose_page`; секция → `get_block` +3. `get_component` для API primitives из `componentsUsed` +4. CLI из `apps/web`: `pnpm dlx shadcn@latest add @reui/ --yes` +5. Post-add: shadcn imports → `@evobgp/ui/components/*` +6. Adapt by reuse → kit / route +7. `validate_usage` + `get_audit_checklist` + +## Размещение + +| Слой | Путь | Импорт | +|------|------|--------| +| shadcn | `packages/ui/src/components/` | `@evobgp/ui/components/*` | +| ReUI CLI | `apps/web/src/components/reui/` | `@/components/reui/*` | +| PRO blocks (reference) | `apps/web/src/components/blocks/` | adapt into kit, не копипаст в routes | +| Kit | `apps/web/src/components/reui-kit/` | `@/components/reui-kit/*` | + +## Установленные ReUI (apps/web) + +**Components:** `frame`, `data-grid/*`, `filters`, `kanban`, `badge`, `alert`, `autocomplete`, `number-field`, `date-selector`, `color-picker`, `timeline`, `rating`, `phone-input`, `icon-stack` + +**Kit:** `ResourcePage`, `KpiStatGrid`, `QuickActionGrid`, `OpsDashboard`, `KanbanBoard`, `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`, … + +## License + +```env +# .env.local (gitignored) +REUI_LICENSE_KEY= +``` + +`apps/web/components.json` → `@reui` с `Authorization: Bearer ${REUI_LICENSE_KEY}`. + +## Эталоны preview + +| Зона | Preview | +|------|---------| +| KPI / Quick Actions | https://reui.io/preview/base/stats-12 · https://reui.io/preview/base/card-12 | +| List | https://reui.io/preview/base/data-grid-filtering-2 | +| Settings | https://reui.io/preview/base/settings-16 | +| Auth | https://reui.io/preview/base/auth-13 | +| Shell | https://reui.io/preview/base/app-shell-12 | +| Empty | https://reui.io/preview/base/empty-state-12 | + +## Запрещено + +- Копипаст с reui.io без CLI +- ReUI в `packages/ui` / импорт как `@evobgp/ui` +- Radix-варианты docs — только Base UI +- Raw `bg-emerald-*` вместо ReUI `variant` +- Hand-roll data-grid/filters/KPI/Quick Actions/settings rows при наличии `@reui` / kit +- Смешивать Card и Frame на ops-экране +- Ставить shadcn/ui blocks выше ReUI PRO + +## Чеклист + +- [ ] `user-reui` search/get_block + previewUrl +- [ ] `surface: frame` +- [ ] CLI add из `apps/web` при новом item +- [ ] Kit / `@/components/reui` / `@evobgp/ui` — правильный слой +- [ ] `pnpm --filter @evobgp/web run build` diff --git a/.cursor/rules/reui.mdc b/.cursor/rules/reui.mdc index 48b05d0..2c881c6 100644 --- a/.cursor/rules/reui.mdc +++ b/.cursor/rules/reui.mdc @@ -11,7 +11,7 @@ user-invocable: false allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) --- -> **ReUI skill version `0e224b0281`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. +> **ReUI skill version `42d70dcc3d`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. # ReUI for Agents diff --git a/.cursor/rules/web-shadcn.mdc b/.cursor/rules/web-shadcn.mdc index 0c6b07c..5453f98 100644 --- a/.cursor/rules/web-shadcn.mdc +++ b/.cursor/rules/web-shadcn.mdc @@ -9,12 +9,12 @@ alwaysApply: false # Web UI — React + shadcn/ui + ReUI **Источники правды:** +- **ReUI PRO first:** MCP `user-reui` ([`reui-mcp.mdc`](reui-mcp.mdc)) — pages / KPI / lists / settings / shell +- shadcn primitives: MCP `plugin-shadcn-shadcn` (secondary) +- ReUI Base UI: https://reui.io/docs/components/base/ · [llms.txt](https://reui.io/llms.txt) - shadcn/ui React: https://ui.shadcn.com/docs/components -- ReUI Base UI: https://reui.io/docs/components/base/ -- ReUI llms.txt: https://reui.io/llms.txt -- MCP `plugin-shadcn-shadcn` (registries: `@shadcn`, `@reui`) — перед любой UI-задачей -Общие правила Go/API: `.cursor/rules/engineering.mdc`. Стек ID: `.cursor/rules/context7-stack.mdc`. +Иерархия: **ReUI PRO > shadcn**. Общие: `.cursor/rules/engineering.mdc`, `context7-stack.mdc`. ## Слои UI @@ -23,18 +23,18 @@ alwaysApply: false | shadcn-примитивы | `packages/ui/src/components/` | output `shadcn add` (не трогать под кейс) | | ReUI enterprise | `apps/web/src/components/reui/` | output `shadcn add @reui/*` | | Shared обёртки | `apps/web/src/components/` | PageHeader, QueryState, ConfirmDialog, StatusBadge, LoadingButton | -| ReUI kit | `apps/web/src/components/reui-kit/` | ResourcePage, KpiStatGrid, OpsDashboard, SettingsShell | +| ReUI kit | `apps/web/src/components/reui-kit/` | ResourcePage, KpiStatGrid, QuickActionGrid, OpsDashboard, SettingsShell | | Роуты | `apps/web/src/routes/` | TanStack Router (file-based) | -**Design contract:** [`docs/ui-design-contract.md`](../../docs/ui-design-contract.md). Surface: **frame**. KPI: [stats-12](https://reui.io/preview/base/stats-12). Lists: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2). +**Design contract:** [`docs/ui-design-contract.md`](../../docs/ui-design-contract.md). Surface: **frame**. KPI hybrid SoT: [stats-12](https://reui.io/preview/base/stats-12). Lists: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2). Quick Actions: `QuickActionGrid`. Тема: `packages/ui/src/styles/globals.css`. CLI из `apps/web`: `pnpm dlx shadcn@latest add `. ## Правила -**WEB-01** | MUST | Перед новым UI — MCP `plugin-shadcn-shadcn`: `search_items_in_registries` → `get_item_examples_from_registries` → `get_add_command_for_items`. Только после — JSX. -*Rationale:* единый источник правды и API. -*Проверка:* review; нет самописных примитивов, если есть registry item. +**WEB-01** | MUST | Перед новым UI — сначала MCP **`user-reui`** (`search` → `get_block` / `compose_page`, `surface: "frame"`) + cite `previewUrl`/`docsUrl`. Primitives — MCP `plugin-shadcn-shadcn`. Только после — JSX. +*Rationale:* ReUI PRO выше shadcn; единый Frame surface. +*Проверка:* review; [`reui-mcp.mdc`](reui-mcp.mdc). **WEB-02** | MUST | Отсутствующий shadcn-примитив — `pnpm dlx shadcn@latest add ` (из `apps/web`). ReUI — `pnpm dlx shadcn@latest add @reui/`. *Проверка:* файлы в `packages/ui/src/components/` (для shadcn) или `apps/web/src/components/reui/` (для ReUI). @@ -73,7 +73,7 @@ alwaysApply: false **WEB-14** | SHOULD | Нетривиальный UI — прочитать страницу компонента shadcn/ReUI (props, a11y). -**WEB-15** | MUST | Сомнения — MCP `plugin-shadcn-shadcn` + shadcn CLI docs + `pnpm --filter @evobgp/web run typecheck`. +**WEB-15** | MUST | Сомнения — MCP `user-reui` + `plugin-shadcn-shadcn` + docs + `pnpm --filter @evobgp/web run typecheck`. **WEB-16** | MUST | Подтверждение удаления — `ConfirmDialog` из `@/components/confirm-dialog`, не `window.confirm`. diff --git a/.cursor/skills/reui/SKILL.md b/.cursor/skills/reui/SKILL.md index 160e7e5..d76c1c2 100644 --- a/.cursor/skills/reui/SKILL.md +++ b/.cursor/skills/reui/SKILL.md @@ -5,7 +5,7 @@ user-invocable: false allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) --- -> **ReUI skill version `0e224b0281`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. +> **ReUI skill version `42d70dcc3d`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. # ReUI for Agents diff --git a/.cursor/skills/reui/rules/cli.md b/.cursor/skills/reui/rules/cli.md index e603164..7c40844 100644 --- a/.cursor/skills/reui/rules/cli.md +++ b/.cursor/skills/reui/rules/cli.md @@ -29,6 +29,8 @@ REUI_LICENSE_KEY=your-license-key } ``` +The shadcn CLI expands `${REUI_LICENSE_KEY}` from `.env.local` inside `components.json`, but an MCP client config never expands variables, so a ReUI MCP server config must carry the raw token instead (for example `reui_pat_your_token_here`). + The MCP `get_project_context` tool returns the right config. Full guide: https://reui.io/docs/registry ## Installing diff --git a/.cursor/skills/reui/rules/components.md b/.cursor/skills/reui/rules/components.md index 8767bbb..0c24578 100644 --- a/.cursor/skills/reui/rules/components.md +++ b/.cursor/skills/reui/rules/components.md @@ -1,6 +1,6 @@ # ReUI components -The 17 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `filters`, `frame`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. +The 19 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. **Rule one: never guess a component's API. Read it first.** Call **`get_component(name)`** for its inline `api` (props + usage, no web fetch), and **share the result's `docsUrl`** (the component's API documentation page) with the user whenever you work with that component's API, so they have the full reference (the `/llms.txt` index is a further fallback). Then call **`get_examples(name)`** to install a worked example and copy real composition. The contracts below are first-try orientation (required props, composition shape, the one gotcha); the inline `api` is the full reference. No single block fits? Compose: search the components you need, read each `get_component`, install a `get_examples` example per component, and adapt. @@ -32,6 +32,34 @@ Common mistakes: - **Incorrect:** a raw `
` / hand-rolled pagination. **Correct:** use `data-grid`; read its API for sticky header, pagination, virtualization, row selection. - **Incorrect:** styling rows/cells with arbitrary classes. **Correct:** drive layout via `tableLayout` and the documented `ColumnMeta` (e.g. `cellClassName`, `headerTitle`). +## event-calendar + +**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** headless-first: `EventCalendarContent` renders the active view (month/week/day/days/agenda; a resource view activates when `resources` is passed) - there is no per-view JSX to compose. Events are `{ id, title, start, end (exclusive), allDay?, color?, recurrence?, resourceId? }`. Mutations flow through `onEventUpdate`/`canDropEvent` (return `false` to reject); the root needs an explicit height because it is a min-h-0 flex column. + +## gantt + +**Required:** `resources` (the left tree) plus bars via `events`/`defaultEvents` attached by `resourceId`. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** bars move along the time axis only (never across rows) and are all-day spans with exclusive `end`; `progress` is 0-100. Scales are `day | week | month | quarter | year`. Zoom control, infinite scroll, summary rollups, and row checkboxes are ON by default - turn off what you do not need. Same `onEventUpdate`/`canDropEvent` commit pipeline as `event-calendar`; the root needs an explicit height. + ## kanban **Required:** `value` (`Record`), `onValueChange`, `getItemValue` diff --git a/.opencode/skills/reui/SKILL.md b/.opencode/skills/reui/SKILL.md index 160e7e5..d76c1c2 100644 --- a/.opencode/skills/reui/SKILL.md +++ b/.opencode/skills/reui/SKILL.md @@ -5,7 +5,7 @@ user-invocable: false allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) --- -> **ReUI skill version `0e224b0281`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. +> **ReUI skill version `42d70dcc3d`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. # ReUI for Agents diff --git a/.opencode/skills/reui/rules/cli.md b/.opencode/skills/reui/rules/cli.md index e603164..7c40844 100644 --- a/.opencode/skills/reui/rules/cli.md +++ b/.opencode/skills/reui/rules/cli.md @@ -29,6 +29,8 @@ REUI_LICENSE_KEY=your-license-key } ``` +The shadcn CLI expands `${REUI_LICENSE_KEY}` from `.env.local` inside `components.json`, but an MCP client config never expands variables, so a ReUI MCP server config must carry the raw token instead (for example `reui_pat_your_token_here`). + The MCP `get_project_context` tool returns the right config. Full guide: https://reui.io/docs/registry ## Installing diff --git a/.opencode/skills/reui/rules/components.md b/.opencode/skills/reui/rules/components.md index 8767bbb..0c24578 100644 --- a/.opencode/skills/reui/rules/components.md +++ b/.opencode/skills/reui/rules/components.md @@ -1,6 +1,6 @@ # ReUI components -The 17 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `filters`, `frame`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. +The 19 ReUI building blocks: `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree`. Examples and blocks are composed from these. **Rule one: never guess a component's API. Read it first.** Call **`get_component(name)`** for its inline `api` (props + usage, no web fetch), and **share the result's `docsUrl`** (the component's API documentation page) with the user whenever you work with that component's API, so they have the full reference (the `/llms.txt` index is a further fallback). Then call **`get_examples(name)`** to install a worked example and copy real composition. The contracts below are first-try orientation (required props, composition shape, the one gotcha); the inline `api` is the full reference. No single block fits? Compose: search the components you need, read each `get_component`, install a `get_examples` example per component, and adapt. @@ -32,6 +32,34 @@ Common mistakes: - **Incorrect:** a raw `
` / hand-rolled pagination. **Correct:** use `data-grid`; read its API for sticky header, pagination, virtualization, row selection. - **Incorrect:** styling rows/cells with arbitrary classes. **Correct:** drive layout via `tableLayout` and the documented `ColumnMeta` (e.g. `cellClassName`, `headerTitle`). +## event-calendar + +**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** headless-first: `EventCalendarContent` renders the active view (month/week/day/days/agenda; a resource view activates when `resources` is passed) - there is no per-view JSX to compose. Events are `{ id, title, start, end (exclusive), allDay?, color?, recurrence?, resourceId? }`. Mutations flow through `onEventUpdate`/`canDropEvent` (return `false` to reject); the root needs an explicit height because it is a min-h-0 flex column. + +## gantt + +**Required:** `resources` (the left tree) plus bars via `events`/`defaultEvents` attached by `resourceId`. +**Shape:** + +```tsx + + + + +``` + +**Gotcha:** bars move along the time axis only (never across rows) and are all-day spans with exclusive `end`; `progress` is 0-100. Scales are `day | week | month | quarter | year`. Zoom control, infinite scroll, summary rollups, and row checkboxes are ON by default - turn off what you do not need. Same `onEventUpdate`/`canDropEvent` commit pipeline as `event-calendar`; the root needs an explicit height. + ## kanban **Required:** `value` (`Record`), `onValueChange`, `getItemValue` diff --git a/apps/web/src/components/layout/app-shell.tsx b/apps/web/src/components/layout/app-shell.tsx index 848f6ec..7d46168 100644 --- a/apps/web/src/components/layout/app-shell.tsx +++ b/apps/web/src/components/layout/app-shell.tsx @@ -46,7 +46,6 @@ import { AppsMenu } from '@/components/layout/apps-menu' import { CommandPalette, type CommandPaletteItem } from '@/components/layout/command-palette' import { NavUser } from '@/components/layout/nav-user' import { SystemMonitorPopover } from '@/components/layout/system-monitor-popover' -import { ModeToggle } from '@/components/mode-toggle' import { can, isAuthEnabled, permissionForPath } from '@/lib/auth' interface NavItem { @@ -219,7 +218,6 @@ export function AppShell({ children }: { children: ReactNode }) {
-
diff --git a/apps/web/src/components/layout/nav-user.tsx b/apps/web/src/components/layout/nav-user.tsx index e16ace6..3b6e642 100644 --- a/apps/web/src/components/layout/nav-user.tsx +++ b/apps/web/src/components/layout/nav-user.tsx @@ -1,12 +1,20 @@ import { Link } from '@tanstack/react-router' +import { useEffect, useState } from 'react' +import { useTheme } from 'next-themes' import { ChevronsUpDownIcon, ExternalLinkIcon, LogOutIcon, + MonitorIcon, + MoonIcon, + PaletteIcon, SettingsIcon, + SunIcon, } from 'lucide-react' +import { cn } from '@evobgp/ui/lib/utils' import { Avatar, AvatarFallback } from '@evobgp/ui/components/avatar' +import { Button } from '@evobgp/ui/components/button' import { DropdownMenu, DropdownMenuContent, @@ -33,12 +41,71 @@ import { resetPortalHandoff, } from '@/lib/auth' -/** - * Sidebar footer account menu. - * Portal mode → shows JWT email + logout via auth-portal. - * Local mode → shows the API-key hint + clears the local token. - * @see https://reui.io/preview/base/app-shell-12 - */ +/** Sidebar footer account menu — ReUI app-shell-1 NavUser. @see https://reui.io/preview/base/app-shell-1 */ + +const THEMES = [ + { + value: 'light', + label: 'Светлая', + icon: , + }, + { + value: 'dark', + label: 'Тёмная', + icon: , + }, + { + value: 'system', + label: 'Системная', + icon: , + }, +] as const + +function ThemeSegmentedToggle() { + const { theme, setTheme } = useTheme() + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + + const currentTheme = mounted ? (theme ?? 'system') : 'system' + + return ( +
e.stopPropagation()} + onPointerDown={(e) => e.stopPropagation()} + > + {THEMES.map(({ value, label, icon }) => { + const isActive = currentTheme === value + return ( + + ) + })} +
+ ) +} + function initials(source: string): string { const base = source.trim() if (!base) return '?' @@ -56,8 +123,7 @@ export function NavUser() { const name = claims?.name?.trim() || (authOn ? 'Пользователь' : 'Гость') const email = - claims?.email?.trim() || - (authOn ? '' : 'локальный API-токен') + claims?.email?.trim() || (authOn ? '' : 'локальный API-токен') const fallback = initials(name || email) function handleSignOut() { @@ -87,9 +153,7 @@ export function NavUser() { } > - - {fallback} - + {fallback}
{name} @@ -140,6 +204,13 @@ export function NavUser() { Открыть Auth Portal ) : null} + + + Тема +
+ +
+
diff --git a/apps/web/src/components/mode-toggle.tsx b/apps/web/src/components/mode-toggle.tsx deleted file mode 100644 index 14be8e7..0000000 --- a/apps/web/src/components/mode-toggle.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Moon, Sun } from 'lucide-react' -import { useTheme } from 'next-themes' - -import { Button } from '@evobgp/ui/components/button' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@evobgp/ui/components/dropdown-menu' - -export function ModeToggle() { - const { setTheme } = useTheme() - - return ( - - }> - - - Сменить тему - - - setTheme('light')}>Светлая - setTheme('dark')}>Тёмная - setTheme('system')}>Системная - - - ) -} diff --git a/apps/web/src/components/reui-kit/kpi-cols.ts b/apps/web/src/components/reui-kit/kpi-cols.ts new file mode 100644 index 0000000..cd8054a --- /dev/null +++ b/apps/web/src/components/reui-kit/kpi-cols.ts @@ -0,0 +1,10 @@ +/** Shared grid column classes for hybrid KPI / Quick Actions tiles. */ +export function kpiCols(count: number): string { + if (count <= 1) return 'grid-cols-1' + if (count === 2) return 'grid-cols-1 @xl:grid-cols-2' + if (count === 3) return 'grid-cols-1 @3xl:grid-cols-3' + if (count === 4) return 'grid-cols-1 @3xl:grid-cols-2 @6xl:grid-cols-4' + if (count === 5) return 'grid-cols-2 @3xl:grid-cols-3 xl:grid-cols-5' + if (count === 6) return 'grid-cols-2 sm:grid-cols-3 xl:grid-cols-6' + return 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-4' +} 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 dd70b21..28b3fac 100644 --- a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx +++ b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx @@ -4,6 +4,7 @@ import { Link } from '@tanstack/react-router' import { Frame, FramePanel } from '@/components/reui/frame' import { Badge } from '@/components/reui/badge' import { cn } from '@evobgp/ui/lib/utils' +import { kpiCols } from './kpi-cols' import { Item, ItemMedia } from '@evobgp/ui/components/item' import { Skeleton } from '@evobgp/ui/components/skeleton' @@ -47,16 +48,6 @@ const VALUE_VARIANT_CLASS: Record = { destructive: 'text-destructive', } -function kpiCols(count: number): string { - if (count <= 1) return 'grid-cols-1' - if (count === 2) return 'grid-cols-1 @xl:grid-cols-2' - if (count === 3) return 'grid-cols-1 @3xl:grid-cols-3' - if (count === 4) return 'grid-cols-1 @3xl:grid-cols-2 @6xl:grid-cols-4' - if (count === 5) return 'grid-cols-2 @3xl:grid-cols-3 xl:grid-cols-5' - if (count === 6) return 'grid-cols-2 sm:grid-cols-3 xl:grid-cols-6' - return 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-4' -} - function handleCardKeyDown(onActivate: () => void, event: KeyboardEvent) { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() 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 43c976f..0358511 100644 --- a/apps/web/src/components/reui-kit/quick-action-grid.tsx +++ b/apps/web/src/components/reui-kit/quick-action-grid.tsx @@ -10,6 +10,7 @@ import { import { Badge } from '@/components/reui/badge' import { Item, ItemMedia } from '@evobgp/ui/components/item' import { cn } from '@evobgp/ui/lib/utils' +import { kpiCols } from './kpi-cols' export interface QuickActionItem { id: string @@ -30,16 +31,6 @@ interface QuickActionGridProps { const DEFAULT_ICON_CLASS = 'text-muted-foreground [&_svg]:text-current' -function kpiCols(count: number): string { - if (count <= 1) return 'grid-cols-1' - if (count === 2) return 'grid-cols-1 @xl:grid-cols-2' - if (count === 3) return 'grid-cols-1 @3xl:grid-cols-3' - if (count === 4) return 'grid-cols-1 @3xl:grid-cols-2 @6xl:grid-cols-4' - if (count === 5) return 'grid-cols-2 @3xl:grid-cols-3 xl:grid-cols-5' - if (count === 6) return 'grid-cols-2 sm:grid-cols-3 xl:grid-cols-6' - return 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-4' -} - function QuickActionBody({ action }: { action: QuickActionItem }) { return (
diff --git a/docs/ui-design-contract.md b/docs/ui-design-contract.md index e35b447..753e641 100644 --- a/docs/ui-design-contract.md +++ b/docs/ui-design-contract.md @@ -1,100 +1,119 @@ # UI Design Contract (ops apps) -Единый контракт для vps-tracker, CFDM и EvoBGP. Surface: **ReUI Frame**. Kit API: `apps/web/src/components/reui-kit/`. +Единый контракт для **CFDM · vps-tracker · EvoBGP · EvoFirewall · auth-portal**. +Surface: **ReUI Frame**. Kit: `apps/web/src/components/reui-kit/`. +Иерархия: **ReUI PRO > shadcn primitives**. -Карта: [llms.txt](https://reui.io/llms.txt) · [Styling](https://reui.io/docs/styling) · [License](https://reui.io/docs/license-setup) · [Blocks](https://reui.io/blocks) +Карта: [llms.txt](https://reui.io/llms.txt) · [Styling](https://reui.io/docs/styling) · [License](https://reui.io/docs/license-setup) · [Blocks](https://reui.io/blocks) · [MCP](https://reui.io/docs/mcp) ## Surface +Project lock: **`surface: frame`**. Ops / list / dashboard / detail / settings — только **Frame**, не shadcn Card как shell. Не смешивать Card и Frame на одном ops-экране. + ```ts -// apps/web/src/lib/ui-surface.ts +// apps/web/src/lib/ui-surface.ts (где есть) export const UI_SURFACE = 'frame' as const ``` -Ops / list / dashboard / detail / settings — только **Frame**, не shadcn Card как shell. Не смешивать Card и Frame на одном ops-экране. +Settings: секции через Frame + `gap` (без hairline `Separator` под PageHeader); `SettingRow` без `FieldSeparator` по умолчанию (`separated` opt-in). Preview: [settings-3](https://reui.io/preview/base/settings-3) · [settings-16](https://reui.io/preview/base/settings-16). ## Canonical PRO references | Зона | Block | Preview | |------|-------|---------| | Shell | `app-shell-12` (+ cmdk/monitor где нужно) | https://reui.io/preview/base/app-shell-12 · https://reui.io/preview/base/app-shell-7 | -| KPI | horizontal compact hybrid (icon left + label/Badge + value ± variant; EvoBGP visual) | https://reui.io/preview/base/stats-12 | +| KPI | horizontal compact hybrid (EvoBGP SoT: icon left + label/Badge + value ± variant) | https://reui.io/preview/base/stats-12 | +| Quick Actions | Frame tiles (sibling KPI) + Badge «Перейти» | https://reui.io/preview/base/stats-12 · https://reui.io/preview/base/card-12 | | Dashboard | `dashboard-1` | https://reui.io/preview/base/dashboard-1 | | Lists | `data-grid-filtering-2` | https://reui.io/preview/base/data-grid-filtering-2 | -| Settings | `settings-16` + SettingRow (`settings-7`) | https://reui.io/preview/base/settings-16 · https://reui.io/preview/base/settings-7 | +| 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 | | Forms | `form-7` → Sheet/Drawer | https://reui.io/preview/base/form-7 | -| Lookup | `/lookup` — Frame form + `KpiStatGrid` + DataGrid | https://reui.io/preview/base/form-7 · https://reui.io/preview/base/stats-12 · https://reui.io/preview/base/data-grid-filtering-2 · https://reui.io/preview/base/empty-state-2 | ## Kit API (`reui-kit/`) | Component | Role | |-----------|------| | `ResourcePage` | Frame + line tabs + Filters + DataGrid | -| `KpiStatGrid` | horizontal compact hybrid KPI tiles (`variant`, Badge) | -| `QuickActionGrid` | KPI-like quick action tiles under KPI (gated by `ui_show_quick_actions`) | -| `OpsDashboard` | KPI + charts + attention queue | +| `KpiStatGrid` | EvoBGP hybrid KPI tiles (`items`/`cards`, `variant`, Badge) | +| `QuickActionGrid` | KPI-like quick action tiles (gated by `showQuickActions`) | +| `OpsDashboard` | KPI + optional `afterKpi` + charts + attention queue | | `SettingsShell` | settings nav + Outlet | | `DetailPanel` | detail Frame sections | | `filter-utils` | apply/clear ReUI Filters | +`KpiStatGrid` / `QuickActionGrid` markup — SoT **EvoBGP**; в остальных apps diff только `@scope/ui` imports. + ## Dashboard layout | App | Section order | |-----|---------------| -| EvoBGP / CFDM | KPI → **QuickActionGrid** → charts / rest | +| EvoBGP / CFDM / EvoFirewall | KPI → **QuickActionGrid** → charts / rest | | vps-tracker | banner → KPI → charts → attention → **QuickActionGrid** → CSV | +| auth-portal | portal-specific; Quick Actions при наличии dashboard | -Gating: KV `ui_show_quick_actions` in `global_settings` via `PATCH /v1/settings` (default `true`). +Gating: DB `show_quick_actions` / `showQuickActions` / `ui_show_quick_actions` (default `true`). ## Shared App Shell chrome -Эталон: **EvoBGP** production [`apps/web/src/components/layout/app-shell.tsx`](../apps/web/src/components/layout/app-shell.tsx) + ReUI [app-shell-12](https://reui.io/preview/base/app-shell-12). - -При переключении между vps-tracker / CFDM / EvoBGP меняются **только** sidebar nav labels/hrefs и `main` content. Разметка, ширина, фон и hover chrome идентичны. +Эталон разметки: production apps + ReUI [app-shell-12](https://reui.io/preview/base/app-shell-12). +При переключении между apps меняются **только** sidebar nav labels/hrefs и `main` content. | Токен / зона | Значение | |--------------|----------| -| `SIDEBAR_WIDTH` / `--sidebar-width` | `240px` (в `packages/ui` sidebar + Provider style) | -| Sidebar / hover colors | theme `--sidebar` / `--sidebar-accent` из `globals.css` — **без** AppShell `color-mix` override | +| `SIDEBAR_WIDTH` / `--sidebar-width` | `240px` | +| Sidebar / hover colors | theme `--sidebar` / `--sidebar-accent` — **без** AppShell `color-mix` override | | Header | `h-12`, `sticky`, `border-b`, `px-4 md:px-6` | | Header left | `SidebarTrigger` + `Separator` + Breadcrumb | -| Header right | **AppsMenu** → **SystemMonitorPopover** → **ModeToggle** (без Search в chrome) | -| Sidebar | AppSwitcher → groups (`SidebarGroupContent`) → icons `size-4` → **пустой** `SidebarFooter` | +| Header right | **AppsMenu** → **SystemMonitorPopover** (тема — в NavUser) | +| Sidebar | AppSwitcher → groups → icons `size-4` → **NavUser** в `SidebarFooter` | | `main` | `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` | | Search | hotkey ⌘K / Ctrl+K only (не кнопка в header) | -Запрещено в chrome: `SidebarRail`, `NavUser` footer, sync-row footer, Search/Ctrl+K pill в header, issues Badge в header, muted/hover cascade на right-cluster, Provider `color-mix` для `--sidebar*`. +Запрещено в chrome: `SidebarRail`, sync-row footer, Search pill в header, issues Badge в header, `ModeToggle` в header (тема только в NavUser), Provider `color-mix` для `--sidebar*`. -App Switcher: source of truth — auth-portal `GET /api/v1/app-switcher`. Id: `bgp`. Admin: portal `/admin/apps`. +NavUser (footer): avatar + name/email; dropdown — Настройки / Тема (segmented) / Выйти. Preview: [app-shell-1](https://reui.io/preview/base/app-shell-1). -QuickActionGrid icons: только semantic **text** (`text-info` / `text-primary` / …) на kit `bg-muted` — без solid `bg-primary` fills. Preview: [stats-12](https://reui.io/preview/base/stats-12). +App Switcher: auth-portal `GET /api/v1/app-switcher`. Ids: `cfdm` · `vps` · `bgp` · `fw`. Admin: portal `/admin/apps`. + +QuickActionGrid / KPI icons: только semantic **text** (`text-info` / `text-primary` / …) на kit `bg-muted` — без solid fills. ## System monitor -`SystemMonitorPopover` in app-shell header next to `ModeToggle` (после AppsMenu). Preview: https://reui.io/preview/base/app-shell-12 · https://reui.io/preview/base/app-shell-7 +`SystemMonitorPopover` in header after AppsMenu. Preview: https://reui.io/preview/base/app-shell-12 · https://reui.io/preview/base/app-shell-7 ## MCP workflow -1. MCP `user-reui` — `search` / `get_block` / `get_component` with `surface: "frame"` +1. MCP `user-reui` — `search` / `get_block` / `compose_page` / `get_component` with `surface: "frame"` 2. Cite `previewUrl` + `docsUrl` 3. CLI from `apps/web`: `pnpm dlx shadcn@latest add @reui/...` -4. Adapt into kit — do not hand-roll KPI/grid/settings rows +4. Adapt into kit — do not hand-roll KPI / Quick Actions / grid / settings rows 5. `validate_usage` / `get_audit_checklist` -Primitives: MCP `plugin-shadcn-shadcn` + `@evobgp/ui`. +Primitives: MCP `plugin-shadcn-shadcn` + project `@scope/ui` (`@cfdm/ui` / `@evobgp/ui` / `@evofw/ui` / `@authportal/ui`). + +## License + +```env +# apps/web/.env.local (gitignored) +REUI_LICENSE_KEY= +``` + +`apps/web/components.json` → `@reui` с `Authorization: Bearer ${REUI_LICENSE_KEY}`. ## Spacing -- AppShell main: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` (shared chrome) +- AppShell main / PageShell: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` - No `space-y-*` / `space-x-*` — use `flex` + `gap-*` - Max 1 primary CTA per screen -- Semantic tokens only (`variant="success"|"info"|"warning"`) — no raw `bg-emerald-*` +- Semantic tokens only — no raw `bg-emerald-*` ## Forbidden - Card as ops list/dashboard shell -- Hand-rolled data tables when ReUI DataGrid exists -- Hand-rolled KPI grids when `KpiStatGrid` exists +- Hand-rolled data tables when ReUI DataGrid / `ResourcePage` exists +- Hand-rolled KPI when `KpiStatGrid` exists +- Hand-rolled Quick Actions when `QuickActionGrid` exists +- SectionCards / DataGridCard as design эталон - Mixing Card and Frame surfaces on one ops screen