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/frontend-shadcn.mdc b/.cursor/rules/frontend-shadcn.mdc
index 3541b27..e21ec6e 100644
--- a/.cursor/rules/frontend-shadcn.mdc
+++ b/.cursor/rules/frontend-shadcn.mdc
@@ -35,7 +35,7 @@ Monorepo — [`frontend-monorepo.mdc`](frontend-monorepo.mdc). ReUI — [`reui-m
|-----------|------|
| `PageShell` | `page-shell.tsx` |
| `ResourcePage` | `reui-kit/resource-page.tsx` |
-| `OpsDashboard` / `KpiStatGrid` | `reui-kit/ops-dashboard.tsx`, `kpi-stat-grid.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` |
diff --git a/.cursor/rules/frontend-ui-patterns.mdc b/.cursor/rules/frontend-ui-patterns.mdc
index 038f679..184d82f 100644
--- a/.cursor/rules/frontend-ui-patterns.mdc
+++ b/.cursor/rules/frontend-ui-patterns.mdc
@@ -27,7 +27,8 @@ alwaysApply: false
| Зона | Preview |
|------|---------|
-| KPI | [stats-12](https://reui.io/preview/base/stats-12) (primary); [card-35](https://reui.io/preview/base/card-35) compact |
+| KPI | [stats-12](https://reui.io/preview/base/stats-12) — EvoBGP hybrid SoT |
+| Quick Actions | [stats-12](https://reui.io/preview/base/stats-12) · [card-12](https://reui.io/preview/base/card-12) → `QuickActionGrid` |
| List | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) |
| Settings | [settings-16](https://reui.io/preview/base/settings-16), [Application Settings](https://reui.io/blocks/application/settings) |
| Settings rows / Health-check | [settings-2](https://reui.io/preview/base/settings-2), [settings-3](https://reui.io/preview/base/settings-3) |
@@ -73,7 +74,8 @@ apps/web/src/components/ ← shared + domain + layout
| 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) |
+| Dashboard KPI | `OpsDashboard` / `KpiStatGrid` | ReUI Frame [stats-12](https://reui.io/preview/base/stats-12) hybrid |
+| Quick Actions | `QuickActionGrid` | Frame tiles + Badge «Перейти» |
| Empty | `EmptyState` | `Empty` |
| Loading / Error | `QueryState` / kit skeletons | `Skeleton`, `Alert` |
| Status | `StatusBadge` | ReUI `Badge` (`success`/`info`/`warning`) |
diff --git a/.cursor/rules/reui-mcp.mdc b/.cursor/rules/reui-mcp.mdc
index 5342bc9..8857b60 100644
--- a/.cursor/rules/reui-mcp.mdc
+++ b/.cursor/rules/reui-mcp.mdc
@@ -36,11 +36,13 @@ alwaysApply: true
| 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` |
+| 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.
+**Сложные списки** — `ResourcePage` (Frame + data-grid + filters), не raw `
`, не DataGridCard.
+**Quick Actions** — только `QuickActionGrid` (не Card / Button grid).
## MCP workflow
@@ -66,7 +68,7 @@ alwaysApply: true
**Components:** `frame`, `data-grid/*`, `filters`, `kanban`, `badge`, `alert`, `autocomplete`, `number-field`, `date-selector`, `color-picker`, `timeline`, `rating`, `phone-input`, `icon-stack`
-**Kit:** `ResourcePage`, `KpiStatGrid`, `OpsDashboard`, `KanbanBoard`, `DetailPanel`, `SettingsShell`
+**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`, …
@@ -83,7 +85,7 @@ REUI_LICENSE_KEY=
| Зона | Preview |
|------|---------|
-| KPI | https://reui.io/preview/base/stats-12 |
+| 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 |
@@ -96,8 +98,9 @@ REUI_LICENSE_KEY=
- ReUI в `packages/ui` / импорт как `@cfdm/ui`
- Radix-варианты docs — только Base UI
- Raw `bg-emerald-*` вместо ReUI `variant`
-- Hand-roll data-grid/filters/KPI/settings rows при наличии `@reui` / kit
+- Hand-roll data-grid/filters/KPI/Quick Actions/settings rows при наличии `@reui` / kit
- Смешивать Card и Frame на ops-экране
+- Ставить shadcn/ui blocks выше ReUI PRO
## Чеклист
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/shadcn-mcp.mdc b/.cursor/rules/shadcn-mcp.mdc
index 0e990e9..41f0268 100644
--- a/.cursor/rules/shadcn-mcp.mdc
+++ b/.cursor/rules/shadcn-mcp.mdc
@@ -1,49 +1,44 @@
---
-description: ВСЕГДА использовать MCP-плагин shadcn UI перед любым UI-кодом
+description: shadcn MCP — primitives secondary; pages/blocks → user-reui first
alwaysApply: true
---
-# shadcn MCP — обязательно
+# shadcn MCP — primitives (secondary)
-Перед **любой** задачей с UI (новый экран, компонент, стили, рефакторинг внешнего вида) — **сначала MCP** `plugin-shadcn-shadcn`, не память и не веб-поиск.
+Перед UI-задачей: **сначала** MCP `user-reui` для pages / KPI / lists / settings / Frame ([`reui-mcp.mdc`](reui-mcp.mdc)).
+MCP `plugin-shadcn-shadcn` — для **primitives** `@shadcn` и registry sync, не вместо PRO blocks.
CLI и docs — **после** MCP, по команде из `get_add_command_for_items`.
-## Порядок (строго)
+## Порядок (primitives)
-0. **Codegraph** `codegraph_explore` — найти существующие реализации и shared-обёртки (один вызов перед правками)
-1. **`get_project_registries`** — какие registry доступны в проекте
-2. **`search_items_in_registries`** — компонент, block, example (`query`: `"card"`, `"tabs demo"`, `"dashboard"`, `"scroll-area"`)
-3. **`get_item_examples_from_registries`** — полный код примера перед написанием JSX
-4. **`get_add_command_for_items`** — точная CLI-команда `pnpm dlx shadcn@latest add ...`
-5. Выполнить add из `apps/web` (см. [`frontend-monorepo.mdc`](frontend-monorepo.mdc))
-6. **CLI docs (обязательно):** `cd apps/web && pnpm dlx shadcn@latest docs ` — сверить API/props с [ui.shadcn.com/docs/components](https://ui.shadcn.com/docs/components)
-7. Сверить examples из MCP с API из docs CLI — реализовать только после совпадения
-8. Адаптировать пример под TanStack Router / Query → `apps/web/src/`
-9. **Context7** — только TanStack / Recharts / не-shadcn (не заменяет шаги 1–8 для примитивов)
-10. **`get_audit_checklist`** — перед merge PR
-11. **Codegraph** `codegraph_status` — Pending sync пустой после правок
+0. Поиск существующих shared / `reui-kit`
+1. **`get_project_registries`**
+2. **`search_items_in_registries`** — primitive / example
+3. **`get_item_examples_from_registries`**
+4. **`get_add_command_for_items`** → CLI из `apps/web`
+5. `pnpm dlx shadcn@latest docs ` — сверить API
+6. Адаптировать под TanStack → `apps/web/src/`
+7. Context7 — только TanStack / Recharts / не-shadcn
+8. **`get_audit_checklist`** — перед merge
-## Когда вызывать MCP
+## Когда
| Задача | MCP |
|--------|-----|
-| Новая страница / layout | `search` → `types: ["block"]` → examples → add block |
-| Нет примитива в `@cfdm/ui` | `search` → `get_add_command_for_items` → add |
-| Сомнение в API/props | `get_item_examples_from_registries` |
-| Ревью UI перед сдачей | `get_audit_checklist` |
+| Новая страница / KPI / list / settings | **`user-reui`** ([`reui-mcp.mdc`](reui-mcp.mdc)) |
+| Нет примитива в `@cfdm/ui` | `plugin-shadcn-shadcn` → add |
+| Сомнение в props примитива | examples + docs CLI |
## Запрещено
-- Писать UI по памяти, не проверив MCP
-- Копипаст с ui.shadcn.com без examples/add из MCP
-- Самописные примитивы, если есть item в registry
-- Пропускать MCP «потому что компонент простой»
+- Писать UI по памяти
+- Ставить ui.shadcn.com/blocks выше ReUI PRO
+- Самописные примитивы при наличии registry item
-## Сервер и инструменты
+## Сервер
-- **MCP server:** `plugin-shadcn-shadcn`
-- **Инструменты:** `get_project_registries`, `search_items_in_registries`, `get_item_examples_from_registries`, `get_add_command_for_items`, `view_items_in_registries`, `list_items_in_registries`, `get_audit_checklist`
-- Перед вызовом — прочитать schema в `mcps/plugin-shadcn-shadcn/tools/`
+- **MCP:** `plugin-shadcn-shadcn` (+ `user-reui` primary)
+- Schema: `mcps/plugin-shadcn-shadcn/tools/`
-Связанные правила: [`shadcn-ui-production.mdc`](shadcn-ui-production.mdc), [`frontend-shadcn.mdc`](frontend-shadcn.mdc).
+Связанные: [`shadcn-ui-production.mdc`](shadcn-ui-production.mdc), [`reui-mcp.mdc`](reui-mcp.mdc), [`frontend-shadcn.mdc`](frontend-shadcn.mdc).
diff --git a/.cursor/rules/shadcn-ui-production.mdc b/.cursor/rules/shadcn-ui-production.mdc
index 279c56b..ca32eb1 100644
--- a/.cursor/rules/shadcn-ui-production.mdc
+++ b/.cursor/rules/shadcn-ui-production.mdc
@@ -1,26 +1,33 @@
---
-description: shadcn/ui — глобальные UI-принципы проекта; frontend см. frontend-shadcn.mdc
+description: shadcn/ui + ReUI PRO — глобальные UI-принципы; ReUI PRO выше shadcn
alwaysApply: true
---
-# shadcn/ui — правила проекта
+# shadcn/ui + ReUI PRO — правила проекта
-UI строится **исключительно** по [shadcn/ui](https://ui.shadcn.com/docs/installation): [Components](https://ui.shadcn.com/docs/components), [Blocks](https://ui.shadcn.com/blocks), [Monorepo](https://ui.shadcn.com/docs/monorepo).
+UI строится по [shadcn/ui](https://ui.shadcn.com/docs/installation) + **ReUI PRO** (`@reui`): [Components](https://ui.shadcn.com/docs/components), [Blocks](https://ui.shadcn.com/blocks), [ReUI Get Started](https://reui.io/docs/get-started), [llms.txt](https://reui.io/llms.txt).
-**Первый шаг любой UI-задачи — MCP `plugin-shadcn-shadcn`** (см. [`shadcn-mcp.mdc`](shadcn-mcp.mdc)): search → examples → add command → CLI.
+**Иерархия:** ReUI PRO (`user-reui`) **выше** базового shadcn. Pages / KPI / lists / settings / shell → ReUI; shadcn — primitives.
-## Разработка frontend
+**Первый шаг UI-задачи:**
+1. MCP **`user-reui`** для pages / KPI / data-grid / settings / Frame / Quick Actions (`surface: frame`)
+2. MCP **`plugin-shadcn-shadcn`** для primitives `@shadcn`
-**Все правила frontend** — в [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`frontend-ui-patterns.mdc`](frontend-ui-patterns.mdc) и [`frontend-monorepo.mdc`](frontend-monorepo.mdc) (globs: `apps/web/**`, `packages/ui/**`).
+См. [`shadcn-mcp.mdc`](shadcn-mcp.mdc), [`reui-mcp.mdc`](reui-mcp.mdc). Contract: [`docs/ui-design-contract.md`](../../docs/ui-design-contract.md).
-Кратко: docs → CLI из `apps/web` → Block → композиция → `pnpm --filter web build`. Кастомный CSS и самописные примитивы **запрещены**.
+## Frontend
+
+Детали: [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`frontend-ui-patterns.mdc`](frontend-ui-patterns.mdc), [`frontend-monorepo.mdc`](frontend-monorepo.mdc).
+
+Кратко: MCP → CLI из `apps/web` → Block → kit → `pnpm --filter web build`. Кастомный CSS и самописные примитивы **запрещены**.
## Стек
- Monorepo: `apps/web` + `packages/ui` (`@cfdm/ui`), pnpm workspaces
-- Vite + TanStack Router/Query + shadcn **base-nova**
+- Vite + TanStack Router/Query + shadcn **base-nova** + ReUI **@reui**
- Конфиг: [`apps/web/components.json`](apps/web/components.json), [`packages/ui/components.json`](packages/ui/components.json)
-- Тема: `pnpm dlx shadcn@latest apply b2fA --only theme -y` — единственный способ менять базовую тему `globals.css`; ReUI semantic tokens — [Styling](https://reui.io/docs/styling)
+- Тема: `pnpm dlx shadcn@latest apply b2fA --only theme -y`; ReUI tokens — [Styling](https://reui.io/docs/styling)
+- License: `REUI_LICENSE_KEY` в `apps/web/.env.local`
## Backend → UI
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/reui-kit/index.ts b/apps/web/src/components/reui-kit/index.ts
index 466bd24..5ed2012 100644
--- a/apps/web/src/components/reui-kit/index.ts
+++ b/apps/web/src/components/reui-kit/index.ts
@@ -1,7 +1,17 @@
export { applyFiltersToData, getActiveFilters, renderSingleSelectedLabel } from './filter-utils'
export { CertStatusChart, GroupDomainsChart } from './dashboard-analytics'
export { ResourcePage, type ResourcePageProps, type ResourcePageTab } from './resource-page'
-export { KpiStatGrid, type KpiStatCard, type KpiStatVariant, type OpsKpiCard } from './kpi-stat-grid'
+export {
+ KpiStatGrid,
+ KpiStatCard,
+ KpiStatCardTile,
+ kpiStatItemKey,
+ type KpiStatItem,
+ type KpiStatCardData,
+ type KpiStatCard as KpiStatCardType,
+ type KpiStatVariant,
+ type OpsKpiCard,
+} from './kpi-stat-grid'
export { QuickActionGrid, type QuickActionItem } from './quick-action-grid'
export { OpsDashboard } from './ops-dashboard'
export { KanbanBoard, KanbanBoardSkeleton, type KanbanBoardProps, type KanbanColumnConfig } from './kanban-board'
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 121215c..05caecc 100644
--- a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx
+++ b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx
@@ -1,40 +1,44 @@
-import type { ReactNode } from 'react'
+import type { KeyboardEvent, ReactNode } from 'react'
import { Link } from '@tanstack/react-router'
+
import { Frame, FramePanel } from '@/components/reui/frame'
import { Badge } from '@/components/reui/badge'
-import { Item, ItemMedia } from '@cfdm/ui/components/item'
import { cn } from '@cfdm/ui/lib/utils'
+import { kpiCols } from './kpi-cols'
+import { Item, ItemMedia } from '@cfdm/ui/components/item'
import { Skeleton } from '@cfdm/ui/components/skeleton'
export type KpiStatVariant = 'default' | 'warning' | 'destructive'
-export interface KpiStatCard {
- id: string
- label: string
- value: string | number
- hint?: string
+/**
+ * KPI tile data — horizontal compact hybrid (icon left + label/Badge + value).
+ * @see https://reui.io/preview/base/stats-12
+ */
+export type KpiStatItem = {
+ id?: string
+ label: ReactNode
+ value: ReactNode
+ hint?: ReactNode
to?: string
search?: Record
onSelect?: () => void
+ onClick?: () => void
selected?: boolean
+ active?: boolean
icon?: ReactNode
iconClassName?: string
- /** Semantic color for the primary value */
variant?: KpiStatVariant
footer?: ReactNode
}
-/** @deprecated Use KpiStatCard */
-export type OpsKpiCard = KpiStatCard
+/** CFDM-compatible card shape (id required). */
+export type KpiStatCardData = KpiStatItem & { id: string }
-interface KpiStatGridProps {
- cards: KpiStatCard[]
- isLoading?: boolean
- emptyMessage?: ReactNode
- emptyIcon?: ReactNode
- className?: string
- skeletonCount?: number
-}
+/** @deprecated Use KpiStatCardData */
+export type OpsKpiCard = KpiStatCardData
+
+/** @deprecated Use KpiStatCardData — type alias for CFDM kit parity */
+export type KpiStatCard = KpiStatCardData
const DEFAULT_ICON_CLASS = 'text-muted-foreground [&_svg]:text-current'
@@ -44,103 +48,146 @@ 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()
+ onActivate()
+ }
}
-function resolveFooter(card: KpiStatCard): ReactNode {
- if (card.footer) return card.footer
- if (card.hint) {
+function resolveActivate(item: KpiStatItem): (() => void) | undefined {
+ return item.onClick ?? item.onSelect
+}
+
+function isSelected(item: KpiStatItem): boolean {
+ return Boolean(item.selected ?? item.active)
+}
+
+function resolveFooter(item: KpiStatItem): ReactNode {
+ if (item.footer) return item.footer
+ if (typeof item.hint === 'string') {
return (
- {card.hint}
+ {item.hint}
)
}
+ if (item.hint) return item.hint
return null
}
-function KpiStatCardBody({ card }: { card: KpiStatCard }) {
- const footer = resolveFooter(card)
- const valueVariant = card.variant ?? 'default'
+function KpiStatCardBody({ item }: { item: KpiStatItem }) {
+ const footer = resolveFooter(item)
+ const valueVariant = item.variant ?? 'default'
return (