Init Commit
quality / commitlint (push) Skipped
CD / update-wiki (push) Failing after 7s
quality / changes (push) Successful in 4s
quality / docker-check (push) Skipped
quality / web (push) Failing after 38s
quality / api (push) Successful in 49s
CD / quality (push) Failing after 1m36s
CD / publish (push) Skipped
quality / commitlint (push) Skipped
CD / update-wiki (push) Failing after 7s
quality / changes (push) Successful in 4s
quality / docker-check (push) Skipped
quality / web (push) Failing after 38s
quality / api (push) Successful in 49s
CD / quality (push) Failing after 1m36s
CD / publish (push) Skipped
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
---
|
||||
description: Concise AI assistant — clean code, token efficiency, codebase alignment
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# AI Coding Assistant
|
||||
|
||||
You work inside a real codebase. Be precise, concise, and aligned with existing patterns.
|
||||
|
||||
## Clean Code
|
||||
|
||||
- Minimal, readable, maintainable code; simple over clever
|
||||
- Meaningful names; DRY; small single-responsibility functions
|
||||
- Follow existing project style and patterns
|
||||
|
||||
## Token Efficiency
|
||||
|
||||
- Do not explain obvious things
|
||||
- No step-by-step reasoning unless explicitly asked
|
||||
- Output only what is necessary: code, brief comments when needed
|
||||
- No long prose, summaries, or repetition
|
||||
- If unsure — ask a short clarifying question instead of guessing
|
||||
|
||||
## Work With Existing Codebase
|
||||
|
||||
- Analyze surrounding code before generating new code
|
||||
- Reuse existing utilities, helpers, and patterns
|
||||
- Do not reinvent functionality already in the project
|
||||
- Respect project architecture
|
||||
|
||||
## Documentation Awareness
|
||||
|
||||
- Check project docs, README, comments, and types before implementing
|
||||
- If behavior is unclear: infer from types/tests/examples, or ask
|
||||
- Prefer documented approaches over assumptions
|
||||
|
||||
## Output Format
|
||||
|
||||
- Default: only code
|
||||
- If explanation is required — keep it under 3–5 lines
|
||||
- Highlight only important decisions
|
||||
|
||||
## Refactoring
|
||||
|
||||
- Preserve behavior unless told otherwise
|
||||
- Improve readability and structure; reduce complexity and duplication
|
||||
|
||||
## Debugging
|
||||
|
||||
- Identify root cause, not symptoms
|
||||
- Suggest minimal fix; avoid rewriting large parts unless necessary
|
||||
|
||||
## Missing Context
|
||||
|
||||
- Ask concise, targeted questions
|
||||
- Do not hallucinate APIs or project structure
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
description: Backend API — при изменениях, затрагивающих UI, строго следовать shadcn Components/Blocks
|
||||
globs: apps/api/**/*,packages/shared/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Backend API + shadcn/ui
|
||||
|
||||
Fastify backend: `apps/api/`. Контракты — `@cdnmanager/shared` (Zod). Frontend — TanStack Query.
|
||||
|
||||
## Обязательный порядок
|
||||
|
||||
1. **Backend** — route, service, Vitest (`app.inject()`)
|
||||
2. **Схемы** — `@cdnmanager/shared` (не дублировать в `apps/web/src/lib/schemas.ts`)
|
||||
3. **UI** — shadcn MCP ([`shadcn-mcp.mdc`](shadcn-mcp.mdc))
|
||||
|
||||
## Запрещено на frontend при доработке API
|
||||
|
||||
- Новые raw `<table>` / `<select>` / кастомные badge-цвета
|
||||
- Дублирование Zod schemas в `apps/web` — только re-export из `@cdnmanager/shared`
|
||||
- Самописные формы без `Field` + RHF + Zod
|
||||
|
||||
## UI-паттерны (ReUI Frame)
|
||||
|
||||
| API-данные | UI |
|
||||
|------------|-----|
|
||||
| Список | `ResourcePage` (Frame + DataGrid + Filters) |
|
||||
| Создание / редактирование | `FormSheet` + `FieldGroup` + RHF |
|
||||
| Settings | `SettingsShell` + Frame + `SettingRow` |
|
||||
| Статус | ReUI `Badge` / `StatusBadge` |
|
||||
| Ошибка | `sonner` `toast.error` |
|
||||
|
||||
## Согласованность
|
||||
|
||||
- JSON поля — snake_case как в существующем API
|
||||
- Новый endpoint → `queryOptions` в `apps/web/src/queries/`
|
||||
|
||||
MCP backend: [`backend-mcp.mdc`](backend-mcp.mdc) · Fastify: [`backend-fastify.mdc`](backend-fastify.mdc)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
description: Drizzle ORM + SQLite — schema, migrations, queries
|
||||
globs: packages/db/**/*,apps/api/src/services/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Backend Drizzle
|
||||
|
||||
MCP Context7 (`drizzle-orm`, `drizzle-kit`) — [`backend-mcp.mdc`](backend-mcp.mdc).
|
||||
|
||||
## Schema
|
||||
|
||||
- `packages/db/src/schema/` — source of truth
|
||||
- Migrations: `drizzle-kit generate` / `migrate`
|
||||
- WAL + `foreign_keys` при открытии SQLite
|
||||
- Индекс `idx_dns_records_domain_cf_id` на `(domain_id, cf_record_id)`
|
||||
|
||||
## Queries
|
||||
|
||||
- Batch queries (`inArray`, JOINs) — не N+1 loops
|
||||
- UNIQUE violations → `CONFLICT` (409)
|
||||
- Multi-step → `db.transaction()`
|
||||
|
||||
## SQLite
|
||||
|
||||
См. [`sqlite.mdc`](sqlite.mdc).
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
description: Fastify API — слои, плагины, контракт ошибок
|
||||
globs: apps/api/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Backend Fastify
|
||||
|
||||
Стек: **Node.js 22**, **Fastify 5**, `@fastify/*` plugins, `@cdnmanager/shared`, `@cdnmanager/db`.
|
||||
|
||||
MCP — [`backend-mcp.mdc`](backend-mcp.mdc).
|
||||
|
||||
## Слои
|
||||
|
||||
```
|
||||
routes/ → services/ → @cdnmanager/db (repositories)
|
||||
↘ lib/cf-client.ts
|
||||
```
|
||||
|
||||
- Routes — тонкие Fastify plugins (`fastify-plugin`)
|
||||
- **Запрещено:** SQL в routes, `fetch` к CF вне `cf-client`
|
||||
|
||||
## Плагины (официальные)
|
||||
|
||||
`@fastify/jwt`, `@fastify/cors`, `@fastify/sensible`, `@fastify/static`, `@fastify/helmet`, `@fastify/rate-limit`, `@fastify/type-provider-zod`, `fastify-plugin`
|
||||
|
||||
## Ошибки
|
||||
|
||||
Формат: `{ error: { code, message } }`
|
||||
|
||||
Коды: `NOT_FOUND`, `VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `CONFLICT`, `CLOUDFLARE_ERROR`, `INTERNAL_ERROR`
|
||||
|
||||
## Правила
|
||||
|
||||
- Zod schemas только из `@cdnmanager/shared`
|
||||
- `db.transaction()` для multi-step writes
|
||||
- Операции >2s → async job (`sync_jobs` + `p-queue`)
|
||||
- Env через Zod в `config.ts`; prod fail-fast на dev `JWT_SECRET`
|
||||
- TypeScript strict; `function` для handlers/services
|
||||
|
||||
## API + UI
|
||||
|
||||
[`backend-api-ui.mdc`](backend-api-ui.mdc)
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
description: Обязательный порядок MCP перед backend-кодом (Fastify, Drizzle, Cloudflare API)
|
||||
globs: apps/api/**/*,packages/db/**/*,packages/shared/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Backend MCP — обязательно
|
||||
|
||||
Перед **любой** задачей в `apps/api`, `packages/db`, `packages/shared` — сначала MCP, не training data.
|
||||
|
||||
## Порядок
|
||||
|
||||
| Задача | MCP |
|
||||
|--------|-----|
|
||||
| Fastify plugins, routes, hooks | **Context7** `resolve-library-id` → `query-docs` (`fastify`, `@fastify/jwt`, `@fastify/type-provider-zod`) |
|
||||
| Drizzle schema, queries, migrations | **Context7** (`drizzle-orm`, `drizzle-kit`, `better-sqlite3`) |
|
||||
| Cloudflare DNS/Zones API | **`plugin-cloudflare-cloudflare-docs`** `search_cloudflare_documentation` |
|
||||
| API + UI | `packages/shared` → **shadcn MCP** ([`shadcn-mcp.mdc`](shadcn-mcp.mdc)) |
|
||||
| E2E / cutover | **cursor-ide-browser** |
|
||||
| Неизвестный инструмент | **user-mcp-on-demand** `search_tools` |
|
||||
|
||||
## Запрещено
|
||||
|
||||
- Угадывать API Fastify/Drizzle/CF из памяти
|
||||
- Самописные аналоги `@fastify/*` (CORS, static, JWT, rate-limit)
|
||||
- Дублировать Zod schemas вне `@cdnmanager/shared`
|
||||
|
||||
Связанные: [`backend-fastify.mdc`](backend-fastify.mdc), [`backend-drizzle.mdc`](backend-drizzle.mdc), [`backend-testing.mdc`](backend-testing.mdc).
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
description: Backend Vitest + Fastify inject
|
||||
globs: apps/api/**/*,packages/db/**/*,packages/shared/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Backend Testing
|
||||
|
||||
Vitest + `app.inject()` (встроено в Fastify).
|
||||
|
||||
## Требования
|
||||
|
||||
- Каждый route plugin → минимум 1 integration test
|
||||
- Sync/DNS → parity fixtures
|
||||
- `:memory:` SQLite для unit; file DB для integration
|
||||
- `beforeEach` — fresh schema migrate
|
||||
|
||||
## Паттерн
|
||||
|
||||
```ts
|
||||
const app = await buildApp({ db: testDb })
|
||||
const res = await app.inject({ method: 'GET', url: '/health' })
|
||||
expect(res.statusCode).toBe(200)
|
||||
```
|
||||
|
||||
См. [`vitest-best-practices.mdc`](vitest-best-practices.mdc).
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
description: Conventional commits на русском языке
|
||||
globs: "**/*"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Commit messages (русский)
|
||||
|
||||
Формат: `<type>[optional scope]: <описание>`
|
||||
|
||||
## Типы
|
||||
|
||||
- `feat` — только новая UX-фича для пользователя
|
||||
- `fix` — исправление бага
|
||||
- `chore` — конфиг, зависимости, правила, CI
|
||||
- `refactor` — рефакторинг без изменения поведения
|
||||
- `docs` — документация
|
||||
- `test` — тесты
|
||||
- `perf` — производительность
|
||||
|
||||
## Правила
|
||||
|
||||
- Subject в **императиве**, без точки в конце
|
||||
- Subject и body — **на русском**
|
||||
- Body (опционально) — что и зачем, не как
|
||||
- Scope в скобках при необходимости: `feat(domains): добавить фильтр по статусу`
|
||||
|
||||
## Примеры
|
||||
|
||||
```
|
||||
fix(frontend): заменить raw table на shadcn Table на странице доменов
|
||||
|
||||
feat(certificates): добавить предупреждение об истечении срока
|
||||
|
||||
chore(rules): консолидировать правила shadcn/ui для Cursor
|
||||
```
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
description: shadcn/ui Monorepo — структура apps/web + packages/ui, CLI workflow, импорты @cdnmanager/ui
|
||||
globs: apps/web/**/*,packages/ui/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Frontend Monorepo (shadcn/ui)
|
||||
|
||||
**Обязательный стандарт структуры** — [Monorepo docs](https://ui.shadcn.com/docs/monorepo).
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
apps/web/ # Vite SPA (routes, queries, domain components)
|
||||
apps/api/ # Fastify API + static SPA in prod
|
||||
packages/ui/ # @cdnmanager/ui — shadcn primitives
|
||||
packages/shared/ # @cdnmanager/shared — Zod schemas, parse-fqdn
|
||||
packages/db/ # @cdnmanager/db — Drizzle schema, repositories
|
||||
```
|
||||
|
||||
## Два components.json
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|------------|
|
||||
| [`apps/web/components.json`](apps/web/components.json) | App aliases; `ui` → `@cdnmanager/ui/components` |
|
||||
| [`packages/ui/components.json`](packages/ui/components.json) | UI package aliases |
|
||||
|
||||
**Синхронизировать:** `style`, `iconLibrary`, `baseColor` в обоих файлах.
|
||||
|
||||
## CLI — только из apps/web
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
pnpm dlx shadcn@latest docs button
|
||||
pnpm dlx shadcn@latest add button
|
||||
pnpm dlx shadcn@latest add sidebar-07
|
||||
pnpm dlx shadcn@latest add login-03
|
||||
pnpm dlx shadcn@latest apply b2fA --only theme -y
|
||||
```
|
||||
|
||||
Перед обновлением существующих компонентов:
|
||||
|
||||
```bash
|
||||
pnpm dlx shadcn@latest add button --dry-run
|
||||
pnpm dlx shadcn@latest add button --diff
|
||||
pnpm dlx shadcn@latest info --json
|
||||
```
|
||||
|
||||
## Куда CLI кладёт файлы
|
||||
|
||||
| Команда | Куда |
|
||||
|---------|------|
|
||||
| `add button` | `packages/ui/src/components/button.tsx` |
|
||||
| `add login-03` | примитивы → `packages/ui`, block → `apps/web/src/components/` |
|
||||
|
||||
## Импорты
|
||||
|
||||
```tsx
|
||||
import { Button } from '@cdnmanager/ui/components/button'
|
||||
import { cn } from '@cdnmanager/ui/lib/utils'
|
||||
import { useIsMobile } from '@cdnmanager/ui/hooks/use-mobile'
|
||||
import '@cdnmanager/ui/globals.css' // только в main.tsx
|
||||
```
|
||||
|
||||
| Запрещено | Разрешено |
|
||||
|-----------|-----------|
|
||||
| `@/components/ui/*` | `@cdnmanager/ui/components/*` |
|
||||
| `apps/web/src/components/ui/` | `packages/ui/src/components/` |
|
||||
| Ручное редактирование `globals.css` | `pnpm dlx shadcn@latest apply b2fA --only theme` |
|
||||
|
||||
Community registry: переписывать импорты на `@cdnmanager/ui/...`.
|
||||
|
||||
## Разделение ответственности
|
||||
|
||||
- **`packages/ui`** — только output `shadcn add` (примитивы, registry hooks, `cn`)
|
||||
- **`apps/web/src/components`** — blocks, layout, domain (`login-form`, `app-shell`, `PageHeader`)
|
||||
|
||||
## Стили (Tailwind v4 monorepo)
|
||||
|
||||
`packages/ui/src/styles/globals.css` — единственный CSS-файл. **Обязательно** `@source` для обоих workspace:
|
||||
|
||||
```css
|
||||
@source "../"; /* packages/ui/src */
|
||||
@source "../../../apps/web/src"; /* apps/web/src */
|
||||
```
|
||||
|
||||
Без `@source` Tailwind не видит классы из `packages/ui` и `apps/web` — UI ломается (нет sidebar, card, и т.д.).
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm --filter web dev
|
||||
pnpm --filter web build
|
||||
```
|
||||
|
||||
## Чеклист
|
||||
|
||||
- [ ] Два `components.json` согласованы
|
||||
- [ ] `shadcn add` из `apps/web`
|
||||
- [ ] UI-импорты через `@cdnmanager/ui/components/*`
|
||||
- [ ] Нет `apps/web/src/components/ui/`
|
||||
- [ ] `pnpm --filter web build` без ошибок
|
||||
|
||||
См. также: [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`vite-tanstack-frontend.mdc`](vite-tanstack-frontend.mdc).
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
description: Frontend — shadcn primitives + ReUI PRO blocks; CLI-first; kit patterns
|
||||
globs: apps/web/**/*,packages/ui/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Frontend — shadcn/ui + ReUI PRO
|
||||
|
||||
**Источник истины:** MCP **`user-reui`** (pages/blocks/KPI/settings) + MCP `plugin-shadcn-shadcn` (primitives) + docs. Не выдумывать UI.
|
||||
|
||||
Monorepo — [`frontend-monorepo.mdc`](frontend-monorepo.mdc). ReUI — [`reui-mcp.mdc`](reui-mcp.mdc). Patterns — [`frontend-ui-patterns.mdc`](frontend-ui-patterns.mdc).
|
||||
|
||||
| Документ | URL |
|
||||
|----------|-----|
|
||||
| **shadcn Components** | https://ui.shadcn.com/docs/components |
|
||||
| **ReUI Blocks** | https://reui.io/blocks |
|
||||
| **ReUI llms.txt** | https://reui.io/llms.txt |
|
||||
| **ReUI Settings** | https://reui.io/blocks/application/settings |
|
||||
| **Installation** | https://ui.shadcn.com/docs/installation |
|
||||
| **Monorepo** | https://ui.shadcn.com/docs/monorepo |
|
||||
| **Forms (RHF)** | https://ui.shadcn.com/docs/forms/react-hook-form |
|
||||
|
||||
## Шаг 0 — перед любым UI-кодом
|
||||
|
||||
0. Найти существующие `reui-kit/*` / shared
|
||||
1. **Pages / KPI / settings / enterprise** → MCP `user-reui` (`search` → `get_block` / `compose_page`) + cite `previewUrl`
|
||||
2. **Primitives** → MCP `plugin-shadcn-shadcn` + `pnpm dlx shadcn@latest docs <component>`
|
||||
3. CLI add из `apps/web` → adapt
|
||||
|
||||
**Новая страница** → сначала ReUI PRO `compose_page` / blocks ([reui.io/blocks](https://reui.io/blocks)), не ui.shadcn.com/blocks как primary.
|
||||
|
||||
## Shared / kit (обязательно)
|
||||
|
||||
| Component | Файл |
|
||||
|-----------|------|
|
||||
| `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` |
|
||||
| `QueryState` | `query-state.tsx` |
|
||||
| `ConfirmDialog` | `confirm-dialog.tsx` |
|
||||
| `StatusBadge` | `status-badge.tsx` |
|
||||
| `FormSheet` | `form-sheet.tsx` |
|
||||
| `FormField` | `form-field.tsx` |
|
||||
| `LoadingButton` | `loading-button.tsx` |
|
||||
| `SettingRow` | `setting-row.tsx` |
|
||||
|
||||
**Не использовать как эталон:** устаревшие `PageHeader` / `SectionCards` / `DataGridCard` / `TableCard` (если удалены или мёртвы).
|
||||
|
||||
**Overlay:** Sheet — forms; AlertDialog — destructive confirm.
|
||||
|
||||
## Шаг 1 — CLI
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
pnpm dlx shadcn@latest add button field input ...
|
||||
pnpm dlx shadcn@latest add @reui/frame @reui/data-grid @reui/filters
|
||||
pnpm dlx shadcn@latest add @reui/stats-12 @reui/card-35 @reui/settings-16 @reui/auth-13
|
||||
pnpm dlx shadcn@latest apply b2fA --only theme -y
|
||||
```
|
||||
|
||||
## Приоритет композиции
|
||||
|
||||
1. `@cdnmanager/ui/components/*` (primitives)
|
||||
2. ReUI PRO block → adapt (`blocks/` reference + kit)
|
||||
3. `reui-kit/*` + shared
|
||||
4. Domain-обёртка
|
||||
|
||||
## Запрещено
|
||||
|
||||
| ❌ | ✅ |
|
||||
|----|-----|
|
||||
| `<table>`, raw select | DataGrid / `Select` |
|
||||
| `bg-emerald-*` | semantic / ReUI `variant` |
|
||||
| Card как ops-shell | ReUI `Frame` |
|
||||
| Hand-roll KPI | `KpiStatGrid` / [stats-12](https://reui.io/preview/base/stats-12) |
|
||||
| `space-y-*` | `flex` + `gap-*` |
|
||||
| `@/components/ui/*` | `@cdnmanager/ui/components/*` |
|
||||
|
||||
## Эталоны проекта
|
||||
|
||||
| Зона | Файл | Preview |
|
||||
|------|------|---------|
|
||||
| Shell | `layout/app-shell.tsx` | [app-shell-12](https://reui.io/preview/base/app-shell-12) |
|
||||
| Login | `routes/login.tsx` | [auth-13](https://reui.io/preview/base/auth-13) |
|
||||
| Dashboard | `routes/_auth/index.tsx` | [stats-12](https://reui.io/preview/base/stats-12) / dashboard-1 |
|
||||
| Lists | `ResourcePage` | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) |
|
||||
| Settings | `settings/integrations.tsx` | [settings-16](https://reui.io/preview/base/settings-16) |
|
||||
|
||||
## Чеклист
|
||||
|
||||
- [ ] `user-reui` + preview/docs для зоны
|
||||
- [ ] Frame surface
|
||||
- [ ] CLI add при новых items
|
||||
- [ ] `pnpm --filter web build`
|
||||
|
||||
## Язык
|
||||
|
||||
Ответы — русский. Commits — [`commit-messages-ru.mdc`](commit-messages-ru.mdc).
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
description: Единые UI-паттерны web — shared components, docs workflow, матрица стандартизации
|
||||
globs: apps/web/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Frontend UI Patterns
|
||||
|
||||
См. также: [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`shadcn-mcp.mdc`](shadcn-mcp.mdc), [`reui-mcp.mdc`](reui-mcp.mdc), [Components](https://ui.shadcn.com/docs/components), [ReUI llms.txt](https://reui.io/llms.txt).
|
||||
|
||||
## Docs workflow (обязательно)
|
||||
|
||||
0. Codegraph / поиск существующих shared/domain / `reui-kit`
|
||||
1. Skill ReUI + shadcn — component selection
|
||||
2. **Primary:** MCP `user-reui` — search → `get_block` / `compose_page` (`surface: "frame"`) — **всегда cite `previewUrl` + `docsUrl`**
|
||||
3. Primitives: MCP `plugin-shadcn-shadcn` + `pnpm dlx shadcn@latest docs <component>`
|
||||
4. CLI: `cd apps/web && pnpm dlx shadcn@latest add @reui/...` → сверить API
|
||||
5. Код по examples + docs (только после MCP ↔ docs)
|
||||
6. Context7 — **только** TanStack Router/Query, Recharts
|
||||
7. MCP `validate_usage` / `get_audit_checklist` — перед merge
|
||||
|
||||
## Surface
|
||||
|
||||
Проект использует **ReUI Frame** (`surface: frame`), не shadcn Card как оболочку list/ops-экранов. Эталон списка: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2).
|
||||
|
||||
## Обязательные референсы по зонам
|
||||
|
||||
| Зона | Preview |
|
||||
|------|---------|
|
||||
| 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) |
|
||||
| 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](https://reui.io/preview/base/form-7) |
|
||||
| Shell | [app-shell-12](https://reui.io/preview/base/app-shell-12) |
|
||||
|
||||
**SettingRow:** `FieldSeparator` opt-in (`separated`); не между toggle-row и nested fields (Health-check). Settings-секции — отдельные Frame + `gap`, без hairline под PageHeader.
|
||||
|
||||
## Иерархия компонентов
|
||||
|
||||
```
|
||||
@cdnmanager/ui/components/* ← shadcn CLI (packages/ui)
|
||||
@/components/reui/* ← ReUI CLI @reui/* (apps/web)
|
||||
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
|
||||
catalog-board-toggle.tsx
|
||||
empty-state.tsx
|
||||
query-state.tsx
|
||||
confirm-dialog.tsx
|
||||
status-badge.tsx
|
||||
form-sheet.tsx
|
||||
form-field.tsx
|
||||
loading-button.tsx
|
||||
layout/ ← app-shell, site-header, app-sidebar
|
||||
domain-* ← бизнес-компоненты
|
||||
```
|
||||
|
||||
## Матрица стандартизации
|
||||
|
||||
| Элемент | Shared | Primitive |
|
||||
|---------|--------|-----------|
|
||||
| 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 |
|
||||
| Quick Actions | `QuickActionGrid` | Frame tiles + Badge «Перейти» |
|
||||
| Empty | `EmptyState` | `Empty` |
|
||||
| Loading / Error | `QueryState` / kit skeletons | `Skeleton`, `Alert` |
|
||||
| Status | `StatusBadge` | ReUI `Badge` (`success`/`info`/`warning`) |
|
||||
| Create/Edit | `FormSheet` + `*-edit-sheet.tsx` | `Sheet`, `Field` |
|
||||
| Form field | `FormField` | `Field`, `Input`, `Select` |
|
||||
| Submit button | `LoadingButton` | `Button`, `Spinner` |
|
||||
| Delete confirm | `ConfirmDialog` | `AlertDialog` |
|
||||
| Nav | `AppSidebar` (`isActive` обязателен) | `Sidebar` |
|
||||
| Breadcrumbs | `SiteHeader` | `Breadcrumb` |
|
||||
| Dates | `lib/format.ts` | — |
|
||||
|
||||
## Header actions
|
||||
|
||||
Независимые CTA в header → `flex shrink-0 flex-wrap items-center justify-end gap-2`.
|
||||
|
||||
**Не** оборачивать в `ButtonGroup` (он склеивает кнопки). `ButtonGroup` — только для связанных контролок (filter chips и т.п.).
|
||||
|
||||
Max 1 primary (`default`) на экран; остальные `outline` / `ghost`.
|
||||
|
||||
Toggle «Доска» / «К каталогу» — всегда `outline` в `primaryAction` / `KanbanBoard.toolbarActions` (не отдельный Frame-shell).
|
||||
|
||||
## Line tabs (project standard)
|
||||
|
||||
Эталон: [c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) + counted [filtering-2](https://reui.io/preview/base/data-grid-filtering-2).
|
||||
|
||||
```tsx
|
||||
<TabsList variant="line" className="gap-5">
|
||||
<TabsTrigger value="…" className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3">
|
||||
<span>Label</span>
|
||||
<span className="bg-muted text-muted-foreground … tabular-nums rounded-md">{count}</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
```
|
||||
|
||||
- Примитив: [`packages/ui/.../tabs.tsx`](../../packages/ui/src/components/tabs.tsx) — line = `h-auto`, без `flex-1`, без `dark:data-active:bg-input/30`, underline `after:bottom-0`
|
||||
- Active = яркий текст + **foreground underline**, без фона
|
||||
- Count pill всегда `bg-muted`
|
||||
- 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).
|
||||
|
||||
## Sidebar
|
||||
|
||||
- Каждый `SidebarMenuButton` получает `isActive` от pathname (`useRouterState`)
|
||||
- `--sidebar-accent` в `AppShell` — заметный mix primary (~14%), не 5%
|
||||
|
||||
## Overlay selection
|
||||
|
||||
| Сценарий | Компонент |
|
||||
|----------|-----------|
|
||||
| Create/edit форма | `Sheet` |
|
||||
| Destructive confirm | `AlertDialog` via `ConfirmDialog` |
|
||||
| Modal preview | `Dialog` |
|
||||
|
||||
## Block registry
|
||||
|
||||
| Зона | Block / эталон |
|
||||
|------|----------------|
|
||||
| Shell | [app-shell-12](https://reui.io/preview/base/app-shell-12) |
|
||||
| List + tabs + filters | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) |
|
||||
| Line tabs | [c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) |
|
||||
| KPI | [stats-12](https://reui.io/preview/base/stats-12) — на dashboard, certificates, domains, groups, services |
|
||||
| Login | [auth-13](https://reui.io/preview/base/auth-13) |
|
||||
| Settings | [settings-16](https://reui.io/preview/base/settings-16) / [settings-6](https://reui.io/preview/base/settings-6) |
|
||||
| Empty | [empty-state-12](https://reui.io/preview/base/empty-state-12) |
|
||||
|
||||
## Spacing
|
||||
|
||||
```
|
||||
AppShell main: gap-4 md:gap-6, px-4 md:px-6, py-4 md:py-5
|
||||
(--sidebar-width: 240px; header h-12; AppSwitcher + AppsMenu — Shared App Shell chrome)
|
||||
PageShell: gap-4 md:gap-6
|
||||
Title/desc: gap-px
|
||||
Card/Frame grid: gap-4 (dashboard denser: gap-2 md:gap-3)
|
||||
FieldGroup: gap-4
|
||||
Item list: gap-2
|
||||
Toolbar / header actions: gap-2
|
||||
Tabs list (line): gap-5
|
||||
```
|
||||
|
||||
Shared chrome (vps-tracker / CDNManager / EvoBGP): см. [`docs/ui-design-contract.md`](../../docs/ui-design-contract.md) — секция **Shared App Shell chrome**. Preview: [app-shell-12](https://reui.io/preview/base/app-shell-12).
|
||||
|
||||
**Запрещено:** `space-y-*`, raw colors (`bg-emerald-*`), custom empty divs, page-level Spinner / plain «Загрузка…» без Skeleton.
|
||||
|
||||
## UX/UI (состояния данных)
|
||||
|
||||
Каждый блок: **default, hover, focus, disabled, empty, loading, error**.
|
||||
|
||||
- **Loading** — `Skeleton` / `ResourcePage` skeleton / `KanbanBoardSkeleton` / `OpsDashboard` skeleton, не Spinner на странице
|
||||
- **Empty** — `EmptyState` с CTA
|
||||
- **Zero-results** — message внутри DataGrid (+ «Сбросить»)
|
||||
- **Error** — `QueryState` / `Alert` + `onRetry`
|
||||
- **Overflow** — `truncate`, `max-w-*`, `Tooltip`; `tabular-nums` для чисел
|
||||
- **Density** — operational (`dense` Frame/DataGrid); max 1 primary CTA
|
||||
- **A11y** — `aria-invalid`, `aria-label`/`sr-only` на icon-only, `aria-current="page"` на active nav/settings
|
||||
|
||||
## Button hierarchy (max 1 primary per screen)
|
||||
|
||||
1. `default` — главный CTA
|
||||
2. `outline` — вторичные действия
|
||||
3. `ghost` / `link` — навигация, cancel
|
||||
4. `destructive` — только с `ConfirmDialog`
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
description: Gitflow Workflow Rules. These rules should be applied when performing git operations.
|
||||
globs: ["**/*"]
|
||||
alwaysApply: false
|
||||
---
|
||||
# Gitflow Workflow Rules
|
||||
|
||||
## Main Branches
|
||||
|
||||
### main (or master)
|
||||
- Contains production-ready code
|
||||
- Never commit directly to main
|
||||
- Only accepts merges from:
|
||||
- hotfix/* branches
|
||||
- release/* branches
|
||||
- Must be tagged with version number after each merge
|
||||
|
||||
### develop
|
||||
- Main development branch
|
||||
- Contains latest delivered development changes
|
||||
- Source branch for feature branches
|
||||
- Never commit directly to develop
|
||||
|
||||
## Supporting Branches
|
||||
|
||||
### feature/*
|
||||
- Branch from: develop
|
||||
- Merge back into: develop
|
||||
- Naming convention: feature/[issue-id]-descriptive-name
|
||||
- Example: feature/123-user-authentication
|
||||
- Must be up-to-date with develop before creating PR
|
||||
- Delete after merge
|
||||
|
||||
### release/*
|
||||
- Branch from: develop
|
||||
- Merge back into:
|
||||
- main
|
||||
- develop
|
||||
- Naming convention: release/vX.Y.Z
|
||||
- Example: release/v1.2.0
|
||||
- Only bug fixes, documentation, and release-oriented tasks
|
||||
- No new features
|
||||
- Delete after merge
|
||||
|
||||
### hotfix/*
|
||||
- Branch from: main
|
||||
- Merge back into:
|
||||
- main
|
||||
- develop
|
||||
- Naming convention: hotfix/vX.Y.Z
|
||||
- Example: hotfix/v1.2.1
|
||||
- Only for urgent production fixes
|
||||
- Delete after merge
|
||||
|
||||
## Commit Messages
|
||||
|
||||
- Format: `type(scope): description`
|
||||
- Types:
|
||||
- feat: New feature
|
||||
- fix: Bug fix
|
||||
- docs: Documentation changes
|
||||
- style: Formatting, missing semicolons, etc.
|
||||
- refactor: Code refactoring
|
||||
- test: Adding tests
|
||||
- chore: Maintenance tasks
|
||||
|
||||
## Version Control
|
||||
|
||||
### Semantic Versioning
|
||||
- MAJOR version for incompatible API changes
|
||||
- MINOR version for backwards-compatible functionality
|
||||
- PATCH version for backwards-compatible bug fixes
|
||||
|
||||
## Pull Request Rules
|
||||
|
||||
1. All changes must go through Pull Requests
|
||||
2. Required approvals: minimum 1
|
||||
3. CI checks must pass
|
||||
4. No direct commits to protected branches (main, develop)
|
||||
5. Branch must be up to date before merging
|
||||
6. Delete branch after merge
|
||||
|
||||
## Branch Protection Rules
|
||||
|
||||
### main & develop
|
||||
- Require pull request reviews
|
||||
- Require status checks to pass
|
||||
- Require branches to be up to date
|
||||
- Include administrators in restrictions
|
||||
- No force pushes
|
||||
- No deletions
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Create release branch from develop
|
||||
2. Bump version numbers
|
||||
3. Fix any release-specific issues
|
||||
4. Create PR to main
|
||||
5. After merge to main:
|
||||
- Tag release
|
||||
- Merge back to develop
|
||||
- Delete release branch
|
||||
|
||||
## Hotfix Process
|
||||
|
||||
1. Create hotfix branch from main
|
||||
2. Fix the issue
|
||||
3. Bump patch version
|
||||
4. Create PR to main
|
||||
5. After merge to main:
|
||||
- Tag release
|
||||
- Merge back to develop
|
||||
- Delete hotfix branch
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
description: Только hybrid KPI — KpiStatGrid / row tile DNA (stats-12 + IconTile). Запрет SectionCards и hand-roll.
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# KPI hybrid — только kit (stats-12 + IconTile)
|
||||
|
||||
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).
|
||||
|
||||
## MUST
|
||||
|
||||
| Зона | Компонент / DNA |
|
||||
|------|-----------------|
|
||||
| KPI-полосы / dashboard metrics | только `reui-kit/KpiStatGrid` (через `OpsDashboard` / `DetailPanel.Metrics` при наличии) |
|
||||
| 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: `@cdnmanager/ui/components/*`. IconTile: `@/components/reui/icon-tile`.
|
||||
|
||||
## NEVER
|
||||
|
||||
- SectionCards / vertical-only KPI / hand-roll Frame/Card KPI
|
||||
- `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 без IconTile
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
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), [`frontend-monorepo.mdc`](frontend-monorepo.mdc), [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`frontend-ui-patterns.mdc`](frontend-ui-patterns.mdc).
|
||||
|
||||
| Документ | URL |
|
||||
|----------|-----|
|
||||
| **Introduction** | https://reui.io/docs |
|
||||
| **llms.txt** | https://reui.io/llms.txt |
|
||||
| **Get Started** | https://reui.io/docs/get-started |
|
||||
| **Styling** | https://reui.io/docs/styling |
|
||||
| **Registry** | https://reui.io/docs/registry |
|
||||
| **MCP** | https://reui.io/docs/mcp |
|
||||
| **Agent Skills** | https://reui.io/docs/agent-skills |
|
||||
| **Cursor MCP** | https://reui.io/docs/cursor |
|
||||
| **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/<name> |
|
||||
|
||||
## 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-зоны.
|
||||
|
||||
**Registry (актуально):** 20 free components — `alert`, `autocomplete`, `badge`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `icon-tile`, `kanban`, `number-field`, `phone-input`, `rating`, `scrollspy`, `sortable`, `stepper`, `timeline`, `tree` ([docs](https://reui.io/docs), [MCP](https://reui.io/docs/mcp)). Skill: `.claude/skills/reui` (v `668fb463eb`); обновление: `curl.exe -fsSL https://mcp.reui.io/install | node -` из корня проекта.
|
||||
|
||||
**Важно:** skill описывает текущий registry (в т.ч. data-grid на TanStack Table v9). Установленный в проекте `@reui/data-grid` может оставаться на v8 до явного CLI upgrade — не ломать kit без миграции.
|
||||
|
||||
## Когда ReUI vs shadcn
|
||||
|
||||
| Задача | Registry | Импорт |
|
||||
|--------|----------|--------|
|
||||
| Button, Sheet, Field, Sidebar, Tabs | `@shadcn` | `@cdnmanager/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` — см. [`kpi-hybrid.mdc`](kpi-hybrid.mdc) |
|
||||
| Quick Actions | Frame tiles sibling KPI | `reui-kit/QuickActionGrid` |
|
||||
| Semantic badge / alert | `@reui` | `@/components/reui/badge`, `@/components/reui/alert` |
|
||||
| Number / date / autocomplete / color / kanban / timeline / stepper / tree | `@reui` | `@/components/reui/*` |
|
||||
| Event Calendar / Gantt / Icon Tile (registry, 20 free) | `@reui` | CLI `@reui/<name>` → `@/components/reui/*` при установке |
|
||||
|
||||
**Сложные списки** — `ResourcePage` (Frame + data-grid + filters), не raw `<table>`, не 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/<name> --yes`
|
||||
5. Post-add: shadcn imports → `@cdnmanager/ui/components/*`
|
||||
6. Adapt by reuse → kit / route
|
||||
7. `validate_usage` + `get_audit_checklist`
|
||||
|
||||
## Размещение
|
||||
|
||||
| Слой | Путь | Импорт |
|
||||
|------|------|--------|
|
||||
| shadcn | `packages/ui/src/components/` | `@cdnmanager/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`, `icon-tile`
|
||||
|
||||
**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` / импорт как `@cdnmanager/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` / `@cdnmanager/ui` — правильный слой
|
||||
- [ ] `pnpm --filter web build`
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
description: Use the ReUI registry (blocks, primitives, icons) correctly
|
||||
globs: ["**/*.tsx","**/*.ts"]
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
---
|
||||
name: reui
|
||||
description: Use the ReUI registry from your AI agent - find, install, and correctly use ReUI components (the 20 free building blocks like data-grid, kanban, filters), their free examples, premium blocks, and Motion Icons. Applies in any project using ReUI, the @reui registry, REUI_LICENSE_KEY, or any shadcn project where the user asks for premium blocks, data grids, kanban boards, dashboards, or full pages. Pairs with the free ReUI MCP server for live, scored registry search and inline component APIs.
|
||||
user-invocable: false
|
||||
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
|
||||
---
|
||||
|
||||
> **ReUI skill version `3bdbad788a`.** 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
|
||||
|
||||
ReUI is a shadcn-compatible registry. It ships four things you **reuse** - never redesign:
|
||||
|
||||
- **components** - the 20 ReUI building blocks with real APIs: `data-grid`, `kanban`, `filters`, `date-selector`, `tree`, `stepper`, ... (free)
|
||||
- **examples** - free `c-*` single-pattern use-cases of a component (`c-kanban-1`); install one and read it to see exact composition
|
||||
- **blocks** - premium full-page sections that compose components (`data-grid-2`, `pricing-page-1`); Pro or Ultimate license at install
|
||||
- **icons** - Motion Icons in 4 styles, static + hover-animated variants; Ultimate license at install
|
||||
|
||||
The skill is free and this MCP is free to use; it just needs a ReUI account. On first use your agent opens a browser "Sign in with ReUI" prompt (a free account is created if you don't have one). Free covers components and examples with a daily request allowance; a Pro or Ultimate license unlocks premium blocks and Motion Icons and removes the limit (see [rules/registry.md](./rules/registry.md)). The same account and skill work in every agent and service the MCP connects to - this skill is agent-agnostic.
|
||||
|
||||
Skill + MCP are a team: this skill is the workflow (how to find, install, read the API, and adapt by reuse); the MCP is the live data and the hands (search, get_component, install commands). Your job: find the right item, install it with the shadcn CLI, read its real API, and **adapt by reuse** - wire real data and theme it; do not hand-roll or restyle what ReUI already provides. This skill **layers on the shadcn skill**: follow that for generic rules (spacing, `cn()`, semantic colors, forms); follow this for everything ReUI-specific.
|
||||
|
||||
## The core loop (MCP-native)
|
||||
|
||||
1. **Find** - call the ReUI MCP `search` tool with the user's intent. It returns a ranked, scored list across components/examples/blocks/icons, each with an `install` command, `previewUrl`, `docsUrl`, and `componentsUsed`. Pass hints (`type`, `component`, `category`, `features`, `free`) when you can infer them.
|
||||
2. **Install** - run the returned command non-interactively (`npx shadcn@latest add @reui/<name> --yes`). The CLI resolves deps, aliases, and the base/style from `components.json`. See [cli.md](./rules/cli.md).
|
||||
3. **Read the API (on your base)** - first note your base from `components.json` -> `style` (`base-nova` -> Base UI, `radix-nova` -> Radix UI). For each component an item uses, call `get_component(name)` and read its **inline `api`** (no web fetch); then `get_examples(name)` to install a worked example and copy its composition - the installed files are already in your base. Whenever you work with a component's API, also **share its `docsUrl`** (the primitive's API documentation page) with the user so they have the full reference. See [components.md](./rules/components.md).
|
||||
4. **Adapt (reuse-first)** - swap demo data for real data, fix icon imports, align tokens. Do not redesign. See [adapting.md](./rules/adapting.md).
|
||||
|
||||
**Always show the preview.** Every item a tool returns carries a `previewUrl` (a live preview page). Whenever you list, recommend, or present ReUI items to the user - blocks, components, examples, or icons, whether from `search`, `search_icons`, `list_components`, `compose_page`, or any getter - include each item's `previewUrl` so they can SEE it before installing. Blocks and examples open an individual live preview; icons and components link to their live category/component page. Never present an item without its preview link.
|
||||
|
||||
If the ReUI MCP is not configured, fall back to `npx shadcn@latest search @reui -q "..."` then `add` - but the MCP gives scored matches + inline APIs; prefer it.
|
||||
|
||||
## Commands
|
||||
|
||||
Run ReUI as explicit slash commands (via the ReUI MCP) **or** just ask in plain language - both run the same workflow.
|
||||
|
||||
| Command | Invoke | Does |
|
||||
| ----------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| **build** | `/mcp__reui__build <what>` | Compose a page/section/feature from ReUI: plan → install → read API → adapt → craft → audit. |
|
||||
| **add** | `/mcp__reui__add <item>` | Find & install one component/example/block/icon and wire it in. |
|
||||
| **fix** | `/mcp__reui__fix [target]` | Diagnose & fix ReUI usage: wrong/undocumented props, base/radix mismatch, missing states, a11y/scroll. |
|
||||
| **improve** | `/mcp__reui__improve [target]` | Refine + extend existing ReUI UI to a production-exceptional bar (hierarchy, density, states, responsive, motion). |
|
||||
|
||||
Invocation differs slightly per agent (`/mcp__reui__build` in Claude Code/Cursor/Windsurf, `/mcp.reui.build` in VS Code). No command surface? Just describe what you want - this skill drives the identical loop.
|
||||
|
||||
## When to reach for ReUI vs plain shadcn
|
||||
|
||||
| Need | Reach for |
|
||||
| -------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
||||
| A full page or section (dashboard, billing, auth, pricing, settings) | `compose_page` first (plans sections + best blocks), then ReUI **blocks** |
|
||||
| A data table with sorting/filtering/pagination/virtualization | the **data-grid** component (never hand-roll a `<table>`) |
|
||||
| A drag-and-drop board | the **kanban** component |
|
||||
| Advanced column filtering, date range, tree, stepper, ... | the matching ReUI **component** |
|
||||
| A single generic control already in shadcn (Button, Dialog, Select) | plain **shadcn** |
|
||||
|
||||
## Detailed references
|
||||
|
||||
- [rules/registry.md](./rules/registry.md) - the four types, the @reui registry, base/radix, free vs premium + license
|
||||
- [rules/workflow.md](./rules/workflow.md) - the find -> install -> read-API -> adapt loop (most important)
|
||||
- [rules/components.md](./rules/components.md) - the 20 components, the data-grid contract, base vs radix
|
||||
- [rules/adapting.md](./rules/adapting.md) - reuse-first: preserve the design (no over-customizing), reuse examples + a block's own elements, real data, don't invent APIs
|
||||
- [rules/craft.md](./rules/craft.md) - make it exceptional: point of view, hierarchy, density, states, responsive, motion, the bar
|
||||
- [rules/quality.md](./rules/quality.md) - security, accessibility, and scroll gates (the done gate)
|
||||
- [rules/styling.md](./rules/styling.md) - ReUI extended tokens, theme adaptation, density
|
||||
- [rules/icons.md](./rules/icons.md) - portable icons, swapping imports, Motion Icons (static + animated)
|
||||
- [tools.md](./tools.md) - the ReUI MCP: golden path, the 19 tools, token rules, result shapes, errors
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
description: shadcn MCP — primitives secondary; pages/blocks → user-reui first
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# shadcn MCP — primitives (secondary)
|
||||
|
||||
Перед 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. Поиск существующих 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 <component>` — сверить API
|
||||
6. Адаптировать под TanStack → `apps/web/src/`
|
||||
7. Context7 — только TanStack / Recharts / не-shadcn
|
||||
8. **`get_audit_checklist`** — перед merge
|
||||
|
||||
## Когда
|
||||
|
||||
| Задача | MCP |
|
||||
|--------|-----|
|
||||
| Новая страница / KPI / list / settings | **`user-reui`** ([`reui-mcp.mdc`](reui-mcp.mdc)) |
|
||||
| Нет примитива в `@cdnmanager/ui` | `plugin-shadcn-shadcn` → add |
|
||||
| Сомнение в props примитива | examples + docs CLI |
|
||||
|
||||
## Запрещено
|
||||
|
||||
- Писать UI по памяти
|
||||
- Ставить ui.shadcn.com/blocks выше ReUI PRO
|
||||
- Самописные примитивы при наличии registry item
|
||||
|
||||
## Сервер
|
||||
|
||||
- **MCP:** `plugin-shadcn-shadcn` (+ `user-reui` primary)
|
||||
- Schema: `mcps/plugin-shadcn-shadcn/tools/`
|
||||
|
||||
Связанные: [`shadcn-ui-production.mdc`](shadcn-ui-production.mdc), [`reui-mcp.mdc`](reui-mcp.mdc), [`frontend-shadcn.mdc`](frontend-shadcn.mdc).
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
description: shadcn/ui + ReUI PRO — глобальные UI-принципы; ReUI PRO выше shadcn
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# shadcn/ui + ReUI PRO — правила проекта
|
||||
|
||||
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).
|
||||
|
||||
**Иерархия:** ReUI PRO (`user-reui`) **выше** базового shadcn. Pages / KPI / lists / settings / shell → ReUI; shadcn — primitives.
|
||||
|
||||
**Первый шаг UI-задачи:**
|
||||
1. MCP **`user-reui`** для pages / KPI / data-grid / settings / Frame / Quick Actions (`surface: frame`)
|
||||
2. MCP **`plugin-shadcn-shadcn`** для primitives `@shadcn`
|
||||
|
||||
См. [`shadcn-mcp.mdc`](shadcn-mcp.mdc), [`reui-mcp.mdc`](reui-mcp.mdc). Contract: [`docs/ui-design-contract.md`](../../docs/ui-design-contract.md).
|
||||
|
||||
## 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` (`@cdnmanager/ui`), pnpm workspaces
|
||||
- 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`; ReUI tokens — [Styling](https://reui.io/docs/styling)
|
||||
- License: `REUI_LICENSE_KEY` в `apps/web/.env.local`
|
||||
|
||||
## Backend → UI
|
||||
|
||||
При правках API с экранами: [`backend-api-ui.mdc`](backend-api-ui.mdc). Backend: `apps/api` (Fastify + Drizzle).
|
||||
|
||||
## Язык
|
||||
|
||||
Русский. Commits: [`commit-messages-ru.mdc`](commit-messages-ru.mdc).
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
description: Definitive guidelines for writing robust, performant, and secure SQLite code. Focuses on schema design, query optimization, and transaction management.
|
||||
globs: **/*
|
||||
---
|
||||
# sqlite Best Practices
|
||||
|
||||
SQLite is the go-to embedded SQL engine for local, reliable storage. Adhere to these rules to ensure your SQLite code is maintainable, performant, and secure.
|
||||
|
||||
## 1. Data Modeling & Schema Design
|
||||
|
||||
Design your schema for integrity and performance from day one.
|
||||
|
||||
* **Primary Keys**: Always use `INTEGER PRIMARY KEY AUTOINCREMENT` for ID columns. This optimizes `rowid` lookups and simplifies ID generation.
|
||||
* ❌ BAD:
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY, -- Manual UUIDs or similar
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
* ✅ GOOD:
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
* **Data Types & Constraints**: Declare appropriate data types and enforce integrity with `NOT NULL`, `UNIQUE`, and `FOREIGN KEY` constraints.
|
||||
* ❌ BAD:
|
||||
```sql
|
||||
CREATE TABLE products (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT, -- Allows NULL, no uniqueness
|
||||
price REAL
|
||||
);
|
||||
```
|
||||
* ✅ GOOD:
|
||||
```sql
|
||||
CREATE TABLE products (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
price REAL NOT NULL,
|
||||
stock INTEGER DEFAULT 0,
|
||||
category_id INTEGER,
|
||||
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
|
||||
);
|
||||
```
|
||||
|
||||
* **Naming Conventions**: Use `lower_case_snake_case` for all table, column, and index names. Avoid SQLite keywords as identifiers.
|
||||
* ❌ BAD: `CREATE TABLE My_Users ( UserId INTEGER PRIMARY KEY );`
|
||||
* ✅ GOOD: `CREATE TABLE my_users ( user_id INTEGER PRIMARY KEY );`
|
||||
|
||||
## 2. Performance Considerations
|
||||
|
||||
Optimize for speed by minimizing I/O and leveraging the SQLite engine.
|
||||
|
||||
* **Enable WAL Mode**: Always enable Write-Ahead Logging for better concurrency and write performance.
|
||||
* ❌ BAD: Default journal mode (`DELETE`).
|
||||
* ✅ GOOD (at database open or once):
|
||||
```sql
|
||||
PRAGMA journal_mode = WAL;
|
||||
```
|
||||
|
||||
* **Relax Synchronous Mode**: When using WAL, set `synchronous` to `NORMAL` for faster commits, accepting minimal risk of data loss on power failure (not app crash).
|
||||
* ❌ BAD: Default `synchronous = FULL`.
|
||||
* ✅ GOOD (at database open or once):
|
||||
```sql
|
||||
PRAGMA synchronous = NORMAL;
|
||||
```
|
||||
|
||||
* **Indexes**: Create indexes on columns frequently used in `WHERE`, `ORDER BY`, `GROUP BY`, or `JOIN` clauses. Avoid over-indexing.
|
||||
* ❌ BAD:
|
||||
```sql
|
||||
SELECT * FROM users WHERE email = '[email protected]'; -- No index on email
|
||||
```
|
||||
* ✅ GOOD:
|
||||
```sql
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
SELECT id, name FROM users WHERE email = '[email protected]';
|
||||
```
|
||||
* **Multi-column Indexes**: For queries filtering/sorting on multiple columns, create a multi-column index matching the query order.
|
||||
```sql
|
||||
CREATE INDEX idx_products_category_price ON products(category_id, price);
|
||||
SELECT * FROM products WHERE category_id = 1 ORDER BY price DESC;
|
||||
```
|
||||
|
||||
* **Query Optimization**: Select only the columns you need. Push filtering, sorting, and aggregation into SQL.
|
||||
* ❌ BAD:
|
||||
```sql
|
||||
SELECT * FROM products; -- Fetch all columns
|
||||
-- Then filter/sort in application code
|
||||
```
|
||||
* ✅ GOOD:
|
||||
```sql
|
||||
SELECT id, name, price FROM products WHERE stock > 0 ORDER BY price ASC LIMIT 10;
|
||||
```
|
||||
|
||||
## 3. Transactions & Concurrency
|
||||
|
||||
Ensure data consistency and improve write performance with explicit transactions.
|
||||
|
||||
* **Wrap Writes in Transactions**: Group multiple `INSERT`, `UPDATE`, `DELETE` operations within a single transaction. This significantly reduces disk I/O.
|
||||
* ❌ BAD:
|
||||
```sql
|
||||
INSERT INTO logs (action) VALUES ('User created');
|
||||
INSERT INTO users (name) VALUES ('New User');
|
||||
INSERT INTO logs (action) VALUES ('User name updated');
|
||||
UPDATE users SET name = 'Updated User' WHERE id = 1;
|
||||
```
|
||||
* ✅ GOOD:
|
||||
```sql
|
||||
BEGIN;
|
||||
INSERT INTO logs (action) VALUES ('User created');
|
||||
INSERT INTO users (name) VALUES ('New User');
|
||||
INSERT INTO logs (action) VALUES ('User name updated');
|
||||
UPDATE users SET name = 'Updated User' WHERE id = 1;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
* **Error Handling**: Use `ROLLBACK` to revert all changes if any operation within a transaction fails.
|
||||
* ✅ GOOD:
|
||||
```sql
|
||||
BEGIN;
|
||||
-- Perform operations
|
||||
INSERT INTO users (name) VALUES ('Valid User');
|
||||
INSERT INTO users (name) VALUES (NULL); -- This will fail due to NOT NULL
|
||||
-- If an error occurs, catch it and:
|
||||
ROLLBACK;
|
||||
-- Else:
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
## 4. Security Best Practices
|
||||
|
||||
Prevent common vulnerabilities like SQL injection.
|
||||
|
||||
* **Prepared Statements**: Always use prepared statements with bound parameters. NEVER concatenate user input directly into SQL queries.
|
||||
* ❌ BAD:
|
||||
```sql
|
||||
String name = userInput.getName();
|
||||
String sql = "INSERT INTO users (name) VALUES ('" + name + "');"; // SQL Injection risk!
|
||||
```
|
||||
* ✅ GOOD (using a typical API pattern):
|
||||
```sql
|
||||
PreparedStatement stmt = connection.prepareStatement("INSERT INTO users (name) VALUES (?);");
|
||||
stmt.setString(1, userInput.getName());
|
||||
stmt.executeUpdate();
|
||||
```
|
||||
|
||||
* **Enable Foreign Key Enforcement**: Always enable foreign key constraints at runtime. SQLite defaults to `OFF` for backward compatibility.
|
||||
* ❌ BAD: Forgetting to enable foreign keys, leading to orphaned records.
|
||||
* ✅ GOOD (at database open or once per connection):
|
||||
```sql
|
||||
PRAGMA foreign_keys = ON;
|
||||
```
|
||||
|
||||
* **File Permissions**: Store database files in write-protected directories and set restrictive file permissions to limit unauthorized access. This is OS-specific but critical.
|
||||
|
||||
## 5. Common Pitfalls & Gotchas
|
||||
|
||||
Avoid these common mistakes that lead to bugs and performance issues.
|
||||
|
||||
* **Forgetting `PRAGMA foreign_keys = ON;`**: This is the most common pitfall. Always enable it.
|
||||
* **Selecting `*`**: Only retrieve the columns you actually need.
|
||||
* **Application-level Filtering/Sorting**: Delegate these operations to SQL for better performance, especially on large datasets.
|
||||
* **Not Using Transactions**: Leads to slow writes and potential data inconsistencies.
|
||||
* **Using SQLite for High-Concurrency Writes**: SQLite is a single-writer database. If multiple processes need to write concurrently, consider a client-server RDBMS.
|
||||
|
||||
## 6. Testing Approaches
|
||||
|
||||
Ensure your data access logic is robust and correct.
|
||||
|
||||
* **In-Memory Databases**: Use `:memory:` databases for fast, isolated unit and integration tests of your data access layer.
|
||||
* ✅ GOOD (example in Python, similar patterns exist in other languages):
|
||||
```python
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(':memory:')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("CREATE TABLE test_data (id INTEGER PRIMARY KEY, value TEXT)")
|
||||
# ... run tests ...
|
||||
conn.close() # Database vanishes
|
||||
```
|
||||
|
||||
* **Seed Data**: Create consistent, reproducible test data for your tests.
|
||||
* **Mocking**: For higher-level tests, mock your database interactions to focus on business logic.
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
description: Vite + TanStack Router v1 + TanStack Query v5 — routing, loaders, queries, mutations
|
||||
globs: apps/web/**/*.{tsx,ts}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Vite + TanStack Router + Query
|
||||
|
||||
Фронтенд: **Vite SPA**, не Next.js. Нет Server Components, App Router, `'use client'`.
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
apps/web/src/
|
||||
routes/ # file-based routes (__root.tsx, _auth/, ...)
|
||||
queries/ # queryOptions factories + key factories
|
||||
lib/ # api-client, queryClient, auth, schemas
|
||||
components/ # domain + layout (UI primitives → @cdnmanager/ui)
|
||||
main.tsx
|
||||
```
|
||||
|
||||
## Архитектура
|
||||
|
||||
- **Router** — маршрутизация, URL state, navigation, loaders
|
||||
- **Query** — server state, cache, mutations
|
||||
- **Loader** — `queryClient.ensureQueryData()` до рендера → без спиннеров на route data
|
||||
- **Компоненты** — UI; данные из Query cache
|
||||
|
||||
## QueryClient + Router
|
||||
|
||||
```ts
|
||||
// lib/queryClient.ts
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { staleTime: 60_000 } },
|
||||
})
|
||||
|
||||
// lib/router.ts
|
||||
export const router = createRouter({
|
||||
routeTree,
|
||||
context: { queryClient },
|
||||
defaultPreload: 'intent',
|
||||
})
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register { router: typeof router }
|
||||
}
|
||||
```
|
||||
|
||||
## Query definitions
|
||||
|
||||
- `queryOptions` factories в `queries/`, не inline в компонентах
|
||||
- Key factories: `all` → `lists` / `details` → `list(filters)` / `detail(id)`
|
||||
|
||||
```ts
|
||||
export const serviceKeys = {
|
||||
all: ['services'] as const,
|
||||
list: () => [...serviceKeys.all, 'list'] as const,
|
||||
}
|
||||
|
||||
export const servicesQueryOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: serviceKeys.list(),
|
||||
queryFn: () => api.get('/api/v1/services'),
|
||||
})
|
||||
```
|
||||
|
||||
## Loader + component
|
||||
|
||||
```tsx
|
||||
export const Route = createFileRoute('/_auth/services')({
|
||||
loader: ({ context: { queryClient } }) =>
|
||||
queryClient.ensureQueryData(servicesQueryOptions()),
|
||||
component: ServicesPage,
|
||||
})
|
||||
|
||||
function ServicesPage() {
|
||||
const { data } = useQuery(servicesQueryOptions()) // из cache loader
|
||||
return ...
|
||||
}
|
||||
```
|
||||
|
||||
## Search params
|
||||
|
||||
- Zod + `validateSearch`; доступ через `Route.useSearch()`
|
||||
- Search params = source of truth для фильтров/пагинации
|
||||
- Передавать в `queryOptions` для query key и fetcher
|
||||
|
||||
## Mutations
|
||||
|
||||
```ts
|
||||
onSuccess: (newItem) => {
|
||||
queryClient.setQueryData(keys.detail(newItem.id), newItem)
|
||||
queryClient.invalidateQueries({ queryKey: keys.lists() })
|
||||
}
|
||||
```
|
||||
|
||||
- `setQueryData` + `invalidateQueries`, не только invalidate
|
||||
- Навигация после create — когда cache уже тёплый
|
||||
|
||||
## Routing
|
||||
|
||||
- `createFileRoute` для file-based routes
|
||||
- `<Link>` для внутренней навигации, не `<a href>`
|
||||
- Pathless layouts: `_auth/` для protected routes
|
||||
- Auth guard в `beforeLoad` pathless route
|
||||
|
||||
## Запреты
|
||||
|
||||
- `useEffect` для fetch данных — только loader / `useQuery`
|
||||
- Inline `queryKey` в компонентах — только factories из `queries/`
|
||||
- `useQuery` с позиционными аргументами (v5 — только options object)
|
||||
- `window.location` для search params
|
||||
|
||||
## Prefetch
|
||||
|
||||
`onMouseEnter` на `<Link>` → `queryClient.prefetchQuery(detailOptions(id))`
|
||||
|
||||
## DevTools
|
||||
|
||||
Только в dev: `TanStackRouterDevtools`, `ReactQueryDevtools`
|
||||
@@ -0,0 +1,251 @@
|
||||
---
|
||||
description: Opinionated best practices for fast, reliable Vitest unit and integration tests in JS/TS projects.
|
||||
globs: **/*.{js,ts,jsx,tsx}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Vitest Best Practices
|
||||
|
||||
Vitest is the definitive testing framework for our Vite-powered projects. It offers a fast, Jest-compatible API with deep integration into the Vite ecosystem. Adhering to these guidelines ensures our tests are robust, performant, and easy to maintain.
|
||||
|
||||
## 1. Code Organization & Naming
|
||||
|
||||
**Always co-locate test files with their source.** This improves discoverability and ensures tests are updated alongside their implementation.
|
||||
|
||||
* **File Naming**: Use `*.test.{ts,tsx,js,jsx}`.
|
||||
* **Location**: Place test files directly next to the component or module they test.
|
||||
|
||||
❌ BAD:
|
||||
```
|
||||
// src/components/Button/Button.tsx
|
||||
// tests/components/Button.test.tsx
|
||||
```
|
||||
|
||||
✅ GOOD:
|
||||
```typescript
|
||||
// src/components/Button/Button.tsx
|
||||
// src/components/Button/Button.test.tsx
|
||||
```
|
||||
|
||||
## 2. Test Structure & Isolation
|
||||
|
||||
**Organize tests logically using `describe` and `it` (or `test`) blocks.** Ensure each test is isolated and deterministic.
|
||||
|
||||
* **`describe`**: Group related tests into suites.
|
||||
* **`it` / `test`**: Define individual test cases. Prefer `it` for consistency with Jest.
|
||||
* **Hooks (`beforeEach`, `afterEach`)**: Use these for setup and teardown to ensure test isolation.
|
||||
|
||||
❌ BAD: (Shared state, no cleanup)
|
||||
```typescript
|
||||
let user;
|
||||
test('creates user', () => {
|
||||
user = createUser();
|
||||
expect(user).toBeDefined();
|
||||
});
|
||||
test('updates user', () => { // Depends on previous test
|
||||
user.name = 'New Name';
|
||||
expect(user.name).toBe('New Name');
|
||||
});
|
||||
```
|
||||
|
||||
✅ GOOD: (Isolated tests with hooks)
|
||||
```typescript
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { createUser, deleteUser } from './user-service';
|
||||
|
||||
describe('User Service', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = createUser(); // Create a fresh user for each test
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
deleteUser(user.id); // Clean up after each test
|
||||
});
|
||||
|
||||
it('should create a user', () => {
|
||||
expect(user).toBeDefined();
|
||||
expect(user.id).toBeTypeOf('string');
|
||||
});
|
||||
|
||||
it('should update a user', () => {
|
||||
user.name = 'Jane Doe';
|
||||
expect(user.name).toBe('Jane Doe');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 3. Asynchronous Testing with `vi.waitFor`
|
||||
|
||||
**Always use `vi.waitFor` for polling conditions in asynchronous tests.** Avoid arbitrary `setTimeout` calls or manual polling loops. `vi.waitFor` is designed for reliable synchronization.
|
||||
|
||||
❌ BAD: (Flaky, relies on arbitrary timeout)
|
||||
```typescript
|
||||
test('data loads after delay', async () => {
|
||||
let data = null;
|
||||
fetchData().then(res => (data = res));
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // Arbitrary wait
|
||||
expect(data).toEqual('some data');
|
||||
});
|
||||
```
|
||||
|
||||
✅ GOOD: (Reliable polling with `vi.waitFor`)
|
||||
```typescript
|
||||
import { it, expect, vi } from 'vitest';
|
||||
import { fetchData } from './api'; // Assume fetchData returns a Promise
|
||||
|
||||
it('should load data after delay', async () => {
|
||||
let data = null;
|
||||
fetchData().then(res => (data = res));
|
||||
|
||||
// Polls until data is not null, with a 2-second timeout
|
||||
await vi.waitFor(() => expect(data).not.toBeNull(), { timeout: 2000 });
|
||||
|
||||
expect(data).toEqual('some data');
|
||||
});
|
||||
```
|
||||
|
||||
## 4. Mocking Strategies
|
||||
|
||||
**Leverage Vitest's `vi` API for all mocking.** This provides Jest-compatible syntax and seamless integration. Always clean up mocks after each test.
|
||||
|
||||
* **`vi.fn()`**: Mock individual functions.
|
||||
* **`vi.spyOn()`**: Spy on existing object methods.
|
||||
* **`vi.mock()`**: Mock entire modules.
|
||||
|
||||
### Function Mocking
|
||||
|
||||
❌ BAD: (Manual mock, no easy reset)
|
||||
```typescript
|
||||
const originalFetch = global.fetch;
|
||||
global.fetch = () => Promise.resolve({ json: () => ({ id: 1 }) });
|
||||
// ... test ...
|
||||
global.fetch = originalFetch; // Easy to forget cleanup
|
||||
```
|
||||
|
||||
✅ GOOD: (Using `vi.fn` with `afterEach` cleanup)
|
||||
```typescript
|
||||
import { it, expect, vi, afterEach } from 'vitest';
|
||||
import { getUser } from './user-api';
|
||||
|
||||
// Mock the module containing fetchUser
|
||||
vi.mock('./user-api', async (importOriginal) => {
|
||||
const mod = await importOriginal();
|
||||
return {
|
||||
...mod,
|
||||
fetchUser: vi.fn(), // Mock specific function within the module
|
||||
};
|
||||
});
|
||||
|
||||
// Import the mocked function after vi.mock
|
||||
import { fetchUser } from './user-api';
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks(); // Clear mock calls after each test to prevent state leakage
|
||||
});
|
||||
|
||||
it('should fetch user data', async () => {
|
||||
fetchUser.mockResolvedValueOnce({ id: 1, name: 'Test User' });
|
||||
const user = await getUser(1);
|
||||
expect(fetchUser).toHaveBeenCalledWith(1);
|
||||
expect(user.name).toBe('Test User');
|
||||
});
|
||||
```
|
||||
|
||||
### Module Mocking
|
||||
|
||||
**Mock modules at the top of the file.** This ensures the mock is applied before the module under test imports it.
|
||||
|
||||
✅ GOOD: (Module mock before imports)
|
||||
```typescript
|
||||
import { vi, it, expect } from 'vitest';
|
||||
|
||||
// Mock the entire 'lodash' module to control its behavior
|
||||
vi.mock('lodash', () => ({
|
||||
debounce: vi.fn((fn) => fn), // Mock debounce to execute immediately
|
||||
}));
|
||||
|
||||
import { debounce } from 'lodash'; // Import the mocked debounce
|
||||
import { saveInput } from './input-handler'; // Module using debounce
|
||||
|
||||
it('should call save function without debounce delay', () => {
|
||||
saveInput('test');
|
||||
expect(debounce).toHaveBeenCalledOnce();
|
||||
});
|
||||
```
|
||||
|
||||
## 5. DOM Environment & Component Testing
|
||||
|
||||
**Use `happy-dom` for lightweight DOM environments.** It's generally faster and sufficient for most component tests. Switch to `jsdom` only if specific browser APIs are missing in `happy-dom`.
|
||||
|
||||
* Configure in `vite.config.ts` or `vitest.config.ts`.
|
||||
|
||||
✅ GOOD: (Configuring `happy-dom`)
|
||||
```typescript
|
||||
// vite.config.ts or vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'happy-dom', // Use happy-dom for faster DOM mocking
|
||||
globals: true, // Auto-import test APIs globally (e.g., describe, it, expect)
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 6. Performance & Concurrent Tests
|
||||
|
||||
**Utilize `.concurrent` for tests that can run in parallel.** This significantly speeds up test suites where tests are independent.
|
||||
|
||||
* Use `it.concurrent` for individual tests.
|
||||
* Use `describe.concurrent` for entire suites.
|
||||
* **Important**: When using `.concurrent`, always destructure `expect` from the test context to avoid issues with snapshot and assertion tracking.
|
||||
|
||||
❌ BAD: (Sequential tests, slow)
|
||||
```typescript
|
||||
describe('My Feature', () => {
|
||||
it('test A', async () => { /* ... */ });
|
||||
it('test B', async () => { /* ... */ });
|
||||
});
|
||||
```
|
||||
|
||||
✅ GOOD: (Concurrent tests, faster)
|
||||
```typescript
|
||||
import { describe, it } from 'vitest';
|
||||
|
||||
describe.concurrent('My Feature', () => {
|
||||
it('test A', async ({ expect }) => { // Destructure expect for concurrent tests
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
|
||||
it.concurrent('test B', async ({ expect }) => { // Destructure expect
|
||||
expect(2).toBe(2);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 7. Code Coverage
|
||||
|
||||
**Enable V8-based code coverage.** It offers near-zero overhead and integrates seamlessly.
|
||||
|
||||
* Add `coverage` configuration to `vite.config.ts` or `vitest.config.ts`.
|
||||
* Run with `vitest run --coverage`.
|
||||
|
||||
✅ GOOD: (V8 coverage configuration)
|
||||
```typescript
|
||||
// vite.config.ts or vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'happy-dom',
|
||||
globals: true,
|
||||
coverage: {
|
||||
provider: 'v8', // Use V8 for native, fast coverage
|
||||
reporter: ['text', 'json', 'html'], // Output formats for reports
|
||||
exclude: ['node_modules/', 'dist/', '.eslintrc.cjs'], // Exclude common directories from coverage
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user