Compare commits
201
Commits
116632aa9e
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68c5774df9 | ||
|
|
85ac5b15cb | ||
|
|
8ae07628c6 | ||
|
|
a4a8b9562d | ||
|
|
148ef8efb7 | ||
|
|
06d767fde7 | ||
|
|
a26647a52d | ||
|
|
d0e262c41d | ||
|
|
5e29ff7ec3 | ||
|
|
a185ffa659 | ||
|
|
f38694da63 | ||
|
|
80af888cdb | ||
|
|
0b9b1cad54 | ||
|
|
273883f657 | ||
|
|
78f413324a | ||
|
|
01a771407d | ||
|
|
e37f69ebf0 | ||
|
|
a70955f90d | ||
|
|
166abb03b1 | ||
|
|
9483e1122d | ||
|
|
e3403bcb56 | ||
|
|
b5300b9082 | ||
|
|
10b20b9d55 | ||
|
|
b3712af0c0 | ||
|
|
b00fb9345f | ||
|
|
e6ddb24964 | ||
|
|
a64d8b9755 | ||
|
|
5a61ce33b4 | ||
|
|
4dc5609f17 | ||
|
|
29325179f4 | ||
|
|
573ca097aa | ||
|
|
6e401533c7 | ||
|
|
adea0ab070 | ||
|
|
f53ca1ed7d | ||
|
|
d7bcd0d4ec | ||
|
|
ecd053b832 | ||
|
|
72aad059bd | ||
|
|
a9e3cce113 | ||
|
|
c844874b0a | ||
|
|
b03318800f | ||
|
|
7480b9dc11 | ||
|
|
235ae03489 | ||
|
|
60a22b9450 | ||
|
|
efebcaf16d | ||
|
|
9254b8dc22 | ||
|
|
a5f83c6ecb | ||
|
|
5847188dc7 | ||
|
|
ca7f37f980 | ||
|
|
d0853a7fd1 | ||
|
|
8ec1ccfecc | ||
|
|
5c43d88f1a | ||
|
|
9e0311b53a | ||
|
|
9214d6fbab | ||
|
|
e25317bba7 | ||
|
|
55014105f5 | ||
|
|
edf1da165a | ||
|
|
54f3587b45 | ||
|
|
c14cf3953e | ||
|
|
0177f5d539 | ||
|
|
619385981c | ||
|
|
d4d59f4b8e | ||
|
|
c5198cdd7f | ||
|
|
fd2d5f39c0 | ||
|
|
b8ee99ca03 | ||
|
|
033557a859 | ||
|
|
5bbf0061cf | ||
|
|
53d5ed5d33 | ||
|
|
72f9597ede | ||
|
|
c849dd5ff8 | ||
|
|
a02daba69b | ||
|
|
8cee00e6e3 | ||
|
|
fe85cdd18d | ||
|
|
78bde2da74 | ||
|
|
f11d82d89d | ||
|
|
9faec59338 | ||
|
|
8a4934940f | ||
|
|
d25beec289 | ||
|
|
b7a4a10333 | ||
|
|
11a0a85ff0 | ||
|
|
4c6240c636 | ||
|
|
83ed3b8a42 | ||
|
|
4dda77f118 | ||
|
|
6f0a238c95 | ||
|
|
42c567e5b5 | ||
|
|
670b5a4468 | ||
|
|
74af90270f | ||
|
|
e079a61811 | ||
|
|
2931d6ee0f | ||
|
|
5060b78875 | ||
|
|
03e951af18 | ||
|
|
54ffed6e57 | ||
|
|
51a52cbf79 | ||
|
|
7f3371591e | ||
|
|
e18d3b4ce6 | ||
|
|
56556ea736 | ||
|
|
1f24dbe2a5 | ||
|
|
72a51b22fd | ||
|
|
da818fe104 | ||
|
|
c90b42c9c4 | ||
|
|
a61b352f0a | ||
|
|
47c1f37912 | ||
|
|
e360efb885 | ||
|
|
f26b2c8777 | ||
|
|
812aa614df | ||
|
|
84cc800d54 | ||
|
|
f8e13a4a72 | ||
|
|
da97abce41 | ||
|
|
d0c515767a | ||
|
|
d04630a7d1 | ||
|
|
29e096862a | ||
|
|
267aa1c230 | ||
|
|
d52c633d90 | ||
|
|
9c43b45245 | ||
|
|
52665843d6 | ||
|
|
35e5d8430d | ||
|
|
a11fbc6efd | ||
|
|
77099b9ab0 | ||
|
|
0e9a6a6a21 | ||
|
|
2b01063279 | ||
|
|
eed73a39f0 | ||
|
|
eaa5db38bf | ||
|
|
ea9c996838 | ||
|
|
7c777bffc2 | ||
|
|
5c1c94bc57 | ||
|
|
447d499a8d | ||
|
|
0bcea39868 | ||
|
|
46267669ce | ||
|
|
5998887148 | ||
|
|
0682dd0e2f | ||
|
|
60332db1e4 | ||
|
|
9a29d88b95 | ||
|
|
ce6414c65f | ||
|
|
5983c911f2 | ||
|
|
87aa6949b3 | ||
|
|
579c00392d | ||
|
|
b15e10f117 | ||
|
|
031fc3aaa0 | ||
|
|
6c5eb04fae | ||
|
|
87ab13e9f6 | ||
|
|
f2c4c7a412 | ||
|
|
9be50aa03b | ||
|
|
05e7bf829e | ||
|
|
7f91bc3624 | ||
|
|
2c66d0c0ee | ||
|
|
31a4798d78 | ||
|
|
f3dbd85523 | ||
|
|
baa13260c6 | ||
|
|
246ad1738a | ||
|
|
52a8b0db85 | ||
|
|
18072fdeea | ||
|
|
f1fbd637a7 | ||
|
|
5a3241a7d1 | ||
|
|
2e1ca9f2a7 | ||
|
|
c80090ed08 | ||
|
|
06f5b27e53 | ||
|
|
11b67a1cef | ||
|
|
0ea92746e4 | ||
|
|
dc0650b61e | ||
|
|
5d83b1a95d | ||
|
|
921feb926f | ||
|
|
a53192207d | ||
|
|
1e291b3759 | ||
|
|
9df91bf2cc | ||
|
|
0de0538218 | ||
|
|
836c8a2936 | ||
|
|
a954591493 | ||
|
|
f681f72e8c | ||
|
|
4c7dfc45d6 | ||
|
|
a6f202f659 | ||
|
|
bbb6de29be | ||
|
|
af9bd44d44 | ||
|
|
b1315ffe0a | ||
|
|
63da57687d | ||
|
|
b87d7c6fad | ||
|
|
db1d26d324 | ||
|
|
a51b5f3594 | ||
|
|
6310ed8e02 | ||
|
|
7ef42ee7e8 | ||
|
|
ee5d5b228b | ||
|
|
e43cdc1b85 | ||
|
|
525eff0d09 | ||
|
|
8b38021200 | ||
|
|
bc0da072ee | ||
|
|
b2611d951b | ||
|
|
3e57728ddc | ||
|
|
3026f1d1ce | ||
|
|
6fbd1a9113 | ||
|
|
8408155be6 | ||
|
|
24e1b8f4ef | ||
|
|
0b4507e2d2 | ||
|
|
6bf270d715 | ||
|
|
92a986c858 | ||
|
|
a97ec8c121 | ||
|
|
c161eaf84a | ||
|
|
a4752d4379 | ||
|
|
f4517f03fc | ||
|
|
524cbba5e7 | ||
|
|
d717719e35 | ||
|
|
a1227a13e8 | ||
|
|
5cd11fc454 | ||
|
|
3c950db2ad |
No files matched your search
@@ -0,0 +1,67 @@
|
||||
---
|
||||
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,43 @@
|
||||
# Adapting installed ReUI code (reuse-first, no AI slop)
|
||||
|
||||
ReUI items ship production-quality. Your job is to **adapt by reuse** - wire real data and fit the app - not to redesign or hand-roll. The output should look like ReUI built it for this product.
|
||||
|
||||
## Preserve the design - don't over-customize
|
||||
|
||||
The design IS the product. A ReUI block/component encodes senior-designer decisions: spacing, hierarchy, density, color treatment, and component choices. The fastest way to turn a premium block back into generic AI slop is to "improve" its look - so don't.
|
||||
|
||||
- Change **data, copy, and props**; keep the **structure and styling** it ships with. Make the **smallest** change that wires the real data. If your diff touches `className` / JSX structure more than data / props, you are over-customizing - stop and reuse.
|
||||
- Don't swap ReUI components for hand-rolled ones, restructure the layout, re-skin spacing / radius / colors, or add decorative chrome. Let the installed components carry the default spacing, radius, sizing, icon rhythm, density, and state styling; add custom Tailwind only when a component genuinely lacks a contract you need.
|
||||
- Want a different look? `search` for a block whose design already fits and reuse that - don't restyle this one into a new design.
|
||||
|
||||
## Reuse the parts: examples and the block's own elements
|
||||
|
||||
- **Examples are building parts.** A free `c-*` example is a correct, single-pattern composition you can reuse. Before composing from scratch, `get_examples(component)`, install the closest one, and reuse its wiring - assemble UI from examples instead of hand-rolling what an example already shows.
|
||||
- **Reuse a block's own elements.** Need more rows, cards, items, or sections than ship by default? Repeat the block's **existing** element by mapping real data through the same markup - never invent parallel markup that drifts from its design. Need a variant (empty / loading / expanded)? Derive it from an element the block already has.
|
||||
|
||||
## Don't invent (read, don't guess)
|
||||
|
||||
- Never write a prop, variant value, import path, or `@reui/...` name you didn't read in a component's inline `api`, an installed example, or a `search` result. If you didn't see it, treat it as nonexistent - call `get_component` / `get_examples` / `search` first, or run the MCP `validate_usage` tool to check planned names + props against the docs before writing code.
|
||||
- If a getter returns `found: false` or `search` returns nothing, say so and fall back (plain shadcn, or ask) - never fabricate an install command or an API.
|
||||
|
||||
## What to change vs leave alone
|
||||
|
||||
- **Change:** the item's own data, copy, props, and layout to fit the app.
|
||||
- **Leave alone:** installed component files, hooks, and the shared theme - do not edit vendored ReUI internals; change behavior through props and the documented API.
|
||||
- Blocks are **portable React** - no `next/link`, `next/image`, or other framework-runtime imports inside them. Keep them portable.
|
||||
|
||||
## Demo data -> real data
|
||||
|
||||
- Replace every placeholder with the user's real data. Model it as **typed data structures** and **map over arrays** - never duplicate JSX per row/card. Keep small block-specific formatters next to the data.
|
||||
- Wire the real source (columns, fields, fetch). For `data-grid`, implement the server fetch contract if the user needs server-side data.
|
||||
- **Type from the component API, derive during render.** Type domain state through the component's own types - e.g. map status to `BadgeProps["variant"]` via a typed `Record<Status, …>` - instead of stringly-typed values. Compute view state during render; don't mirror derived data into `useState`/`useEffect`.
|
||||
- **Adapt on the right base.** Use the API for the project's base (Base UI vs Radix - see [components.md](./components.md)); the installed files are already base-correct, so reuse their shape rather than translating from memory.
|
||||
|
||||
## Believable content (no AI tells)
|
||||
|
||||
- Use realistic labels, counts, timestamps, and statuses that map to a real workflow.
|
||||
- No decorative buttons, fake tabs, meaningless toggles, equal-weight card walls, empty gradients, ornamental icons, or generic SaaS filler. Every element should do something.
|
||||
|
||||
## Operational surfaces (settings / profile / admin)
|
||||
|
||||
Pick ONE archetype and keep the family consistent: a vertical rail (3-6 sections), horizontal tabs (5-8), or a frame/stack. Prefer `frame` for tool-like surfaces, a card for profile-like ones. Don't mix archetypes in one surface.
|
||||
@@ -0,0 +1,60 @@
|
||||
# CLI: registry setup, license, non-interactive install
|
||||
|
||||
## Registry setup (one-time, per project)
|
||||
|
||||
Free items (the 20 components and all `c-*` examples) need only the plain string registry in `components.json`:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium items (blocks; Motion Icons and templates) require a ReUI license at install:
|
||||
|
||||
1. Add the key to `.env.local`:
|
||||
|
||||
```bash
|
||||
REUI_LICENSE_KEY=your-license-key
|
||||
```
|
||||
|
||||
2. Switch `components.json` to the authenticated object form:
|
||||
|
||||
```json
|
||||
{
|
||||
"registries": {
|
||||
"@reui": {
|
||||
"url": "https://reui.io/r/{style}/{name}.json",
|
||||
"headers": { "Authorization": "Bearer ${REUI_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
|
||||
|
||||
Use the project's package runner (check `packageManager`):
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes # npm
|
||||
pnpm dlx shadcn@latest add @reui/<name> --yes # pnpm
|
||||
bunx --bun shadcn@latest add @reui/<name> --yes # bun
|
||||
```
|
||||
|
||||
`--yes` skips confirmation prompts. The CLI auto-detects the package manager from the lockfile (there is no `--package-manager` flag). It also resolves the correct base+style variant from `components.json`, so do not pass a style.
|
||||
|
||||
## Handling prompts and conflicts
|
||||
|
||||
- **Always pass `--yes`** so the CLI does not block on confirmation prompts.
|
||||
- **Do NOT pass `--overwrite` by default.** If the CLI reports an existing file, read the output and resolve deliberately: install under a different name, adjust the path, or ask the user. Only use `--overwrite` when the user explicitly wants to replace a file.
|
||||
- **Preview first when touching an existing project**: `npx shadcn@latest add @reui/<name> --dry-run` shows what would change; `--diff <file>` shows a specific file's diff. Use these before overwriting.
|
||||
- Run from the **project root** so `components.json` and `.env.local` are found.
|
||||
|
||||
## Free vs premium boundary
|
||||
|
||||
- Public, no key: `c-*` examples and the 20 components (`@reui/data-grid`, `@reui/badge`, ...) that those examples depend on.
|
||||
- Key required at install: blocks (`@reui/<category>-N`) need a Pro or Ultimate license; Motion Icons (`@reui/icons/...`) and templates need Ultimate.
|
||||
|
||||
If an install 401/403s, the license key is missing, invalid, or the plan does not cover that resource (blocks: Pro or higher; icons and templates: Ultimate). Point the user to https://reui.io/account (their key) or https://reui.io/pricing (upgrade).
|
||||
@@ -0,0 +1,408 @@
|
||||
# ReUI components
|
||||
|
||||
The 21 ReUI building blocks: `alert`, `autocomplete`, `badge`, `cascader`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `icon-tile`, `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.
|
||||
|
||||
## data-grid (the flagship - read its API every time)
|
||||
|
||||
`data-grid` wraps TanStack Table v9. It is NOT a styled `<table>` and does NOT take `data`/`columns` props directly. The contract:
|
||||
|
||||
- Build a TanStack table instance with `useTable({ features: dataGridFeatures, ... })` (columns, data). `dataGridFeatures` is exported by the primitive and already bundles sorting, filtering, pagination, row selection, expanding, pinning, resizing and faceting, so there are no per-table row models to wire.
|
||||
- Pass that instance to `<DataGrid table={table} recordCount={total}>`.
|
||||
- Compose the body with `DataGridTable` inside `DataGrid`, and enable features through `tableLayout` (e.g. `{ headerSticky: true, columnsResizable: true }`), not ad-hoc classes.
|
||||
- Server-side data uses the documented fetch shape (`recordCount` is the total for pagination).
|
||||
|
||||
```tsx
|
||||
const table = useTable({
|
||||
features: dataGridFeatures,
|
||||
data,
|
||||
columns,
|
||||
})
|
||||
|
||||
<DataGrid table={table} recordCount={data.length}>
|
||||
<DataGridTable />
|
||||
</DataGrid>
|
||||
```
|
||||
|
||||
Common mistakes:
|
||||
|
||||
- **Incorrect:** `<DataGrid data={rows} columns={cols} />` - these props do not exist. **Correct:** build a `useTable({ features: dataGridFeatures, ... })` instance and pass `table={table}` + `recordCount`.
|
||||
- **Incorrect:** a raw `<table>` / 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 primitive's `DataGridColumnMeta` (e.g. `cellClassName`, `headerTitle`), set through the bundle's `columnMeta` slot.
|
||||
|
||||
## event-calendar
|
||||
|
||||
**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<EventCalendar defaultEvents={events} defaultView="month" className="h-[560px]">
|
||||
<EventCalendarNav />
|
||||
<EventCalendarContent />
|
||||
</EventCalendar>
|
||||
```
|
||||
|
||||
**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
|
||||
<Gantt defaultEvents={bars} resources={tasks} defaultScale="month" className="h-[480px]">
|
||||
<GanttNav />
|
||||
<GanttView />
|
||||
</Gantt>
|
||||
```
|
||||
|
||||
**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<string, T[]>`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Kanban value={cols} onValueChange={setCols} getItemValue={(i) => i.id}>
|
||||
<KanbanBoard>
|
||||
{Object.entries(cols).map(([id, items]) => (
|
||||
<KanbanColumn key={id} value={id}>
|
||||
<KanbanColumnHandle><h3>{id}</h3></KanbanColumnHandle>
|
||||
<KanbanColumnContent value={id}>
|
||||
{items.map((i) => (
|
||||
<KanbanItem key={i.id} value={i.id}>
|
||||
<KanbanItemHandle>{i.title}</KanbanItemHandle>
|
||||
</KanbanItem>
|
||||
))}
|
||||
</KanbanColumnContent>
|
||||
</KanbanColumn>
|
||||
))}
|
||||
</KanbanBoard>
|
||||
<KanbanOverlay><div className="bg-muted size-full rounded-md" /></KanbanOverlay>
|
||||
</Kanban>
|
||||
```
|
||||
|
||||
**Gotcha:** state is `Record<columnId, T[]>`. Each `KanbanColumnContent value` must match its parent `KanbanColumn value`. Omit `KanbanOverlay` and the drag preview silently breaks.
|
||||
|
||||
## sortable
|
||||
|
||||
**Required:** `value` (`T[]`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Sortable value={items} onValueChange={setItems} getItemValue={(i) => i.id}>
|
||||
{items.map((i) => (
|
||||
<SortableItem key={i.id} value={i.id}>
|
||||
<SortableItemHandle><GripVertical /></SortableItemHandle>
|
||||
{i.label}
|
||||
</SortableItem>
|
||||
))}
|
||||
</Sortable>
|
||||
```
|
||||
|
||||
**Gotcha:** a flat 1D reorder list (not columns - that is `kanban`). `getItemValue` must return a stable, unique string. Pass `layout="grid"` or `layout="nested"` for non-list layouts.
|
||||
|
||||
## filters
|
||||
|
||||
**Required:** `fields` (`FilterField[]`). The value is ONE `FilterQuery` tree - `query` + `onQueryChange`, or uncontrolled `defaultQuery`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const fields: FilterField[] = [
|
||||
{ id: "title", label: "Title", type: "text" },
|
||||
{
|
||||
id: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "archived", label: "Archived" },
|
||||
],
|
||||
},
|
||||
]
|
||||
const [query, setQuery] = useState<FilterQuery>(() => createFilterQuery())
|
||||
|
||||
<Filters fields={fields} query={query} onQueryChange={setQuery} />
|
||||
```
|
||||
|
||||
**Gotcha:** the state is a TREE, not a list of chips. `FilterQuery` is a group of rules joined by `and`/`or` and a group may hold another group, so `(A and B) or C` is expressible; a rule is `{ id, type: "rule", path: ["status"], operator, value }` and `path` is the whole nested attribute path, root first. The pre-rewrite API is GONE: there is no `filters`/`onChange` prop, no `FilterFieldConfig` (fields are `FilterField`, nested through their own `fields`, keyed `id` not `key`), and no `createFilter()` - it minted ids inside a pure function and broke hydration, so ids now come from `createFilterIdFactory(seed)` seeded off `useId`, and `createFilterQuery()` / `createFilterRule()` take one. Read the query back with `flattenFilterConditions` (`{ path, field, operator, values, negated }` per rule, incomplete rules skipped) and walk the tree yourself when the parentheses carry meaning - the primitive compiles nothing, no SQL, no query string.
|
||||
|
||||
`variant` picks the chrome over that one query: `"basic"`, the default, is the flat chip row for a toolbar over a table; `"advanced"` is the condition builder, hung off a trigger or rendered in place with `advancedMode="inline"`. Both read and write the same tree, so a saved view built in one opens in the other. Other props worth knowing before you hand-roll them: `size` is two rungs, `"sm" | "default"`, resolved per style (there is no `lg`); `reorderable` turns on drag and Alt+Arrow row moves in the builder; `onBeforeQueryChange` is the ONE veto point for every write (return `false` to refuse, it cannot rewrite); `editors` registers custom value editors a field selects by `editor` name; `labels` / `operatorLabels` own every rendered string; `pathCollapse` + `maxPathSegments` shorten deep attribute paths; `renderChip` / `renderValue` / `renderEmpty` replace rendered parts. On a field, `loadOptions` supplies async options with paging and `resolveValues` renders a chip restored from a saved view whose option was never loaded. Pairs naturally with `data-grid`.
|
||||
|
||||
## cascader
|
||||
|
||||
**Required:** `items` (a tree of `{ value, label, children? }`), plus the panel parts inside `CascaderContent`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Cascader items={items} value={value} onValueChange={setValue}>
|
||||
<CascaderTrigger render={<Button variant="outline" />}>
|
||||
<CascaderValue placeholder="Select an attribute" />
|
||||
</CascaderTrigger>
|
||||
<CascaderContent className="w-80">
|
||||
<CascaderPanel>
|
||||
<CascaderNav>
|
||||
<CascaderBreadcrumb />
|
||||
<CascaderInput />
|
||||
</CascaderNav>
|
||||
<CascaderEmpty />
|
||||
<CascaderList maxHeight={288}>
|
||||
<CascaderItems />
|
||||
</CascaderList>
|
||||
<CascaderStatus />
|
||||
</CascaderPanel>
|
||||
</CascaderContent>
|
||||
</Cascader>
|
||||
```
|
||||
|
||||
**Gotcha:** pressing a branch NAVIGATES, it does not select - only leaves are selectable until you pass `selectable="any"` or a predicate, and once a branch is selectable its chevron becomes the only way to open it. `CascaderInput` must stay inside `CascaderContent` (Base UI refills the query from the selection when the input sits outside the popup). Always include `CascaderStatus`: it is the live region announcing level changes, which the visual breadcrumb does not provide to screen readers. Accepts a flat adjacency list via `getParent` as well as nested `children`. `searchScope="deep"` searches every level and annotates results with their path; `multiple` gives checkbox rows; `inline` + a bare `CascaderPanel` embeds it with no popover.
|
||||
|
||||
The shape above is `mode="drill"`, the default. `mode="tree"` keeps the same parts (drop `CascaderBreadcrumb`, pass `showBack={false}`, drive expansion with `expanded`/`onExpandedChange`); `mode="columns"` REPLACES `CascaderList` + `CascaderItems` with a single `CascaderColumns`, and has no breadcrumb. Other props worth knowing before you hand-roll them: `cascade` (multi-select only, parent/child selection with indeterminate branches - pair it with `selectable="any"`, since a leaf-only tree can never cascade), `indicator={false}` to drop the single-select check and its gutter (visual only, no-op with `multiple`), `virtualize`/`virtualizeThreshold` plus `CascaderVirtualItems` for long levels, and `getChildren` for async levels with cursor paging, retry on failure and optional `prefetch`. `CascaderFooter` pins commands below the list (`actions` is the quick path) and `CascaderSubmenu` opens one as a side-anchored flyout with the full menu keyboard model. To head a run of rows use `CascaderGroup` wrapping a `CascaderLabel` - a bare label inside a listbox names nothing and is dropped from the accessibility tree - and `CascaderSeparator` for the rule between runs. Every rendered string comes from `labels`, and the panel is RTL-correct under a `DirectionProvider` or `dir="rtl"`.
|
||||
|
||||
## date-selector
|
||||
|
||||
**Required:** none, but wire `onChange` to capture the value.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const [value, setValue] = useState<DateSelectorValue | undefined>()
|
||||
|
||||
<DateSelector value={value} onChange={setValue} label="Due date" />
|
||||
```
|
||||
|
||||
**Gotcha:** the value is a structured `DateSelectorValue` (period / operator / start+end dates), NOT a `Date` - never pass a raw `Date`. Use `allowRange={false}` to lock single-date picking. Read `get_component("date-selector")` for the value shape.
|
||||
|
||||
## tree
|
||||
|
||||
**Required:** `tree` (a `@headless-tree/core` instance you construct)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Tree tree={tree}>
|
||||
{tree.getItems().map((item) => (
|
||||
<TreeItem key={item.getId()} item={item}>
|
||||
<TreeItemLabel />
|
||||
</TreeItem>
|
||||
))}
|
||||
</Tree>
|
||||
```
|
||||
|
||||
**Gotcha:** `Tree` is a styled shell - it takes a headless-tree instance via `tree`, NOT `data`/`items` props. Build the instance with `@headless-tree/react`. External API: https://headless-tree.lukasbach.com/
|
||||
|
||||
## stepper
|
||||
|
||||
**Required:** `StepperItem step` (number), `StepperContent value` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Stepper defaultValue={1}>
|
||||
<StepperNav>
|
||||
<StepperItem step={1}>
|
||||
<StepperTrigger><StepperIndicator>1</StepperIndicator></StepperTrigger>
|
||||
<StepperSeparator />
|
||||
</StepperItem>
|
||||
<StepperItem step={2}>
|
||||
<StepperTrigger><StepperIndicator>2</StepperIndicator></StepperTrigger>
|
||||
</StepperItem>
|
||||
</StepperNav>
|
||||
<StepperPanel>
|
||||
<StepperContent value={1}>Step 1 content</StepperContent>
|
||||
<StepperContent value={2}>Step 2 content</StepperContent>
|
||||
</StepperPanel>
|
||||
</Stepper>
|
||||
```
|
||||
|
||||
**Gotcha:** steps are 1-indexed. Without `StepperPanel` + `StepperContent` you render the nav trail but no body. Put `StepperSeparator` in every `StepperItem` except the last.
|
||||
|
||||
## timeline
|
||||
|
||||
**Required:** `TimelineItem step` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Timeline>
|
||||
<TimelineItem step={1}>
|
||||
<TimelineHeader>
|
||||
<TimelineDate>March 2024</TimelineDate>
|
||||
<TimelineTitle>Project initialized</TimelineTitle>
|
||||
</TimelineHeader>
|
||||
<TimelineIndicator />
|
||||
<TimelineSeparator />
|
||||
<TimelineContent>Repo and architecture set up.</TimelineContent>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
```
|
||||
|
||||
**Gotcha:** each item needs a unique `step`. `orientation` is `"vertical"` (default) or `"horizontal"`. This is a static event display, not interactive like `stepper`.
|
||||
|
||||
## autocomplete
|
||||
|
||||
**Required:** `items` (array; each item has at least `value`)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Autocomplete items={items}>
|
||||
<AutocompleteInput placeholder="Search..." />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No results found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.value} value={item}>{item.label}</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
```
|
||||
|
||||
**Gotcha:** `AutocompleteList` takes a render-prop `(item) => ReactNode`, NOT a mapped array of children. External API: https://base-ui.com/react/components/autocomplete
|
||||
|
||||
## phone-input
|
||||
|
||||
**Required:** none, but wire `onChange`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<PhoneInput placeholder="Enter phone number" defaultCountry="US" value={value} onChange={setValue} />
|
||||
```
|
||||
|
||||
**Gotcha:** `value`/`onChange` use an E.164 string (e.g. `"+14155551234"`), not a display-formatted string; `onChange` can fire `undefined`. `defaultCountry` is a 2-letter ISO code. Wraps `react-phone-number-input`.
|
||||
|
||||
## number-field
|
||||
|
||||
**Required:** wrap the controls in `NumberFieldGroup`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<NumberField defaultValue={0}>
|
||||
<NumberFieldScrubArea label="Quantity" />
|
||||
<NumberFieldGroup>
|
||||
<NumberFieldDecrement />
|
||||
<NumberFieldInput />
|
||||
<NumberFieldIncrement />
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
```
|
||||
|
||||
**Gotcha:** import from `@/components/ui/number-field`. The accessible label goes on `NumberFieldScrubArea`, not `NumberField`. External API: https://base-ui.com/react/components/number-field
|
||||
|
||||
## rating
|
||||
|
||||
**Required:** `rating` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Rating rating={4.5} showValue editable onRatingChange={setRating} />
|
||||
```
|
||||
|
||||
**Gotcha:** supports decimals (partial stars). Pass `editable` + `onRatingChange` for interactive input; omit both for a read-only display.
|
||||
|
||||
## scrollspy
|
||||
|
||||
**Required:** `targetRef` (the scroll container ref)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Scrollspy targetRef={containerRef}>
|
||||
<a href="#s1" data-scrollspy-anchor="s1">Section 1</a>
|
||||
<a href="#s2" data-scrollspy-anchor="s2">Section 2</a>
|
||||
</Scrollspy>
|
||||
<div ref={containerRef}>
|
||||
<div id="s1">...</div>
|
||||
<div id="s2">...</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Gotcha:** each link's `data-scrollspy-anchor` must match a section `id`. `targetRef` is the scrollable container (defaults to the window).
|
||||
|
||||
## frame
|
||||
|
||||
**Required:** `Frame` > `FramePanel`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Frame>
|
||||
<FramePanel>
|
||||
<FrameHeader>
|
||||
<FrameTitle>Title</FrameTitle>
|
||||
<FrameDescription>Description</FrameDescription>
|
||||
</FrameHeader>
|
||||
<div className="p-5">Content</div>
|
||||
<FrameFooter>Footer</FrameFooter>
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
```
|
||||
|
||||
**Gotcha:** a structured card shell for tool-like surfaces. `stacked` connects multiple panels with shared borders; `dense` removes panel padding; radius via the `--frame-radius` CSS variable.
|
||||
|
||||
## icon-stack
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconStack aria-hidden="true">
|
||||
<InboxIcon className="size-4" />
|
||||
</IconStack>
|
||||
```
|
||||
|
||||
**Gotcha:** isometric layered artwork for empty states and illustrations; style the inner icon via its own `className`. Mark purely decorative stacks `aria-hidden="true"` and keep the real label in surrounding copy.
|
||||
|
||||
## icon-tile
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconTile variant="elevated" size="lg">
|
||||
<PackageIcon />
|
||||
</IconTile>
|
||||
```
|
||||
|
||||
**Gotcha:** the square container an icon sits in, so every list row, feature card and empty state shares one affordance. `variant`: `outline` (default) | `elevated` (muted fill, raised ring) | `soft` (tinted nested, tone from currentColor) | `solid` (filled tone, contrasting glyph) | `frame` (double container). `soft` and `solid` retint from one text color class (they default to `text-primary`). `size`: `xs | sm | default | lg | xl` (24/32/40/48/64px tile, glyph scales 12/14/16/20/24px). `radius`: `default | full`. Do not set a `size-*` class on the child icon unless you mean to override the tile's glyph size; recolor with `className` on the tile, not the icon.
|
||||
|
||||
## alert
|
||||
|
||||
**Required:** `Alert` > `AlertTitle`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Alert variant="success">
|
||||
<ShieldCheckIcon />
|
||||
<AlertTitle>Security update</AlertTitle>
|
||||
<AlertDescription>Enable two-factor authentication.</AlertDescription>
|
||||
<AlertAction><Button size="xs">Update</Button></AlertAction>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible API. `variant`: `default | destructive | info | success | warning | invert`. The non-default variants use ReUI extended color tokens (`--success`/`--info`/`--warning`/`--invert`), which the install adds. Defer generic alert rules to the shadcn skill.
|
||||
|
||||
## badge
|
||||
|
||||
**Required:** none (text child).
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Badge variant="success-light" size="sm">Success</Badge>
|
||||
<Badge variant="outline" radius="full">Pill</Badge>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible. Rich `variant` set (solid, `-outline`, `-light` per color), `size` `xs..xl`, `radius` `default | full`. Like `alert`, the color variants rely on ReUI extended tokens. Prefer `Badge` variants over raw color classes for statuses.
|
||||
|
||||
## base vs radix - write for the project's base
|
||||
|
||||
ReUI ships every component in two builds: `base` (Base UI) and `radix` (Radix UI). The install command and name are identical, and the CLI installs the build matching the project. But you must write/adapt code against the **right base**, because their APIs differ.
|
||||
|
||||
**Detect the base first.** Read `components.json` -> `style` and take the segment before the first `-`:
|
||||
|
||||
- `"style": "base-nova"` -> **Base UI**
|
||||
- `"style": "radix-nova"` -> **Radix UI**
|
||||
|
||||
**Then use that base's API.** The deltas mirror shadcn's base-vs-radix split:
|
||||
|
||||
- Slot/composition: Base UI `render={<… />}` vs Radix `asChild`.
|
||||
- `Select`: Base UI takes `items`; Radix uses `<SelectItem>` children.
|
||||
- `ToggleGroup`: Base UI `multiple` boolean vs Radix `type="single" | "multiple"`.
|
||||
|
||||
The safest path is to **read the installed files and `c-*` examples** - they're already in your base, so reuse their wiring instead of guessing. When `get_component`'s inline `api` or an example shows the other base's shape, translate it to your base (or `validate_usage` to confirm). Defer the generic base/radix mechanics to the shadcn skill.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Craft: make ReUI UI exceptional, not generic
|
||||
|
||||
ReUI items ship senior-designer quality. Your adaptation has to hold that bar, so the result reads like a real product surface a team would keep - not a wireframe an AI generated. Use these alongside the reuse rules in [adapting.md](./adapting.md).
|
||||
|
||||
## Have a point of view
|
||||
|
||||
Pick an emotional register before you compose - calm, operational, premium, editorial, dense, energetic - and let layout, spacing, surface treatment, and icon behavior all reinforce it. One or two memorable decisions and restraint everywhere else beats ten generic ones. UI with no point of view reads as generated.
|
||||
|
||||
## Brutally clear hierarchy
|
||||
|
||||
One focal point per card or panel: the dominant metric or task first, its label second, supporting detail third. The first thing the eye lands on should be the right thing; secondary text must read as secondary. Borders, separators, and surfaces do real work to create 2-3 information bands - don't flatten everything to equal weight.
|
||||
|
||||
## Spacing rhythm and deliberate density
|
||||
|
||||
Gaps are a signal, not a default. Keep them intentional and consistent within a family (`gap-1`/`gap-2` for tight operational rows, larger gaps for section breaks), and smaller within a group than between groups. Match the surrounding ReUI density; don't pad an operational surface like a marketing page, and don't drift density mid-section. The composition should still feel authored in grayscale.
|
||||
|
||||
## Cover the real states (the usual miss)
|
||||
|
||||
A surface isn't done at the happy path. Compose, and wire:
|
||||
|
||||
- **Empty** - a purposeful empty state (short message + the primary action), never a blank panel.
|
||||
- **Loading** - a **skeleton** that matches the real layout, not a centered spinner.
|
||||
- **Error** - an inline, recoverable error with a retry, announced via `role="status"`/`aria-live`.
|
||||
|
||||
Derive these from an element the block already has (don't invent parallel markup), or `get_examples` for a state-specific example.
|
||||
|
||||
## Responsive by default
|
||||
|
||||
Mobile-first, not mobile-afterthought. In constrained rows/cards/sidebars, put `min-w-0` on the shrinking container and `truncate` long single-line labels; protect the primary label's width and let secondary content compress. Reflow layouts (multi-column -> single column) rather than just shrinking them. Desktop and mobile should both look designed.
|
||||
|
||||
## Motion, subtly
|
||||
|
||||
Motion should clarify, not decorate. Use ReUI Motion Icons on primary actions for a subtle hover cue; keep transitions short (~200-300ms) with calm easing; prefer a skeleton pulse over a spinner. No bouncing, no gratuitous entrance animations on every element.
|
||||
|
||||
## Real, activated content
|
||||
|
||||
Use believable, typed data (realistic labels, counts, timestamps, statuses that map to a real workflow) - never lorem or abstract filler. Every visible control does something: no decorative buttons, fake tabs, meaningless toggles, or stats with no job. It must still hold with long names, empty values, and crowded data.
|
||||
|
||||
## Avoid the AI tells
|
||||
|
||||
These instantly read as generated - don't ship them: equal-weight card walls, empty gradients, repetitive padding everywhere, generic enterprise copy, ornamental icons, and number tiles that don't earn their place.
|
||||
|
||||
## The bar
|
||||
|
||||
Before you finish, ask: **would a product team keep this instead of replacing it? Does it still feel strong after swapping in real content?** If not, reuse the shipped ReUI design harder - don't restyle it into something new - then run the [quality.md](./quality.md) gates.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Icons (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn icon rules (use the project's configured `iconLibrary`, `data-icon` on icons inside `Button`, no sizing classes on icons inside components, pass icons as component objects not string keys). ReUI adds the following.
|
||||
|
||||
## Portable icons (library-agnostic)
|
||||
|
||||
ReUI components, examples, and blocks are authored to be icon-library-agnostic. When `iconLibrary` is set in `components.json`, the shadcn CLI installs each item's icons in **your** library automatically - you swap nothing. If an installed item's icons don't match your project (for example `iconLibrary` isn't set, so they came in from the item's demo library), change the **import source and component name** to your library, keeping the same icon-name semantics:
|
||||
|
||||
- `lucide` -> `lucide-react`
|
||||
- `tabler` -> `@tabler/icons-react`
|
||||
- `phosphor` -> `@phosphor-icons/react`
|
||||
- `remix` -> `@remixicon/react`
|
||||
- `hugeicons` -> `@hugeicons/react`
|
||||
|
||||
Don't assume `lucide-react`; read `iconLibrary` from `components.json`.
|
||||
|
||||
## Keep icons purposeful
|
||||
|
||||
Icons support the hierarchy, they don't replace it: keep them small, matched to the surrounding density, and decorative ones `aria-hidden="true"` (an icon-only control still needs an accessible label on the control). Don't add ornamental icons that do no job.
|
||||
|
||||
## Motion Icons (the `@reui/icons/...` set)
|
||||
|
||||
ReUI ships its own icon set in 4 styles (outline, solid, duotone, filled), each icon in two variants:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/icons/default/<style>/<name> --yes # static
|
||||
npx shadcn@latest add @reui/icons/animated/<style>/<name> --yes # hover-animated (motion/react)
|
||||
```
|
||||
|
||||
Finding them via the MCP is free; installing requires an Ultimate license (`REUI_LICENSE_KEY`, see [cli.md](./cli.md)). Reach for a Motion Icon on a primary action when a subtle hover cue helps; keep motion restrained.
|
||||
|
||||
Finding icons:
|
||||
|
||||
- Several icons (the common case): **`search_icons(concepts[])`** - up to 24 concepts in one call, the best icons per concept with install commands. Pass `animated: true` to get only icons with a hover-animated Motion variant.
|
||||
- One icon: `search` with `type: "icon"`.
|
||||
- Icon results and `get_icon` carry `animated: true` and `installAnimated` when an animated variant exists - use those install strings, do not construct paths by hand.
|
||||
- Every icon result carries a `previewUrl` (its live icon-category page) - **share it with the user** so they can SEE the icon before installing.
|
||||
|
||||
The `icon-stack` component composes multiple icons into a stacked display.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Quality gates (security, accessibility, scroll)
|
||||
|
||||
These are the **done gate**, not a nice-to-have: before you call any ReUI work finished, call the MCP `get_audit_checklist` tool and pass every item below (plus the craft bar in [craft.md](./craft.md)). Then typecheck and lint.
|
||||
|
||||
## Security
|
||||
|
||||
- Never `dangerouslySetInnerHTML`. Render data as text/components.
|
||||
- External links (`target="_blank"`) must always pair `rel="noopener noreferrer"`.
|
||||
- No real PII, secrets, or tokens in demo or committed code. Remote media only from sources the project already allows.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Implicit list/card items that navigate get real anchors with a standard hover affordance.
|
||||
- Icon-only or numeric buttons need an `aria-label`; decorative icons get `aria-hidden`.
|
||||
- Every non-submit button is `type="button"`.
|
||||
- Keyboard + focus: everything interactive is reachable in a sensible Tab order with a visible focus ring; layers (dialogs/sheets/menus) trap focus and close on `Escape`. ReUI components ship standard keyboard behavior - read each component's inline `api` rather than re-implementing it.
|
||||
- Announce async UI: loading and error messages use `role="status"` / `aria-live` so they're not silent to screen readers.
|
||||
|
||||
## Scroll mechanics
|
||||
|
||||
- Make scroll regions with a parent-owned height: a `min-h-0` + flex chain down to the scroll container. Never guess a `max-h`.
|
||||
- The scroll container owns `overflow-auto`; ancestors stay `min-h-0` so the height resolves.
|
||||
@@ -0,0 +1,35 @@
|
||||
# ReUI registry structure
|
||||
|
||||
ReUI is a shadcn-compatible registry with four entity types. **Examples and blocks are built FROM components** - reuse them, don't rebuild.
|
||||
|
||||
- **component** - one of the 20 ReUI building blocks with a real API (`data-grid`, `kanban`, `filters`, `date-selector`, `tree`, ...). Install directly (`@reui/data-grid`) or let it come in as a dependency of an example/block. Free. Read its API with `get_component(name)`.
|
||||
- **example** - a free `c-*` single-pattern use-case of a component (`c-kanban-1`, `c-data-grid-3`). Install one and read it to copy real composition. Find a component's examples with `get_examples(name)`.
|
||||
- **block** - a premium, full-page section that composes several components (`data-grid-2`, `pricing-page-1`). Pro or Ultimate license at install. Adapts to your active theme via semantic tokens.
|
||||
- **icon** - Motion Icons in 4 styles (outline, solid, duotone, filled), static (`@reui/icons/default/<style>/<name>`) and hover-animated (`@reui/icons/animated/<style>/<name>`). Ultimate license at install. See [icons.md](./icons.md).
|
||||
|
||||
## The @reui registry
|
||||
|
||||
Install everything through the shadcn CLI: `npx shadcn@latest add @reui/<name> --yes`. The CLI reads the `@reui` registry from the project's `components.json`. Free items need only the plain string form:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium installs need the authenticated form + `REUI_LICENSE_KEY` in `.env.local` - see [cli.md](./cli.md). The MCP `get_project_context` tool returns the right config.
|
||||
|
||||
## Know your base: base or radix
|
||||
|
||||
ReUI ships every item in two builds - `base` (Base UI) and `radix` (Radix UI) - with mirrored names. The CLI installs the build matching your project automatically, but **you must write code against the right base's API**. Detect it from `components.json` -> `style`: the segment before the first `-` is the base (`base-nova` -> Base UI, `radix-nova` -> Radix UI). The installed files and `c-*` examples are already in your base - read them and adapt on that base. See [components.md](./components.md) for the API deltas.
|
||||
|
||||
**So the MCP's own `docsUrl` and `previewUrl` match your base**, send your `style` to the MCP: add `?style=<your components.json style>` to the ReUI MCP server URL (or set an `X-Reui-Style` header) in your MCP client config - set once, applies to every call. The MCP then resolves docs/preview links to YOUR library (`/docs/components/radix/...`, `/preview/radix/...` for a radix project) instead of the default base; `get_project_context` echoes back the style it currently sees so you can confirm it. Install commands are unaffected (the CLI already installs the right variant). If you notice the MCP returning `base` links for a `radix` project, tell the user to add `?style=` to the server URL.
|
||||
|
||||
Blocks adapt to your active theme through semantic tokens and CSS variables - change the theme and every block follows.
|
||||
|
||||
## Free vs premium
|
||||
|
||||
- **Free, no key:** the 20 components, all `c-*` examples, the ReUI MCP, and this skill.
|
||||
- **Premium, license required at install:** blocks (Pro or Ultimate), Motion Icons and templates (Ultimate). Set `REUI_LICENSE_KEY` (see [cli.md](./cli.md)).
|
||||
|
||||
## Component API index
|
||||
|
||||
The canonical index of every component's API docs is **https://reui.io/llms.txt** (returned as `componentsApiUrl` in MCP results). Prefer the inline `api` from `get_component`; use the index/docs as the fallback.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Styling (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn skill for the generic rules (semantic colors not raw values, `gap-*` not `space-y-*`, `size-*`, `cn()`, no manual `dark:` overrides, no overlay `z-index`). This file is only the ReUI-specific additions.
|
||||
|
||||
## ReUI extended semantic tokens
|
||||
|
||||
ReUI adds semantic tokens beyond shadcn's base set. Use these instead of raw colors for status and emphasis:
|
||||
|
||||
- `--success` / `--success-foreground`
|
||||
- `--info` / `--info-foreground`
|
||||
- `--warning` / `--warning-foreground`
|
||||
- `--destructive-foreground` (paired with shadcn's `--destructive`)
|
||||
- `--invert` / `--invert-foreground` (inverted surfaces)
|
||||
|
||||
Use them as Tailwind utilities (`bg-success text-success-foreground`, `text-warning`, ...). They are defined in the project's global CSS and registered with Tailwind (`@theme inline` on v4). If a token is missing in the project, add it to the global CSS file (never a new file) following the same `name` / `name-foreground` convention, exactly as the shadcn customization rules describe.
|
||||
|
||||
**Incorrect:** `<span className="text-green-600">Active</span>`
|
||||
**Correct:** `<Badge variant="success">Active</Badge>` or `<span className="text-success">Active</span>`
|
||||
|
||||
## Blocks follow your theme
|
||||
|
||||
When you install a block it adapts to your active theme through the semantic tokens above and the project's CSS variables. Don't hardcode style-specific values into installed block code and don't fork it to "restyle" - change the theme via the CSS variables / a preset and every block follows. Want a different look? `search` for a block whose design already fits instead of re-skinning one.
|
||||
|
||||
## Density and typography rhythm
|
||||
|
||||
ReUI operational UI usually feels dense, not airy. Keep the gap between a title and its supporting description tight by default (`gap-0.5`, `space-y-1`, or `space-y-px`), and smaller than the gap between sections. Match the surrounding ReUI density when you add rows or fields; do not pad operational surfaces like a marketing page.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Workflow: find -> install -> read API -> adapt
|
||||
|
||||
The core ReUI loop. The MCP tells you what to install and gives you the API; the shadcn CLI installs it; you turn the installed files into correct, themed, data-wired code by **reuse**, not redesign.
|
||||
|
||||
## 1. Find (ReUI MCP `search` / `compose_page`)
|
||||
|
||||
**Full multi-section page ask?** Call `compose_page(intent, sections?)` FIRST, before searching block-by-block. It returns ordered sections, each with the best block for the intent (top pick + alternates); sections listed in `unavailableSections` have no real inventory - compose those from components, do not force a bad block.
|
||||
|
||||
For everything else, call `search` with the user's intent. Pass structured hints whenever you can infer them - you are an LLM, so do the parsing the server cannot:
|
||||
|
||||
- `type`: `"component"` (one of the 20 building blocks), `"example"` (a c-\* use-case), `"block"` (a full page/section), `"icon"`.
|
||||
- `component`: the ReUI component the request implies (`"data-grid"`, `"kanban"`, ...).
|
||||
- `category`, `features` (e.g. `["sortable","pagination"]`), `free`.
|
||||
|
||||
Example: "build a users management page with filters" -> `search({ query: "users management page with filters", type: "block", component: "data-grid", features: ["filters"] })`.
|
||||
|
||||
Each result has `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `termCoverage`, and `whyMatch`. `score` is relative to the top hit (the top is ~100 by construction), not an absolute quality - compare results to each other, and show the user the top options if several score closely; do not silently guess. A low `termCoverage` means a weak match even with a high score - rephrase or widen.
|
||||
|
||||
**Always show the preview link.** Whenever you list or recommend items - from `search`, `search_icons`, `list_components`, `compose_page`, or a getter - include each item's `previewUrl` (a live preview page) so the user can SEE it before you install. Blocks and examples link to an individual live preview; icons and components to their live category/component page. This applies to every listing, not only a single pick.
|
||||
|
||||
## 2. Install (shadcn CLI)
|
||||
|
||||
Run the result's `install` command from the project root, non-interactively:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes
|
||||
```
|
||||
|
||||
The CLI reads `components.json`, installs the correct base+style variant, resolves `registryDependencies` (a block pulls in its components), installs npm deps, and rewrites aliases. Do not pass the base/style. See [cli.md](./cli.md).
|
||||
|
||||
## 3. Read the API (do not guess props)
|
||||
|
||||
Before writing code against any component an item uses:
|
||||
|
||||
1. The item's `componentDigests` already give a 1-line contract per component - often enough to wire it. For the full API, call **`get_component(names)`** with ALL of `componentsUsed` in ONE call (it accepts an array) and read each inline `api` - no web fetch. **Share the component's `docsUrl`** (its 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.
|
||||
2. Call **`get_examples(name)`** for the free `c-*` examples of that component; install one and **read the added files** to copy the exact composition. This is the fastest correct path - the example shows real wiring you adapt, not invent.
|
||||
3. About to write a prop you did not see in an `api` or installed file? Run **`validate_usage`** BEFORE writing the code - per-prop documented / notDocumented verdicts plus did-you-mean suggestions. notDocumented means read the API, not push on.
|
||||
|
||||
## 4. Adapt (reuse-first) - do not skip
|
||||
|
||||
Installing files is not the end, and redesigning them defeats the point. First note the project's **base** so you write the right API - read `components.json` -> `style` and take the segment before the first `-` (`base-nova` -> Base UI, `radix-nova` -> Radix UI), see [components.md](./components.md). After `add`:
|
||||
|
||||
1. **Read the added files**; keep the composition intact. For a block, verify the components are wired correctly (for `data-grid`: a `useTable({ features: dataGridFeatures, ... })` instance passed as `table`, `recordCount` set - see [components.md](./components.md)).
|
||||
2. **Replace demo data with the user's real data** via typed structures (see [adapting.md](./adapting.md)).
|
||||
3. **Fix icon imports** to the project's icon library (see [icons.md](./icons.md)).
|
||||
4. **Align styling** to semantic tokens and the active theme - no raw colors (see [styling.md](./styling.md)).
|
||||
5. **Validate before finalizing**: if your adaptation introduced components or props you did not read in an `api` or example, run `validate_usage` on them.
|
||||
6. **Hit the craft bar** - clear hierarchy, deliberate density, the empty / loading / error states, subtle motion, and mobile-first responsiveness (see [craft.md](./craft.md)). Generic-looking output means you under-reused the design, not that it needs restyling.
|
||||
7. **Pass the quality gates** (security, a11y, scroll) - call the MCP `get_audit_checklist` tool and clear every item (see [quality.md](./quality.md)).
|
||||
8. **Typecheck / lint**.
|
||||
|
||||
## If no single block fits
|
||||
|
||||
Compose from components (`compose_page` tells you which sections have no block inventory via `unavailableSections`). `search` the components you need, read each `get_component` API, install a worked `get_examples` example for each, and assemble by adapting those examples. A block in the same category is a useful reference - install it and read its files to see how ReUI composes those components, then adapt.
|
||||
@@ -0,0 +1,58 @@
|
||||
# ReUI MCP: full reference
|
||||
|
||||
The ReUI MCP (`https://mcp.reui.io`, Streamable HTTP) is free to use but needs a ReUI account: on first use the agent signs in with ReUI (a free account is created if the user has none), so every request is tied to an account. Free covers components and examples; a Pro or Ultimate license unlocks premium blocks and Motion Icons and removes the daily request limit. It does **discovery + guidance** (search, inline APIs, page planning, validation) and never serves source; the shadcn CLI does **installation**, and the license key lives there (the `@reui` entry in `components.json`, backed by `.env.local`). Goal: from the user's intent to correct, themed, data-wired ReUI code in the **fewest tokens and calls**, with **no guessing**.
|
||||
|
||||
## Golden path (token-optimal - follow this order)
|
||||
|
||||
Most tasks need 2-4 MCP calls and ZERO web fetches:
|
||||
|
||||
1. **`search(query, ...hints)`** -> pick the top 1-3 results. Each result already carries `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `whyMatch`. The payload is complete - do not call another tool just to "confirm" a result.
|
||||
2. **`get_component([...componentsUsed])`** in ONE batched call (one name or an array of up to 20) -> read each inline `api`. This **replaces** fetching docs pages. Often skippable: search responses carry `componentDigests`, a compact API contract per referenced component.
|
||||
3. **`get_examples(component)`** -> install ONE returned `c-*` example, read the added files, copy the composition.
|
||||
4. **`get_install_command(item)`** only to validate a name you are unsure of (results already include `install`). Run the install with the shadcn CLI (`--yes`).
|
||||
5. **`get_audit_checklist()`** before declaring done.
|
||||
|
||||
If you already know the exact item name, skip `search`. Everything else is situational.
|
||||
|
||||
## The 5 task-specific tools (when to reach for each)
|
||||
|
||||
- **`compose_page`** - BEFORE building any full page (dashboard, settings, billing, landing). Pass the intent (and optionally the sections you want); it returns ordered sections, each with the best premium block for the intent (top pick + alternates). Sections with no real inventory are listed honestly in `unavailableSections` - compose those from components instead of forcing a bad block.
|
||||
- **`search_icons`** - whenever you need icons, especially several. Batch up to 24 concepts in one call; each concept returns its best icons with install commands. Pass `animated: true` to get only icons that have a hover-animated Motion variant.
|
||||
- **`validate_usage`** - BEFORE writing code with component names or props you have not read in an inline `api` or an installed example. It checks planned names + props against the indexed API docs and registry item names; returns did-you-mean suggestions and per-prop documented / notDocumented verdicts. Deterministic, no inference - a notDocumented prop means stop and read the API, not push on.
|
||||
- **`whats_new`** - when your registry knowledge might be stale (a name 404s, the user mentions an item you don't know). Returns items added/removed per build, newest first.
|
||||
- **`report_issue`** - when an installed item is actually broken (bad source, wrong dependency, broken preview). Goes straight to the ReUI team; rate-limited 5/hour. Not for usage questions.
|
||||
|
||||
## All 19 tools
|
||||
|
||||
`search`, `get_block`, `get_example`, `get_icon`, `list_block_groups`, `list_block_categories`, `list_example_categories`, `list_icon_categories`, `list_components`, `get_component`, `get_examples`, `search_icons`, `compose_page`, `validate_usage`, `whats_new`, `report_issue`, `get_install_command`, `get_project_context`, `get_audit_checklist`. The MCP serves the full parameter schemas; do not guess parameters beyond them.
|
||||
|
||||
## Token + speed rules
|
||||
|
||||
- **Batch `get_component`** - ONE call with the whole `componentsUsed` array, never N calls. Skip it entirely when `componentDigests` already answers the question.
|
||||
- **Read source by installing** - the MCP serves no source. To read or analyze an item's real code, install it with the shadcn CLI and open the local files. Learn an API from the inline `api` / `componentDigests`, never by reading raw source.
|
||||
- **Infer `search` hints yourself** (`type`, `component`, `category`, `features`, `free`) - hints shrink the result set and the tokens. Keep `limit` low; one right result beats ten.
|
||||
- Run independent calls (and the shadcn install) concurrently in one turn - serial tool calls are the main source of slowness.
|
||||
- Don't repeat a search for the same intent; don't call `list_*` to "see everything" - `search` is the entry point, `list_*` is only for browsing a taxonomy the user explicitly wants to explore.
|
||||
- Prefer `get_component`'s inline `api` over `docsUrl` / `/llms.txt`. Fetch a web page only as a last resort.
|
||||
|
||||
## Result shapes (so you don't re-fetch)
|
||||
|
||||
- `score` is 0-100 RELATIVE to the top hit (the top is ~100 by construction), not absolute - compare results to each other.
|
||||
- `termCoverage` (0-1) is the share of the query the item matched - low means a weak match even if the score looks high; rephrase or widen the search.
|
||||
- Each result carries `whyMatch`, `install`, docs/preview URLs, and a `free` flag; premium items carry `requiredPlan` (`"pro"` for blocks, `"ultimate"` for icons).
|
||||
- `componentDigests` is a top-level map: a compact API contract per referenced component - often enough to wire an item without a `get_component` call.
|
||||
- Icon results and `get_icon` include `animated: true` and `installAnimated` when a hover-animated Motion variant exists (animated: `@reui/icons/animated/<style>/<name>`; static: `@reui/icons/default/<style>/<name>`).
|
||||
|
||||
## Error playbook
|
||||
|
||||
- **401** - the MCP requires a signed-in ReUI account. The client prompts "Sign in with ReUI" (OAuth) on first use; a free account is created if needed. For headless/CI, pass a personal token (`reui_pat_...`, created at https://reui.io/account/mcp) as `Authorization: Bearer`.
|
||||
- **403 / locked result** - a valid account but the plan does not cover the item: premium blocks need Pro, Motion Icons need Ultimate. Point to https://reui.io/pricing (upgrade). Free accounts still get all components + examples.
|
||||
- **429** - rate limited (120 requests/min per IP); back off, honor `Retry-After`.
|
||||
- **not found** (`found: false`) - use the returned `suggestions`, or `search`; check `whats_new` if you suspect a stale name. Never run a fabricated install command.
|
||||
|
||||
## Fallbacks
|
||||
|
||||
- No ReUI MCP: `npx shadcn@latest search @reui -q "..."` then `add` (generic, no scoring / inline API).
|
||||
- The shadcn project's own MCP also works over the `@reui` registry: https://ui.shadcn.com/docs/mcp.
|
||||
|
||||
Per-agent MCP setup: https://reui.io/docs/mcp
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"reui_pro": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
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,43 @@
|
||||
# Adapting installed ReUI code (reuse-first, no AI slop)
|
||||
|
||||
ReUI items ship production-quality. Your job is to **adapt by reuse** - wire real data and fit the app - not to redesign or hand-roll. The output should look like ReUI built it for this product.
|
||||
|
||||
## Preserve the design - don't over-customize
|
||||
|
||||
The design IS the product. A ReUI block/component encodes senior-designer decisions: spacing, hierarchy, density, color treatment, and component choices. The fastest way to turn a premium block back into generic AI slop is to "improve" its look - so don't.
|
||||
|
||||
- Change **data, copy, and props**; keep the **structure and styling** it ships with. Make the **smallest** change that wires the real data. If your diff touches `className` / JSX structure more than data / props, you are over-customizing - stop and reuse.
|
||||
- Don't swap ReUI components for hand-rolled ones, restructure the layout, re-skin spacing / radius / colors, or add decorative chrome. Let the installed components carry the default spacing, radius, sizing, icon rhythm, density, and state styling; add custom Tailwind only when a component genuinely lacks a contract you need.
|
||||
- Want a different look? `search` for a block whose design already fits and reuse that - don't restyle this one into a new design.
|
||||
|
||||
## Reuse the parts: examples and the block's own elements
|
||||
|
||||
- **Examples are building parts.** A free `c-*` example is a correct, single-pattern composition you can reuse. Before composing from scratch, `get_examples(component)`, install the closest one, and reuse its wiring - assemble UI from examples instead of hand-rolling what an example already shows.
|
||||
- **Reuse a block's own elements.** Need more rows, cards, items, or sections than ship by default? Repeat the block's **existing** element by mapping real data through the same markup - never invent parallel markup that drifts from its design. Need a variant (empty / loading / expanded)? Derive it from an element the block already has.
|
||||
|
||||
## Don't invent (read, don't guess)
|
||||
|
||||
- Never write a prop, variant value, import path, or `@reui/...` name you didn't read in a component's inline `api`, an installed example, or a `search` result. If you didn't see it, treat it as nonexistent - call `get_component` / `get_examples` / `search` first, or run the MCP `validate_usage` tool to check planned names + props against the docs before writing code.
|
||||
- If a getter returns `found: false` or `search` returns nothing, say so and fall back (plain shadcn, or ask) - never fabricate an install command or an API.
|
||||
|
||||
## What to change vs leave alone
|
||||
|
||||
- **Change:** the item's own data, copy, props, and layout to fit the app.
|
||||
- **Leave alone:** installed component files, hooks, and the shared theme - do not edit vendored ReUI internals; change behavior through props and the documented API.
|
||||
- Blocks are **portable React** - no `next/link`, `next/image`, or other framework-runtime imports inside them. Keep them portable.
|
||||
|
||||
## Demo data -> real data
|
||||
|
||||
- Replace every placeholder with the user's real data. Model it as **typed data structures** and **map over arrays** - never duplicate JSX per row/card. Keep small block-specific formatters next to the data.
|
||||
- Wire the real source (columns, fields, fetch). For `data-grid`, implement the server fetch contract if the user needs server-side data.
|
||||
- **Type from the component API, derive during render.** Type domain state through the component's own types - e.g. map status to `BadgeProps["variant"]` via a typed `Record<Status, …>` - instead of stringly-typed values. Compute view state during render; don't mirror derived data into `useState`/`useEffect`.
|
||||
- **Adapt on the right base.** Use the API for the project's base (Base UI vs Radix - see [components.md](./components.md)); the installed files are already base-correct, so reuse their shape rather than translating from memory.
|
||||
|
||||
## Believable content (no AI tells)
|
||||
|
||||
- Use realistic labels, counts, timestamps, and statuses that map to a real workflow.
|
||||
- No decorative buttons, fake tabs, meaningless toggles, equal-weight card walls, empty gradients, ornamental icons, or generic SaaS filler. Every element should do something.
|
||||
|
||||
## Operational surfaces (settings / profile / admin)
|
||||
|
||||
Pick ONE archetype and keep the family consistent: a vertical rail (3-6 sections), horizontal tabs (5-8), or a frame/stack. Prefer `frame` for tool-like surfaces, a card for profile-like ones. Don't mix archetypes in one surface.
|
||||
@@ -0,0 +1,60 @@
|
||||
# CLI: registry setup, license, non-interactive install
|
||||
|
||||
## Registry setup (one-time, per project)
|
||||
|
||||
Free items (the 20 components and all `c-*` examples) need only the plain string registry in `components.json`:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium items (blocks; Motion Icons and templates) require a ReUI license at install:
|
||||
|
||||
1. Add the key to `.env.local`:
|
||||
|
||||
```bash
|
||||
REUI_LICENSE_KEY=your-license-key
|
||||
```
|
||||
|
||||
2. Switch `components.json` to the authenticated object form:
|
||||
|
||||
```json
|
||||
{
|
||||
"registries": {
|
||||
"@reui": {
|
||||
"url": "https://reui.io/r/{style}/{name}.json",
|
||||
"headers": { "Authorization": "Bearer ${REUI_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
|
||||
|
||||
Use the project's package runner (check `packageManager`):
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes # npm
|
||||
pnpm dlx shadcn@latest add @reui/<name> --yes # pnpm
|
||||
bunx --bun shadcn@latest add @reui/<name> --yes # bun
|
||||
```
|
||||
|
||||
`--yes` skips confirmation prompts. The CLI auto-detects the package manager from the lockfile (there is no `--package-manager` flag). It also resolves the correct base+style variant from `components.json`, so do not pass a style.
|
||||
|
||||
## Handling prompts and conflicts
|
||||
|
||||
- **Always pass `--yes`** so the CLI does not block on confirmation prompts.
|
||||
- **Do NOT pass `--overwrite` by default.** If the CLI reports an existing file, read the output and resolve deliberately: install under a different name, adjust the path, or ask the user. Only use `--overwrite` when the user explicitly wants to replace a file.
|
||||
- **Preview first when touching an existing project**: `npx shadcn@latest add @reui/<name> --dry-run` shows what would change; `--diff <file>` shows a specific file's diff. Use these before overwriting.
|
||||
- Run from the **project root** so `components.json` and `.env.local` are found.
|
||||
|
||||
## Free vs premium boundary
|
||||
|
||||
- Public, no key: `c-*` examples and the 20 components (`@reui/data-grid`, `@reui/badge`, ...) that those examples depend on.
|
||||
- Key required at install: blocks (`@reui/<category>-N`) need a Pro or Ultimate license; Motion Icons (`@reui/icons/...`) and templates need Ultimate.
|
||||
|
||||
If an install 401/403s, the license key is missing, invalid, or the plan does not cover that resource (blocks: Pro or higher; icons and templates: Ultimate). Point the user to https://reui.io/account (their key) or https://reui.io/pricing (upgrade).
|
||||
@@ -0,0 +1,408 @@
|
||||
# ReUI components
|
||||
|
||||
The 21 ReUI building blocks: `alert`, `autocomplete`, `badge`, `cascader`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `icon-tile`, `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.
|
||||
|
||||
## data-grid (the flagship - read its API every time)
|
||||
|
||||
`data-grid` wraps TanStack Table v9. It is NOT a styled `<table>` and does NOT take `data`/`columns` props directly. The contract:
|
||||
|
||||
- Build a TanStack table instance with `useTable({ features: dataGridFeatures, ... })` (columns, data). `dataGridFeatures` is exported by the primitive and already bundles sorting, filtering, pagination, row selection, expanding, pinning, resizing and faceting, so there are no per-table row models to wire.
|
||||
- Pass that instance to `<DataGrid table={table} recordCount={total}>`.
|
||||
- Compose the body with `DataGridTable` inside `DataGrid`, and enable features through `tableLayout` (e.g. `{ headerSticky: true, columnsResizable: true }`), not ad-hoc classes.
|
||||
- Server-side data uses the documented fetch shape (`recordCount` is the total for pagination).
|
||||
|
||||
```tsx
|
||||
const table = useTable({
|
||||
features: dataGridFeatures,
|
||||
data,
|
||||
columns,
|
||||
})
|
||||
|
||||
<DataGrid table={table} recordCount={data.length}>
|
||||
<DataGridTable />
|
||||
</DataGrid>
|
||||
```
|
||||
|
||||
Common mistakes:
|
||||
|
||||
- **Incorrect:** `<DataGrid data={rows} columns={cols} />` - these props do not exist. **Correct:** build a `useTable({ features: dataGridFeatures, ... })` instance and pass `table={table}` + `recordCount`.
|
||||
- **Incorrect:** a raw `<table>` / 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 primitive's `DataGridColumnMeta` (e.g. `cellClassName`, `headerTitle`), set through the bundle's `columnMeta` slot.
|
||||
|
||||
## event-calendar
|
||||
|
||||
**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<EventCalendar defaultEvents={events} defaultView="month" className="h-[560px]">
|
||||
<EventCalendarNav />
|
||||
<EventCalendarContent />
|
||||
</EventCalendar>
|
||||
```
|
||||
|
||||
**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
|
||||
<Gantt defaultEvents={bars} resources={tasks} defaultScale="month" className="h-[480px]">
|
||||
<GanttNav />
|
||||
<GanttView />
|
||||
</Gantt>
|
||||
```
|
||||
|
||||
**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<string, T[]>`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Kanban value={cols} onValueChange={setCols} getItemValue={(i) => i.id}>
|
||||
<KanbanBoard>
|
||||
{Object.entries(cols).map(([id, items]) => (
|
||||
<KanbanColumn key={id} value={id}>
|
||||
<KanbanColumnHandle><h3>{id}</h3></KanbanColumnHandle>
|
||||
<KanbanColumnContent value={id}>
|
||||
{items.map((i) => (
|
||||
<KanbanItem key={i.id} value={i.id}>
|
||||
<KanbanItemHandle>{i.title}</KanbanItemHandle>
|
||||
</KanbanItem>
|
||||
))}
|
||||
</KanbanColumnContent>
|
||||
</KanbanColumn>
|
||||
))}
|
||||
</KanbanBoard>
|
||||
<KanbanOverlay><div className="bg-muted size-full rounded-md" /></KanbanOverlay>
|
||||
</Kanban>
|
||||
```
|
||||
|
||||
**Gotcha:** state is `Record<columnId, T[]>`. Each `KanbanColumnContent value` must match its parent `KanbanColumn value`. Omit `KanbanOverlay` and the drag preview silently breaks.
|
||||
|
||||
## sortable
|
||||
|
||||
**Required:** `value` (`T[]`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Sortable value={items} onValueChange={setItems} getItemValue={(i) => i.id}>
|
||||
{items.map((i) => (
|
||||
<SortableItem key={i.id} value={i.id}>
|
||||
<SortableItemHandle><GripVertical /></SortableItemHandle>
|
||||
{i.label}
|
||||
</SortableItem>
|
||||
))}
|
||||
</Sortable>
|
||||
```
|
||||
|
||||
**Gotcha:** a flat 1D reorder list (not columns - that is `kanban`). `getItemValue` must return a stable, unique string. Pass `layout="grid"` or `layout="nested"` for non-list layouts.
|
||||
|
||||
## filters
|
||||
|
||||
**Required:** `fields` (`FilterField[]`). The value is ONE `FilterQuery` tree - `query` + `onQueryChange`, or uncontrolled `defaultQuery`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const fields: FilterField[] = [
|
||||
{ id: "title", label: "Title", type: "text" },
|
||||
{
|
||||
id: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "archived", label: "Archived" },
|
||||
],
|
||||
},
|
||||
]
|
||||
const [query, setQuery] = useState<FilterQuery>(() => createFilterQuery())
|
||||
|
||||
<Filters fields={fields} query={query} onQueryChange={setQuery} />
|
||||
```
|
||||
|
||||
**Gotcha:** the state is a TREE, not a list of chips. `FilterQuery` is a group of rules joined by `and`/`or` and a group may hold another group, so `(A and B) or C` is expressible; a rule is `{ id, type: "rule", path: ["status"], operator, value }` and `path` is the whole nested attribute path, root first. The pre-rewrite API is GONE: there is no `filters`/`onChange` prop, no `FilterFieldConfig` (fields are `FilterField`, nested through their own `fields`, keyed `id` not `key`), and no `createFilter()` - it minted ids inside a pure function and broke hydration, so ids now come from `createFilterIdFactory(seed)` seeded off `useId`, and `createFilterQuery()` / `createFilterRule()` take one. Read the query back with `flattenFilterConditions` (`{ path, field, operator, values, negated }` per rule, incomplete rules skipped) and walk the tree yourself when the parentheses carry meaning - the primitive compiles nothing, no SQL, no query string.
|
||||
|
||||
`variant` picks the chrome over that one query: `"basic"`, the default, is the flat chip row for a toolbar over a table; `"advanced"` is the condition builder, hung off a trigger or rendered in place with `advancedMode="inline"`. Both read and write the same tree, so a saved view built in one opens in the other. Other props worth knowing before you hand-roll them: `size` is two rungs, `"sm" | "default"`, resolved per style (there is no `lg`); `reorderable` turns on drag and Alt+Arrow row moves in the builder; `onBeforeQueryChange` is the ONE veto point for every write (return `false` to refuse, it cannot rewrite); `editors` registers custom value editors a field selects by `editor` name; `labels` / `operatorLabels` own every rendered string; `pathCollapse` + `maxPathSegments` shorten deep attribute paths; `renderChip` / `renderValue` / `renderEmpty` replace rendered parts. On a field, `loadOptions` supplies async options with paging and `resolveValues` renders a chip restored from a saved view whose option was never loaded. Pairs naturally with `data-grid`.
|
||||
|
||||
## cascader
|
||||
|
||||
**Required:** `items` (a tree of `{ value, label, children? }`), plus the panel parts inside `CascaderContent`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Cascader items={items} value={value} onValueChange={setValue}>
|
||||
<CascaderTrigger render={<Button variant="outline" />}>
|
||||
<CascaderValue placeholder="Select an attribute" />
|
||||
</CascaderTrigger>
|
||||
<CascaderContent className="w-80">
|
||||
<CascaderPanel>
|
||||
<CascaderNav>
|
||||
<CascaderBreadcrumb />
|
||||
<CascaderInput />
|
||||
</CascaderNav>
|
||||
<CascaderEmpty />
|
||||
<CascaderList maxHeight={288}>
|
||||
<CascaderItems />
|
||||
</CascaderList>
|
||||
<CascaderStatus />
|
||||
</CascaderPanel>
|
||||
</CascaderContent>
|
||||
</Cascader>
|
||||
```
|
||||
|
||||
**Gotcha:** pressing a branch NAVIGATES, it does not select - only leaves are selectable until you pass `selectable="any"` or a predicate, and once a branch is selectable its chevron becomes the only way to open it. `CascaderInput` must stay inside `CascaderContent` (Base UI refills the query from the selection when the input sits outside the popup). Always include `CascaderStatus`: it is the live region announcing level changes, which the visual breadcrumb does not provide to screen readers. Accepts a flat adjacency list via `getParent` as well as nested `children`. `searchScope="deep"` searches every level and annotates results with their path; `multiple` gives checkbox rows; `inline` + a bare `CascaderPanel` embeds it with no popover.
|
||||
|
||||
The shape above is `mode="drill"`, the default. `mode="tree"` keeps the same parts (drop `CascaderBreadcrumb`, pass `showBack={false}`, drive expansion with `expanded`/`onExpandedChange`); `mode="columns"` REPLACES `CascaderList` + `CascaderItems` with a single `CascaderColumns`, and has no breadcrumb. Other props worth knowing before you hand-roll them: `cascade` (multi-select only, parent/child selection with indeterminate branches - pair it with `selectable="any"`, since a leaf-only tree can never cascade), `indicator={false}` to drop the single-select check and its gutter (visual only, no-op with `multiple`), `virtualize`/`virtualizeThreshold` plus `CascaderVirtualItems` for long levels, and `getChildren` for async levels with cursor paging, retry on failure and optional `prefetch`. `CascaderFooter` pins commands below the list (`actions` is the quick path) and `CascaderSubmenu` opens one as a side-anchored flyout with the full menu keyboard model. To head a run of rows use `CascaderGroup` wrapping a `CascaderLabel` - a bare label inside a listbox names nothing and is dropped from the accessibility tree - and `CascaderSeparator` for the rule between runs. Every rendered string comes from `labels`, and the panel is RTL-correct under a `DirectionProvider` or `dir="rtl"`.
|
||||
|
||||
## date-selector
|
||||
|
||||
**Required:** none, but wire `onChange` to capture the value.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const [value, setValue] = useState<DateSelectorValue | undefined>()
|
||||
|
||||
<DateSelector value={value} onChange={setValue} label="Due date" />
|
||||
```
|
||||
|
||||
**Gotcha:** the value is a structured `DateSelectorValue` (period / operator / start+end dates), NOT a `Date` - never pass a raw `Date`. Use `allowRange={false}` to lock single-date picking. Read `get_component("date-selector")` for the value shape.
|
||||
|
||||
## tree
|
||||
|
||||
**Required:** `tree` (a `@headless-tree/core` instance you construct)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Tree tree={tree}>
|
||||
{tree.getItems().map((item) => (
|
||||
<TreeItem key={item.getId()} item={item}>
|
||||
<TreeItemLabel />
|
||||
</TreeItem>
|
||||
))}
|
||||
</Tree>
|
||||
```
|
||||
|
||||
**Gotcha:** `Tree` is a styled shell - it takes a headless-tree instance via `tree`, NOT `data`/`items` props. Build the instance with `@headless-tree/react`. External API: https://headless-tree.lukasbach.com/
|
||||
|
||||
## stepper
|
||||
|
||||
**Required:** `StepperItem step` (number), `StepperContent value` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Stepper defaultValue={1}>
|
||||
<StepperNav>
|
||||
<StepperItem step={1}>
|
||||
<StepperTrigger><StepperIndicator>1</StepperIndicator></StepperTrigger>
|
||||
<StepperSeparator />
|
||||
</StepperItem>
|
||||
<StepperItem step={2}>
|
||||
<StepperTrigger><StepperIndicator>2</StepperIndicator></StepperTrigger>
|
||||
</StepperItem>
|
||||
</StepperNav>
|
||||
<StepperPanel>
|
||||
<StepperContent value={1}>Step 1 content</StepperContent>
|
||||
<StepperContent value={2}>Step 2 content</StepperContent>
|
||||
</StepperPanel>
|
||||
</Stepper>
|
||||
```
|
||||
|
||||
**Gotcha:** steps are 1-indexed. Without `StepperPanel` + `StepperContent` you render the nav trail but no body. Put `StepperSeparator` in every `StepperItem` except the last.
|
||||
|
||||
## timeline
|
||||
|
||||
**Required:** `TimelineItem step` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Timeline>
|
||||
<TimelineItem step={1}>
|
||||
<TimelineHeader>
|
||||
<TimelineDate>March 2024</TimelineDate>
|
||||
<TimelineTitle>Project initialized</TimelineTitle>
|
||||
</TimelineHeader>
|
||||
<TimelineIndicator />
|
||||
<TimelineSeparator />
|
||||
<TimelineContent>Repo and architecture set up.</TimelineContent>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
```
|
||||
|
||||
**Gotcha:** each item needs a unique `step`. `orientation` is `"vertical"` (default) or `"horizontal"`. This is a static event display, not interactive like `stepper`.
|
||||
|
||||
## autocomplete
|
||||
|
||||
**Required:** `items` (array; each item has at least `value`)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Autocomplete items={items}>
|
||||
<AutocompleteInput placeholder="Search..." />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No results found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.value} value={item}>{item.label}</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
```
|
||||
|
||||
**Gotcha:** `AutocompleteList` takes a render-prop `(item) => ReactNode`, NOT a mapped array of children. External API: https://base-ui.com/react/components/autocomplete
|
||||
|
||||
## phone-input
|
||||
|
||||
**Required:** none, but wire `onChange`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<PhoneInput placeholder="Enter phone number" defaultCountry="US" value={value} onChange={setValue} />
|
||||
```
|
||||
|
||||
**Gotcha:** `value`/`onChange` use an E.164 string (e.g. `"+14155551234"`), not a display-formatted string; `onChange` can fire `undefined`. `defaultCountry` is a 2-letter ISO code. Wraps `react-phone-number-input`.
|
||||
|
||||
## number-field
|
||||
|
||||
**Required:** wrap the controls in `NumberFieldGroup`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<NumberField defaultValue={0}>
|
||||
<NumberFieldScrubArea label="Quantity" />
|
||||
<NumberFieldGroup>
|
||||
<NumberFieldDecrement />
|
||||
<NumberFieldInput />
|
||||
<NumberFieldIncrement />
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
```
|
||||
|
||||
**Gotcha:** import from `@/components/ui/number-field`. The accessible label goes on `NumberFieldScrubArea`, not `NumberField`. External API: https://base-ui.com/react/components/number-field
|
||||
|
||||
## rating
|
||||
|
||||
**Required:** `rating` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Rating rating={4.5} showValue editable onRatingChange={setRating} />
|
||||
```
|
||||
|
||||
**Gotcha:** supports decimals (partial stars). Pass `editable` + `onRatingChange` for interactive input; omit both for a read-only display.
|
||||
|
||||
## scrollspy
|
||||
|
||||
**Required:** `targetRef` (the scroll container ref)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Scrollspy targetRef={containerRef}>
|
||||
<a href="#s1" data-scrollspy-anchor="s1">Section 1</a>
|
||||
<a href="#s2" data-scrollspy-anchor="s2">Section 2</a>
|
||||
</Scrollspy>
|
||||
<div ref={containerRef}>
|
||||
<div id="s1">...</div>
|
||||
<div id="s2">...</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Gotcha:** each link's `data-scrollspy-anchor` must match a section `id`. `targetRef` is the scrollable container (defaults to the window).
|
||||
|
||||
## frame
|
||||
|
||||
**Required:** `Frame` > `FramePanel`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Frame>
|
||||
<FramePanel>
|
||||
<FrameHeader>
|
||||
<FrameTitle>Title</FrameTitle>
|
||||
<FrameDescription>Description</FrameDescription>
|
||||
</FrameHeader>
|
||||
<div className="p-5">Content</div>
|
||||
<FrameFooter>Footer</FrameFooter>
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
```
|
||||
|
||||
**Gotcha:** a structured card shell for tool-like surfaces. `stacked` connects multiple panels with shared borders; `dense` removes panel padding; radius via the `--frame-radius` CSS variable.
|
||||
|
||||
## icon-stack
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconStack aria-hidden="true">
|
||||
<InboxIcon className="size-4" />
|
||||
</IconStack>
|
||||
```
|
||||
|
||||
**Gotcha:** isometric layered artwork for empty states and illustrations; style the inner icon via its own `className`. Mark purely decorative stacks `aria-hidden="true"` and keep the real label in surrounding copy.
|
||||
|
||||
## icon-tile
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconTile variant="elevated" size="lg">
|
||||
<PackageIcon />
|
||||
</IconTile>
|
||||
```
|
||||
|
||||
**Gotcha:** the square container an icon sits in, so every list row, feature card and empty state shares one affordance. `variant`: `outline` (default) | `elevated` (muted fill, raised ring) | `soft` (tinted nested, tone from currentColor) | `solid` (filled tone, contrasting glyph) | `frame` (double container). `soft` and `solid` retint from one text color class (they default to `text-primary`). `size`: `xs | sm | default | lg | xl` (24/32/40/48/64px tile, glyph scales 12/14/16/20/24px). `radius`: `default | full`. Do not set a `size-*` class on the child icon unless you mean to override the tile's glyph size; recolor with `className` on the tile, not the icon.
|
||||
|
||||
## alert
|
||||
|
||||
**Required:** `Alert` > `AlertTitle`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Alert variant="success">
|
||||
<ShieldCheckIcon />
|
||||
<AlertTitle>Security update</AlertTitle>
|
||||
<AlertDescription>Enable two-factor authentication.</AlertDescription>
|
||||
<AlertAction><Button size="xs">Update</Button></AlertAction>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible API. `variant`: `default | destructive | info | success | warning | invert`. The non-default variants use ReUI extended color tokens (`--success`/`--info`/`--warning`/`--invert`), which the install adds. Defer generic alert rules to the shadcn skill.
|
||||
|
||||
## badge
|
||||
|
||||
**Required:** none (text child).
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Badge variant="success-light" size="sm">Success</Badge>
|
||||
<Badge variant="outline" radius="full">Pill</Badge>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible. Rich `variant` set (solid, `-outline`, `-light` per color), `size` `xs..xl`, `radius` `default | full`. Like `alert`, the color variants rely on ReUI extended tokens. Prefer `Badge` variants over raw color classes for statuses.
|
||||
|
||||
## base vs radix - write for the project's base
|
||||
|
||||
ReUI ships every component in two builds: `base` (Base UI) and `radix` (Radix UI). The install command and name are identical, and the CLI installs the build matching the project. But you must write/adapt code against the **right base**, because their APIs differ.
|
||||
|
||||
**Detect the base first.** Read `components.json` -> `style` and take the segment before the first `-`:
|
||||
|
||||
- `"style": "base-nova"` -> **Base UI**
|
||||
- `"style": "radix-nova"` -> **Radix UI**
|
||||
|
||||
**Then use that base's API.** The deltas mirror shadcn's base-vs-radix split:
|
||||
|
||||
- Slot/composition: Base UI `render={<… />}` vs Radix `asChild`.
|
||||
- `Select`: Base UI takes `items`; Radix uses `<SelectItem>` children.
|
||||
- `ToggleGroup`: Base UI `multiple` boolean vs Radix `type="single" | "multiple"`.
|
||||
|
||||
The safest path is to **read the installed files and `c-*` examples** - they're already in your base, so reuse their wiring instead of guessing. When `get_component`'s inline `api` or an example shows the other base's shape, translate it to your base (or `validate_usage` to confirm). Defer the generic base/radix mechanics to the shadcn skill.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Craft: make ReUI UI exceptional, not generic
|
||||
|
||||
ReUI items ship senior-designer quality. Your adaptation has to hold that bar, so the result reads like a real product surface a team would keep - not a wireframe an AI generated. Use these alongside the reuse rules in [adapting.md](./adapting.md).
|
||||
|
||||
## Have a point of view
|
||||
|
||||
Pick an emotional register before you compose - calm, operational, premium, editorial, dense, energetic - and let layout, spacing, surface treatment, and icon behavior all reinforce it. One or two memorable decisions and restraint everywhere else beats ten generic ones. UI with no point of view reads as generated.
|
||||
|
||||
## Brutally clear hierarchy
|
||||
|
||||
One focal point per card or panel: the dominant metric or task first, its label second, supporting detail third. The first thing the eye lands on should be the right thing; secondary text must read as secondary. Borders, separators, and surfaces do real work to create 2-3 information bands - don't flatten everything to equal weight.
|
||||
|
||||
## Spacing rhythm and deliberate density
|
||||
|
||||
Gaps are a signal, not a default. Keep them intentional and consistent within a family (`gap-1`/`gap-2` for tight operational rows, larger gaps for section breaks), and smaller within a group than between groups. Match the surrounding ReUI density; don't pad an operational surface like a marketing page, and don't drift density mid-section. The composition should still feel authored in grayscale.
|
||||
|
||||
## Cover the real states (the usual miss)
|
||||
|
||||
A surface isn't done at the happy path. Compose, and wire:
|
||||
|
||||
- **Empty** - a purposeful empty state (short message + the primary action), never a blank panel.
|
||||
- **Loading** - a **skeleton** that matches the real layout, not a centered spinner.
|
||||
- **Error** - an inline, recoverable error with a retry, announced via `role="status"`/`aria-live`.
|
||||
|
||||
Derive these from an element the block already has (don't invent parallel markup), or `get_examples` for a state-specific example.
|
||||
|
||||
## Responsive by default
|
||||
|
||||
Mobile-first, not mobile-afterthought. In constrained rows/cards/sidebars, put `min-w-0` on the shrinking container and `truncate` long single-line labels; protect the primary label's width and let secondary content compress. Reflow layouts (multi-column -> single column) rather than just shrinking them. Desktop and mobile should both look designed.
|
||||
|
||||
## Motion, subtly
|
||||
|
||||
Motion should clarify, not decorate. Use ReUI Motion Icons on primary actions for a subtle hover cue; keep transitions short (~200-300ms) with calm easing; prefer a skeleton pulse over a spinner. No bouncing, no gratuitous entrance animations on every element.
|
||||
|
||||
## Real, activated content
|
||||
|
||||
Use believable, typed data (realistic labels, counts, timestamps, statuses that map to a real workflow) - never lorem or abstract filler. Every visible control does something: no decorative buttons, fake tabs, meaningless toggles, or stats with no job. It must still hold with long names, empty values, and crowded data.
|
||||
|
||||
## Avoid the AI tells
|
||||
|
||||
These instantly read as generated - don't ship them: equal-weight card walls, empty gradients, repetitive padding everywhere, generic enterprise copy, ornamental icons, and number tiles that don't earn their place.
|
||||
|
||||
## The bar
|
||||
|
||||
Before you finish, ask: **would a product team keep this instead of replacing it? Does it still feel strong after swapping in real content?** If not, reuse the shipped ReUI design harder - don't restyle it into something new - then run the [quality.md](./quality.md) gates.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Icons (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn icon rules (use the project's configured `iconLibrary`, `data-icon` on icons inside `Button`, no sizing classes on icons inside components, pass icons as component objects not string keys). ReUI adds the following.
|
||||
|
||||
## Portable icons (library-agnostic)
|
||||
|
||||
ReUI components, examples, and blocks are authored to be icon-library-agnostic. When `iconLibrary` is set in `components.json`, the shadcn CLI installs each item's icons in **your** library automatically - you swap nothing. If an installed item's icons don't match your project (for example `iconLibrary` isn't set, so they came in from the item's demo library), change the **import source and component name** to your library, keeping the same icon-name semantics:
|
||||
|
||||
- `lucide` -> `lucide-react`
|
||||
- `tabler` -> `@tabler/icons-react`
|
||||
- `phosphor` -> `@phosphor-icons/react`
|
||||
- `remix` -> `@remixicon/react`
|
||||
- `hugeicons` -> `@hugeicons/react`
|
||||
|
||||
Don't assume `lucide-react`; read `iconLibrary` from `components.json`.
|
||||
|
||||
## Keep icons purposeful
|
||||
|
||||
Icons support the hierarchy, they don't replace it: keep them small, matched to the surrounding density, and decorative ones `aria-hidden="true"` (an icon-only control still needs an accessible label on the control). Don't add ornamental icons that do no job.
|
||||
|
||||
## Motion Icons (the `@reui/icons/...` set)
|
||||
|
||||
ReUI ships its own icon set in 4 styles (outline, solid, duotone, filled), each icon in two variants:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/icons/default/<style>/<name> --yes # static
|
||||
npx shadcn@latest add @reui/icons/animated/<style>/<name> --yes # hover-animated (motion/react)
|
||||
```
|
||||
|
||||
Finding them via the MCP is free; installing requires an Ultimate license (`REUI_LICENSE_KEY`, see [cli.md](./cli.md)). Reach for a Motion Icon on a primary action when a subtle hover cue helps; keep motion restrained.
|
||||
|
||||
Finding icons:
|
||||
|
||||
- Several icons (the common case): **`search_icons(concepts[])`** - up to 24 concepts in one call, the best icons per concept with install commands. Pass `animated: true` to get only icons with a hover-animated Motion variant.
|
||||
- One icon: `search` with `type: "icon"`.
|
||||
- Icon results and `get_icon` carry `animated: true` and `installAnimated` when an animated variant exists - use those install strings, do not construct paths by hand.
|
||||
- Every icon result carries a `previewUrl` (its live icon-category page) - **share it with the user** so they can SEE the icon before installing.
|
||||
|
||||
The `icon-stack` component composes multiple icons into a stacked display.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Quality gates (security, accessibility, scroll)
|
||||
|
||||
These are the **done gate**, not a nice-to-have: before you call any ReUI work finished, call the MCP `get_audit_checklist` tool and pass every item below (plus the craft bar in [craft.md](./craft.md)). Then typecheck and lint.
|
||||
|
||||
## Security
|
||||
|
||||
- Never `dangerouslySetInnerHTML`. Render data as text/components.
|
||||
- External links (`target="_blank"`) must always pair `rel="noopener noreferrer"`.
|
||||
- No real PII, secrets, or tokens in demo or committed code. Remote media only from sources the project already allows.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Implicit list/card items that navigate get real anchors with a standard hover affordance.
|
||||
- Icon-only or numeric buttons need an `aria-label`; decorative icons get `aria-hidden`.
|
||||
- Every non-submit button is `type="button"`.
|
||||
- Keyboard + focus: everything interactive is reachable in a sensible Tab order with a visible focus ring; layers (dialogs/sheets/menus) trap focus and close on `Escape`. ReUI components ship standard keyboard behavior - read each component's inline `api` rather than re-implementing it.
|
||||
- Announce async UI: loading and error messages use `role="status"` / `aria-live` so they're not silent to screen readers.
|
||||
|
||||
## Scroll mechanics
|
||||
|
||||
- Make scroll regions with a parent-owned height: a `min-h-0` + flex chain down to the scroll container. Never guess a `max-h`.
|
||||
- The scroll container owns `overflow-auto`; ancestors stay `min-h-0` so the height resolves.
|
||||
@@ -0,0 +1,35 @@
|
||||
# ReUI registry structure
|
||||
|
||||
ReUI is a shadcn-compatible registry with four entity types. **Examples and blocks are built FROM components** - reuse them, don't rebuild.
|
||||
|
||||
- **component** - one of the 20 ReUI building blocks with a real API (`data-grid`, `kanban`, `filters`, `date-selector`, `tree`, ...). Install directly (`@reui/data-grid`) or let it come in as a dependency of an example/block. Free. Read its API with `get_component(name)`.
|
||||
- **example** - a free `c-*` single-pattern use-case of a component (`c-kanban-1`, `c-data-grid-3`). Install one and read it to copy real composition. Find a component's examples with `get_examples(name)`.
|
||||
- **block** - a premium, full-page section that composes several components (`data-grid-2`, `pricing-page-1`). Pro or Ultimate license at install. Adapts to your active theme via semantic tokens.
|
||||
- **icon** - Motion Icons in 4 styles (outline, solid, duotone, filled), static (`@reui/icons/default/<style>/<name>`) and hover-animated (`@reui/icons/animated/<style>/<name>`). Ultimate license at install. See [icons.md](./icons.md).
|
||||
|
||||
## The @reui registry
|
||||
|
||||
Install everything through the shadcn CLI: `npx shadcn@latest add @reui/<name> --yes`. The CLI reads the `@reui` registry from the project's `components.json`. Free items need only the plain string form:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium installs need the authenticated form + `REUI_LICENSE_KEY` in `.env.local` - see [cli.md](./cli.md). The MCP `get_project_context` tool returns the right config.
|
||||
|
||||
## Know your base: base or radix
|
||||
|
||||
ReUI ships every item in two builds - `base` (Base UI) and `radix` (Radix UI) - with mirrored names. The CLI installs the build matching your project automatically, but **you must write code against the right base's API**. Detect it from `components.json` -> `style`: the segment before the first `-` is the base (`base-nova` -> Base UI, `radix-nova` -> Radix UI). The installed files and `c-*` examples are already in your base - read them and adapt on that base. See [components.md](./components.md) for the API deltas.
|
||||
|
||||
**So the MCP's own `docsUrl` and `previewUrl` match your base**, send your `style` to the MCP: add `?style=<your components.json style>` to the ReUI MCP server URL (or set an `X-Reui-Style` header) in your MCP client config - set once, applies to every call. The MCP then resolves docs/preview links to YOUR library (`/docs/components/radix/...`, `/preview/radix/...` for a radix project) instead of the default base; `get_project_context` echoes back the style it currently sees so you can confirm it. Install commands are unaffected (the CLI already installs the right variant). If you notice the MCP returning `base` links for a `radix` project, tell the user to add `?style=` to the server URL.
|
||||
|
||||
Blocks adapt to your active theme through semantic tokens and CSS variables - change the theme and every block follows.
|
||||
|
||||
## Free vs premium
|
||||
|
||||
- **Free, no key:** the 20 components, all `c-*` examples, the ReUI MCP, and this skill.
|
||||
- **Premium, license required at install:** blocks (Pro or Ultimate), Motion Icons and templates (Ultimate). Set `REUI_LICENSE_KEY` (see [cli.md](./cli.md)).
|
||||
|
||||
## Component API index
|
||||
|
||||
The canonical index of every component's API docs is **https://reui.io/llms.txt** (returned as `componentsApiUrl` in MCP results). Prefer the inline `api` from `get_component`; use the index/docs as the fallback.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Styling (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn skill for the generic rules (semantic colors not raw values, `gap-*` not `space-y-*`, `size-*`, `cn()`, no manual `dark:` overrides, no overlay `z-index`). This file is only the ReUI-specific additions.
|
||||
|
||||
## ReUI extended semantic tokens
|
||||
|
||||
ReUI adds semantic tokens beyond shadcn's base set. Use these instead of raw colors for status and emphasis:
|
||||
|
||||
- `--success` / `--success-foreground`
|
||||
- `--info` / `--info-foreground`
|
||||
- `--warning` / `--warning-foreground`
|
||||
- `--destructive-foreground` (paired with shadcn's `--destructive`)
|
||||
- `--invert` / `--invert-foreground` (inverted surfaces)
|
||||
|
||||
Use them as Tailwind utilities (`bg-success text-success-foreground`, `text-warning`, ...). They are defined in the project's global CSS and registered with Tailwind (`@theme inline` on v4). If a token is missing in the project, add it to the global CSS file (never a new file) following the same `name` / `name-foreground` convention, exactly as the shadcn customization rules describe.
|
||||
|
||||
**Incorrect:** `<span className="text-green-600">Active</span>`
|
||||
**Correct:** `<Badge variant="success">Active</Badge>` or `<span className="text-success">Active</span>`
|
||||
|
||||
## Blocks follow your theme
|
||||
|
||||
When you install a block it adapts to your active theme through the semantic tokens above and the project's CSS variables. Don't hardcode style-specific values into installed block code and don't fork it to "restyle" - change the theme via the CSS variables / a preset and every block follows. Want a different look? `search` for a block whose design already fits instead of re-skinning one.
|
||||
|
||||
## Density and typography rhythm
|
||||
|
||||
ReUI operational UI usually feels dense, not airy. Keep the gap between a title and its supporting description tight by default (`gap-0.5`, `space-y-1`, or `space-y-px`), and smaller than the gap between sections. Match the surrounding ReUI density when you add rows or fields; do not pad operational surfaces like a marketing page.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Workflow: find -> install -> read API -> adapt
|
||||
|
||||
The core ReUI loop. The MCP tells you what to install and gives you the API; the shadcn CLI installs it; you turn the installed files into correct, themed, data-wired code by **reuse**, not redesign.
|
||||
|
||||
## 1. Find (ReUI MCP `search` / `compose_page`)
|
||||
|
||||
**Full multi-section page ask?** Call `compose_page(intent, sections?)` FIRST, before searching block-by-block. It returns ordered sections, each with the best block for the intent (top pick + alternates); sections listed in `unavailableSections` have no real inventory - compose those from components, do not force a bad block.
|
||||
|
||||
For everything else, call `search` with the user's intent. Pass structured hints whenever you can infer them - you are an LLM, so do the parsing the server cannot:
|
||||
|
||||
- `type`: `"component"` (one of the 20 building blocks), `"example"` (a c-\* use-case), `"block"` (a full page/section), `"icon"`.
|
||||
- `component`: the ReUI component the request implies (`"data-grid"`, `"kanban"`, ...).
|
||||
- `category`, `features` (e.g. `["sortable","pagination"]`), `free`.
|
||||
|
||||
Example: "build a users management page with filters" -> `search({ query: "users management page with filters", type: "block", component: "data-grid", features: ["filters"] })`.
|
||||
|
||||
Each result has `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `termCoverage`, and `whyMatch`. `score` is relative to the top hit (the top is ~100 by construction), not an absolute quality - compare results to each other, and show the user the top options if several score closely; do not silently guess. A low `termCoverage` means a weak match even with a high score - rephrase or widen.
|
||||
|
||||
**Always show the preview link.** Whenever you list or recommend items - from `search`, `search_icons`, `list_components`, `compose_page`, or a getter - include each item's `previewUrl` (a live preview page) so the user can SEE it before you install. Blocks and examples link to an individual live preview; icons and components to their live category/component page. This applies to every listing, not only a single pick.
|
||||
|
||||
## 2. Install (shadcn CLI)
|
||||
|
||||
Run the result's `install` command from the project root, non-interactively:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes
|
||||
```
|
||||
|
||||
The CLI reads `components.json`, installs the correct base+style variant, resolves `registryDependencies` (a block pulls in its components), installs npm deps, and rewrites aliases. Do not pass the base/style. See [cli.md](./cli.md).
|
||||
|
||||
## 3. Read the API (do not guess props)
|
||||
|
||||
Before writing code against any component an item uses:
|
||||
|
||||
1. The item's `componentDigests` already give a 1-line contract per component - often enough to wire it. For the full API, call **`get_component(names)`** with ALL of `componentsUsed` in ONE call (it accepts an array) and read each inline `api` - no web fetch. **Share the component's `docsUrl`** (its 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.
|
||||
2. Call **`get_examples(name)`** for the free `c-*` examples of that component; install one and **read the added files** to copy the exact composition. This is the fastest correct path - the example shows real wiring you adapt, not invent.
|
||||
3. About to write a prop you did not see in an `api` or installed file? Run **`validate_usage`** BEFORE writing the code - per-prop documented / notDocumented verdicts plus did-you-mean suggestions. notDocumented means read the API, not push on.
|
||||
|
||||
## 4. Adapt (reuse-first) - do not skip
|
||||
|
||||
Installing files is not the end, and redesigning them defeats the point. First note the project's **base** so you write the right API - read `components.json` -> `style` and take the segment before the first `-` (`base-nova` -> Base UI, `radix-nova` -> Radix UI), see [components.md](./components.md). After `add`:
|
||||
|
||||
1. **Read the added files**; keep the composition intact. For a block, verify the components are wired correctly (for `data-grid`: a `useTable({ features: dataGridFeatures, ... })` instance passed as `table`, `recordCount` set - see [components.md](./components.md)).
|
||||
2. **Replace demo data with the user's real data** via typed structures (see [adapting.md](./adapting.md)).
|
||||
3. **Fix icon imports** to the project's icon library (see [icons.md](./icons.md)).
|
||||
4. **Align styling** to semantic tokens and the active theme - no raw colors (see [styling.md](./styling.md)).
|
||||
5. **Validate before finalizing**: if your adaptation introduced components or props you did not read in an `api` or example, run `validate_usage` on them.
|
||||
6. **Hit the craft bar** - clear hierarchy, deliberate density, the empty / loading / error states, subtle motion, and mobile-first responsiveness (see [craft.md](./craft.md)). Generic-looking output means you under-reused the design, not that it needs restyling.
|
||||
7. **Pass the quality gates** (security, a11y, scroll) - call the MCP `get_audit_checklist` tool and clear every item (see [quality.md](./quality.md)).
|
||||
8. **Typecheck / lint**.
|
||||
|
||||
## If no single block fits
|
||||
|
||||
Compose from components (`compose_page` tells you which sections have no block inventory via `unavailableSections`). `search` the components you need, read each `get_component` API, install a worked `get_examples` example for each, and assemble by adapting those examples. A block in the same category is a useful reference - install it and read its files to see how ReUI composes those components, then adapt.
|
||||
@@ -0,0 +1,58 @@
|
||||
# ReUI MCP: full reference
|
||||
|
||||
The ReUI MCP (`https://mcp.reui.io`, Streamable HTTP) is free to use but needs a ReUI account: on first use the agent signs in with ReUI (a free account is created if the user has none), so every request is tied to an account. Free covers components and examples; a Pro or Ultimate license unlocks premium blocks and Motion Icons and removes the daily request limit. It does **discovery + guidance** (search, inline APIs, page planning, validation) and never serves source; the shadcn CLI does **installation**, and the license key lives there (the `@reui` entry in `components.json`, backed by `.env.local`). Goal: from the user's intent to correct, themed, data-wired ReUI code in the **fewest tokens and calls**, with **no guessing**.
|
||||
|
||||
## Golden path (token-optimal - follow this order)
|
||||
|
||||
Most tasks need 2-4 MCP calls and ZERO web fetches:
|
||||
|
||||
1. **`search(query, ...hints)`** -> pick the top 1-3 results. Each result already carries `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `whyMatch`. The payload is complete - do not call another tool just to "confirm" a result.
|
||||
2. **`get_component([...componentsUsed])`** in ONE batched call (one name or an array of up to 20) -> read each inline `api`. This **replaces** fetching docs pages. Often skippable: search responses carry `componentDigests`, a compact API contract per referenced component.
|
||||
3. **`get_examples(component)`** -> install ONE returned `c-*` example, read the added files, copy the composition.
|
||||
4. **`get_install_command(item)`** only to validate a name you are unsure of (results already include `install`). Run the install with the shadcn CLI (`--yes`).
|
||||
5. **`get_audit_checklist()`** before declaring done.
|
||||
|
||||
If you already know the exact item name, skip `search`. Everything else is situational.
|
||||
|
||||
## The 5 task-specific tools (when to reach for each)
|
||||
|
||||
- **`compose_page`** - BEFORE building any full page (dashboard, settings, billing, landing). Pass the intent (and optionally the sections you want); it returns ordered sections, each with the best premium block for the intent (top pick + alternates). Sections with no real inventory are listed honestly in `unavailableSections` - compose those from components instead of forcing a bad block.
|
||||
- **`search_icons`** - whenever you need icons, especially several. Batch up to 24 concepts in one call; each concept returns its best icons with install commands. Pass `animated: true` to get only icons that have a hover-animated Motion variant.
|
||||
- **`validate_usage`** - BEFORE writing code with component names or props you have not read in an inline `api` or an installed example. It checks planned names + props against the indexed API docs and registry item names; returns did-you-mean suggestions and per-prop documented / notDocumented verdicts. Deterministic, no inference - a notDocumented prop means stop and read the API, not push on.
|
||||
- **`whats_new`** - when your registry knowledge might be stale (a name 404s, the user mentions an item you don't know). Returns items added/removed per build, newest first.
|
||||
- **`report_issue`** - when an installed item is actually broken (bad source, wrong dependency, broken preview). Goes straight to the ReUI team; rate-limited 5/hour. Not for usage questions.
|
||||
|
||||
## All 19 tools
|
||||
|
||||
`search`, `get_block`, `get_example`, `get_icon`, `list_block_groups`, `list_block_categories`, `list_example_categories`, `list_icon_categories`, `list_components`, `get_component`, `get_examples`, `search_icons`, `compose_page`, `validate_usage`, `whats_new`, `report_issue`, `get_install_command`, `get_project_context`, `get_audit_checklist`. The MCP serves the full parameter schemas; do not guess parameters beyond them.
|
||||
|
||||
## Token + speed rules
|
||||
|
||||
- **Batch `get_component`** - ONE call with the whole `componentsUsed` array, never N calls. Skip it entirely when `componentDigests` already answers the question.
|
||||
- **Read source by installing** - the MCP serves no source. To read or analyze an item's real code, install it with the shadcn CLI and open the local files. Learn an API from the inline `api` / `componentDigests`, never by reading raw source.
|
||||
- **Infer `search` hints yourself** (`type`, `component`, `category`, `features`, `free`) - hints shrink the result set and the tokens. Keep `limit` low; one right result beats ten.
|
||||
- Run independent calls (and the shadcn install) concurrently in one turn - serial tool calls are the main source of slowness.
|
||||
- Don't repeat a search for the same intent; don't call `list_*` to "see everything" - `search` is the entry point, `list_*` is only for browsing a taxonomy the user explicitly wants to explore.
|
||||
- Prefer `get_component`'s inline `api` over `docsUrl` / `/llms.txt`. Fetch a web page only as a last resort.
|
||||
|
||||
## Result shapes (so you don't re-fetch)
|
||||
|
||||
- `score` is 0-100 RELATIVE to the top hit (the top is ~100 by construction), not absolute - compare results to each other.
|
||||
- `termCoverage` (0-1) is the share of the query the item matched - low means a weak match even if the score looks high; rephrase or widen the search.
|
||||
- Each result carries `whyMatch`, `install`, docs/preview URLs, and a `free` flag; premium items carry `requiredPlan` (`"pro"` for blocks, `"ultimate"` for icons).
|
||||
- `componentDigests` is a top-level map: a compact API contract per referenced component - often enough to wire an item without a `get_component` call.
|
||||
- Icon results and `get_icon` include `animated: true` and `installAnimated` when a hover-animated Motion variant exists (animated: `@reui/icons/animated/<style>/<name>`; static: `@reui/icons/default/<style>/<name>`).
|
||||
|
||||
## Error playbook
|
||||
|
||||
- **401** - the MCP requires a signed-in ReUI account. The client prompts "Sign in with ReUI" (OAuth) on first use; a free account is created if needed. For headless/CI, pass a personal token (`reui_pat_...`, created at https://reui.io/account/mcp) as `Authorization: Bearer`.
|
||||
- **403 / locked result** - a valid account but the plan does not cover the item: premium blocks need Pro, Motion Icons need Ultimate. Point to https://reui.io/pricing (upgrade). Free accounts still get all components + examples.
|
||||
- **429** - rate limited (120 requests/min per IP); back off, honor `Retry-After`.
|
||||
- **not found** (`found: false`) - use the returned `suggestions`, or `search`; check `whats_new` if you suspect a stale name. Never run a fabricated install command.
|
||||
|
||||
## Fallbacks
|
||||
|
||||
- No ReUI MCP: `npx shadcn@latest search @reui -q "..."` then `add` (generic, no scoring / inline API).
|
||||
- The shadcn project's own MCP also works over the `@reui` registry: https://ui.shadcn.com/docs/mcp.
|
||||
|
||||
Per-agent MCP setup: https://reui.io/docs/mcp
|
||||
@@ -0,0 +1,16 @@
|
||||
# CodeGraph data files
|
||||
# These are local to each machine and should not be committed
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
# Cache
|
||||
cache/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Hook markers
|
||||
.dirty
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"codegraph": {
|
||||
"type": "stdio",
|
||||
"command": "codegraph",
|
||||
"args": [
|
||||
"serve",
|
||||
"--mcp",
|
||||
"--path",
|
||||
"C:\\Users\\shats\\Dev\\vps-tracker"
|
||||
]
|
||||
},
|
||||
"reui": {
|
||||
"url": "https://mcp.reui.io",
|
||||
"headers": {
|
||||
"Authorization": "Bearer <REUI_LICENSE_KEY>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
name: Access form-8 polish
|
||||
overview: "Доработать `/access` по DNA ReUI form-8: удобное копирование токена (полный — только после create/rotate; в списке — prefix), больше метаданных в таблице и лёгкий Frame/Alert polish без пересборки всей страницы."
|
||||
todos:
|
||||
- id: install-form-8
|
||||
content: "CLI: pnpm dlx shadcn@latest add @reui/form-8 --yes → blocks/form-8, fix imports"
|
||||
status: pending
|
||||
- id: token-dialog
|
||||
content: "ApiKeyTokenDialog: Alert + InputGroup reveal/copy + Bearer snippet"
|
||||
status: pending
|
||||
- id: grid-copy-meta
|
||||
content: "Grid: copyable prefix/id, created_at, richer name cell"
|
||||
status: pending
|
||||
- id: frame-shell-session
|
||||
content: Keys card → Frame+Alert; session copy tenant_id/user_id
|
||||
status: pending
|
||||
- id: web-verify
|
||||
content: typecheck + lint + build @evobgp/web
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Доработка страницы Доступов (form-8)
|
||||
|
||||
## Контекст
|
||||
|
||||
- Preview: [form-8](https://reui.io/preview/base/form-8) · Docs: [blocks](https://reui.io/blocks)
|
||||
- Сейчас: [`apps/web/src/routes/_auth/access.tsx`](apps/web/src/routes/_auth/access.tsx) + [`access-api-keys-card.tsx`](apps/web/src/components/access/access-api-keys-card.tsx) / [`access-api-keys-grid.tsx`](apps/web/src/components/access/access-api-keys-grid.tsx) / [`api-key-token-dialog.tsx`](apps/web/src/components/access/api-key-token-dialog.tsx)
|
||||
- Полный секрет токена API отдаёт **только** в `201` create / `200` rotate; в списке есть лишь `prefix` (8 символов). Копирование «ключа» в таблице = **prefix**; полный токен — в диалоге после создания/ротации.
|
||||
- Уже есть хук [`useCopyToClipboard`](apps/web/src/hooks/use-copy-to-clipboard.ts) и DNA копирования в [`settings-14/.../columns.tsx`](apps/web/src/components/blocks/settings-14/components/columns.tsx) (`ApiKeyCell`).
|
||||
- `InputGroup` уже в [`packages/ui/src/components/input-group.tsx`](packages/ui/src/components/input-group.tsx).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
createOrRotate["Create / Rotate"] --> fullToken["token once"]
|
||||
fullToken --> tokenDialog["Token dialog InputGroup copy"]
|
||||
listRow["List row"] --> prefixOnly["prefix only"]
|
||||
prefixOnly --> copyPrefix["Copy prefix"]
|
||||
```
|
||||
|
||||
## Подход
|
||||
|
||||
Сохранить KPI + сессию + DataGrid-список (раздел в целом ок). Оформить блок ключей и диалог токена по form-8 (Frame/Alert/InputGroup copy), не переписывать страницу целиком.
|
||||
|
||||
## Шаги
|
||||
|
||||
### 1. Установить референс form-8
|
||||
|
||||
Из `apps/web`:
|
||||
|
||||
```bash
|
||||
pnpm dlx shadcn@latest add @reui/form-8 --yes
|
||||
```
|
||||
|
||||
Сложить в `apps/web/src/components/blocks/form-8/` (reference). Post-add: импорты shadcn → `@evobgp/ui/components/*`. Не копипастить block в route — adapt в domain-компоненты.
|
||||
|
||||
### 2. Диалог полного токена (главное копирование)
|
||||
|
||||
Переработать [`api-key-token-dialog.tsx`](apps/web/src/components/access/api-key-token-dialog.tsx) по form-8:
|
||||
|
||||
- ReUI `Alert` (`warning`): «токен больше не покажем»
|
||||
- `InputGroup` + read-only input (masked toggle reveal/hide) + кнопка Copy (`useCopyToClipboard` + toast)
|
||||
- Доп. кнопка/строка: копировать `Authorization: Bearer <token>` (удобно для curl/UI settings)
|
||||
- Автофокус / явный CTA «Копировать»
|
||||
|
||||
### 3. Таблица ключей — copy + больше info
|
||||
|
||||
В [`access-api-keys-grid.tsx`](apps/web/src/components/access/access-api-keys-grid.tsx):
|
||||
|
||||
- Ячейка **prefix**: mono + Copy (как `ApiKeyCell` в settings-14); tooltip «полный токен недоступен — только prefix»
|
||||
- Добавить колонки: **создан** (`created_at`), **id** (mono, copy)
|
||||
- Name-cell: hybrid tile DNA (icon `KeyRound` на `bg-muted`) + subtitle с role badge
|
||||
- Actions: оставить rotate/revoke; при необходимости ButtonGroup как в form-8
|
||||
|
||||
Маленький shared helper при необходимости: `apps/web/src/components/access/copyable-mono.tsx` (reuse в grid + session).
|
||||
|
||||
### 4. Оболочка блока ключей
|
||||
|
||||
В [`access-api-keys-card.tsx`](apps/web/src/components/access/access-api-keys-card.tsx):
|
||||
|
||||
- Заменить `DataGridCard` на ReUI `Frame` / `FramePanel` (+ header) — surface lock form-8 / contract
|
||||
- Короткий `Alert` info над гридом: полный токен только при создании/ротации; в списке копируется prefix
|
||||
|
||||
### 5. Сессия — доп. info + copy
|
||||
|
||||
В [`access.tsx`](apps/web/src/routes/_auth/access.tsx):
|
||||
|
||||
- Copy для `tenant_id` (и `user_id`, если есть)
|
||||
- Для JWT: показать `is_admin` badge и число permissions (без смены layout)
|
||||
|
||||
### 6. Проверка
|
||||
|
||||
```powershell
|
||||
pnpm --filter @evobgp/web run typecheck
|
||||
pnpm --filter @evobgp/web run lint
|
||||
pnpm --filter @evobgp/web run build
|
||||
```
|
||||
|
||||
## Вне скоупа
|
||||
|
||||
- Смена backend (reveal полного токена из БД невозможен — SHA-256)
|
||||
- Полный redesign KPI (`SectionCards` → `KpiStatGrid`) — отдельная задача
|
||||
- Перенос списка с DataGrid на row-list form-8
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: EvoBGP без NATS
|
||||
overview: "Код EvoBGP уже работает без NATS и прочих брокеров: очередь задач in-process в `evobgp-all`. Оптимизировать Go под ваш compose не требуется — достаточно подчистить сам compose (убрать мёртвый сервис nats) и по желанию ужесточить prod-флаги."
|
||||
todos:
|
||||
- id: compose-drop-nats
|
||||
content: Удалить nats + depends_on + runtime-logs entry из production.example (и sync microvps-full при необходимости)
|
||||
status: pending
|
||||
- id: docs-align
|
||||
content: "Синхронизировать quickstart/architecture: NATS optional, minimal stack без брокера"
|
||||
status: pending
|
||||
- id: server-apply
|
||||
content: На сервере применить тот же compose diff (без правок Go)
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Анализ compose без NATS: нужен ли рефакторинг EvoBGP?
|
||||
|
||||
## Вердикт
|
||||
|
||||
**Нет — оптимизировать текущий Go-код под этот compose не нужно.** Приложение уже спроектировано как monolith (`evobgp-all`) с in-process очередью. NATS в репозитории — задел на будущее, не рабочая зависимость.
|
||||
|
||||
## Как устроен runtime без брокера
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph host [Хост /opt/evobgp]
|
||||
Web[evobgp-web nginx]
|
||||
All[evobgp-all]
|
||||
PG[(postgres)]
|
||||
Bird[bird2]
|
||||
Agent[evobgp-agent]
|
||||
Edge[evobgp-edge Traefik]
|
||||
end
|
||||
Edge --> Web
|
||||
Web -->|"/v1 proxy"| All
|
||||
All --> PG
|
||||
All --> Bird
|
||||
Agent --> Bird
|
||||
All -->|"HTTP wake NODE_DISPATCH"| Agent
|
||||
```
|
||||
|
||||
| Компонент | Роль для кода | Нужен ли NATS? |
|
||||
|-----------|---------------|----------------|
|
||||
| `evobgp-all` | API + jobs + scheduler/ingest/render/deploy in-process | Нет |
|
||||
| `postgres` | Store + `job_audit` reclaim (ADR-001) | Нет |
|
||||
| `bird2` + `evobgp-agent` | Локальный speaker / birdc | Нет |
|
||||
| `EVOBGP_NODE_DISPATCH_ENABLED` | HTTP `POST` на remote agent (`internal/nodedispatch`) | Нет |
|
||||
| `EVOBGP_BROKER_URL` | Только [`internal/broker/connect.go`](c:/Users/shats/Dev/EvoBGP/internal/broker/connect.go) — лог, если URL задан | Нет |
|
||||
| NATS-контейнер | Не читается бинарником | Можно удалить |
|
||||
|
||||
Источник правды: [`docs/architecture.md`](c:/Users/shats/Dev/EvoBGP/docs/architecture.md), [`docs/adr/001-durable-job-queue.md`](c:/Users/shats/Dev/EvoBGP/docs/adr/001-durable-job-queue.md) — «NATS JetStream remains optional (`EVOBGP_BROKER_URL` logged only)».
|
||||
|
||||
Ваш закомментированный `# EVOBGP_BROKER_URL` — корректно. Даже если URL оставить, поведение jobs не изменится.
|
||||
|
||||
## Что в вашем YAML лишнее / рискованное (ops, не код)
|
||||
|
||||
**Можно убрать без правок Go:**
|
||||
|
||||
1. Сервис `nats` целиком.
|
||||
2. `depends_on: nats` у `evobgp-all`.
|
||||
3. `nats` из списка `SERVICES` в `stack-runtime-logs`.
|
||||
4. Проброс `4222:4222` (если не нужен снаружи).
|
||||
|
||||
**Prometheus** — тоже опционален для функционала: скрейпит `/metrics` с `evobgp-all`. Удаление не ломает BGP/UI; ломает только локальный monitoring. Код менять не нужно.
|
||||
|
||||
**Не трогать без замены:** `postgres`, `bird2`, `evobgp-agent`, `evobgp-all`, `evobgp-web`, `evobgp-edge`, volumes `bird_*` / `pgdata` / ACME.
|
||||
|
||||
## Prod-флаги в compose (важно, но это конфиг, не рефакторинг)
|
||||
|
||||
Сейчас example и ваш файл совпадают с lab-режимом:
|
||||
|
||||
- `EVOBGP_SEED_DEMO: "1"` → доступен `Bearer dev` (см. [`docs/access.md`](c:/Users/shats/Dev/EvoBGP/docs/access.md), [`docs/production-checklist.md`](c:/Users/shats/Dev/EvoBGP/docs/production-checklist.md))
|
||||
- `EVOBGP_DEV_INSECURE: "1"` — dead/lab флаг; при `EVOBGP_PRODUCTION=1` процесс **не стартует** ([`internal/config/production.go`](c:/Users/shats/Dev/EvoBGP/internal/config/production.go))
|
||||
- `POSTGRES_PASSWORD: evobgp` + `sslmode=disable` — ок только в private Docker network
|
||||
- `EVOBGP_BUNDLE_SEED_HEX` захардкожен в yaml — ок для lab; в бою лучше свой секрет в `.env`
|
||||
|
||||
Для «настоящего» prod достаточно env, не переписывания архитектуры.
|
||||
|
||||
## Нужны ли изменения в коде проекта?
|
||||
|
||||
| Вопрос | Ответ |
|
||||
|--------|--------|
|
||||
| Убрать/упростить `internal/broker`? | Не обязательно: пакет уже no-op без URL |
|
||||
| Переписать jobs на PG-only? | Уже есть durable reclaim через `job_audit`; брокер не требуется |
|
||||
| Вырезать NATS из бинарника/зависимостей? | В Go NATS-клиент как runtime consumer не подключён — нечего «оптимизировать» |
|
||||
| Менять `evobgp-all` под monolith? | Уже monolith; reference-воркеры (`scheduler`/`ingest`/…) вам не нужны |
|
||||
|
||||
**Итог:** отказ от NATS — правильное решение для текущего этапа; код уже соответствует этой модели. Оптимизация = правка compose/docs, не приложения.
|
||||
|
||||
## Рекомендуемые действия (если согласуете выполнение)
|
||||
|
||||
Только инфраструктура/документация в репо:
|
||||
|
||||
1. Обновить [`deploy/compose/docker-compose.production.example.yaml`](c:/Users/shats/Dev/EvoBGP/deploy/compose/docker-compose.production.example.yaml) (и при необходимости `stack.microvps-full.yaml` / `docker-compose.microvps-full.yaml`): удалить `nats`, `depends_on`, порт 4222, упоминание в runtime-logs.
|
||||
2. Подправить [`docs/quickstart.md`](c:/Users/shats/Dev/EvoBGP/docs/quickstart.md) / architecture: NATS — optional future, не часть minimal production stack.
|
||||
3. На сервере: тот же diff в `/opt/evobgp/docker-compose.yaml` → `docker compose up -d` (nats исчезнет).
|
||||
|
||||
Go/CI/образы — **не трогать** ради этого решения.
|
||||
|
||||
Опциональный hardening env на сервере (отдельно от «оптимизации кода»): `EVOBGP_SEED_DEMO=0`, убрать `EVOBGP_DEV_INSECURE`, при желании `EVOBGP_PRODUCTION=1` + checklist.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: EvoBGP без NATS
|
||||
overview: "Код EvoBGP уже работает без NATS и прочих брокеров: очередь задач in-process в `evobgp-all`. Оптимизировать Go под ваш compose не требуется — достаточно подчистить сам compose (убрать мёртвый сервис nats) и по желанию ужесточить prod-флаги."
|
||||
todos:
|
||||
- id: compose-drop-nats
|
||||
content: Удалить nats + depends_on + runtime-logs entry из production.example (и sync microvps-full при необходимости)
|
||||
status: pending
|
||||
- id: docs-align
|
||||
content: "Синхронизировать quickstart/architecture: NATS optional, minimal stack без брокера"
|
||||
status: pending
|
||||
- id: server-apply
|
||||
content: На сервере применить тот же compose diff (без правок Go)
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Анализ compose без NATS: нужен ли рефакторинг EvoBGP?
|
||||
|
||||
## Вердикт
|
||||
|
||||
**Нет — оптимизировать текущий Go-код под этот compose не нужно.** Приложение уже спроектировано как monolith (`evobgp-all`) с in-process очередью. NATS в репозитории — задел на будущее, не рабочая зависимость.
|
||||
|
||||
## Как устроен runtime без брокера
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph host [Хост /opt/evobgp]
|
||||
Web[evobgp-web nginx]
|
||||
All[evobgp-all]
|
||||
PG[(postgres)]
|
||||
Bird[bird2]
|
||||
Agent[evobgp-agent]
|
||||
Edge[evobgp-edge Traefik]
|
||||
end
|
||||
Edge --> Web
|
||||
Web -->|"/v1 proxy"| All
|
||||
All --> PG
|
||||
All --> Bird
|
||||
Agent --> Bird
|
||||
All -->|"HTTP wake NODE_DISPATCH"| Agent
|
||||
```
|
||||
|
||||
| Компонент | Роль для кода | Нужен ли NATS? |
|
||||
|-----------|---------------|----------------|
|
||||
| `evobgp-all` | API + jobs + scheduler/ingest/render/deploy in-process | Нет |
|
||||
| `postgres` | Store + `job_audit` reclaim (ADR-001) | Нет |
|
||||
| `bird2` + `evobgp-agent` | Локальный speaker / birdc | Нет |
|
||||
| `EVOBGP_NODE_DISPATCH_ENABLED` | HTTP `POST` на remote agent (`internal/nodedispatch`) | Нет |
|
||||
| `EVOBGP_BROKER_URL` | Только [`internal/broker/connect.go`](c:/Users/shats/Dev/EvoBGP/internal/broker/connect.go) — лог, если URL задан | Нет |
|
||||
| NATS-контейнер | Не читается бинарником | Можно удалить |
|
||||
|
||||
Источник правды: [`docs/architecture.md`](c:/Users/shats/Dev/EvoBGP/docs/architecture.md), [`docs/adr/001-durable-job-queue.md`](c:/Users/shats/Dev/EvoBGP/docs/adr/001-durable-job-queue.md) — «NATS JetStream remains optional (`EVOBGP_BROKER_URL` logged only)».
|
||||
|
||||
Ваш закомментированный `# EVOBGP_BROKER_URL` — корректно. Даже если URL оставить, поведение jobs не изменится.
|
||||
|
||||
## Что в вашем YAML лишнее / рискованное (ops, не код)
|
||||
|
||||
**Можно убрать без правок Go:**
|
||||
|
||||
1. Сервис `nats` целиком.
|
||||
2. `depends_on: nats` у `evobgp-all`.
|
||||
3. `nats` из списка `SERVICES` в `stack-runtime-logs`.
|
||||
4. Проброс `4222:4222` (если не нужен снаружи).
|
||||
|
||||
**Prometheus** — тоже опционален для функционала: скрейпит `/metrics` с `evobgp-all`. Удаление не ломает BGP/UI; ломает только локальный monitoring. Код менять не нужно.
|
||||
|
||||
**Не трогать без замены:** `postgres`, `bird2`, `evobgp-agent`, `evobgp-all`, `evobgp-web`, `evobgp-edge`, volumes `bird_*` / `pgdata` / ACME.
|
||||
|
||||
## Prod-флаги в compose (важно, но это конфиг, не рефакторинг)
|
||||
|
||||
Сейчас example и ваш файл совпадают с lab-режимом:
|
||||
|
||||
- `EVOBGP_SEED_DEMO: "1"` → доступен `Bearer dev` (см. [`docs/access.md`](c:/Users/shats/Dev/EvoBGP/docs/access.md), [`docs/production-checklist.md`](c:/Users/shats/Dev/EvoBGP/docs/production-checklist.md))
|
||||
- `EVOBGP_DEV_INSECURE: "1"` — dead/lab флаг; при `EVOBGP_PRODUCTION=1` процесс **не стартует** ([`internal/config/production.go`](c:/Users/shats/Dev/EvoBGP/internal/config/production.go))
|
||||
- `POSTGRES_PASSWORD: evobgp` + `sslmode=disable` — ок только в private Docker network
|
||||
- `EVOBGP_BUNDLE_SEED_HEX` захардкожен в yaml — ок для lab; в бою лучше свой секрет в `.env`
|
||||
|
||||
Для «настоящего» prod достаточно env, не переписывания архитектуры.
|
||||
|
||||
## Нужны ли изменения в коде проекта?
|
||||
|
||||
| Вопрос | Ответ |
|
||||
|--------|--------|
|
||||
| Убрать/упростить `internal/broker`? | Не обязательно: пакет уже no-op без URL |
|
||||
| Переписать jobs на PG-only? | Уже есть durable reclaim через `job_audit`; брокер не требуется |
|
||||
| Вырезать NATS из бинарника/зависимостей? | В Go NATS-клиент как runtime consumer не подключён — нечего «оптимизировать» |
|
||||
| Менять `evobgp-all` под monolith? | Уже monolith; reference-воркеры (`scheduler`/`ingest`/…) вам не нужны |
|
||||
|
||||
**Итог:** отказ от NATS — правильное решение для текущего этапа; код уже соответствует этой модели. Оптимизация = правка compose/docs, не приложения.
|
||||
|
||||
## Рекомендуемые действия (если согласуете выполнение)
|
||||
|
||||
Только инфраструктура/документация в репо:
|
||||
|
||||
1. Обновить [`deploy/compose/docker-compose.production.example.yaml`](c:/Users/shats/Dev/EvoBGP/deploy/compose/docker-compose.production.example.yaml) (и при необходимости `stack.microvps-full.yaml` / `docker-compose.microvps-full.yaml`): удалить `nats`, `depends_on`, порт 4222, упоминание в runtime-logs.
|
||||
2. Подправить [`docs/quickstart.md`](c:/Users/shats/Dev/EvoBGP/docs/quickstart.md) / architecture: NATS — optional future, не часть minimal production stack.
|
||||
3. На сервере: тот же diff в `/opt/evobgp/docker-compose.yaml` → `docker compose up -d` (nats исчезнет).
|
||||
|
||||
Go/CI/образы — **не трогать** ради этого решения.
|
||||
|
||||
Опциональный hardening env на сервере (отдельно от «оптимизации кода»): `EVOBGP_SEED_DEMO=0`, убрать `EVOBGP_DEV_INSECURE`, при желании `EVOBGP_PRODUCTION=1` + checklist.
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: EvoFirewall full hardening
|
||||
overview: "Полная поэтапная доработка EvoFirewall: сначала frontend (удаление dead blocks, wiring kit, UX ReUI), затем backend (split monolith, баги, N+1, RBAC/Zod), затем OpenAPI и тестовый пояс — всё в рамках project rules и ReUI PRO Frame."
|
||||
todos:
|
||||
- id: p1-delete-blocks
|
||||
content: "Phase 1: удалить components/blocks/, почистить ссылки; встроить или удалить agents-fleet-chart"
|
||||
status: completed
|
||||
- id: p1-kit-wiring
|
||||
content: "Phase 1: ui-surface.ts; LoadingButton/QueryState/FormSheet wiring; auth.callback Frame"
|
||||
status: completed
|
||||
- id: p1-agents-toolbar
|
||||
content: "Phase 1: общий toolbar cards/table на /agents без hand-roll дубля"
|
||||
status: completed
|
||||
- id: p1-settings-qa
|
||||
content: "Phase 1: SettingsShell hygiene + show_quick_actions UI; sync frontend rules docs"
|
||||
status: completed
|
||||
- id: p2-bugfixes
|
||||
content: "Phase 2: bumpAgentsForList no-op; Zod settings whitelist; RBAC install-links/integrations"
|
||||
status: completed
|
||||
- id: p2-perf-split
|
||||
content: "Phase 2: N+1 batch; split control.ts + repos; row-mappers"
|
||||
status: completed
|
||||
- id: p3-openapi-tests
|
||||
content: "Phase 3: OpenAPI parity + error envelope + inject tests critical paths"
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# EvoFirewall — полный hardening (UI → API → OpenAPI/тесты)
|
||||
|
||||
Выбор: **D** (полный поэтапный) + **удалить** [`apps/web/src/components/blocks/`](apps/web/src/components/blocks/).
|
||||
|
||||
Эталоны ReUI (обязательные ссылки по зонам):
|
||||
- KPI / Quick Actions: [stats-12](https://reui.io/preview/base/stats-12) · [card-12](https://reui.io/preview/base/card-12)
|
||||
- Lists: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)
|
||||
- Agents: [solution-agents-1](https://reui.io/preview/base/solution-agents-1)
|
||||
- Settings: [settings-16](https://reui.io/preview/base/settings-16) · [settings-3](https://reui.io/preview/base/settings-3)
|
||||
- Shell: [app-shell-12](https://reui.io/preview/base/app-shell-12)
|
||||
- Empty / Auth callback: [empty-state-12](https://reui.io/preview/base/empty-state-12) · [auth-13](https://reui.io/preview/base/auth-13)
|
||||
- Docs: [llms.txt](https://reui.io/llms.txt) · [MCP](https://reui.io/docs/mcp)
|
||||
|
||||
Перед UI-правками: MCP `user-reui` (`surface: frame`) + `validate_usage` / `get_audit_checklist`. После web: `pnpm --filter @evofw/web build`. После API: `pnpm --filter @evofw/api test`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P1[Phase1_UI] --> P2[Phase2_API]
|
||||
P2 --> P3[Phase3_ContractTests]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Frontend / ReUI
|
||||
|
||||
### 1.1 Dead code
|
||||
- Удалить весь [`apps/web/src/components/blocks/`](apps/web/src/components/blocks/) (card-3, solution-agents-2/3, solution-crm-4, solution-inventory-9).
|
||||
- Почистить комментарии-ссылки на `blocks/...` в domain (`agent-card.tsx` и др.) — оставить только `previewUrl` reui.io.
|
||||
- Решить судьбу [`agents-fleet-chart.tsx`](apps/web/src/components/agents/agents-fleet-chart.tsx): **встроить** в dashboard (`/_auth/index.tsx`) как chart-секцию после Quick Actions (контракт KPI → QA → charts), иначе удалить. Выбор по умолчанию: **встроить** на dashboard (данные уже есть через `agents` / `recentStats`).
|
||||
|
||||
### 1.2 Project lock + kit wiring
|
||||
- Добавить [`apps/web/src/lib/ui-surface.ts`](apps/web/src/lib/ui-surface.ts): `export const UI_SURFACE = 'frame' as const`.
|
||||
- Подключить shared kit вместо ad-hoc:
|
||||
- [`LoadingButton`](apps/web/src/components/loading-button.tsx) — settings save, approve/delete mutations.
|
||||
- [`QueryState`](apps/web/src/components/query-state.tsx) — error/retry на lists/rules/agents где сейчас raw Alert+Button.
|
||||
- [`FormSheet`](apps/web/src/components/form-sheet.tsx) — миграция create/edit sheets (`add-agent-sheet`, list/rule create) **только если** API FormSheet совместим с текущими формами; иначе удалить FormSheet как мёртвый код (не держать orphan). Default: **мигрировать** `AddAgentSheet` + один list create; остальные — если fit без redesign.
|
||||
|
||||
### 1.3 `/agents` — убрать дубль toolbar
|
||||
- Файл: [`routes/_auth/agents/index.tsx`](apps/web/src/routes/_auth/agents/index.tsx) (~19KB).
|
||||
- Cards view сейчас hand-roll Tabs+Filters+Search (строки ~504–550); table уже через [`AgentFleetDataGrid`](apps/web/src/components/agents/agent-fleet-data-grid.tsx) → `ResourcePage`.
|
||||
- Вынести общий toolbar (CountedLineTabs + Filters + search + viewToggle + clear) в shared helper/component (например `agents-fleet-toolbar.tsx`) **или** расширить `ResourcePage` slot `toolbarExtra` / `headerExtra` так, чтобы cards mode использовал тот же chrome, что table (без копипаста FramePanel).
|
||||
- Preview: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) · [solution-agents-1](https://reui.io/preview/base/solution-agents-1).
|
||||
|
||||
### 1.4 Settings
|
||||
- [`SettingsShell`](apps/web/src/components/reui-kit/settings-shell.tsx) сейчас ведёт на несуществующие `/settings/appearance|integrations`.
|
||||
- **Default:** один экран settings остаётся ([`routes/_auth/settings.tsx`](apps/web/src/routes/_auth/settings.tsx) — Frame + `SettingRow` по [settings-16](https://reui.io/preview/base/settings-16)); починить `SettingsShell` defaults (убрать фантомные табы / сделать configurable tabs без мёртвых href) **или** не экспортировать мёртвый shell до появления 2+ секций.
|
||||
- Добавить gating Quick Actions: ключ settings `show_quick_actions` (default `true`) + UI SettingRow (Switch) + чтение на `/` и `/agents`. Backend whitelist ключа — в Phase 2 вместе с Zod settings.
|
||||
|
||||
### 1.5 Auth callback + docs sync
|
||||
- [`auth.callback.tsx`](apps/web/src/routes/auth.callback.tsx) — Frame + Empty/loading по DNA [auth-13](https://reui.io/preview/base/auth-13) / [empty-state-12](https://reui.io/preview/base/empty-state-12) (без локального login — portal SSO).
|
||||
- Поправить `.cursor/rules/frontend-*.mdc`: убрать/пометить отсутствующие `login.tsx`, `KanbanBoard`, `settings/integrations.tsx`.
|
||||
|
||||
### 1.6 Verify Phase 1
|
||||
- `pnpm --filter @evofw/web build` (+ typecheck/lint если есть в package).
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Backend correctness + structure
|
||||
|
||||
### 2.1 Critical bugfixes (сначала)
|
||||
- **`bumpAgentsForList`**: при `setIds.length === 0` — **no-op**, не `bumpAllApprovedAgents` ([`packages/db/src/repositories/index.ts`](packages/db/src/repositories/index.ts)).
|
||||
- **Zod `PUT /settings`**: whitelist ключей (`enroll_seed`, `evobgp_api_url`, `evobgp_api_token`, `agent_sync_interval_sec`, `show_quick_actions`, …) в `@evofw/shared`.
|
||||
- **RBAC**: дополнить [`permissions.ts`](packages/shared/src/permissions.ts) — `install-links` → `fw:agents:*`, `integrations/*` → `fw:settings:read` / `fw:lists:read`.
|
||||
- Тест на bump no-op + settings reject unknown key + RBAC 403.
|
||||
|
||||
### 2.2 N+1 / perf
|
||||
- `GET /agents`: join/batch install links вместо per-row `getInstallLinkByAgentId`.
|
||||
- `GET /lists` / policy-sets: batch counts (GROUP BY), не `listEntries().length` / dual count per row.
|
||||
- `replaceIpListEntries` / `replaceResolvedForRule`: batch `values([...])`.
|
||||
|
||||
### 2.3 Split monoliths (как vps-tracker)
|
||||
- Разрезать [`control.ts`](apps/api/src/routes/control.ts) (~1020 строк) → `routes/{agents,lists,policy-sets,rules,install-links,settings,integrations-evobgp,stats,dashboard}.ts` + thin register в `app.ts`.
|
||||
- Разрезать [`repositories/index.ts`](packages/db/src/repositories/index.ts) → `repositories/{agents,lists,policy,stats,install-links,settings}.ts` + barrel.
|
||||
- Вынести `mapAgent` / `mapPolicySet` / `mapPolicyRule` → `apps/api/src/services/row-mappers.ts`.
|
||||
- Общий `uniq()` → один util.
|
||||
|
||||
### 2.4 Legacy hygiene (без breaking agent binary)
|
||||
- Документировать `policy_mode` как deprecated read-only; не расширять usage.
|
||||
- `domains` list type: UI/API уже нормализуют в `static` — убрать из новых create paths / labels где ещё светится.
|
||||
- Не дропать колонки в этой фазе (отдельная migration later).
|
||||
|
||||
### 2.5 Verify Phase 2
|
||||
- `pnpm --filter @evofw/api test` + smoke inject на approve/list/settings.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Contract + tests
|
||||
|
||||
### 3.1 OpenAPI
|
||||
- Дописать [`docs/openapi.yaml`](docs/openapi.yaml) до паритета с реальными routes (agents, lists, policy-sets, rules, overrides, install-links, preview, stats, settings, integrations, agent enroll/policy/apply).
|
||||
- Пометить в [`docs/README.md`](docs/README.md): OpenAPI = SoT для HTTP; живой runtime validation = Zod shared.
|
||||
- CI: redocly lint (по паттерну EvoBGP) — добавить job/script если CI уже есть.
|
||||
|
||||
### 3.2 Error envelope
|
||||
- Унифицировать SPA 404 и API errors на один формат `{ error: { code, message } }` (текущий API) **или** problem+json везде. Default: **оставить API `{error}`**, поправить SPA fallback в [`app.ts`](apps/api/src/app.ts) под тот же envelope.
|
||||
|
||||
### 3.3 Test belt
|
||||
- Inject tests: agents CRUD approve/revoke, policy rule create+reorder, list refresh bump, settings whitelist, RBAC install-links/integrations, `bumpAgentsForList` unused list.
|
||||
- Не гнаться за 100% coverage — critical paths only.
|
||||
|
||||
### 3.4 Verify Phase 3
|
||||
- API tests green; OpenAPI lint; web build still green.
|
||||
|
||||
---
|
||||
|
||||
## Out of scope (явно)
|
||||
- Локальный login page (SSO portal).
|
||||
- KanbanBoard (нет домена board в EvoFirewall).
|
||||
- Drop DB columns `policy_mode` / migrate all `domains` rows (отдельный breaking change).
|
||||
- Prefix-overlap conflict resolution в evaluate (менять семантику только отдельным RFC).
|
||||
@@ -0,0 +1,216 @@
|
||||
---
|
||||
name: EvoFW classic policy
|
||||
overview: Классическая цепочка deny→allow→default_action (без white/blacklist). Страница агента по layout DNA solution-agents-3. Multi-set preview. EvoBGP community autocomplete/refresh.
|
||||
todos:
|
||||
- id: policy-semantics
|
||||
content: Убрать white/blacklist; default_action accept|drop; единый apply deny→allow→default; deny-wins; apply_version; тесты
|
||||
status: completed
|
||||
- id: preview-api
|
||||
content: "GET /agents/:id/preview: summary, chain, CIDRs, conflicts; поле default_action"
|
||||
status: completed
|
||||
- id: agent-page-sa3
|
||||
content: Layout DNA solution-agents-3; Facts = default_action + identity (без mode toggle BL/WL)
|
||||
status: completed
|
||||
- id: agent-rules-ui
|
||||
content: Sortable sets; set detail без policy_mode; CIDR tabs Блок/Accept
|
||||
status: completed
|
||||
- id: evobgp-prefixes
|
||||
content: EvoBGP prefixes-by-community + FW refresh + docs
|
||||
status: completed
|
||||
- id: community-autocomplete
|
||||
content: FW proxy GET /v1/communities + ReUI Autocomplete
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Классическая политика EvoFirewall (без white/blacklist)
|
||||
|
||||
## Решение по white/blacklist
|
||||
|
||||
**White/blacklist как режимы не нужны.** Правила уже несут `deny`/`allow`; exclusive mode, который применял только один мешок CIDR, — ошибка модели.
|
||||
|
||||
Остаётся одна вещь, которую режимы маскировали: **что делать с пакетом, не попавшим ни в deny, ни в allow.**
|
||||
|
||||
| Было | Стало |
|
||||
|------|--------|
|
||||
| `policy_mode: blacklist` → только deny-set, default accept | `default_action: accept` |
|
||||
| `policy_mode: whitelist` → только allow-set, default drop | `default_action: drop` |
|
||||
| Набор со своим `policy_mode` + same-mode lock | У набора **нет** mode; только правила |
|
||||
| Две разные nft/MikroTik цепочки | **Одна** цепочка всегда |
|
||||
|
||||
```text
|
||||
1. established / lo → accept (Linux)
|
||||
2. @deny → drop
|
||||
3. @allow → accept
|
||||
4. default_action → accept | drop
|
||||
```
|
||||
|
||||
Миграция значений: `blacklist` → `accept`, `whitelist` → `drop`.
|
||||
|
||||
---
|
||||
|
||||
## Вердикт SA3 для страницы агента
|
||||
|
||||
**Да как layout DNA** (Header + Trace 2/3 + Facts 1/3), **нет** как AI-run шаблон.
|
||||
|
||||
| Элемент SA3 | Решение |
|
||||
|-------------|---------|
|
||||
| Header + actions | Approve / Revoke / Install / More |
|
||||
| Trace Timeline | Policy chain (правила Блок/Accept) |
|
||||
| Facts EditableDetailRow | **`default_action`** + identity/timestamps |
|
||||
| Retry / Pause / Owners / tool calls | Не копировать |
|
||||
| QuickActionGrid на detail | Убрать (действия в header) |
|
||||
|
||||
Preview: [solution-agents-3](https://reui.io/preview/base/solution-agents-3) · [Solutions/Agents](https://reui.io/blocks/solutions/agents)
|
||||
Страница: [`agents/$id.tsx`](c:\Users\shats\Dev\EvoFirewall\apps\web\src\routes\_auth\agents\$id.tsx)
|
||||
|
||||
---
|
||||
|
||||
## Исходное состояние (проблемы)
|
||||
|
||||
- Same-mode lock на assign sets ([`setAgentPolicySets`](c:\Users\shats\Dev\EvoFirewall\packages\db\src\repositories\index.ts))
|
||||
- Evaluate берёт mode с первого set; whitelist **игнорирует deny** в nft; blacklist **игнорирует allow**
|
||||
- MikroTik RSC **переключает** bl/wl, а не держит обе list-правила
|
||||
- Preview UI нет; EvoBGP refresh path мёртвый
|
||||
|
||||
---
|
||||
|
||||
## 1. Backend
|
||||
|
||||
### Модель
|
||||
|
||||
- **Правило:** `action: deny | allow` + источник (list / cidr / hostname)
|
||||
- **Агент:** `default_action: accept | drop` (колонка: migrate `agents.policy_mode` → `default_action` **или** переиспользовать колонку с новыми значениями + backfill)
|
||||
- **Набор:** без mode; убрать UI/валидацию `policy_mode`; колонка `policy_sets.policy_mode` — legacy ignore / drop в след. релизе
|
||||
- Убрать same-mode check; **не** писать default агента из sets
|
||||
|
||||
### Evaluate
|
||||
|
||||
[`evaluate.ts`](c:\Users\shats\Dev\EvoFirewall\apps\api\src\services\policy\evaluate.ts):
|
||||
|
||||
- Сбор bags по цепочке set.sort → rule.priority → overrides
|
||||
- `allowCidrs = allow \ deny` (exact) → `conflicts_dropped`
|
||||
- `defaultAction` с агента
|
||||
- Hash: `{ apply_version: 2, defaultAction, denyCidrs, allowCidrs, generation }`
|
||||
|
||||
### Agent protocol / apply
|
||||
|
||||
Единая цепочка (nft, ipset/iptables, MikroTik install + [`mikrotik-rsc.ts`](c:\Users\shats\Dev\EvoFirewall\apps\api\src\services\policy\mikrotik-rsc.ts)):
|
||||
|
||||
1. established/lo accept
|
||||
2. deny → drop
|
||||
3. allow → accept
|
||||
4. default = `default_action`
|
||||
|
||||
JSON агенту (apply_version ≥ 2):
|
||||
|
||||
```json
|
||||
{
|
||||
"apply_version": 2,
|
||||
"default_action": "accept",
|
||||
"deny_cidrs": [],
|
||||
"allow_cidrs": [],
|
||||
"hash": "...",
|
||||
"generation": 1
|
||||
}
|
||||
```
|
||||
|
||||
Compat на один релиз (опционально): зеркало `policy_mode: accept→blacklist, drop→whitelist` **только** для старых бинарников; новые скрипты читают `default_action` + всегда единую цепочку. После обновления one-liner — убрать зеркало.
|
||||
|
||||
### API / contracts
|
||||
|
||||
[`packages/shared/src/contracts.ts`](c:\Users\shats\Dev\EvoFirewall\packages\shared\src\contracts.ts):
|
||||
|
||||
- `defaultActionSchema = z.enum(['accept', 'drop'])`
|
||||
- Agent PATCH/response: `default_action` (accept legacy `policy_mode` blacklist/whitelist → map)
|
||||
- Policy set create/patch: **не** требовать / не экспозить mode в UI; API может игнорировать поле
|
||||
- Preview:
|
||||
|
||||
```ts
|
||||
{
|
||||
default_action, hash, generation, sync_interval_sec, apply_version: 2,
|
||||
summary: { sets, rules_deny, rules_allow, cidrs_deny, cidrs_allow, overrides, conflicts_dropped },
|
||||
chain: [{ set_id, set_name, rule_id, action, source_kind, source_label, cidr_count }],
|
||||
deny_cidrs, allow_cidrs, deny_cidrs_total, allow_cidrs_total
|
||||
}
|
||||
```
|
||||
|
||||
Только snake_case. Удалить `PolicyModeToggle` / mode с set detail.
|
||||
|
||||
Тесты: mixed deny+allow; exact conflict; unified apply оба default; assign разных бывших «режимов» sets без 400; hash apply_version.
|
||||
|
||||
---
|
||||
|
||||
## 2. UI: агент (SA3 layout DNA)
|
||||
|
||||
```bash
|
||||
cd apps/web && pnpm dlx shadcn@latest add @reui/solution-agents-3 --yes
|
||||
```
|
||||
|
||||
```
|
||||
PageShell
|
||||
├── AgentHeader
|
||||
├── KpiStatGrid ← summary [stats-12]
|
||||
├── grid @4xl: 2/3 + 1/3
|
||||
│ ├── AgentPolicyTrace ← chain Timeline + Collapsible + Alert
|
||||
│ └── AgentFactsPanel ← default_action Select + identity/dates
|
||||
├── AgentPolicySetsSortable ← без фильтра mode
|
||||
└── Effective CIDR Tabs ← Блок / Accept
|
||||
```
|
||||
|
||||
Facts editable: «Если не совпало» → Accept / Drop (`default_action`).
|
||||
Не показывать слова blacklist/whitelist.
|
||||
|
||||
### Preview-референсы
|
||||
|
||||
| Зона | Preview | Docs |
|
||||
|------|---------|------|
|
||||
| Каркас | [solution-agents-3](https://reui.io/preview/base/solution-agents-3) | [agents blocks](https://reui.io/blocks/solutions/agents) |
|
||||
| Frame | [frame](https://reui.io/components/frame) | [docs](https://reui.io/docs/components/base/frame) |
|
||||
| Timeline | [c-timeline-6](https://reui.io/preview/base/components/c-timeline-6) | [timeline](https://reui.io/docs/components/base/timeline) |
|
||||
| Facts rows | SA3 RunFacts · [settings-3](https://reui.io/preview/base/settings-3) | [settings](https://reui.io/blocks/application/settings) |
|
||||
| KPI | [stats-12](https://reui.io/preview/base/stats-12) | `KpiStatGrid` |
|
||||
| Sortable | [c-sortable-5](https://reui.io/preview/base/components/c-sortable-5) | [sortable](https://reui.io/components/sortable) |
|
||||
| CIDR tabs | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) · [c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) | [data-grid](https://reui.io/docs/components/base/data-grid) |
|
||||
| Alert | [c-alert-7](https://reui.io/preview/base/components/c-alert-7) | [alert](https://reui.io/docs/components/base/alert) |
|
||||
| Empty | [empty-state-12](https://reui.io/preview/base/empty-state-12) | — |
|
||||
| Sheets | [c-sheet-1](https://reui.io/preview/base/components/c-sheet-1) · [form-7](https://reui.io/preview/base/form-7) | [sheet](https://reui.io/components/sheet) |
|
||||
| Autocomplete | [c-autocomplete-8](https://reui.io/preview/base/components/c-autocomplete-8) · [c-autocomplete-9](https://reui.io/preview/base/components/c-autocomplete-9) | [autocomplete](https://reui.io/docs/components/base/autocomplete) |
|
||||
| Badge | [badge](https://reui.io/components/badge) | [docs](https://reui.io/docs/components/base/badge) |
|
||||
|
||||
Set detail: только правила deny/allow + sortable; без mode.
|
||||
Lists: community Autocomplete.
|
||||
|
||||
---
|
||||
|
||||
## 3. EvoBGP
|
||||
|
||||
- Autocomplete: proxy → `GET /v1/communities`
|
||||
- Refresh: новый `GET /v1/communities/{id}/prefixes` в EvoBGP + fix [`refresh.ts`](c:\Users\shats\Dev\EvoFirewall\apps\api\src\services\lists\refresh.ts)
|
||||
- Docs [`integrate-evobgp.md`](c:\Users\shats\Dev\EvoFirewall\docs\integrate-evobgp.md)
|
||||
|
||||
---
|
||||
|
||||
## 4. Docs
|
||||
|
||||
- architecture / agents: нет BL/WL; `default_action`; единый apply
|
||||
- OpenAPI: `default_action`, preview, communities proxy
|
||||
|
||||
---
|
||||
|
||||
## Порядок работ
|
||||
|
||||
1. Schema/contracts: `default_action`; deprecate set/agent `policy_mode` semantics
|
||||
2. Evaluate + unified apply (nft/ipset/MikroTik) + tests + apply_version
|
||||
3. Preview API
|
||||
4. SA3 agent page + Facts `default_action`
|
||||
5. Sets sortable / set detail cleanup
|
||||
6. EvoBGP prefixes + refresh
|
||||
7. Communities autocomplete
|
||||
8. Docs + build
|
||||
|
||||
## Вне скоупа
|
||||
|
||||
- Full first-match без sets
|
||||
- IPv6
|
||||
- AI-run UX из SA3 (Retry/Pause/Owners)
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: MM ReUI Dashboard
|
||||
overview: Wave D — /dashboard и /traffic: **hybrid KpiStatGrid** (EvoBGP DNA) + OpsDashboard + QuickActionGrid + Frame charts.
|
||||
todos:
|
||||
- id: dashboard-ops
|
||||
content: "Dashboard: StatCard → hybrid KpiStatGrid + OpsDashboard"
|
||||
status: pending
|
||||
- id: traffic-frame
|
||||
content: "Traffic: hybrid KpiStatGrid + Frame charts"
|
||||
status: pending
|
||||
- id: qa-grid
|
||||
content: QuickActionGrid (sibling hybrid DNA)
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Wave D — Dashboard & overview
|
||||
|
||||
Родитель: [MM ReUI Master](mm_reui_master_b80dbef6.plan.md). Зависимости: **A** (hybrid kit), **B** (эталон).
|
||||
|
||||
## Цель
|
||||
|
||||
Убрать hand-roll `StatCard` (Card). KPI = **hybrid как в старых apps**.
|
||||
|
||||
## KPI (MUST)
|
||||
|
||||
- `/dashboard` и `/traffic`: только `KpiStatGrid` hybrid ([stats-12](https://reui.io/preview/base/stats-12))
|
||||
- Удалить локальный `StatCard` / Card metric grids
|
||||
- NEVER: SectionCards, sparkline-inside-Card как KPI shell (sparkline может быть *внутри* hybrid tile через kit API, не отдельный Card KPI)
|
||||
- Quick Actions: только `QuickActionGrid` (sibling hybrid), не Button/Card grid
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | ~LOC | Работа |
|
||||
|-------|------|--------|
|
||||
| `/dashboard` | ~926 | `StatCard` → **hybrid** `KpiStatGrid`; `OpsDashboard`; Frame probes |
|
||||
| `/traffic` | ~1020 | **hybrid** KPI; Frame charts; semantic tokens |
|
||||
|
||||
## ReUI refs (MCP `surface: frame`)
|
||||
|
||||
- [dashboard-1](https://reui.io/preview/base/dashboard-1)
|
||||
- KPI: **только** [stats-12](https://reui.io/preview/base/stats-12) · QA [card-12](https://reui.io/preview/base/card-12) DNA via kit
|
||||
- Charts: [chart-15](https://reui.io/preview/base/chart-15) adapt (не KPI substitute)
|
||||
- Shell: [app-shell-12](https://reui.io/preview/base/app-shell-12)
|
||||
|
||||
## Ключевые файлы
|
||||
|
||||
- [`app/(main)/dashboard/page.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/dashboard/page.tsx)
|
||||
- [`components/dashboard/latency-chart.tsx`](c:/Users/shats/Dev/MikrotikManager-3/components/dashboard/latency-chart.tsx), `bandwidth-chart.tsx`, `internet-path-map.tsx`
|
||||
- [`components/data-grids/dashboard-active-probes-data-grid.tsx`](c:/Users/shats/Dev/MikrotikManager-3/components/data-grids/dashboard-active-probes-data-grid.tsx)
|
||||
- [`app/(main)/traffic/page.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/traffic/page.tsx)
|
||||
|
||||
## Порядок секций
|
||||
|
||||
**Hybrid KPI** → QuickActionGrid → charts / path map → attention + probes.
|
||||
`showQuickActions` default true.
|
||||
|
||||
## Инварианты
|
||||
|
||||
- Не менять API fetch
|
||||
- Path map — Frame wrap, не rewrite
|
||||
- Не смешивать Card KPI с Frame
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] Нет `StatCard`/Card KPI на `/dashboard` и `/traffic`
|
||||
- [ ] Только hybrid `KpiStatGrid` (EvoBGP DNA)
|
||||
- [ ] Charts/path в Frame panels
|
||||
- [ ] lint/build OK
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: MM ReUI Foundation
|
||||
overview: "Wave A — foundation: ui-design-contract, kpi-hybrid rule, порт KpiStatGrid (EvoBGP hybrid DNA 1:1), Card→Frame shell, chrome (240px, NavUser)."
|
||||
todos:
|
||||
- id: docs-contract
|
||||
content: ui-design-contract + reui-mcp/kpi-hybrid rules + AGENTS
|
||||
status: pending
|
||||
- id: port-kit
|
||||
content: "Порт reui-kit: KpiStatGrid hybrid DNA 1:1 с EvoBGP"
|
||||
status: pending
|
||||
- id: frame-bridge
|
||||
content: DataPageCard → Frame surface bridge
|
||||
status: pending
|
||||
- id: chrome-240-navuser
|
||||
content: Sidebar 240px + NavUser segmented theme
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Wave A — Foundation (MikrotikManager ReUI)
|
||||
|
||||
Родитель: [MM ReUI Master](mm_reui_master_b80dbef6.plan.md). Источники: [Explore MikrotikManager UI](a9e538ec-d664-4bf5-a712-4978beefe057), [ReUI SoT from siblings](97b837f2-382e-4bf9-b0fc-84678deb882c).
|
||||
|
||||
## Цель
|
||||
|
||||
Подготовить инфраструктуру ReUI PRO **без** массовой миграции страниц. После Wave A любой экран переводится на Frame + **hybrid KPI** (`KpiStatGrid`) по шаблону старых apps.
|
||||
|
||||
## KPI hybrid (закладывается здесь)
|
||||
|
||||
- Порт `KpiStatGrid` **байт-в-байт DNA** с EvoBGP (horizontal compact: icon `size-10.5` `bg-muted` + label/Badge + value ± variant)
|
||||
- Preview: [stats-12](https://reui.io/preview/base/stats-12) · rule `kpi-hybrid.mdc` в MM
|
||||
- **NEVER** в kit: SectionCards, vertical-only stats, Card KPI wrappers
|
||||
- Волны B–G только **потребляют** этот kit — не рисуют свои KPI
|
||||
|
||||
## Scope
|
||||
|
||||
### 1. Документы и rules
|
||||
|
||||
- Создать [`docs/ui-design-contract.md`](c:/Users/shats/Dev/MikrotikManager-3/docs/ui-design-contract.md) — адапт семейного контракта под пути MM (`components/reui`, `components/reui-kit`, `@/components/ui`, Next App Router); секция KPI = hybrid only
|
||||
- Cursor rules: `reui-mcp.mdc`, **`kpi-hybrid.mdc`** (копия семейного MUST/NEVER); обновить [`next-shadcn-production.mdc`](c:/Users/shats/Dev/MikrotikManager-3/.cursor/rules/next-shadcn-production.mdc)
|
||||
- Секция ReUI + KPI hybrid в [`AGENTS.md`](c:/Users/shats/Dev/MikrotikManager-3/AGENTS.md)
|
||||
|
||||
### 2. Kit port (SoT = EvoBGP)
|
||||
|
||||
Создать `components/reui-kit/`:
|
||||
|
||||
| Component | Источник |
|
||||
|-----------|----------|
|
||||
| **`KpiStatGrid`** | EvoBGP `kpi-stat-grid.tsx` — **hybrid DNA 1:1**, импорты → `@/components/ui/*` |
|
||||
| `QuickActionGrid` | EvoBGP sibling hybrid DNA (не Card grid) |
|
||||
| `ResourcePage` | Frame + tabs + Filters + DataGrid |
|
||||
| `OpsDashboard` | **hybrid** KPI strip + afterKpi + charts slot |
|
||||
| `DetailPanel` | Frame sections (+ Metrics → KpiStatGrid) |
|
||||
| `SettingsShell` | Next nested layout / local nav (не TanStack Outlet) |
|
||||
| `filter-utils` | apply/clear Filters |
|
||||
|
||||
`"use client"` на всех kit-компонентах с хуками (`rsc: true` в MM).
|
||||
|
||||
### 3. Surface bridge
|
||||
|
||||
- [`DataPageCard`](c:/Users/shats/Dev/MikrotikManager-3/components/data-page-card.tsx): заменить внутренний `Card` на ReUI `Frame`/`FramePanel` **или** ввести `DataPageFrame` + deprecate Card-shell (совместимый API `children`/`className`, чтобы Wave B+ меняли минимум)
|
||||
- Сохранить [`DataGridShell`](c:/Users/shats/Dev/MikrotikManager-3/components/data-grids/shared/data-grid-shell.tsx) как есть
|
||||
|
||||
### 4. Chrome
|
||||
|
||||
- [`components/ui/sidebar.tsx`](c:/Users/shats/Dev/MikrotikManager-3/components/ui/sidebar.tsx): `SIDEBAR_WIDTH` `16rem` → **`240px`**
|
||||
- [`app-sidebar.tsx`](c:/Users/shats/Dev/MikrotikManager-3/components/app-sidebar.tsx): подключить [`NavUser`](c:/Users/shats/Dev/MikrotikManager-3/components/nav-user.tsx) с segmented theme (убрать demo Sparkles/Billing); убрать дублирующий theme toggle из footer если переезжает в NavUser
|
||||
- Search: оставить ⌘K (не Search pill в header)
|
||||
- **AppsMenu / auth-portal / SystemMonitor — вне scope** (см. master)
|
||||
|
||||
### 5. MCP / CLI hygiene
|
||||
|
||||
- `user-reui` `get_project_context` + `surface: frame` на будущих задачах
|
||||
- При add PRO blocks: `npx shadcn@latest add @reui/<name> --dry-run` перед overwrite существующих `components/reui/*`
|
||||
- Preview refs: [app-shell-12](https://reui.io/preview/base/app-shell-12), **[stats-12](https://reui.io/preview/base/stats-12)** (hybrid KPI)
|
||||
|
||||
## Out of scope
|
||||
|
||||
Миграция `app/(main)/*/page.tsx` (кроме smoke-проверки, что Frame-bridge не ломает layout).
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] `docs/ui-design-contract.md` + `kpi-hybrid.mdc` на месте
|
||||
- [ ] `KpiStatGrid` markup совпадает с EvoBGP (diff только imports)
|
||||
- [ ] `components/reui-kit/*` собирается, импорты `@/components/ui`
|
||||
- [ ] List shell = Frame (не Card)
|
||||
- [ ] Sidebar 240px + NavUser theme
|
||||
- [ ] `npm run lint` / `npm run build` без регрессий от foundation
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: MM ReUI Lists Medium
|
||||
overview: "Wave C — medium CRUD: Frame + **hybrid KpiStatGrid** (EvoBGP DNA) на communities/wg/vxlan/containers/gre/backups/certificates."
|
||||
todos:
|
||||
- id: c1-small-crud
|
||||
content: "communities/wg/vxlan/containers: hybrid KPI + Frame"
|
||||
status: pending
|
||||
- id: c2-tunnels-certs
|
||||
content: "gre/backups/certificates: hybrid KPI + Frame"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Wave C — Medium CRUD lists
|
||||
|
||||
Родитель: [MM ReUI Master](mm_reui_master_b80dbef6.plan.md). Зависимость: **Wave B** (эталон hybrid KPI).
|
||||
|
||||
## Цель
|
||||
|
||||
Тиражировать `/servers`: Frame shell + **hybrid KPI** на medium CRUD.
|
||||
|
||||
## KPI (MUST)
|
||||
|
||||
- Любой metric strip на этих routes → только `KpiStatGrid` hybrid ([stats-12](https://reui.io/preview/base/stats-12))
|
||||
- Замена существующих `Card` KPI grids — без исключений
|
||||
- NEVER: StatCard, SectionCards, vertical-only, raw emerald
|
||||
|
||||
## Routes (priority)
|
||||
|
||||
| Route | ~LOC | Паттерн |
|
||||
|-------|------|---------|
|
||||
| `/communities` | ~268 | Card KPI → **hybrid** `KpiStatGrid`; Frame + CommunitiesDataGrid |
|
||||
| `/wireguard` | ~256 | То же + peers detail |
|
||||
| `/vxlan` | ~250 | То же |
|
||||
| `/containers` | ~435 | Card list → DataGrid/Frame tiles; KPI → **hybrid** |
|
||||
| `/gre` | ~778 | Dual grids + **hybrid** KPI strip |
|
||||
| `/backups` | ~752 | **hybrid** KPI + DataGrid + Stepper |
|
||||
| `/certificates` | ~779 | **hybrid** KPI + DataGrid + Stepper |
|
||||
|
||||
Split PR: C1 / C2 при необходимости.
|
||||
|
||||
## Refs
|
||||
|
||||
- KPI: **только** [stats-12](https://reui.io/preview/base/stats-12) via kit
|
||||
- Lists: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)
|
||||
- Stepper — reuse
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] Нет Card как ops shell
|
||||
- [ ] Все KPI-полосы — hybrid `KpiStatGrid` (как EvoBGP)
|
||||
- [ ] Визуально = `/servers`
|
||||
- [ ] lint/build OK
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
name: MM ReUI Lists Simple
|
||||
overview: Wave B — /servers и simple lists на Frame + **hybrid KpiStatGrid** (EvoBGP DNA) + ResourcePage.
|
||||
todos:
|
||||
- id: servers-etalon
|
||||
content: "/servers: Frame + hybrid KpiStatGrid (не Card KPI)"
|
||||
status: pending
|
||||
- id: simple-lists
|
||||
content: domains / asns / ip-ranges / releases
|
||||
status: pending
|
||||
- id: loading-skeleton
|
||||
content: loading.tsx skeleton под hybrid KPI + Frame
|
||||
status: pending
|
||||
- id: rules-etalon
|
||||
content: Закрепить Frame+/servers hybrid KPI как эталон
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Wave B — Etalon + simple lists
|
||||
|
||||
Родитель: [MM ReUI Master](mm_reui_master_b80dbef6.plan.md). Зависимость: **Wave A** (hybrid `KpiStatGrid` в kit).
|
||||
|
||||
## Цель
|
||||
|
||||
Сделать `/servers` эталоном ReUI PRO: Frame shell + **hybrid KPI** как в EvoBGP/vps/CFDM.
|
||||
|
||||
## KPI (MUST)
|
||||
|
||||
- Метрики на `/servers` (всего / онлайн / JH+Exit / Home) → **только** `KpiStatGrid` hybrid
|
||||
- Preview: [stats-12](https://reui.io/preview/base/stats-12)
|
||||
- Удалить `grid … Card` KPI strip; **запрещены** StatCard / SectionCards / vertical stats
|
||||
- Name-cells в grid: hybrid icon tile (`bg-muted` + semantic text)
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | ~LOC | Работа |
|
||||
|-------|------|--------|
|
||||
| `/servers` | ~724 | **Эталон:** **hybrid** `KpiStatGrid`; Frame; toolbar+Filters+`ServersDataGrid`; Sheet+Stepper без редизайна |
|
||||
| `/domains` | ~98 | Frame + DataGrid (+ empty); KPI — только если появятся метрики → hybrid |
|
||||
| `/asns` | ~98 | То же |
|
||||
| `/ip-ranges` | ~99 | То же |
|
||||
| `/releases` | ~97 | Frame/settings-lite; без KPI, если нет метрик |
|
||||
|
||||
## ReUI refs
|
||||
|
||||
- List: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) · [data-grid-filtering-1](https://reui.io/preview/base/data-grid-filtering-1)
|
||||
- KPI: **только** [stats-12](https://reui.io/preview/base/stats-12) hybrid via kit
|
||||
- Empty: [empty-state-12](https://reui.io/preview/base/empty-state-12)
|
||||
- Forms sheet: [form-7](https://reui.io/preview/base/form-7)
|
||||
|
||||
MCP: `search`/`get_block`/`validate_usage` с `surface: "frame"`.
|
||||
|
||||
## Ключевые файлы
|
||||
|
||||
- [`app/(main)/servers/page.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/servers/page.tsx)
|
||||
- [`components/data-grids/servers-data-grid.tsx`](c:/Users/shats/Dev/MikrotikManager-3/components/data-grids/servers-data-grid.tsx)
|
||||
- [`app/(main)/loading.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/loading.tsx) — skeleton под Frame + hybrid KPI
|
||||
- [`lib/data-filters/server-filter-fields.ts`](c:/Users/shats/Dev/MikrotikManager-3/lib/data-filters/server-filter-fields.ts)
|
||||
|
||||
## Правила
|
||||
|
||||
- Не менять API CRUD / poll / test connection
|
||||
- После миграции: `ma-ui-guardian` / `next-shadcn-production` — эталон = Frame + hybrid KPI `/servers`
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] `/servers`: нет Card KPI; только hybrid `KpiStatGrid`
|
||||
- [ ] domains/asns/ip-ranges на Frame+DataGrid
|
||||
- [ ] loading skeleton согласован с hybrid strip
|
||||
- [ ] UI Guardian сверка с новым эталоном
|
||||
- [ ] lint/build OK
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
name: MM ReUI Master
|
||||
overview: Мастер-план поэтапного перевода MikrotikManager-3 на ReUI PRO (surface Frame + kit), без смены Next.js/API. DataGrid уже на ReUI — фокус на Frame, hybrid KPI, shell chrome и волнах экранов через отдельные подпланы.
|
||||
todos:
|
||||
- id: wave-a
|
||||
content: "Подплан A: Foundation (contract, kit, Frame shell, chrome)"
|
||||
status: pending
|
||||
- id: wave-b
|
||||
content: "Подплан B: /servers эталон + simple lists"
|
||||
status: pending
|
||||
- id: wave-c
|
||||
content: "Подплан C: medium CRUD lists"
|
||||
status: pending
|
||||
- id: wave-d
|
||||
content: "Подплан D: dashboard + traffic"
|
||||
status: pending
|
||||
- id: wave-e
|
||||
content: "Подплан E: network ops giants"
|
||||
status: pending
|
||||
- id: wave-f
|
||||
content: "Подплан F: settings / alerts / system"
|
||||
status: pending
|
||||
- id: wave-g
|
||||
content: "Подплан G: map / terminal / uptime / optimizer"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# MikrotikManager → ReUI PRO (мастер-план)
|
||||
|
||||
## Вердикт
|
||||
|
||||
Проект **уже на полпути**: `base-nova`, `@reui` registry, `REUI_LICENSE_KEY`, MCP/`reui` skill, ~33 domain DataGrid на ReUI. Главный разрыв с ops-contract (EvoBGP/vps/CFDM): **shell списков = shadcn Card** ([`DataPageCard`](c:/Users/shats/Dev/MikrotikManager-3/components/data-page-card.tsx)), **Frame не используется**, **нет `reui-kit`**, KPI — hand-roll Card. Стек остаётся **Next.js 16 App Router** (не Vite monorepo).
|
||||
|
||||
## Зафиксированные решения
|
||||
|
||||
| Решение | Выбор |
|
||||
|---------|--------|
|
||||
| Surface lock | `frame` (не Card) |
|
||||
| Runtime | Next.js 16 + `@/components/ui` + `@/components/reui` |
|
||||
| Kit SoT | Порт DNA из EvoBGP (`KpiStatGrid`, `QuickActionGrid`, `ResourcePage`, `OpsDashboard`, `SettingsShell`, `DetailPanel`) → `components/reui-kit/` |
|
||||
| **KPI (все волны)** | **Только hybrid** как в EvoBGP / vps / CFDM / EvoFirewall / auth-portal: `reui-kit/KpiStatGrid` (DNA [stats-12](https://reui.io/preview/base/stats-12)). Markup 1:1 с EvoBGP — менять только import path |
|
||||
| API / UX behavior | Без изменений контрактов ([`docs/refactor-baseline.md`](c:/Users/shats/Dev/MikrotikManager-3/docs/refactor-baseline.md)) |
|
||||
| App Switcher / auth-portal | **Вне scope** этой миграции (MM standalone); chrome только выравниваем под contract |
|
||||
| Эталон | `/servers` остаётся эталоном — после Wave A+B **обновляется** под Frame + **hybrid KPI** |
|
||||
|
||||
## Целевая архитектура UI
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph chrome [App Shell]
|
||||
Sidebar[AppSidebar NavUser theme]
|
||||
Header[PageHeader sticky]
|
||||
end
|
||||
subgraph kit [components/reui-kit]
|
||||
ResourcePage
|
||||
KpiStatGrid
|
||||
QuickActionGrid
|
||||
OpsDashboard
|
||||
SettingsShell
|
||||
DetailPanel
|
||||
end
|
||||
subgraph reui [components/reui]
|
||||
Frame
|
||||
DataGrid
|
||||
Filters
|
||||
Badge
|
||||
Stepper
|
||||
end
|
||||
subgraph pages [app main routes]
|
||||
RoutePages[page.tsx domain wiring]
|
||||
end
|
||||
chrome --> pages
|
||||
pages --> kit
|
||||
kit --> reui
|
||||
pages --> DataGrids[components/data-grids]
|
||||
DataGrids --> DataGrid
|
||||
```
|
||||
|
||||
**Замена surface:** `DataPageCard` (Card) → Frame-обёртка (например `DataPageFrame` / `ResourcePage`), без смешивания Card+Frame на ops-экране.
|
||||
|
||||
## KPI hybrid — жёсткий контракт (все подпланы A–G)
|
||||
|
||||
Как в старых ops-apps. SoT markup: EvoBGP `apps/web/src/components/reui-kit/kpi-stat-grid.tsx`. Preview: [stats-12](https://reui.io/preview/base/stats-12). Rule: `kpi-hybrid.mdc`.
|
||||
|
||||
**MUST**
|
||||
|
||||
- Любая KPI-полоса / metric strip → только `components/reui-kit/KpiStatGrid` (через `OpsDashboard` / `DetailPanel.Metrics` при наличии)
|
||||
- Horizontal compact hybrid: icon left `Item` `size-10.5` `bg-muted` + `border-background` + shadow + `ItemMedia` + label/Badge + value ± `variant`
|
||||
- Row icon tiles в data-grid — та же DNA (semantic `text-*` на `bg-muted`)
|
||||
- Quick Actions — только `QuickActionGrid` (sibling hybrid DNA), не Card/Button grid
|
||||
|
||||
**NEVER (ни в одной волне)**
|
||||
|
||||
- `StatCard` / SectionCards / vertical-only KPI / hand-roll Frame/Card KPI grid
|
||||
- `stats-7` / `stats-1` / `card-35` как замена hybrid KPI
|
||||
- Solid brand fill вместо `bg-muted`; raw `bg-emerald-*` / `text-emerald-*`
|
||||
- Копипаст ReUI block KPI в route — только adapt через kit
|
||||
|
||||
Мягкие формулировки «KPI если есть» = **если метрики на экране есть — только hybrid `KpiStatGrid`**, иначе блок не рисуем.
|
||||
|
||||
## Canonical ReUI refs (MCP `surface: frame`)
|
||||
|
||||
| Зона | Block / DNA | Preview |
|
||||
|------|-------------|---------|
|
||||
| Shell | `app-shell-12` (+ monitor/cmdk DNA) | https://reui.io/preview/base/app-shell-12 |
|
||||
| KPI | **hybrid only** stats-12 (EvoBGP SoT) | https://reui.io/preview/base/stats-12 |
|
||||
| Lists | `data-grid-filtering-2` / `data-grid-filtering-1` | https://reui.io/preview/base/data-grid-filtering-2 |
|
||||
| Dashboard | `dashboard-1` | https://reui.io/preview/base/dashboard-1 |
|
||||
| Settings | `settings-16` + `settings-3` | https://reui.io/preview/base/settings-16 |
|
||||
| Empty | `empty-state-12` / shadcn Empty | https://reui.io/preview/base/empty-state-12 |
|
||||
| Forms Sheet | `form-7` DNA | https://reui.io/preview/base/form-7 |
|
||||
|
||||
Docs: https://reui.io/llms.txt · https://reui.io/docs/get-started · https://reui.io/docs/styling · https://reui.io/docs/mcp
|
||||
|
||||
## Workflow на каждую волну (обязательный)
|
||||
|
||||
1. MCP `user-reui`: `search` / `compose_page` / `get_block` / `get_component` с `surface: "frame"`
|
||||
2. CLI из корня MM: `npx shadcn@latest add @reui/<name> --yes` (или `--dry-run` перед overwrite)
|
||||
3. Adapt → `reui-kit` / route; **не** копипаст block в `page.tsx`
|
||||
4. `validate_usage` + `get_audit_checklist`
|
||||
5. Gate: `npm run lint` + визуальная сверка с `/servers` (после его миграции) + `npm run build` на milestone
|
||||
|
||||
## Волны = отдельные подпланы
|
||||
|
||||
Не выполнять всё одним PR. Каждый подплан — свой scope, DoD и PR(ы).
|
||||
|
||||
| ID | Подплан | Scope (кратко) |
|
||||
|----|---------|----------------|
|
||||
| **A** | [Foundation](mm_reui_foundation_8b1c5482.plan.md) | Contract, **kpi-hybrid**, порт hybrid `KpiStatGrid`, Frame shell, chrome |
|
||||
| **B** | [Etalon + simple lists](mm_reui_lists_simple_d9c1b930.plan.md) | `/servers` эталон Frame + **hybrid KPI** + simple lists |
|
||||
| **C** | [CRUD lists medium](mm_reui_lists_medium_1a708596.plan.md) | Medium CRUD: Frame + **hybrid KPI** |
|
||||
| **D** | [Dashboard & overview](mm_reui_dashboard_b54ab10d.plan.md) | `/dashboard` `/traffic`: StatCard → **hybrid** |
|
||||
| **E** | [Network ops](mm_reui_network_ops_e8a979a4.plan.md) | Network giants: Frame + **hybrid KPI** |
|
||||
| **F** | [System](mm_reui_system_9c5741cb.plan.md) | Settings/alerts/…: Frame + **hybrid** где есть метрики |
|
||||
| **G** | [Special surfaces](mm_reui_special_04f7eebc.plan.md) | Map/terminal/uptime/optimizer: Frame + **hybrid KPI** |
|
||||
|
||||
Порядок: **A → B → C → D**, затем **E∥F** (параллельно разными PR), затем **G**.
|
||||
|
||||
## Инварианты
|
||||
|
||||
- Не менять API shape / бизнес-логику backend
|
||||
- Не ломать ReUI DataGrid shell без нужды — переиспользовать [`DataGridShell`](c:/Users/shats/Dev/MikrotikManager-3/components/data-grids/shared/data-grid-shell.tsx)
|
||||
- **KPI: только hybrid `KpiStatGrid` (EvoBGP DNA)** — см. секцию выше; действует на волны B–G
|
||||
- Запрет: Card как ops list shell; hand-roll / Card / SectionCards KPI; `space-y-*`; raw `bg-emerald-*`
|
||||
- Giant pages (>1500 LOC): сначала вынос секций в компоненты, потом Frame — не «big bang rewrite»
|
||||
|
||||
## Документы / rules (часть Wave A)
|
||||
|
||||
- Добавить [`docs/ui-design-contract.md`](c:/Users/shats/Dev/MikrotikManager-3/docs/ui-design-contract.md) (адапт EvoBGP contract под MM paths)
|
||||
- Cursor rules: `reui-mcp.mdc`, `kpi-hybrid.mdc`, обновить `next-shadcn-production.mdc` (эталон после Wave B)
|
||||
- `AGENTS.md` — секция ReUI + команды CLI
|
||||
|
||||
## Риски
|
||||
|
||||
- **PRO license** нужна для premium blocks; free components уже стоят
|
||||
- Overwrite shadcn CLI на существующие `components/reui/*` — всегда `--dry-run` / diff
|
||||
- Giant pages (`alerts` ~3k, `uptime` ~2.7k) — отдельный подплан, иначе регрессии
|
||||
- Theme: сейчас toggle в sidebar footer кастомный → NavUser segmented (contract)
|
||||
|
||||
## Definition of Done (весь продукт)
|
||||
|
||||
- Все ops-экраны на Frame surface; Card только где нужен interactive container внутри, не как page shell
|
||||
- Все KPI-полосы — **hybrid** `KpiStatGrid` (идентично EvoBGP/vps/CFDM/FW); lists через Frame + DataGrid (+ Filters где есть)
|
||||
- `/servers` = актуальный эталон под ReUI PRO
|
||||
- Rules + `ui-design-contract.md` в репо
|
||||
- Lint/build зелёные на milestone после каждой волны
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: MM ReUI Network Ops
|
||||
overview: "Wave E — network ops: Frame + **hybrid KpiStatGrid** на filters/firewall/bgp/ospf/recursive (+ хвост C)."
|
||||
todos:
|
||||
- id: e1-bgp-rr
|
||||
content: "E1: recursive-routes + bgp Frame + hybrid KPI"
|
||||
status: pending
|
||||
- id: e2-filters
|
||||
content: "E2: filters extract + Frame + hybrid KPI if any"
|
||||
status: pending
|
||||
- id: e3-firewall
|
||||
content: "E3: firewall Frame + hybrid KPI if any"
|
||||
status: pending
|
||||
- id: e4-ospf
|
||||
content: "E4: ospf hybrid KPI + Frame (+ leftover C)"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Wave E — Network ops
|
||||
|
||||
Родитель: [MM ReUI Master](mm_reui_master_b80dbef6.plan.md). Зависимости: **A+B**; пересечение с **C** без дубля.
|
||||
|
||||
## Цель
|
||||
|
||||
Giant network pages → Frame + kit; метрики только **hybrid KPI**.
|
||||
|
||||
## KPI (MUST)
|
||||
|
||||
- Любые analytics/summary Cards (`/bgp`, `/ospf`, `/firewall`, …) → **hybrid** `KpiStatGrid`
|
||||
- Preview: [stats-12](https://reui.io/preview/base/stats-12)
|
||||
- Если метрик на экране нет — KPI-блок не рисуем; если есть — только hybrid
|
||||
- NEVER: Card KPI grid, SectionCards, stats-7 vertical
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | ~LOC | Стратегия |
|
||||
|-------|------|-----------|
|
||||
| `/filters` | ~1790 | Frame layout; Sheets logic intact; KPI→hybrid если есть |
|
||||
| `/firewall` | ~1725 | Frame panels; rules DataGrid; summary → hybrid |
|
||||
| `/recursive-routes` | ~661 | Frame + DataGrid; KPI→hybrid если есть |
|
||||
| `/bgp` | ~720 | analytics Cards → **hybrid** `KpiStatGrid`; sessions grid |
|
||||
| `/ospf` | ~1270 | Frame tabs + grids; summary → **hybrid** |
|
||||
| `/gre` `/backups` `/certificates` | — | Только если не закрыты C (там тоже hybrid) |
|
||||
|
||||
## Подход
|
||||
|
||||
1. Extract секций
|
||||
2. Card shells → Frame
|
||||
3. KPI → **hybrid** `KpiStatGrid`
|
||||
4. `validate_usage`
|
||||
|
||||
## ReUI refs
|
||||
|
||||
- Lists: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)
|
||||
- KPI: **только** [stats-12](https://reui.io/preview/base/stats-12)
|
||||
- Frame/DetailPanel docs
|
||||
|
||||
## PR split
|
||||
|
||||
E1 recursive+bgp · E2 filters · E3 firewall · E4 ospf (+ leftover)
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] Нет Card page shell
|
||||
- [ ] Все metric strips — hybrid `KpiStatGrid`
|
||||
- [ ] API/CRUD без регрессий
|
||||
- [ ] lint/build per PR
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: MM ReUI Special
|
||||
overview: "Wave G — special surfaces: Frame chrome; KPI на uptime/optimizer только **hybrid KpiStatGrid** (EvoBGP DNA)."
|
||||
todos:
|
||||
- id: g1-term-opt
|
||||
content: "G1: terminal Frame; optimizer Frame + hybrid KPI"
|
||||
status: pending
|
||||
- id: g2-uptime
|
||||
content: "G2: uptime extract + Frame + hybrid KpiStatGrid"
|
||||
status: pending
|
||||
- id: g3-map
|
||||
content: "G3: network-map Frame chrome (KPI hybrid if any)"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Wave G — Special surfaces
|
||||
|
||||
Родитель: [MM ReUI Master](mm_reui_master_b80dbef6.plan.md). Зависимости: **A+B**.
|
||||
|
||||
## Цель
|
||||
|
||||
Кастом map/CLI без форса DataGrid. Metric strips — **только hybrid KPI** как в старых apps.
|
||||
|
||||
## KPI (MUST)
|
||||
|
||||
- `/uptime`, `/route-optimizer` (и map, если есть summary): Card/Stat KPI → **hybrid** `KpiStatGrid`
|
||||
- Preview: [stats-12](https://reui.io/preview/base/stats-12)
|
||||
- `/terminal`: обычно без KPI-полосы
|
||||
- NEVER: StatCard, SectionCards, vertical-only
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | ~LOC | Стратегия |
|
||||
|-------|------|-----------|
|
||||
| `/network-map` | ~2254 | Frame side panels; canvas без redesign; KPI→hybrid если есть |
|
||||
| `/terminal` | ~552 | Frame CLI; без KPI |
|
||||
| `/uptime` | ~2669 | Extract; **hybrid** `KpiStatGrid`; grids → Frame |
|
||||
| `/route-optimizer` | ~1034 | Frame tabs; Card KPI → **hybrid** |
|
||||
|
||||
## ReUI refs
|
||||
|
||||
- KPI: **только** [stats-12](https://reui.io/preview/base/stats-12)
|
||||
- [dashboard-1](https://reui.io/preview/base/dashboard-1) для uptime overview layout
|
||||
- Lists: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)
|
||||
|
||||
## PR split
|
||||
|
||||
G1 terminal+optimizer · G2 uptime · G3 network-map
|
||||
|
||||
## Инварианты
|
||||
|
||||
- Не менять map/terminal protocols
|
||||
- Card только как interactive widget, не page shell / не KPI
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] Нет Card page shell
|
||||
- [ ] uptime/optimizer KPI = hybrid `KpiStatGrid`
|
||||
- [ ] Map/terminal smoke OK
|
||||
- [ ] Final lint/build + contract checklist
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: MM ReUI System
|
||||
overview: "Wave F — settings/alerts/data-collection/probes: Frame + SettingsShell; metric strips только **hybrid KpiStatGrid**."
|
||||
todos:
|
||||
- id: f-settings
|
||||
content: settings → SettingsShell + Frame (hybrid KPI if metrics)
|
||||
status: pending
|
||||
- id: f-alerts
|
||||
content: "alerts: extract + Frame; hybrid KPI if metrics"
|
||||
status: pending
|
||||
- id: f-collect-probes
|
||||
content: "data-collection/probes: Frame + hybrid KPI strips"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Wave F — System
|
||||
|
||||
Родитель: [MM ReUI Master](mm_reui_master_b80dbef6.plan.md). Зависимости: **A**; параллельно с **E**.
|
||||
|
||||
## Цель
|
||||
|
||||
System screens на Frame/SettingsShell; любые summary metrics → **hybrid KPI** как в старых apps.
|
||||
|
||||
## KPI (MUST)
|
||||
|
||||
- `/data-collection`, `/probes`, `/alerts` (если есть counters/summary) → только `KpiStatGrid` hybrid
|
||||
- Preview: [stats-12](https://reui.io/preview/base/stats-12)
|
||||
- Settings: обычно rows без KPI-полосы; если появятся — hybrid only
|
||||
- NEVER: Card KPI, SectionCards
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | ~LOC | Паттерн |
|
||||
|-------|------|---------|
|
||||
| `/settings` | ~1586 | SettingsShell + Frame rows; DataGrids в Frame |
|
||||
| `/alerts` | ~2989 | Extract → Frame; summary → **hybrid** если есть |
|
||||
| `/data-collection` | ~1250 | Frame grids; summary Cards → **hybrid** `KpiStatGrid` |
|
||||
| `/probes` | ~1085 | Frame; schedule/speed grids; KPI → **hybrid** |
|
||||
|
||||
## Alerts strategy
|
||||
|
||||
F1 extract · F2 Frame + Badge/Alert · F3 DataGrid; KPI hybrid на F2 если есть метрики.
|
||||
|
||||
## ReUI refs
|
||||
|
||||
- Settings: [settings-16](https://reui.io/preview/base/settings-16) · [settings-3](https://reui.io/preview/base/settings-3)
|
||||
- KPI: **только** [stats-12](https://reui.io/preview/base/stats-12)
|
||||
- Alert: https://reui.io/docs/components/base/alert
|
||||
- Lists: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)
|
||||
|
||||
## Ключевые файлы
|
||||
|
||||
- [`app/(main)/settings/page.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/settings/page.tsx)
|
||||
- [`app/(main)/alerts/page.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/alerts/page.tsx)
|
||||
- [`app/(main)/data-collection/page.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/data-collection/page.tsx)
|
||||
- [`app/(main)/probes/page.tsx`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/probes/page.tsx)
|
||||
|
||||
## DoD
|
||||
|
||||
- [ ] Settings на SettingsShell/Frame
|
||||
- [ ] Alerts extract + Frame; API engine без изменений
|
||||
- [ ] data-collection/probes: Frame + **hybrid** KPI где были Card metrics
|
||||
- [ ] lint/build per sub-PR
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
name: UX UI Unification Audit
|
||||
overview: "Discovery завершён. Уровень унификации (решение пользователя): идентичный chrome во всех 6 приложениях + общая UX-система; доменные страницы остаются разными. Deliverable после approval — только vps-tracker/UX-UI-PLAN.md, без правок production-кода."
|
||||
todos:
|
||||
- id: write-plan-file
|
||||
content: Создать c:\Users\shats\Dev\vps-tracker\UX-UI-PLAN.md со всеми секциями правила аудита (без правок production-кода)
|
||||
status: in_progress
|
||||
- id: registry-changes
|
||||
content: "Заполнить REUI Reference Registry + Final Change Matrix: каждый UI CHANGE с Ref ID и официальным URL"
|
||||
status: in_progress
|
||||
- id: consistency-gate
|
||||
content: "Пройти Final Consistency Gate: 6/6 coverage, no fabricated URLs, functionality preserved"
|
||||
status: pending
|
||||
- id: chrome-spec
|
||||
content: В UX-UI-PLAN.md зафиксировать Chrome Identity Spec для всех 6 приложений (header/sidebar/theme/App Switcher) как обязательный 1:1
|
||||
status: in_progress
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# UX/UI Unification — Audit Complete, Plan File Next
|
||||
|
||||
## Scope of this phase
|
||||
|
||||
После подтверждения плана будет создан **только** [`c:\Users\shats\Dev\vps-tracker\UX-UI-PLAN.md`](c:/Users/shats/Dev/vps-tracker/UX-UI-PLAN.md) (выбрано пользователем). Production/source code, зависимости, CSS и компоненты **не изменяются**. Implementation UI — отдельный запрос.
|
||||
|
||||
Источники истины для references: [reui.io/docs](https://reui.io/docs), [reui.io/llms.txt](https://reui.io/llms.txt), MCP `user-reui` (`get_block` / `list_components` / `compose_page` / `get_component`). Skill ReUI локально и в MCP: `668fb463eb` (совпадает). Версию ReUI PRO **не** указываем — её нет.
|
||||
|
||||
## Coverage gate
|
||||
|
||||
`Discovered: 6` · `Audited: 6` · `Planned: 6` · `Coverage: 100%`
|
||||
|
||||
| # | Project | Path | Type | UI |
|
||||
|---|---|---|---|---|
|
||||
| 1 | VPS Tracker | `c:\Users\shats\Dev\vps-tracker` | fullstack Vite SPA | Frame + kit, mixed Card on charts/detail |
|
||||
| 2 | Cloudflare Domain Manager | `c:\Users\shats\Dev\cloudflare-domain-manager` | fullstack Vite SPA | Frame-first, closest to contract |
|
||||
| 3 | EvoBGP | `c:\Users\shats\Dev\EvoBGP` | fullstack (Go API + Vite SPA) | SoT hybrid KPI; leftover Card on stub/empty |
|
||||
| 4 | Auth Portal | `c:\Users\shats\Dev\auth-portal` | fullstack Vite SPA | Frame admin; thin kit; no SystemMonitor |
|
||||
| 5 | EvoFirewall | `c:\Users\shats\Dev\EvoFirewall` | fullstack Vite SPA | Frame + kit; SettingsShell unused |
|
||||
| 6 | MikrotikManager | `c:\Users\shats\Dev\MikrotikManager-3` | fullstack Next.js 16 | **Card SoT `/servers`**; ReUI partial (data-grid/filters/stepper), **нет** `reui-kit` |
|
||||
|
||||
Backend-only проектов нет. Все шесть — frontend/fullstack UI.
|
||||
|
||||
## What the final file will contain
|
||||
|
||||
Полная структура из правила аудита (§37): Executive Summary, Coverage, Inventory, MCP/Skills, Rules, 6 project audits, Cross-project matrix, Duplicates, REUI Registry, Target rules, CHANGE-XXX, Roadmap, Acceptance, Risks, Consistency Gate.
|
||||
|
||||
Ниже — ядро находок, которые попадут в файл с evidence и Ref ID.
|
||||
|
||||
## Что будет одинаковым, а что нет (решение пользователя)
|
||||
|
||||
Уровень унификации: **система + идентичный chrome во всех 6 приложениях**. Доменные экраны **не** копируются.
|
||||
|
||||
### Будет одинаковым (chrome identity — все 6, включая Auth Portal и MikrotikManager)
|
||||
|
||||
Эталон: [app-shell-12](https://reui.io/preview/base/app-shell-12) + [app-shell-7](https://reui.io/preview/base/app-shell-7) (SystemMonitor) + [app-shell-1](https://reui.io/preview/base/app-shell-1) (NavUser).
|
||||
|
||||
- Sidebar `--sidebar-width: 240px`
|
||||
- Sidebar header: App Switcher (ids `cfdm` · `vps` · `bgp` · `fw` · `dns`, источник — auth-portal API)
|
||||
- Sidebar footer: **NavUser** + segmented theme (light / dark / system), без Sun/Moon icon toggle
|
||||
- Header right: **AppsMenu → SystemMonitorPopover**; без ModeToggle и без Search-кнопки (поиск только ⌘K)
|
||||
- Main padding: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5`
|
||||
- Тема: next-themes (или эквивалент с тем же UX) + ReUI semantic tokens (`--success/--warning/--info/--invert`)
|
||||
- SSO/portal: вход через auth-portal, тот же App Switcher
|
||||
|
||||
Сейчас этого нет: Auth Portal (нет SystemMonitor), MikrotikManager (нет AppsMenu/NavUser/switcher, icon theme toggle).
|
||||
|
||||
### Будет одним паттерном (не pixel-copy страниц)
|
||||
|
||||
- Surface ops-экрана: Frame, не Card
|
||||
- Списки: DataGrid + Filters
|
||||
- KPI: stats-12 + IconTile `elevated`
|
||||
- Quick Actions: Frame tiles (card-12 DNA)
|
||||
- Settings rows: settings-3 / integrations: settings-16
|
||||
- Create/edit: Sheet (sheet-8 / sheet-1 / sheet-9)
|
||||
- Empty: Empty + IconStack, Frame-friendly
|
||||
|
||||
Одинаковый **рецепт**, разные данные, колонки, фильтры, действия.
|
||||
|
||||
### Останется разным (домен)
|
||||
|
||||
- VPS: topology canvas, BILLmanager sync, тарифы, баланс
|
||||
- CFDM: DNS, kanban групп/сервисов, сертификаты Cloudflare
|
||||
- EvoBGP: modules/lookup/BIRD jobs/network
|
||||
- Auth Portal: login, users/OIDC/audit (это IdP, не ops CRUD)
|
||||
- EvoFirewall: agent fleet cards, policy sortable, blocklists
|
||||
- MikrotikManager: `/servers` CRUD, terminal, network-map, контейнеры — **функциональность** та же, chrome и surface выравниваются
|
||||
- Framework: Vite SPA vs Next.js 16 — **не** меняем ради UI
|
||||
- Package scopes (`@cfdm/ui`, `@evobgp/ui`, …) — не сливать в один npm без отдельного technical решения
|
||||
|
||||
## Critical cross-project findings
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph viteFamily [Vite plus Frame kit]
|
||||
CFDM[CFDM]
|
||||
VPS[VPS Tracker]
|
||||
BGP[EvoBGP SoT KPI]
|
||||
FW[EvoFirewall]
|
||||
Auth[Auth Portal]
|
||||
end
|
||||
subgraph nextFamily [Next Card SoT]
|
||||
MM[MikrotikManager]
|
||||
end
|
||||
ReUI[REUI PRO Frame]
|
||||
viteFamily --> ReUI
|
||||
MM -.->|"proposed RULE-CHANGE"| ReUI
|
||||
```
|
||||
|
||||
1. **Две дизайн-системы в одном workspace.** Пять Vite-приложений уже lock `surface: frame` + `reui-kit`. MikrotikManager — Next App Router, SoT [`/servers`](c:/Users/shats/Dev/MikrotikManager-3/app/(main)/servers/page.tsx) на `DataPageCard` → shadcn Card. Правила `next-shadcn-production.mdc` / `ma-ui-guardian.mdc` **противоречат** цели унификации. UX/UI rule разрешено менять: target = Frame + DataGrid, **без** смены Next.js (technical constraint).
|
||||
|
||||
2. **KPI DNA устарела относительно официального primitive.** Сейчас kit использует hand-roll `Item` `size-10.5` `bg-muted` и ссылается на [stats-12](https://reui.io/preview/base/stats-12) (MCP: Frame + Badge + Item). Официальный primitive [Icon Tile](https://reui.io/docs/components/base/icon-tile) (`variant="elevated"`, preview [icon-tile](https://reui.io/components/icon-tile)) закрывает ту же задачу. **Ни один** из 6 проектов не установил `icon-tile`. Match текущий KPI → stats-12: **partial**. Target: IconTile elevated + stats-12 composition.
|
||||
|
||||
3. **settings-7 — Card surface.** EvoBGP kit/reference [`settings-7`](https://reui.io/preview/base/settings-7) официально `surface: card`. Это нарушает Frame lock. Frame-эталоны: [settings-3](https://reui.io/preview/base/settings-3), [settings-8](https://reui.io/preview/base/settings-8), [settings-16](https://reui.io/preview/base/settings-16).
|
||||
|
||||
4. **Chrome drift.** Контракты цитируют [app-shell-12](https://reui.io/preview/base/app-shell-12). MCP `compose_page` для ops dashboard вернул **app-shell-7** ([preview](https://reui.io/preview/base/app-shell-7)) — в описании есть system monitor popover, ближе к реальному `SystemMonitorPopover`. Auth Portal: AppsMenu без SystemMonitor. MikrotikManager: нет AppsMenu / NavUser / auth-portal switcher; theme = icon toggle в sidebar.
|
||||
|
||||
5. **Дубли kit.** `KpiStatGrid` / `QuickActionGrid` / `ResourcePage` / `OpsDashboard` скопированы в 5 репо (`@cfdm/ui` vs `@evobgp/ui` vs `@evofw/ui` vs `@authportal/ui`). Нет shared package. Unification UX — через одинаковые rules + DNA; общий npm-пакет = отдельный technical risk (не ломаем package boundaries в этом плане).
|
||||
|
||||
6. **Мёртвый / недоиспользованный kit.** SettingsShell и DetailPanel экспортируются, но не используются в EvoBGP routes и частично в EvoFirewall. Auth Portal `ResourcePage` — stub. CFDM ближе всех к контракту (Card не импортируется в `apps/web`).
|
||||
|
||||
7. **Rules drift.** Правила перечисляют ReUI-компоненты, которых нет на диске (kanban в vps-tracker, stepper unused в FW, Frame unused в MM). `kpi-hybrid.mdc` дублируется 5 раз. CFDM нет `AGENTS.md` / `ui-surface.ts`. Auth contract ids без `dns`, код включает `dns`.
|
||||
|
||||
## Verified REUI PRO registry (will expand in file)
|
||||
|
||||
Каждый CHANGE получит Ref ID. Уже подтверждено MCP `get_block` / `list_components` / official docs:
|
||||
|
||||
| Ref | Item | Official docs | Preview | Typical match |
|
||||
|---|---|---|---|---|
|
||||
| REUI-001 | Frame | [docs/components/base/frame](https://reui.io/docs/components/base/frame) | [components/frame](https://reui.io/components/frame) | 1:1 for ops surface |
|
||||
| REUI-002 | Data Grid | [docs/components/base/data-grid](https://reui.io/docs/components/base/data-grid) | [components/data-grid](https://reui.io/components/data-grid) | 1:1 lists; MCP: TanStack v9 |
|
||||
| REUI-003 | Filters | [docs/components/base/filters](https://reui.io/docs/components/base/filters) | [components/filters](https://reui.io/components/filters) | 1:1 |
|
||||
| REUI-004 | stats-12 | [blocks](https://reui.io/blocks) | [preview/base/stats-12](https://reui.io/preview/base/stats-12) | partial vs current Item hybrid |
|
||||
| REUI-005 | Icon Tile | [docs/components/base/icon-tile](https://reui.io/docs/components/base/icon-tile) | [components/icon-tile](https://reui.io/components/icon-tile) | target for KPI/row tiles |
|
||||
| REUI-006 | data-grid-filtering-2 | [blocks](https://reui.io/blocks) | [preview/base/data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) | partial (kit ResourcePage) |
|
||||
| REUI-007 | settings-16 | [blocks/application/settings](https://reui.io/blocks/application/settings) | [preview/base/settings-16](https://reui.io/preview/base/settings-16) | integrations |
|
||||
| REUI-008 | settings-3 | same category | [preview/base/settings-3](https://reui.io/preview/base/settings-3) | settings rows Frame |
|
||||
| REUI-009 | settings-8 | same | [preview/base/settings-8](https://reui.io/preview/base/settings-8) | webhook-like lists |
|
||||
| REUI-010 | app-shell-12 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-12](https://reui.io/preview/base/app-shell-12) | current chrome cite |
|
||||
| REUI-011 | app-shell-7 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-7](https://reui.io/preview/base/app-shell-7) | better SystemMonitor match |
|
||||
| REUI-012 | app-shell-1 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-1](https://reui.io/preview/base/app-shell-1) | NavUser |
|
||||
| REUI-013 | card-12 | [blocks](https://reui.io/blocks) | [preview/base/card-12](https://reui.io/preview/base/card-12) | Quick Actions |
|
||||
| REUI-014 | dashboard-1 | [blocks](https://reui.io/blocks) | [preview/base/dashboard-1](https://reui.io/preview/base/dashboard-1) | OpsDashboard |
|
||||
| REUI-015 | empty-state-14 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-14](https://reui.io/preview/base/empty-state-14) | Frame empty (prefer over card empty-state-11) |
|
||||
| REUI-016 | empty-state-12 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-12](https://reui.io/preview/base/empty-state-12) | current cite; surface none |
|
||||
| REUI-017 | sheet-8 / sheet-1 / sheet-9 | [blocks](https://reui.io/blocks) | [sheet-8](https://reui.io/preview/base/sheet-8) [sheet-1](https://reui.io/preview/base/sheet-1) [sheet-9](https://reui.io/preview/base/sheet-9) | create/edit/detail sheets |
|
||||
| REUI-018 | auth-13 | [blocks](https://reui.io/blocks) | [preview/base/auth-13](https://reui.io/preview/base/auth-13) | Frame login |
|
||||
| REUI-019 | auth-18 | [blocks](https://reui.io/blocks) | [preview/base/auth-18](https://reui.io/preview/base/auth-18) | auth-portal actual; surface none |
|
||||
| REUI-020 | solution-users-1 / 6 | [blocks](https://reui.io/blocks) | [users-1](https://reui.io/preview/base/solution-users-1) [users-6](https://reui.io/preview/base/solution-users-6) | auth admin |
|
||||
| REUI-021 | form-7 | [blocks](https://reui.io/blocks) | [preview/base/form-7](https://reui.io/preview/base/form-7) | inline detail Frame |
|
||||
| REUI-022 | chart-15 | [blocks](https://reui.io/blocks) | [preview/base/chart-15](https://reui.io/preview/base/chart-15) | Frame charts (replace Card chart shells) |
|
||||
| REUI-023 | kanban / sortable / stepper / timeline / autocomplete | primitive docs under `/docs/components/base/<name>` | matching `/components/<name>` | domain-specific |
|
||||
|
||||
`settings-7` **не** будет target (Card). `empty-state-11` **не** будет target (Card).
|
||||
|
||||
## Target UX/UI rules (summary)
|
||||
|
||||
- **TARGET-001:** Один surface на ops-экран: ReUI Frame. Не смешивать Card+Frame.
|
||||
- **TARGET-002:** Списки = DataGrid + Filters внутри Frame ([data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)). Запрет raw `<table>` / shadcn Table как ops list.
|
||||
- **TARGET-003:** KPI = stats-12 composition + IconTile `elevated` (не hand-roll Item tile). Semantic `text-*` / Badge variants, не `bg-emerald-*`.
|
||||
- **TARGET-004:** Quick Actions = Frame tiles sibling KPI ([card-12](https://reui.io/preview/base/card-12) + stats-12 DNA).
|
||||
- **TARGET-005:** Settings rows = settings-3; integrations = settings-16; не settings-7.
|
||||
- **TARGET-006 (P0, все 6 приложений):** Chrome identity 1:1. Sidebar 240px + App Switcher; header AppsMenu → SystemMonitor; footer NavUser segmented theme. Эталон: [app-shell-12](https://reui.io/preview/base/app-shell-12) + [app-shell-7](https://reui.io/preview/base/app-shell-7) + [app-shell-1](https://reui.io/preview/base/app-shell-1). Auth Portal и MikrotikManager **не** исключение.
|
||||
- **TARGET-007:** Create/edit = Sheet blocks (sheet-8 create/settings, sheet-1 create, sheet-9 detail). Confirm = Alert Dialog examples ([alert-dialog](https://reui.io/components/alert-dialog)).
|
||||
- **TARGET-008:** Empty = Empty + IconStack, Frame-friendly empty-state-14 / empty-state-12; не Card empty-state-11.
|
||||
- **TARGET-009:** MikrotikManager UX SoT сменить с Card `/servers` на Frame+DataGrid **при сохранении** всего CRUD `/servers` (technical: остаётся Next.js).
|
||||
- **TARGET-010:** Rules синхронизировать с диском (не перечислять неустановленные компоненты как «установленные»).
|
||||
|
||||
## Change themes per project (detail in file)
|
||||
|
||||
**VPS Tracker (P1):** Card shells на `vps.$vpsId`, `reports`, `resources`, `renewals`, charts → Frame / chart-15. `spaces.tsx` shadcn Table → ResourcePage/DataGrid. SettingsShell Separator. Dashboard QA order — зафиксировать как exception или выровнять с EvoBGP.
|
||||
|
||||
**CFDM (P2):** Наименьший gap. Добавить `ui-surface.ts` / AGENTS.md. IconTile вместо Item tiles. Удалить naming `*KpiCards`.
|
||||
|
||||
**EvoBGP (P1):** `modules/new.tsx` Card stub → Frame. `illustrated-empty-state` Card → empty-state-14. Подключить SettingsShell/DetailPanel или удалить из barrel. Forms: RHF в package.json, в src не используется — выровнять на Field + sheet-8.
|
||||
|
||||
**Auth Portal (P0 chrome / P1 pages):** Добавить SystemMonitorPopover в header (сейчас только AppsMenu). NavUser уже segmented — оставить. `/apps` tiles → QuickActionGrid. ResourcePage stub. Contract + `dns` id. Login-страница **без** AppShell остаётся отдельным auth surface ([auth-18](https://reui.io/preview/base/auth-18) / [auth-13](https://reui.io/preview/base/auth-13)); chrome identity применяется только к `_auth` shell.
|
||||
|
||||
**EvoFirewall (P1/P2):** `/settings` → SettingsShell или явно REPLACE rule. Agent cards view оставить как Frame domain, не Card. IconTile для AgentPlatformIcon. Orphans: lifecycle timeline, FormFieldSimple, unused stepper.
|
||||
|
||||
**MikrotikManager (P0 chrome, P1 surface, high risk):** Сначала chrome identity: AppsMenu, SystemMonitor, NavUser segmented, App Switcher `dns`/`fw`/…, `--sidebar-width: 240px` (сейчас hand-rolled footer + Sun/Moon). Затем RULE-CHANGE SoT: `DataPageCard` Card → Frame. KPI → stats-12 + IconTile. Не мигрировать Next.js. Сохранить весь CRUD `/servers` и остальные страницы.
|
||||
|
||||
## Roadmap (in file, not executed now)
|
||||
|
||||
- Phase 0: этот план-файл (единственный deliverable сейчас)
|
||||
- Phase 1: **Chrome identity во всех 6** (Auth SystemMonitor; MM AppsMenu/NavUser/Switcher; Vite — сверка 1:1)
|
||||
- Phase 2: shared rules + IconTile + Frame lock (Vite + начало MM)
|
||||
- Phase 3: kit DNA (KPI/QA/ResourcePage) per repo
|
||||
- Phase 4: page-level Card→Frame (VPS charts/detail, BGP stub/empty, MM DataPageCard)
|
||||
- Phase 5: states (empty/loading/error) + sheets
|
||||
- Phase 6: a11y/responsive verification
|
||||
|
||||
## Constraints to preserve
|
||||
|
||||
Не трогать: API contracts, auth/SSO/OIDC, permissions, TanStack vs Next routing, Go vs Fastify, package names, CRUD поведения (create/edit/delete/sync/filter/sort/pagination).
|
||||
|
||||
Technical dependency (отдельная секция Risks): Data Grid docs говорят TanStack Table **v9**; установленный `@reui/data-grid` в проектах может быть v8 — не ломать kit без явной миграции (`<uncertain>` до проверки `package.json` каждого web-приложения в тексте плана).
|
||||
|
||||
## After approval
|
||||
|
||||
Написать полный `UX-UI-PLAN.md` (~все секции правила, CHANGE-XXX с Ref ID и прямыми URL). Остановиться. Implementation — только по отдельному запросу.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: UX UI Unification Implementation
|
||||
overview: "Реализация UX-UI-PLAN.md: идентичный chrome 1:1 во всех 6 приложениях, затем IconTile/rules, kit DNA, Card→Frame. Phase 0 (аудит) уже в UX-UI-PLAN.md. Production-код начинается с Phase 1."
|
||||
todos:
|
||||
- id: phase-1-auth
|
||||
content: "CHANGE-001: Auth Portal SystemMonitorPopover + header AppsMenu→monitor (app-shell-7)"
|
||||
status: completed
|
||||
- id: phase-1-mm
|
||||
content: "CHANGE-002/032/033/034: MM chrome — 240px, App Switcher, NavUser segmented, AppsMenu+SystemMonitor, без Rail/Search pill"
|
||||
status: completed
|
||||
- id: phase-1-vite
|
||||
content: "CHANGE-003: сверка VPS/CFDM/EvoBGP/FW + Auth SIDEBAR_WIDTH 240px"
|
||||
status: completed
|
||||
- id: phase-2-icontile
|
||||
content: "Phase 2: CLI @reui/icon-tile во всех 6 + RULE-CHANGE Frame SoT MM (CHANGE-004, 024–028)"
|
||||
status: completed
|
||||
- id: phase-3-kit
|
||||
content: "Phase 3: KpiStatGrid/QuickActionGrid на IconTile; Auth /apps kit (CHANGE-005–008, 021–022)"
|
||||
status: completed
|
||||
- id: phase-4-frame
|
||||
content: "Phase 4: Card→Frame на ops-страницах VPS/EvoBGP/MM (CHANGE-009–018)"
|
||||
status: completed
|
||||
- id: phase-5-6
|
||||
content: "Phase 6: a11y/responsive verification (Phase 5 CHANGE-016/019/020/030/031 done)"
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# UX/UI Unification — Implementation
|
||||
|
||||
Источник: [`UX-UI-PLAN.md`](c:/Users/shats/Dev/vps-tracker/UX-UI-PLAN.md). Аудит-план [`ux_ui_unification_audit_276a34f8.plan.md`](c:/Users/shats/Dev/vps-tracker/.cursor/plans/ux_ui_unification_audit_276a34f8.plan.md) **не менять**.
|
||||
|
||||
Уровень: **идентичный chrome во всех 6** + общая UX-система; доменные страницы остаются разными.
|
||||
|
||||
Эталоны chrome: [app-shell-12](https://reui.io/preview/base/app-shell-12) · [app-shell-7](https://reui.io/preview/base/app-shell-7) · [app-shell-1](https://reui.io/preview/base/app-shell-1). Docs: [reui.io/blocks](https://reui.io/blocks) · [reui.io/docs](https://reui.io/docs).
|
||||
|
||||
## Chrome Identity Spec (1:1 — все 6)
|
||||
|
||||
- Sidebar `--sidebar-width: 240px`
|
||||
- Sidebar header: App Switcher (`cfdm` · `vps` · `bgp` · `fw` · `dns`; MM локальный `mm` + те же ids)
|
||||
- Footer: NavUser + segmented theme (light/dark/system), без Sun/Moon
|
||||
- Header right: AppsMenu → SystemMonitorPopover; без ModeToggle; поиск только ⌘K
|
||||
- Main: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5`
|
||||
- Запрещено: SidebarRail, Search pill в header/sidebar
|
||||
- Auth **login** без AppShell ([auth-18](https://reui.io/preview/base/auth-18))
|
||||
|
||||
**Не менять:** Vite vs Next, package scopes, API/SSO/OIDC, CRUD. Не мержить kit в один npm. Не мигрировать TanStack Table v8→v9 в этом плане.
|
||||
|
||||
## Phase 1 — Chrome (сейчас)
|
||||
|
||||
| ID | Project | Что сделать |
|
||||
|---|---|---|
|
||||
| CHANGE-001 | Auth Portal | `SystemMonitorPopover` после AppsMenu; header `z-10` `gap-2` |
|
||||
| CHANGE-002 | MM | Switcher, header cluster, убрать Search pill + Sun/Moon |
|
||||
| CHANGE-032 | MM | NavUser в footer, segmented theme |
|
||||
| CHANGE-033 | MM | удалить SidebarRail |
|
||||
| CHANGE-034 | MM | `SIDEBAR_WIDTH = 240px` |
|
||||
| CHANGE-003 | VPS/CFDM/BGP/FW + Auth primitive | сверка; Auth `packages/ui` sidebar 16rem → 240px |
|
||||
|
||||
MM остаётся Next.js 16. Без `@tanstack/react-query` для chrome (fetch + `useEffect`).
|
||||
|
||||
## Later phases (не в этом проходе)
|
||||
|
||||
- **Phase 2:** IconTile CLI, rules Frame SoT MM
|
||||
- **Phase 3:** kit DNA KPI/QA
|
||||
- **Phase 4:** Card→Frame pages
|
||||
- **Phase 5–6:** empty/sheets, a11y
|
||||
@@ -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,37 @@
|
||||
---
|
||||
description: Backend API — при изменениях, затрагивающих UI, строго следовать shadcn Components/Blocks
|
||||
globs: apps/api/**/*,packages/shared/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Backend API + shadcn/ui
|
||||
|
||||
Fastify backend: `apps/api/`. Контракты — `@cfdm/shared` (Zod). Frontend — TanStack Query.
|
||||
|
||||
## Обязательный порядок
|
||||
|
||||
1. **Backend** — route, service, Vitest (`app.inject()`)
|
||||
2. **Схемы** — `@cfdm/shared` (не дублировать в `apps/web/src/lib/schemas.ts`)
|
||||
3. **UI** — shadcn MCP ([`shadcn-mcp.mdc`](shadcn-mcp.mdc)); списки/фильтры — ReUI ([`reui-mcp.mdc`](reui-mcp.mdc))
|
||||
|
||||
## Запрещено на frontend при доработке API
|
||||
|
||||
- Новые raw `<table>` / `<select>` / кастомные badge-цвета
|
||||
- Дублирование Zod schemas в `apps/web` — только re-export из `@cfdm/shared`
|
||||
- Самописные формы без `Field` + RHF + Zod
|
||||
|
||||
## shadcn-паттерны
|
||||
|
||||
| API-данные | UI |
|
||||
|------------|-----|
|
||||
| Список | `ResourcePage` (Frame + DataGrid; Filters optional) |
|
||||
| Создание | `Card` + `FieldGroup` + RHF |
|
||||
| Статус | `Badge` variants |
|
||||
| Ошибка | `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, `@cfdm/shared`, `@cfdm/db`.
|
||||
|
||||
MCP — [`backend-mcp.mdc`](backend-mcp.mdc).
|
||||
|
||||
## Слои
|
||||
|
||||
```
|
||||
routes/ → services/ → @cfdm/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 только из `@cfdm/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,42 @@
|
||||
---
|
||||
description: Обязательный порядок MCP перед backend-кодом (Fastify, Drizzle, SQLite)
|
||||
globs: apps/api/**/*,packages/db/**/*,packages/shared/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Backend MCP — обязательно
|
||||
|
||||
Перед **любой** задачей в `apps/api`, `packages/db`, `packages/shared` — сначала MCP, не training data.
|
||||
|
||||
## Рекомендуемые MCP для проекта
|
||||
|
||||
| Назначение | Где включить |
|
||||
|------------|--------------|
|
||||
| Context7 (Fastify, Drizzle, Vitest, Zod) | `.cursor/settings.json` → `context7-plugin` |
|
||||
| shadcn/ui (экраны с API) | `.cursor/settings.json` → `shadcn` |
|
||||
| Codegraph (поиск по кодовой базе) | `.cursor/mcp.json` → `codegraph` |
|
||||
| E2E / smoke UI | Cursor Settings → `cursor-ide-browser` |
|
||||
| Поиск неизвестного MCP | `user-mcp-on-demand` |
|
||||
|
||||
**Cloudflare не использовать** — стек проекта: Docker + Node + SQLite, без Workers.
|
||||
|
||||
## Порядок
|
||||
|
||||
| Задача | MCP |
|
||||
|--------|-----|
|
||||
| Поиск существующего кода | **Codegraph** `codegraph_explore` |
|
||||
| Fastify plugins, routes, hooks | **Context7** (`fastify`, `@fastify/jwt`, `@fastify/type-provider-zod`) |
|
||||
| Drizzle schema, queries, migrations | **Context7** (`drizzle-orm`, `drizzle-kit`, `better-sqlite3`) |
|
||||
| Zod contracts (`packages/shared`) | **Context7** (`zod`) |
|
||||
| Vitest (`app.inject()`) | **Context7** (`vitest`, `fastify`) |
|
||||
| API + UI | `packages/shared` → **shadcn MCP** ([`shadcn-mcp.mdc`](shadcn-mcp.mdc)) |
|
||||
| E2E / smoke UI | **cursor-ide-browser** |
|
||||
| Неизвестный инструмент | **user-mcp-on-demand** `search_tools` |
|
||||
|
||||
## Запрещено
|
||||
|
||||
- Угадывать API Fastify/Drizzle из памяти
|
||||
- Самописные аналоги `@fastify/*` (CORS, static, JWT, rate-limit)
|
||||
- Дублировать Zod schemas вне `@cfdm/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
|
||||
```
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
description: Паттерны для React, api, utils
|
||||
globs: src/**/*.{js,jsx}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Frontend conventions
|
||||
|
||||
## React
|
||||
|
||||
- Функциональные компоненты
|
||||
- Страницы в `pages/`, общие компоненты в `components/`
|
||||
- Данные загружаются в App.jsx через `loadDataSet()`, передаются в страницы как `db` и `actions`
|
||||
|
||||
## API
|
||||
|
||||
- `src/lib/api.js` — fetchApi, loadDataSet, createRecord, updateRecord, deleteRecord
|
||||
- Коллекции: vps, providers, providerAccounts, payments, balanceLedger, settings
|
||||
- Дополнительно: syncAccount, fetchAccountBalance, testApiConnection
|
||||
|
||||
## Utils
|
||||
|
||||
- `src/lib/utils.js` — форматирование (formatCurrency), конвертация валют, лейблы (paymentTypeLabel), CSV
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
description: shadcn/ui Monorepo — структура apps/web + packages/ui, CLI workflow, импорты @cfdm/ui
|
||||
globs: apps/web/**/*,packages/ui/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Frontend Monorepo (shadcn/ui + ReUI)
|
||||
|
||||
**Обязательный стандарт структуры** — [Monorepo docs](https://ui.shadcn.com/docs/monorepo), ReUI — [Get Started](https://reui.io/docs/get-started).
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
apps/web/ # Vite SPA (routes, queries, domain components)
|
||||
apps/api/ # Fastify API + static SPA in prod
|
||||
packages/ui/ # @cfdm/ui — shadcn primitives
|
||||
packages/shared/ # @cfdm/shared — Zod schemas, parse-fqdn
|
||||
packages/db/ # @cfdm/db — Drizzle schema, repositories
|
||||
```
|
||||
|
||||
## Два components.json
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|------------|
|
||||
| [`apps/web/components.json`](apps/web/components.json) | App aliases; `ui` → `@cfdm/ui/components` |
|
||||
| [`packages/ui/components.json`](packages/ui/components.json) | UI package aliases |
|
||||
|
||||
**Синхронизировать:** `style`, `iconLibrary`, `baseColor`, `registries` в обоих файлах.
|
||||
|
||||
```json
|
||||
"registries": {
|
||||
"@reui": "https://reui.io/r/{style}/{name}.json"
|
||||
}
|
||||
```
|
||||
|
||||
## 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 add @reui/data-grid
|
||||
pnpm dlx shadcn@latest add @reui/filters
|
||||
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/` |
|
||||
| `add @reui/data-grid` | `apps/web/src/components/reui/data-grid/` |
|
||||
| `add @reui/filters` | `apps/web/src/components/reui/filters.tsx` |
|
||||
|
||||
## Импорты
|
||||
|
||||
```tsx
|
||||
import { Button } from '@cfdm/ui/components/button'
|
||||
import { cn } from '@cfdm/ui/lib/utils'
|
||||
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
|
||||
import '@cfdm/ui/globals.css' // только в main.tsx
|
||||
```
|
||||
|
||||
| Запрещено | Разрешено |
|
||||
|-----------|-----------|
|
||||
| `@/components/ui/*` | `@cfdm/ui/components/*` |
|
||||
| `@/components/reui/*` в `packages/ui` | `apps/web/src/components/reui/` |
|
||||
| `apps/web/src/components/ui/` | `packages/ui/src/components/` |
|
||||
| Ручное редактирование `globals.css` | `pnpm dlx shadcn@latest apply b2fA --only theme` |
|
||||
|
||||
**Registry imports после add:**
|
||||
- shadcn community → переписывать на `@cfdm/ui/...`
|
||||
- ReUI `@reui/*` → остаётся `@/components/reui/...`; shadcn-примитивы внутри ReUI → `@cfdm/ui/...`
|
||||
|
||||
**Post-add checklist ReUI:**
|
||||
- [ ] Импорты `@/components/ui/*` → `@cfdm/ui/components/*`
|
||||
- [ ] Зависимости в `apps/web/package.json` (не в `packages/ui`)
|
||||
- [ ] `pnpm --filter web build`
|
||||
|
||||
## Разделение ответственности
|
||||
|
||||
- **`packages/ui`** — только output `shadcn add` (примитивы, registry hooks, `cn`)
|
||||
- **`apps/web/src/components/reui`** — только output `shadcn add @reui/*` (см. [`reui-mcp.mdc`](reui-mcp.mdc))
|
||||
- **`apps/web/src/components`** — blocks, layout, domain (`login-form`, `app-shell`, `reui-kit/*`)
|
||||
|
||||
## Стили (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` согласованы (включая `@reui` registry)
|
||||
- [ ] `shadcn add` из `apps/web`
|
||||
- [ ] shadcn → `@cfdm/ui`; ReUI → `@/components/reui`
|
||||
- [ ] Нет `apps/web/src/components/ui/`
|
||||
- [ ] `pnpm --filter web build` без ошибок
|
||||
|
||||
См. также: [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`reui-mcp.mdc`](reui-mcp.mdc), [`vite-tanstack-frontend.mdc`](vite-tanstack-frontend.mdc).
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
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` |
|
||||
| `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. `@cfdm/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/*` | `@cfdm/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,180 @@
|
||||
---
|
||||
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.
|
||||
|
||||
## Иерархия компонентов
|
||||
|
||||
```
|
||||
@cfdm/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
|
||||
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"` |
|
||||
| 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`.
|
||||
|
||||
Max 1 primary (`default`) на экран; остальные `outline` / `ghost`.
|
||||
|
||||
## 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`
|
||||
|
||||
## 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 / CFDM / 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 / `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: `@cfdm/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
|
||||
@@ -1,26 +1,81 @@
|
||||
---
|
||||
description: Структура проекта vps-tracker и соглашения по именованию
|
||||
description: Структура vps-tracker — pnpm monorepo (apps/web, apps/api, packages/ui, packages/shared, packages/db)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Структура проекта vps-tracker
|
||||
|
||||
## Папки
|
||||
pnpm workspaces monorepo. Frontend — shadcn/ui + ReUI (`@reui`) + TanStack Router/Query + TS. Backend — Fastify + Drizzle + better-sqlite3 + TS.
|
||||
|
||||
- `server/` — Express backend, SQLite (sql.js)
|
||||
- `src/` — React frontend (Vite)
|
||||
- `server/adapters/` — один адаптер на провайдера API (billmanager)
|
||||
- `server/routes/` — Express роутеры по сущностям
|
||||
- `server/db/` — схема, миграции, seed
|
||||
## Layout
|
||||
|
||||
```
|
||||
vps-tracker/
|
||||
├── apps/
|
||||
│ ├── web/ # Vite SPA (TSX) — TanStack Router + Query, shadcn/ui + ReUI
|
||||
│ └── api/ # Fastify 5 API (TS) — @fastify/* + Drizzle
|
||||
├── packages/
|
||||
│ ├── ui/ # @cfdm/ui — shadcn primitives (output `shadcn add`)
|
||||
│ ├── shared/ # @cfdm/shared — Zod-схемы контрактов, общие типы
|
||||
│ └── db/ # @cfdm/db — Drizzle schema, repositories, миграции
|
||||
├── data/ # SQLite база (том Docker, gitignored)
|
||||
├── pnpm-workspace.yaml
|
||||
├── package.json
|
||||
└── tsconfig.base.json
|
||||
```
|
||||
|
||||
## apps/web
|
||||
|
||||
```
|
||||
apps/web/
|
||||
├── components.json # ui alias → @cfdm/ui/components
|
||||
├── vite.config.ts # React + TanStack Router plugin, proxy /api → apps/api
|
||||
├── tsconfig.json
|
||||
└── src/
|
||||
├── main.tsx # QueryClientProvider, createRouter, import '@cfdm/ui/globals.css'
|
||||
├── routes/ # file-based routes (__root.tsx, _auth/...)
|
||||
├── queries/ # queryOptions + key factories по сущностям
|
||||
├── components/ # shared + layout + domain (blocks)
|
||||
└── lib/ # api-client, queryClient, router, schemas
|
||||
```
|
||||
|
||||
## apps/api
|
||||
|
||||
```
|
||||
apps/api/
|
||||
└── src/
|
||||
├── index.ts # buildApp()
|
||||
├── config.ts # env через Zod
|
||||
├── routes/ # тонкие Fastify plugins
|
||||
├── services/ # бизнес-логика + адаптеры (billmanager/*)
|
||||
└── plugins/ # @fastify/* registration
|
||||
```
|
||||
|
||||
## packages/db
|
||||
|
||||
```
|
||||
packages/db/src/
|
||||
├── schema/ # Drizzle tables по сущностям
|
||||
├── repositories/ # typed queries (inArray, JOIN, transaction)
|
||||
└── migrations/ # drizzle-kit generate/migrate
|
||||
```
|
||||
|
||||
## Именование
|
||||
|
||||
- Файлы: kebab-case (provider-accounts.js, row-mappers.js)
|
||||
- Роуты: `/api/vps`, `/api/provider-accounts`, `/api/sync/:accountId`
|
||||
- Файлы: kebab-case (`provider-accounts.ts`, `row-mappers.ts`)
|
||||
- Компоненты: PascalCase (`PageHeader.tsx`)
|
||||
- Роуты API: `/api/vps`, `/api/provider-accounts`, `/api/sync/:accountId`
|
||||
- ID записей: `vps-bm-{accountId}-{externalId}`, `pay-bm-{accountId}-{externalId}`
|
||||
|
||||
## Barrel exports
|
||||
|
||||
Модули с несколькими файлами экспортируют через `index.js`:
|
||||
- `server/adapters/billmanager/index.js` — testConnection, syncFromBillmanager, fetchDashboardInfo
|
||||
- `server/db/index.js` — initDb, getDb, saveDb
|
||||
- `packages/ui` — `@cfdm/ui/components/*`, `@cfdm/ui/lib/utils`, `@cfdm/ui/hooks/*`, `@cfdm/ui/globals.css`
|
||||
- `packages/shared` — `@cfdm/shared/contracts/*` (Zod), `@cfdm/shared/types/*`
|
||||
- `packages/db` — `@cfdm/db/schema`, `@cfdm/db/repositories/*`
|
||||
- `apps/api/src/services/billmanager/index.ts` — `testConnection`, `syncFromBillmanager`, `fetchDashboardInfo`
|
||||
|
||||
## Скоупы правил
|
||||
|
||||
- Frontend (`apps/web`, `packages/ui`) — [`frontend-monorepo.mdc`](frontend-monorepo.mdc), [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`frontend-ui-patterns.mdc`](frontend-ui-patterns.mdc), [`vite-tanstack-frontend.mdc`](vite-tanstack-frontend.mdc), [`shadcn-mcp.mdc`](shadcn-mcp.mdc), [`reui-mcp.mdc`](reui-mcp.mdc), [`shadcn-ui-production.mdc`](shadcn-ui-production.mdc)
|
||||
- Backend (`apps/api`, `packages/db`, `packages/shared`) — [`backend-fastify.mdc`](backend-fastify.mdc), [`backend-drizzle.mdc`](backend-drizzle.mdc), [`backend-mcp.mdc`](backend-mcp.mdc), [`backend-testing.mdc`](backend-testing.mdc), [`backend-api-ui.mdc`](backend-api-ui.mdc), [`sqlite.mdc`](sqlite.mdc)
|
||||
- API + UI-связка — [`backend-api-ui.mdc`](backend-api-ui.mdc)
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
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` | `@cfdm/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` (с Filters) или `FrameDataGrid` (простой CRUD Frame+DataGrid); не raw `<table>`, не Card shell.
|
||||
**Quick Actions** — только `QuickActionGrid` (не Card / Button grid).
|
||||
**KPI** — только `KpiStatGrid` (EvoBGP hybrid); не SectionCards.
|
||||
|
||||
## 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 → `@cfdm/ui/components/*`
|
||||
6. Adapt by reuse → kit / route
|
||||
7. `validate_usage` + `get_audit_checklist`
|
||||
|
||||
## Размещение
|
||||
|
||||
| Слой | Путь | Импорт |
|
||||
|------|------|--------|
|
||||
| shadcn | `packages/ui/src/components/` | `@cfdm/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`, `badge`, `alert`, `autocomplete`, `number-field`, `date-selector`, `color-picker`, `timeline`, `rating`, `phone-input`, `icon-stack`, `icon-tile`
|
||||
|
||||
**Kit:** `ResourcePage`, `KpiStatGrid`, `QuickActionGrid`, `OpsDashboard`, `DetailPanel`, `SettingsShell`
|
||||
|
||||
**Blocks (reference):** `stats-12`, `card-35`, `auth-13`, `app-shell-12`, `settings-16`, `settings-8`, `empty-state-12`, `form-7`, `data-grid-filtering-2`, `dashboard-1`, …
|
||||
|
||||
## 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` / импорт как `@cfdm/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` / `@cfdm/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
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
description: Паттерны для Express, db, adapters
|
||||
globs: server/**/*.js
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Server conventions
|
||||
|
||||
## Express
|
||||
|
||||
- Роутеры в `routes/`, подключаются в `index.js` через `app.use('/api/...', router)`
|
||||
- Ошибки: `res.status(500).json({ error: err.message })`
|
||||
- 404: `res.status(404).json({ error: 'Not found' })`
|
||||
|
||||
## Database
|
||||
|
||||
- Доступ через `getDb()` — обёртка с `prepare().all()`, `prepare().get()`, `run()`
|
||||
- После `run()` вызывается `saveDb()` автоматически
|
||||
- Миграции в `db/migrations.js`, добавляют колонки через ALTER TABLE
|
||||
|
||||
## Adapters
|
||||
|
||||
- Один провайдер = папка в `adapters/` (billmanager)
|
||||
- Разделение: client (HTTP), parsers, mappers, operations, sync
|
||||
- Публичный API через `index.js`
|
||||
@@ -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)) |
|
||||
| Нет примитива в `@cfdm/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` (`@cfdm/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,188 @@
|
||||
---
|
||||
description: Definitive guidelines for writing robust, performant, and secure SQLite code. Focuses on schema design, query optimization, and transaction management.
|
||||
globs: **/*
|
||||
---
|
||||
# sqlite Best Practices
|
||||
|
||||
> В проекте используется **better-sqlite3** через Drizzle (`packages/db`). WASM-`sql.js` выводится из эксплуатации. См. [`backend-drizzle.mdc`](backend-drizzle.mdc).
|
||||
|
||||
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 = 'test@example.com'; -- No index on email
|
||||
```
|
||||
* ✅ GOOD:
|
||||
```sql
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
SELECT id, name FROM users WHERE email = 'test@example.com';
|
||||
```
|
||||
* **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 → @cfdm/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
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"plugins": {
|
||||
"shadcn": {
|
||||
"enabled": true
|
||||
},
|
||||
"context7-plugin": {
|
||||
"enabled": true
|
||||
},
|
||||
"cloudflare": {
|
||||
"enabled": false
|
||||
},
|
||||
"claude-plugins-official/typescript-lsp": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
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,43 @@
|
||||
# Adapting installed ReUI code (reuse-first, no AI slop)
|
||||
|
||||
ReUI items ship production-quality. Your job is to **adapt by reuse** - wire real data and fit the app - not to redesign or hand-roll. The output should look like ReUI built it for this product.
|
||||
|
||||
## Preserve the design - don't over-customize
|
||||
|
||||
The design IS the product. A ReUI block/component encodes senior-designer decisions: spacing, hierarchy, density, color treatment, and component choices. The fastest way to turn a premium block back into generic AI slop is to "improve" its look - so don't.
|
||||
|
||||
- Change **data, copy, and props**; keep the **structure and styling** it ships with. Make the **smallest** change that wires the real data. If your diff touches `className` / JSX structure more than data / props, you are over-customizing - stop and reuse.
|
||||
- Don't swap ReUI components for hand-rolled ones, restructure the layout, re-skin spacing / radius / colors, or add decorative chrome. Let the installed components carry the default spacing, radius, sizing, icon rhythm, density, and state styling; add custom Tailwind only when a component genuinely lacks a contract you need.
|
||||
- Want a different look? `search` for a block whose design already fits and reuse that - don't restyle this one into a new design.
|
||||
|
||||
## Reuse the parts: examples and the block's own elements
|
||||
|
||||
- **Examples are building parts.** A free `c-*` example is a correct, single-pattern composition you can reuse. Before composing from scratch, `get_examples(component)`, install the closest one, and reuse its wiring - assemble UI from examples instead of hand-rolling what an example already shows.
|
||||
- **Reuse a block's own elements.** Need more rows, cards, items, or sections than ship by default? Repeat the block's **existing** element by mapping real data through the same markup - never invent parallel markup that drifts from its design. Need a variant (empty / loading / expanded)? Derive it from an element the block already has.
|
||||
|
||||
## Don't invent (read, don't guess)
|
||||
|
||||
- Never write a prop, variant value, import path, or `@reui/...` name you didn't read in a component's inline `api`, an installed example, or a `search` result. If you didn't see it, treat it as nonexistent - call `get_component` / `get_examples` / `search` first, or run the MCP `validate_usage` tool to check planned names + props against the docs before writing code.
|
||||
- If a getter returns `found: false` or `search` returns nothing, say so and fall back (plain shadcn, or ask) - never fabricate an install command or an API.
|
||||
|
||||
## What to change vs leave alone
|
||||
|
||||
- **Change:** the item's own data, copy, props, and layout to fit the app.
|
||||
- **Leave alone:** installed component files, hooks, and the shared theme - do not edit vendored ReUI internals; change behavior through props and the documented API.
|
||||
- Blocks are **portable React** - no `next/link`, `next/image`, or other framework-runtime imports inside them. Keep them portable.
|
||||
|
||||
## Demo data -> real data
|
||||
|
||||
- Replace every placeholder with the user's real data. Model it as **typed data structures** and **map over arrays** - never duplicate JSX per row/card. Keep small block-specific formatters next to the data.
|
||||
- Wire the real source (columns, fields, fetch). For `data-grid`, implement the server fetch contract if the user needs server-side data.
|
||||
- **Type from the component API, derive during render.** Type domain state through the component's own types - e.g. map status to `BadgeProps["variant"]` via a typed `Record<Status, …>` - instead of stringly-typed values. Compute view state during render; don't mirror derived data into `useState`/`useEffect`.
|
||||
- **Adapt on the right base.** Use the API for the project's base (Base UI vs Radix - see [components.md](./components.md)); the installed files are already base-correct, so reuse their shape rather than translating from memory.
|
||||
|
||||
## Believable content (no AI tells)
|
||||
|
||||
- Use realistic labels, counts, timestamps, and statuses that map to a real workflow.
|
||||
- No decorative buttons, fake tabs, meaningless toggles, equal-weight card walls, empty gradients, ornamental icons, or generic SaaS filler. Every element should do something.
|
||||
|
||||
## Operational surfaces (settings / profile / admin)
|
||||
|
||||
Pick ONE archetype and keep the family consistent: a vertical rail (3-6 sections), horizontal tabs (5-8), or a frame/stack. Prefer `frame` for tool-like surfaces, a card for profile-like ones. Don't mix archetypes in one surface.
|
||||
@@ -0,0 +1,60 @@
|
||||
# CLI: registry setup, license, non-interactive install
|
||||
|
||||
## Registry setup (one-time, per project)
|
||||
|
||||
Free items (the 20 components and all `c-*` examples) need only the plain string registry in `components.json`:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium items (blocks; Motion Icons and templates) require a ReUI license at install:
|
||||
|
||||
1. Add the key to `.env.local`:
|
||||
|
||||
```bash
|
||||
REUI_LICENSE_KEY=your-license-key
|
||||
```
|
||||
|
||||
2. Switch `components.json` to the authenticated object form:
|
||||
|
||||
```json
|
||||
{
|
||||
"registries": {
|
||||
"@reui": {
|
||||
"url": "https://reui.io/r/{style}/{name}.json",
|
||||
"headers": { "Authorization": "Bearer ${REUI_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
|
||||
|
||||
Use the project's package runner (check `packageManager`):
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes # npm
|
||||
pnpm dlx shadcn@latest add @reui/<name> --yes # pnpm
|
||||
bunx --bun shadcn@latest add @reui/<name> --yes # bun
|
||||
```
|
||||
|
||||
`--yes` skips confirmation prompts. The CLI auto-detects the package manager from the lockfile (there is no `--package-manager` flag). It also resolves the correct base+style variant from `components.json`, so do not pass a style.
|
||||
|
||||
## Handling prompts and conflicts
|
||||
|
||||
- **Always pass `--yes`** so the CLI does not block on confirmation prompts.
|
||||
- **Do NOT pass `--overwrite` by default.** If the CLI reports an existing file, read the output and resolve deliberately: install under a different name, adjust the path, or ask the user. Only use `--overwrite` when the user explicitly wants to replace a file.
|
||||
- **Preview first when touching an existing project**: `npx shadcn@latest add @reui/<name> --dry-run` shows what would change; `--diff <file>` shows a specific file's diff. Use these before overwriting.
|
||||
- Run from the **project root** so `components.json` and `.env.local` are found.
|
||||
|
||||
## Free vs premium boundary
|
||||
|
||||
- Public, no key: `c-*` examples and the 20 components (`@reui/data-grid`, `@reui/badge`, ...) that those examples depend on.
|
||||
- Key required at install: blocks (`@reui/<category>-N`) need a Pro or Ultimate license; Motion Icons (`@reui/icons/...`) and templates need Ultimate.
|
||||
|
||||
If an install 401/403s, the license key is missing, invalid, or the plan does not cover that resource (blocks: Pro or higher; icons and templates: Ultimate). Point the user to https://reui.io/account (their key) or https://reui.io/pricing (upgrade).
|
||||
@@ -0,0 +1,408 @@
|
||||
# ReUI components
|
||||
|
||||
The 21 ReUI building blocks: `alert`, `autocomplete`, `badge`, `cascader`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `icon-tile`, `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.
|
||||
|
||||
## data-grid (the flagship - read its API every time)
|
||||
|
||||
`data-grid` wraps TanStack Table v9. It is NOT a styled `<table>` and does NOT take `data`/`columns` props directly. The contract:
|
||||
|
||||
- Build a TanStack table instance with `useTable({ features: dataGridFeatures, ... })` (columns, data). `dataGridFeatures` is exported by the primitive and already bundles sorting, filtering, pagination, row selection, expanding, pinning, resizing and faceting, so there are no per-table row models to wire.
|
||||
- Pass that instance to `<DataGrid table={table} recordCount={total}>`.
|
||||
- Compose the body with `DataGridTable` inside `DataGrid`, and enable features through `tableLayout` (e.g. `{ headerSticky: true, columnsResizable: true }`), not ad-hoc classes.
|
||||
- Server-side data uses the documented fetch shape (`recordCount` is the total for pagination).
|
||||
|
||||
```tsx
|
||||
const table = useTable({
|
||||
features: dataGridFeatures,
|
||||
data,
|
||||
columns,
|
||||
})
|
||||
|
||||
<DataGrid table={table} recordCount={data.length}>
|
||||
<DataGridTable />
|
||||
</DataGrid>
|
||||
```
|
||||
|
||||
Common mistakes:
|
||||
|
||||
- **Incorrect:** `<DataGrid data={rows} columns={cols} />` - these props do not exist. **Correct:** build a `useTable({ features: dataGridFeatures, ... })` instance and pass `table={table}` + `recordCount`.
|
||||
- **Incorrect:** a raw `<table>` / 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 primitive's `DataGridColumnMeta` (e.g. `cellClassName`, `headerTitle`), set through the bundle's `columnMeta` slot.
|
||||
|
||||
## event-calendar
|
||||
|
||||
**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<EventCalendar defaultEvents={events} defaultView="month" className="h-[560px]">
|
||||
<EventCalendarNav />
|
||||
<EventCalendarContent />
|
||||
</EventCalendar>
|
||||
```
|
||||
|
||||
**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
|
||||
<Gantt defaultEvents={bars} resources={tasks} defaultScale="month" className="h-[480px]">
|
||||
<GanttNav />
|
||||
<GanttView />
|
||||
</Gantt>
|
||||
```
|
||||
|
||||
**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<string, T[]>`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Kanban value={cols} onValueChange={setCols} getItemValue={(i) => i.id}>
|
||||
<KanbanBoard>
|
||||
{Object.entries(cols).map(([id, items]) => (
|
||||
<KanbanColumn key={id} value={id}>
|
||||
<KanbanColumnHandle><h3>{id}</h3></KanbanColumnHandle>
|
||||
<KanbanColumnContent value={id}>
|
||||
{items.map((i) => (
|
||||
<KanbanItem key={i.id} value={i.id}>
|
||||
<KanbanItemHandle>{i.title}</KanbanItemHandle>
|
||||
</KanbanItem>
|
||||
))}
|
||||
</KanbanColumnContent>
|
||||
</KanbanColumn>
|
||||
))}
|
||||
</KanbanBoard>
|
||||
<KanbanOverlay><div className="bg-muted size-full rounded-md" /></KanbanOverlay>
|
||||
</Kanban>
|
||||
```
|
||||
|
||||
**Gotcha:** state is `Record<columnId, T[]>`. Each `KanbanColumnContent value` must match its parent `KanbanColumn value`. Omit `KanbanOverlay` and the drag preview silently breaks.
|
||||
|
||||
## sortable
|
||||
|
||||
**Required:** `value` (`T[]`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Sortable value={items} onValueChange={setItems} getItemValue={(i) => i.id}>
|
||||
{items.map((i) => (
|
||||
<SortableItem key={i.id} value={i.id}>
|
||||
<SortableItemHandle><GripVertical /></SortableItemHandle>
|
||||
{i.label}
|
||||
</SortableItem>
|
||||
))}
|
||||
</Sortable>
|
||||
```
|
||||
|
||||
**Gotcha:** a flat 1D reorder list (not columns - that is `kanban`). `getItemValue` must return a stable, unique string. Pass `layout="grid"` or `layout="nested"` for non-list layouts.
|
||||
|
||||
## filters
|
||||
|
||||
**Required:** `fields` (`FilterField[]`). The value is ONE `FilterQuery` tree - `query` + `onQueryChange`, or uncontrolled `defaultQuery`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const fields: FilterField[] = [
|
||||
{ id: "title", label: "Title", type: "text" },
|
||||
{
|
||||
id: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "archived", label: "Archived" },
|
||||
],
|
||||
},
|
||||
]
|
||||
const [query, setQuery] = useState<FilterQuery>(() => createFilterQuery())
|
||||
|
||||
<Filters fields={fields} query={query} onQueryChange={setQuery} />
|
||||
```
|
||||
|
||||
**Gotcha:** the state is a TREE, not a list of chips. `FilterQuery` is a group of rules joined by `and`/`or` and a group may hold another group, so `(A and B) or C` is expressible; a rule is `{ id, type: "rule", path: ["status"], operator, value }` and `path` is the whole nested attribute path, root first. The pre-rewrite API is GONE: there is no `filters`/`onChange` prop, no `FilterFieldConfig` (fields are `FilterField`, nested through their own `fields`, keyed `id` not `key`), and no `createFilter()` - it minted ids inside a pure function and broke hydration, so ids now come from `createFilterIdFactory(seed)` seeded off `useId`, and `createFilterQuery()` / `createFilterRule()` take one. Read the query back with `flattenFilterConditions` (`{ path, field, operator, values, negated }` per rule, incomplete rules skipped) and walk the tree yourself when the parentheses carry meaning - the primitive compiles nothing, no SQL, no query string.
|
||||
|
||||
`variant` picks the chrome over that one query: `"basic"`, the default, is the flat chip row for a toolbar over a table; `"advanced"` is the condition builder, hung off a trigger or rendered in place with `advancedMode="inline"`. Both read and write the same tree, so a saved view built in one opens in the other. Other props worth knowing before you hand-roll them: `size` is two rungs, `"sm" | "default"`, resolved per style (there is no `lg`); `reorderable` turns on drag and Alt+Arrow row moves in the builder; `onBeforeQueryChange` is the ONE veto point for every write (return `false` to refuse, it cannot rewrite); `editors` registers custom value editors a field selects by `editor` name; `labels` / `operatorLabels` own every rendered string; `pathCollapse` + `maxPathSegments` shorten deep attribute paths; `renderChip` / `renderValue` / `renderEmpty` replace rendered parts. On a field, `loadOptions` supplies async options with paging and `resolveValues` renders a chip restored from a saved view whose option was never loaded. Pairs naturally with `data-grid`.
|
||||
|
||||
## cascader
|
||||
|
||||
**Required:** `items` (a tree of `{ value, label, children? }`), plus the panel parts inside `CascaderContent`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Cascader items={items} value={value} onValueChange={setValue}>
|
||||
<CascaderTrigger render={<Button variant="outline" />}>
|
||||
<CascaderValue placeholder="Select an attribute" />
|
||||
</CascaderTrigger>
|
||||
<CascaderContent className="w-80">
|
||||
<CascaderPanel>
|
||||
<CascaderNav>
|
||||
<CascaderBreadcrumb />
|
||||
<CascaderInput />
|
||||
</CascaderNav>
|
||||
<CascaderEmpty />
|
||||
<CascaderList maxHeight={288}>
|
||||
<CascaderItems />
|
||||
</CascaderList>
|
||||
<CascaderStatus />
|
||||
</CascaderPanel>
|
||||
</CascaderContent>
|
||||
</Cascader>
|
||||
```
|
||||
|
||||
**Gotcha:** pressing a branch NAVIGATES, it does not select - only leaves are selectable until you pass `selectable="any"` or a predicate, and once a branch is selectable its chevron becomes the only way to open it. `CascaderInput` must stay inside `CascaderContent` (Base UI refills the query from the selection when the input sits outside the popup). Always include `CascaderStatus`: it is the live region announcing level changes, which the visual breadcrumb does not provide to screen readers. Accepts a flat adjacency list via `getParent` as well as nested `children`. `searchScope="deep"` searches every level and annotates results with their path; `multiple` gives checkbox rows; `inline` + a bare `CascaderPanel` embeds it with no popover.
|
||||
|
||||
The shape above is `mode="drill"`, the default. `mode="tree"` keeps the same parts (drop `CascaderBreadcrumb`, pass `showBack={false}`, drive expansion with `expanded`/`onExpandedChange`); `mode="columns"` REPLACES `CascaderList` + `CascaderItems` with a single `CascaderColumns`, and has no breadcrumb. Other props worth knowing before you hand-roll them: `cascade` (multi-select only, parent/child selection with indeterminate branches - pair it with `selectable="any"`, since a leaf-only tree can never cascade), `indicator={false}` to drop the single-select check and its gutter (visual only, no-op with `multiple`), `virtualize`/`virtualizeThreshold` plus `CascaderVirtualItems` for long levels, and `getChildren` for async levels with cursor paging, retry on failure and optional `prefetch`. `CascaderFooter` pins commands below the list (`actions` is the quick path) and `CascaderSubmenu` opens one as a side-anchored flyout with the full menu keyboard model. To head a run of rows use `CascaderGroup` wrapping a `CascaderLabel` - a bare label inside a listbox names nothing and is dropped from the accessibility tree - and `CascaderSeparator` for the rule between runs. Every rendered string comes from `labels`, and the panel is RTL-correct under a `DirectionProvider` or `dir="rtl"`.
|
||||
|
||||
## date-selector
|
||||
|
||||
**Required:** none, but wire `onChange` to capture the value.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const [value, setValue] = useState<DateSelectorValue | undefined>()
|
||||
|
||||
<DateSelector value={value} onChange={setValue} label="Due date" />
|
||||
```
|
||||
|
||||
**Gotcha:** the value is a structured `DateSelectorValue` (period / operator / start+end dates), NOT a `Date` - never pass a raw `Date`. Use `allowRange={false}` to lock single-date picking. Read `get_component("date-selector")` for the value shape.
|
||||
|
||||
## tree
|
||||
|
||||
**Required:** `tree` (a `@headless-tree/core` instance you construct)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Tree tree={tree}>
|
||||
{tree.getItems().map((item) => (
|
||||
<TreeItem key={item.getId()} item={item}>
|
||||
<TreeItemLabel />
|
||||
</TreeItem>
|
||||
))}
|
||||
</Tree>
|
||||
```
|
||||
|
||||
**Gotcha:** `Tree` is a styled shell - it takes a headless-tree instance via `tree`, NOT `data`/`items` props. Build the instance with `@headless-tree/react`. External API: https://headless-tree.lukasbach.com/
|
||||
|
||||
## stepper
|
||||
|
||||
**Required:** `StepperItem step` (number), `StepperContent value` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Stepper defaultValue={1}>
|
||||
<StepperNav>
|
||||
<StepperItem step={1}>
|
||||
<StepperTrigger><StepperIndicator>1</StepperIndicator></StepperTrigger>
|
||||
<StepperSeparator />
|
||||
</StepperItem>
|
||||
<StepperItem step={2}>
|
||||
<StepperTrigger><StepperIndicator>2</StepperIndicator></StepperTrigger>
|
||||
</StepperItem>
|
||||
</StepperNav>
|
||||
<StepperPanel>
|
||||
<StepperContent value={1}>Step 1 content</StepperContent>
|
||||
<StepperContent value={2}>Step 2 content</StepperContent>
|
||||
</StepperPanel>
|
||||
</Stepper>
|
||||
```
|
||||
|
||||
**Gotcha:** steps are 1-indexed. Without `StepperPanel` + `StepperContent` you render the nav trail but no body. Put `StepperSeparator` in every `StepperItem` except the last.
|
||||
|
||||
## timeline
|
||||
|
||||
**Required:** `TimelineItem step` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Timeline>
|
||||
<TimelineItem step={1}>
|
||||
<TimelineHeader>
|
||||
<TimelineDate>March 2024</TimelineDate>
|
||||
<TimelineTitle>Project initialized</TimelineTitle>
|
||||
</TimelineHeader>
|
||||
<TimelineIndicator />
|
||||
<TimelineSeparator />
|
||||
<TimelineContent>Repo and architecture set up.</TimelineContent>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
```
|
||||
|
||||
**Gotcha:** each item needs a unique `step`. `orientation` is `"vertical"` (default) or `"horizontal"`. This is a static event display, not interactive like `stepper`.
|
||||
|
||||
## autocomplete
|
||||
|
||||
**Required:** `items` (array; each item has at least `value`)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Autocomplete items={items}>
|
||||
<AutocompleteInput placeholder="Search..." />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No results found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.value} value={item}>{item.label}</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
```
|
||||
|
||||
**Gotcha:** `AutocompleteList` takes a render-prop `(item) => ReactNode`, NOT a mapped array of children. External API: https://base-ui.com/react/components/autocomplete
|
||||
|
||||
## phone-input
|
||||
|
||||
**Required:** none, but wire `onChange`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<PhoneInput placeholder="Enter phone number" defaultCountry="US" value={value} onChange={setValue} />
|
||||
```
|
||||
|
||||
**Gotcha:** `value`/`onChange` use an E.164 string (e.g. `"+14155551234"`), not a display-formatted string; `onChange` can fire `undefined`. `defaultCountry` is a 2-letter ISO code. Wraps `react-phone-number-input`.
|
||||
|
||||
## number-field
|
||||
|
||||
**Required:** wrap the controls in `NumberFieldGroup`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<NumberField defaultValue={0}>
|
||||
<NumberFieldScrubArea label="Quantity" />
|
||||
<NumberFieldGroup>
|
||||
<NumberFieldDecrement />
|
||||
<NumberFieldInput />
|
||||
<NumberFieldIncrement />
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
```
|
||||
|
||||
**Gotcha:** import from `@/components/ui/number-field`. The accessible label goes on `NumberFieldScrubArea`, not `NumberField`. External API: https://base-ui.com/react/components/number-field
|
||||
|
||||
## rating
|
||||
|
||||
**Required:** `rating` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Rating rating={4.5} showValue editable onRatingChange={setRating} />
|
||||
```
|
||||
|
||||
**Gotcha:** supports decimals (partial stars). Pass `editable` + `onRatingChange` for interactive input; omit both for a read-only display.
|
||||
|
||||
## scrollspy
|
||||
|
||||
**Required:** `targetRef` (the scroll container ref)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Scrollspy targetRef={containerRef}>
|
||||
<a href="#s1" data-scrollspy-anchor="s1">Section 1</a>
|
||||
<a href="#s2" data-scrollspy-anchor="s2">Section 2</a>
|
||||
</Scrollspy>
|
||||
<div ref={containerRef}>
|
||||
<div id="s1">...</div>
|
||||
<div id="s2">...</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Gotcha:** each link's `data-scrollspy-anchor` must match a section `id`. `targetRef` is the scrollable container (defaults to the window).
|
||||
|
||||
## frame
|
||||
|
||||
**Required:** `Frame` > `FramePanel`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Frame>
|
||||
<FramePanel>
|
||||
<FrameHeader>
|
||||
<FrameTitle>Title</FrameTitle>
|
||||
<FrameDescription>Description</FrameDescription>
|
||||
</FrameHeader>
|
||||
<div className="p-5">Content</div>
|
||||
<FrameFooter>Footer</FrameFooter>
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
```
|
||||
|
||||
**Gotcha:** a structured card shell for tool-like surfaces. `stacked` connects multiple panels with shared borders; `dense` removes panel padding; radius via the `--frame-radius` CSS variable.
|
||||
|
||||
## icon-stack
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconStack aria-hidden="true">
|
||||
<InboxIcon className="size-4" />
|
||||
</IconStack>
|
||||
```
|
||||
|
||||
**Gotcha:** isometric layered artwork for empty states and illustrations; style the inner icon via its own `className`. Mark purely decorative stacks `aria-hidden="true"` and keep the real label in surrounding copy.
|
||||
|
||||
## icon-tile
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconTile variant="elevated" size="lg">
|
||||
<PackageIcon />
|
||||
</IconTile>
|
||||
```
|
||||
|
||||
**Gotcha:** the square container an icon sits in, so every list row, feature card and empty state shares one affordance. `variant`: `outline` (default) | `elevated` (muted fill, raised ring) | `soft` (tinted nested, tone from currentColor) | `solid` (filled tone, contrasting glyph) | `frame` (double container). `soft` and `solid` retint from one text color class (they default to `text-primary`). `size`: `xs | sm | default | lg | xl` (24/32/40/48/64px tile, glyph scales 12/14/16/20/24px). `radius`: `default | full`. Do not set a `size-*` class on the child icon unless you mean to override the tile's glyph size; recolor with `className` on the tile, not the icon.
|
||||
|
||||
## alert
|
||||
|
||||
**Required:** `Alert` > `AlertTitle`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Alert variant="success">
|
||||
<ShieldCheckIcon />
|
||||
<AlertTitle>Security update</AlertTitle>
|
||||
<AlertDescription>Enable two-factor authentication.</AlertDescription>
|
||||
<AlertAction><Button size="xs">Update</Button></AlertAction>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible API. `variant`: `default | destructive | info | success | warning | invert`. The non-default variants use ReUI extended color tokens (`--success`/`--info`/`--warning`/`--invert`), which the install adds. Defer generic alert rules to the shadcn skill.
|
||||
|
||||
## badge
|
||||
|
||||
**Required:** none (text child).
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Badge variant="success-light" size="sm">Success</Badge>
|
||||
<Badge variant="outline" radius="full">Pill</Badge>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible. Rich `variant` set (solid, `-outline`, `-light` per color), `size` `xs..xl`, `radius` `default | full`. Like `alert`, the color variants rely on ReUI extended tokens. Prefer `Badge` variants over raw color classes for statuses.
|
||||
|
||||
## base vs radix - write for the project's base
|
||||
|
||||
ReUI ships every component in two builds: `base` (Base UI) and `radix` (Radix UI). The install command and name are identical, and the CLI installs the build matching the project. But you must write/adapt code against the **right base**, because their APIs differ.
|
||||
|
||||
**Detect the base first.** Read `components.json` -> `style` and take the segment before the first `-`:
|
||||
|
||||
- `"style": "base-nova"` -> **Base UI**
|
||||
- `"style": "radix-nova"` -> **Radix UI**
|
||||
|
||||
**Then use that base's API.** The deltas mirror shadcn's base-vs-radix split:
|
||||
|
||||
- Slot/composition: Base UI `render={<… />}` vs Radix `asChild`.
|
||||
- `Select`: Base UI takes `items`; Radix uses `<SelectItem>` children.
|
||||
- `ToggleGroup`: Base UI `multiple` boolean vs Radix `type="single" | "multiple"`.
|
||||
|
||||
The safest path is to **read the installed files and `c-*` examples** - they're already in your base, so reuse their wiring instead of guessing. When `get_component`'s inline `api` or an example shows the other base's shape, translate it to your base (or `validate_usage` to confirm). Defer the generic base/radix mechanics to the shadcn skill.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Craft: make ReUI UI exceptional, not generic
|
||||
|
||||
ReUI items ship senior-designer quality. Your adaptation has to hold that bar, so the result reads like a real product surface a team would keep - not a wireframe an AI generated. Use these alongside the reuse rules in [adapting.md](./adapting.md).
|
||||
|
||||
## Have a point of view
|
||||
|
||||
Pick an emotional register before you compose - calm, operational, premium, editorial, dense, energetic - and let layout, spacing, surface treatment, and icon behavior all reinforce it. One or two memorable decisions and restraint everywhere else beats ten generic ones. UI with no point of view reads as generated.
|
||||
|
||||
## Brutally clear hierarchy
|
||||
|
||||
One focal point per card or panel: the dominant metric or task first, its label second, supporting detail third. The first thing the eye lands on should be the right thing; secondary text must read as secondary. Borders, separators, and surfaces do real work to create 2-3 information bands - don't flatten everything to equal weight.
|
||||
|
||||
## Spacing rhythm and deliberate density
|
||||
|
||||
Gaps are a signal, not a default. Keep them intentional and consistent within a family (`gap-1`/`gap-2` for tight operational rows, larger gaps for section breaks), and smaller within a group than between groups. Match the surrounding ReUI density; don't pad an operational surface like a marketing page, and don't drift density mid-section. The composition should still feel authored in grayscale.
|
||||
|
||||
## Cover the real states (the usual miss)
|
||||
|
||||
A surface isn't done at the happy path. Compose, and wire:
|
||||
|
||||
- **Empty** - a purposeful empty state (short message + the primary action), never a blank panel.
|
||||
- **Loading** - a **skeleton** that matches the real layout, not a centered spinner.
|
||||
- **Error** - an inline, recoverable error with a retry, announced via `role="status"`/`aria-live`.
|
||||
|
||||
Derive these from an element the block already has (don't invent parallel markup), or `get_examples` for a state-specific example.
|
||||
|
||||
## Responsive by default
|
||||
|
||||
Mobile-first, not mobile-afterthought. In constrained rows/cards/sidebars, put `min-w-0` on the shrinking container and `truncate` long single-line labels; protect the primary label's width and let secondary content compress. Reflow layouts (multi-column -> single column) rather than just shrinking them. Desktop and mobile should both look designed.
|
||||
|
||||
## Motion, subtly
|
||||
|
||||
Motion should clarify, not decorate. Use ReUI Motion Icons on primary actions for a subtle hover cue; keep transitions short (~200-300ms) with calm easing; prefer a skeleton pulse over a spinner. No bouncing, no gratuitous entrance animations on every element.
|
||||
|
||||
## Real, activated content
|
||||
|
||||
Use believable, typed data (realistic labels, counts, timestamps, statuses that map to a real workflow) - never lorem or abstract filler. Every visible control does something: no decorative buttons, fake tabs, meaningless toggles, or stats with no job. It must still hold with long names, empty values, and crowded data.
|
||||
|
||||
## Avoid the AI tells
|
||||
|
||||
These instantly read as generated - don't ship them: equal-weight card walls, empty gradients, repetitive padding everywhere, generic enterprise copy, ornamental icons, and number tiles that don't earn their place.
|
||||
|
||||
## The bar
|
||||
|
||||
Before you finish, ask: **would a product team keep this instead of replacing it? Does it still feel strong after swapping in real content?** If not, reuse the shipped ReUI design harder - don't restyle it into something new - then run the [quality.md](./quality.md) gates.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Icons (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn icon rules (use the project's configured `iconLibrary`, `data-icon` on icons inside `Button`, no sizing classes on icons inside components, pass icons as component objects not string keys). ReUI adds the following.
|
||||
|
||||
## Portable icons (library-agnostic)
|
||||
|
||||
ReUI components, examples, and blocks are authored to be icon-library-agnostic. When `iconLibrary` is set in `components.json`, the shadcn CLI installs each item's icons in **your** library automatically - you swap nothing. If an installed item's icons don't match your project (for example `iconLibrary` isn't set, so they came in from the item's demo library), change the **import source and component name** to your library, keeping the same icon-name semantics:
|
||||
|
||||
- `lucide` -> `lucide-react`
|
||||
- `tabler` -> `@tabler/icons-react`
|
||||
- `phosphor` -> `@phosphor-icons/react`
|
||||
- `remix` -> `@remixicon/react`
|
||||
- `hugeicons` -> `@hugeicons/react`
|
||||
|
||||
Don't assume `lucide-react`; read `iconLibrary` from `components.json`.
|
||||
|
||||
## Keep icons purposeful
|
||||
|
||||
Icons support the hierarchy, they don't replace it: keep them small, matched to the surrounding density, and decorative ones `aria-hidden="true"` (an icon-only control still needs an accessible label on the control). Don't add ornamental icons that do no job.
|
||||
|
||||
## Motion Icons (the `@reui/icons/...` set)
|
||||
|
||||
ReUI ships its own icon set in 4 styles (outline, solid, duotone, filled), each icon in two variants:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/icons/default/<style>/<name> --yes # static
|
||||
npx shadcn@latest add @reui/icons/animated/<style>/<name> --yes # hover-animated (motion/react)
|
||||
```
|
||||
|
||||
Finding them via the MCP is free; installing requires an Ultimate license (`REUI_LICENSE_KEY`, see [cli.md](./cli.md)). Reach for a Motion Icon on a primary action when a subtle hover cue helps; keep motion restrained.
|
||||
|
||||
Finding icons:
|
||||
|
||||
- Several icons (the common case): **`search_icons(concepts[])`** - up to 24 concepts in one call, the best icons per concept with install commands. Pass `animated: true` to get only icons with a hover-animated Motion variant.
|
||||
- One icon: `search` with `type: "icon"`.
|
||||
- Icon results and `get_icon` carry `animated: true` and `installAnimated` when an animated variant exists - use those install strings, do not construct paths by hand.
|
||||
- Every icon result carries a `previewUrl` (its live icon-category page) - **share it with the user** so they can SEE the icon before installing.
|
||||
|
||||
The `icon-stack` component composes multiple icons into a stacked display.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Quality gates (security, accessibility, scroll)
|
||||
|
||||
These are the **done gate**, not a nice-to-have: before you call any ReUI work finished, call the MCP `get_audit_checklist` tool and pass every item below (plus the craft bar in [craft.md](./craft.md)). Then typecheck and lint.
|
||||
|
||||
## Security
|
||||
|
||||
- Never `dangerouslySetInnerHTML`. Render data as text/components.
|
||||
- External links (`target="_blank"`) must always pair `rel="noopener noreferrer"`.
|
||||
- No real PII, secrets, or tokens in demo or committed code. Remote media only from sources the project already allows.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Implicit list/card items that navigate get real anchors with a standard hover affordance.
|
||||
- Icon-only or numeric buttons need an `aria-label`; decorative icons get `aria-hidden`.
|
||||
- Every non-submit button is `type="button"`.
|
||||
- Keyboard + focus: everything interactive is reachable in a sensible Tab order with a visible focus ring; layers (dialogs/sheets/menus) trap focus and close on `Escape`. ReUI components ship standard keyboard behavior - read each component's inline `api` rather than re-implementing it.
|
||||
- Announce async UI: loading and error messages use `role="status"` / `aria-live` so they're not silent to screen readers.
|
||||
|
||||
## Scroll mechanics
|
||||
|
||||
- Make scroll regions with a parent-owned height: a `min-h-0` + flex chain down to the scroll container. Never guess a `max-h`.
|
||||
- The scroll container owns `overflow-auto`; ancestors stay `min-h-0` so the height resolves.
|
||||
@@ -0,0 +1,35 @@
|
||||
# ReUI registry structure
|
||||
|
||||
ReUI is a shadcn-compatible registry with four entity types. **Examples and blocks are built FROM components** - reuse them, don't rebuild.
|
||||
|
||||
- **component** - one of the 20 ReUI building blocks with a real API (`data-grid`, `kanban`, `filters`, `date-selector`, `tree`, ...). Install directly (`@reui/data-grid`) or let it come in as a dependency of an example/block. Free. Read its API with `get_component(name)`.
|
||||
- **example** - a free `c-*` single-pattern use-case of a component (`c-kanban-1`, `c-data-grid-3`). Install one and read it to copy real composition. Find a component's examples with `get_examples(name)`.
|
||||
- **block** - a premium, full-page section that composes several components (`data-grid-2`, `pricing-page-1`). Pro or Ultimate license at install. Adapts to your active theme via semantic tokens.
|
||||
- **icon** - Motion Icons in 4 styles (outline, solid, duotone, filled), static (`@reui/icons/default/<style>/<name>`) and hover-animated (`@reui/icons/animated/<style>/<name>`). Ultimate license at install. See [icons.md](./icons.md).
|
||||
|
||||
## The @reui registry
|
||||
|
||||
Install everything through the shadcn CLI: `npx shadcn@latest add @reui/<name> --yes`. The CLI reads the `@reui` registry from the project's `components.json`. Free items need only the plain string form:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium installs need the authenticated form + `REUI_LICENSE_KEY` in `.env.local` - see [cli.md](./cli.md). The MCP `get_project_context` tool returns the right config.
|
||||
|
||||
## Know your base: base or radix
|
||||
|
||||
ReUI ships every item in two builds - `base` (Base UI) and `radix` (Radix UI) - with mirrored names. The CLI installs the build matching your project automatically, but **you must write code against the right base's API**. Detect it from `components.json` -> `style`: the segment before the first `-` is the base (`base-nova` -> Base UI, `radix-nova` -> Radix UI). The installed files and `c-*` examples are already in your base - read them and adapt on that base. See [components.md](./components.md) for the API deltas.
|
||||
|
||||
**So the MCP's own `docsUrl` and `previewUrl` match your base**, send your `style` to the MCP: add `?style=<your components.json style>` to the ReUI MCP server URL (or set an `X-Reui-Style` header) in your MCP client config - set once, applies to every call. The MCP then resolves docs/preview links to YOUR library (`/docs/components/radix/...`, `/preview/radix/...` for a radix project) instead of the default base; `get_project_context` echoes back the style it currently sees so you can confirm it. Install commands are unaffected (the CLI already installs the right variant). If you notice the MCP returning `base` links for a `radix` project, tell the user to add `?style=` to the server URL.
|
||||
|
||||
Blocks adapt to your active theme through semantic tokens and CSS variables - change the theme and every block follows.
|
||||
|
||||
## Free vs premium
|
||||
|
||||
- **Free, no key:** the 20 components, all `c-*` examples, the ReUI MCP, and this skill.
|
||||
- **Premium, license required at install:** blocks (Pro or Ultimate), Motion Icons and templates (Ultimate). Set `REUI_LICENSE_KEY` (see [cli.md](./cli.md)).
|
||||
|
||||
## Component API index
|
||||
|
||||
The canonical index of every component's API docs is **https://reui.io/llms.txt** (returned as `componentsApiUrl` in MCP results). Prefer the inline `api` from `get_component`; use the index/docs as the fallback.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Styling (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn skill for the generic rules (semantic colors not raw values, `gap-*` not `space-y-*`, `size-*`, `cn()`, no manual `dark:` overrides, no overlay `z-index`). This file is only the ReUI-specific additions.
|
||||
|
||||
## ReUI extended semantic tokens
|
||||
|
||||
ReUI adds semantic tokens beyond shadcn's base set. Use these instead of raw colors for status and emphasis:
|
||||
|
||||
- `--success` / `--success-foreground`
|
||||
- `--info` / `--info-foreground`
|
||||
- `--warning` / `--warning-foreground`
|
||||
- `--destructive-foreground` (paired with shadcn's `--destructive`)
|
||||
- `--invert` / `--invert-foreground` (inverted surfaces)
|
||||
|
||||
Use them as Tailwind utilities (`bg-success text-success-foreground`, `text-warning`, ...). They are defined in the project's global CSS and registered with Tailwind (`@theme inline` on v4). If a token is missing in the project, add it to the global CSS file (never a new file) following the same `name` / `name-foreground` convention, exactly as the shadcn customization rules describe.
|
||||
|
||||
**Incorrect:** `<span className="text-green-600">Active</span>`
|
||||
**Correct:** `<Badge variant="success">Active</Badge>` or `<span className="text-success">Active</span>`
|
||||
|
||||
## Blocks follow your theme
|
||||
|
||||
When you install a block it adapts to your active theme through the semantic tokens above and the project's CSS variables. Don't hardcode style-specific values into installed block code and don't fork it to "restyle" - change the theme via the CSS variables / a preset and every block follows. Want a different look? `search` for a block whose design already fits instead of re-skinning one.
|
||||
|
||||
## Density and typography rhythm
|
||||
|
||||
ReUI operational UI usually feels dense, not airy. Keep the gap between a title and its supporting description tight by default (`gap-0.5`, `space-y-1`, or `space-y-px`), and smaller than the gap between sections. Match the surrounding ReUI density when you add rows or fields; do not pad operational surfaces like a marketing page.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Workflow: find -> install -> read API -> adapt
|
||||
|
||||
The core ReUI loop. The MCP tells you what to install and gives you the API; the shadcn CLI installs it; you turn the installed files into correct, themed, data-wired code by **reuse**, not redesign.
|
||||
|
||||
## 1. Find (ReUI MCP `search` / `compose_page`)
|
||||
|
||||
**Full multi-section page ask?** Call `compose_page(intent, sections?)` FIRST, before searching block-by-block. It returns ordered sections, each with the best block for the intent (top pick + alternates); sections listed in `unavailableSections` have no real inventory - compose those from components, do not force a bad block.
|
||||
|
||||
For everything else, call `search` with the user's intent. Pass structured hints whenever you can infer them - you are an LLM, so do the parsing the server cannot:
|
||||
|
||||
- `type`: `"component"` (one of the 20 building blocks), `"example"` (a c-\* use-case), `"block"` (a full page/section), `"icon"`.
|
||||
- `component`: the ReUI component the request implies (`"data-grid"`, `"kanban"`, ...).
|
||||
- `category`, `features` (e.g. `["sortable","pagination"]`), `free`.
|
||||
|
||||
Example: "build a users management page with filters" -> `search({ query: "users management page with filters", type: "block", component: "data-grid", features: ["filters"] })`.
|
||||
|
||||
Each result has `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `termCoverage`, and `whyMatch`. `score` is relative to the top hit (the top is ~100 by construction), not an absolute quality - compare results to each other, and show the user the top options if several score closely; do not silently guess. A low `termCoverage` means a weak match even with a high score - rephrase or widen.
|
||||
|
||||
**Always show the preview link.** Whenever you list or recommend items - from `search`, `search_icons`, `list_components`, `compose_page`, or a getter - include each item's `previewUrl` (a live preview page) so the user can SEE it before you install. Blocks and examples link to an individual live preview; icons and components to their live category/component page. This applies to every listing, not only a single pick.
|
||||
|
||||
## 2. Install (shadcn CLI)
|
||||
|
||||
Run the result's `install` command from the project root, non-interactively:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes
|
||||
```
|
||||
|
||||
The CLI reads `components.json`, installs the correct base+style variant, resolves `registryDependencies` (a block pulls in its components), installs npm deps, and rewrites aliases. Do not pass the base/style. See [cli.md](./cli.md).
|
||||
|
||||
## 3. Read the API (do not guess props)
|
||||
|
||||
Before writing code against any component an item uses:
|
||||
|
||||
1. The item's `componentDigests` already give a 1-line contract per component - often enough to wire it. For the full API, call **`get_component(names)`** with ALL of `componentsUsed` in ONE call (it accepts an array) and read each inline `api` - no web fetch. **Share the component's `docsUrl`** (its 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.
|
||||
2. Call **`get_examples(name)`** for the free `c-*` examples of that component; install one and **read the added files** to copy the exact composition. This is the fastest correct path - the example shows real wiring you adapt, not invent.
|
||||
3. About to write a prop you did not see in an `api` or installed file? Run **`validate_usage`** BEFORE writing the code - per-prop documented / notDocumented verdicts plus did-you-mean suggestions. notDocumented means read the API, not push on.
|
||||
|
||||
## 4. Adapt (reuse-first) - do not skip
|
||||
|
||||
Installing files is not the end, and redesigning them defeats the point. First note the project's **base** so you write the right API - read `components.json` -> `style` and take the segment before the first `-` (`base-nova` -> Base UI, `radix-nova` -> Radix UI), see [components.md](./components.md). After `add`:
|
||||
|
||||
1. **Read the added files**; keep the composition intact. For a block, verify the components are wired correctly (for `data-grid`: a `useTable({ features: dataGridFeatures, ... })` instance passed as `table`, `recordCount` set - see [components.md](./components.md)).
|
||||
2. **Replace demo data with the user's real data** via typed structures (see [adapting.md](./adapting.md)).
|
||||
3. **Fix icon imports** to the project's icon library (see [icons.md](./icons.md)).
|
||||
4. **Align styling** to semantic tokens and the active theme - no raw colors (see [styling.md](./styling.md)).
|
||||
5. **Validate before finalizing**: if your adaptation introduced components or props you did not read in an `api` or example, run `validate_usage` on them.
|
||||
6. **Hit the craft bar** - clear hierarchy, deliberate density, the empty / loading / error states, subtle motion, and mobile-first responsiveness (see [craft.md](./craft.md)). Generic-looking output means you under-reused the design, not that it needs restyling.
|
||||
7. **Pass the quality gates** (security, a11y, scroll) - call the MCP `get_audit_checklist` tool and clear every item (see [quality.md](./quality.md)).
|
||||
8. **Typecheck / lint**.
|
||||
|
||||
## If no single block fits
|
||||
|
||||
Compose from components (`compose_page` tells you which sections have no block inventory via `unavailableSections`). `search` the components you need, read each `get_component` API, install a worked `get_examples` example for each, and assemble by adapting those examples. A block in the same category is a useful reference - install it and read its files to see how ReUI composes those components, then adapt.
|
||||
@@ -0,0 +1,58 @@
|
||||
# ReUI MCP: full reference
|
||||
|
||||
The ReUI MCP (`https://mcp.reui.io`, Streamable HTTP) is free to use but needs a ReUI account: on first use the agent signs in with ReUI (a free account is created if the user has none), so every request is tied to an account. Free covers components and examples; a Pro or Ultimate license unlocks premium blocks and Motion Icons and removes the daily request limit. It does **discovery + guidance** (search, inline APIs, page planning, validation) and never serves source; the shadcn CLI does **installation**, and the license key lives there (the `@reui` entry in `components.json`, backed by `.env.local`). Goal: from the user's intent to correct, themed, data-wired ReUI code in the **fewest tokens and calls**, with **no guessing**.
|
||||
|
||||
## Golden path (token-optimal - follow this order)
|
||||
|
||||
Most tasks need 2-4 MCP calls and ZERO web fetches:
|
||||
|
||||
1. **`search(query, ...hints)`** -> pick the top 1-3 results. Each result already carries `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `whyMatch`. The payload is complete - do not call another tool just to "confirm" a result.
|
||||
2. **`get_component([...componentsUsed])`** in ONE batched call (one name or an array of up to 20) -> read each inline `api`. This **replaces** fetching docs pages. Often skippable: search responses carry `componentDigests`, a compact API contract per referenced component.
|
||||
3. **`get_examples(component)`** -> install ONE returned `c-*` example, read the added files, copy the composition.
|
||||
4. **`get_install_command(item)`** only to validate a name you are unsure of (results already include `install`). Run the install with the shadcn CLI (`--yes`).
|
||||
5. **`get_audit_checklist()`** before declaring done.
|
||||
|
||||
If you already know the exact item name, skip `search`. Everything else is situational.
|
||||
|
||||
## The 5 task-specific tools (when to reach for each)
|
||||
|
||||
- **`compose_page`** - BEFORE building any full page (dashboard, settings, billing, landing). Pass the intent (and optionally the sections you want); it returns ordered sections, each with the best premium block for the intent (top pick + alternates). Sections with no real inventory are listed honestly in `unavailableSections` - compose those from components instead of forcing a bad block.
|
||||
- **`search_icons`** - whenever you need icons, especially several. Batch up to 24 concepts in one call; each concept returns its best icons with install commands. Pass `animated: true` to get only icons that have a hover-animated Motion variant.
|
||||
- **`validate_usage`** - BEFORE writing code with component names or props you have not read in an inline `api` or an installed example. It checks planned names + props against the indexed API docs and registry item names; returns did-you-mean suggestions and per-prop documented / notDocumented verdicts. Deterministic, no inference - a notDocumented prop means stop and read the API, not push on.
|
||||
- **`whats_new`** - when your registry knowledge might be stale (a name 404s, the user mentions an item you don't know). Returns items added/removed per build, newest first.
|
||||
- **`report_issue`** - when an installed item is actually broken (bad source, wrong dependency, broken preview). Goes straight to the ReUI team; rate-limited 5/hour. Not for usage questions.
|
||||
|
||||
## All 19 tools
|
||||
|
||||
`search`, `get_block`, `get_example`, `get_icon`, `list_block_groups`, `list_block_categories`, `list_example_categories`, `list_icon_categories`, `list_components`, `get_component`, `get_examples`, `search_icons`, `compose_page`, `validate_usage`, `whats_new`, `report_issue`, `get_install_command`, `get_project_context`, `get_audit_checklist`. The MCP serves the full parameter schemas; do not guess parameters beyond them.
|
||||
|
||||
## Token + speed rules
|
||||
|
||||
- **Batch `get_component`** - ONE call with the whole `componentsUsed` array, never N calls. Skip it entirely when `componentDigests` already answers the question.
|
||||
- **Read source by installing** - the MCP serves no source. To read or analyze an item's real code, install it with the shadcn CLI and open the local files. Learn an API from the inline `api` / `componentDigests`, never by reading raw source.
|
||||
- **Infer `search` hints yourself** (`type`, `component`, `category`, `features`, `free`) - hints shrink the result set and the tokens. Keep `limit` low; one right result beats ten.
|
||||
- Run independent calls (and the shadcn install) concurrently in one turn - serial tool calls are the main source of slowness.
|
||||
- Don't repeat a search for the same intent; don't call `list_*` to "see everything" - `search` is the entry point, `list_*` is only for browsing a taxonomy the user explicitly wants to explore.
|
||||
- Prefer `get_component`'s inline `api` over `docsUrl` / `/llms.txt`. Fetch a web page only as a last resort.
|
||||
|
||||
## Result shapes (so you don't re-fetch)
|
||||
|
||||
- `score` is 0-100 RELATIVE to the top hit (the top is ~100 by construction), not absolute - compare results to each other.
|
||||
- `termCoverage` (0-1) is the share of the query the item matched - low means a weak match even if the score looks high; rephrase or widen the search.
|
||||
- Each result carries `whyMatch`, `install`, docs/preview URLs, and a `free` flag; premium items carry `requiredPlan` (`"pro"` for blocks, `"ultimate"` for icons).
|
||||
- `componentDigests` is a top-level map: a compact API contract per referenced component - often enough to wire an item without a `get_component` call.
|
||||
- Icon results and `get_icon` include `animated: true` and `installAnimated` when a hover-animated Motion variant exists (animated: `@reui/icons/animated/<style>/<name>`; static: `@reui/icons/default/<style>/<name>`).
|
||||
|
||||
## Error playbook
|
||||
|
||||
- **401** - the MCP requires a signed-in ReUI account. The client prompts "Sign in with ReUI" (OAuth) on first use; a free account is created if needed. For headless/CI, pass a personal token (`reui_pat_...`, created at https://reui.io/account/mcp) as `Authorization: Bearer`.
|
||||
- **403 / locked result** - a valid account but the plan does not cover the item: premium blocks need Pro, Motion Icons need Ultimate. Point to https://reui.io/pricing (upgrade). Free accounts still get all components + examples.
|
||||
- **429** - rate limited (120 requests/min per IP); back off, honor `Retry-After`.
|
||||
- **not found** (`found: false`) - use the returned `suggestions`, or `search`; check `whats_new` if you suspect a stale name. Never run a fabricated install command.
|
||||
|
||||
## Fallbacks
|
||||
|
||||
- No ReUI MCP: `npx shadcn@latest search @reui -q "..."` then `add` (generic, no scoring / inline API).
|
||||
- The shadcn project's own MCP also works over the `@reui` registry: https://ui.shadcn.com/docs/mcp.
|
||||
|
||||
Per-agent MCP setup: https://reui.io/docs/mcp
|
||||
+6
-7
@@ -1,16 +1,15 @@
|
||||
node_modules
|
||||
**/node_modules
|
||||
dist
|
||||
**/dist
|
||||
data
|
||||
.git
|
||||
.github
|
||||
.cursor
|
||||
eslint.config.js
|
||||
*.md
|
||||
!README.md
|
||||
**/*.log
|
||||
coverage
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
coverage
|
||||
*.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,14 @@
|
||||
# VPS Tracker — API / root env (copy to .env or export in shell)
|
||||
|
||||
# Portal JWT (must match auth-portal JWT_SECRET / ISSUER)
|
||||
AUTH_REQUIRED=false
|
||||
AUTH_JWT_SECRET=dev-secret-change-me
|
||||
AUTH_ISSUER=https://auth.shnt.top
|
||||
AUTH_PORTAL_URL=http://localhost:5175
|
||||
# Shared with auth-portal AUDIT_INGEST_SECRET (Bearer for POST /api/v1/ingest/audit)
|
||||
AUTH_AUDIT_INGEST_SECRET=dev-audit-ingest-secret
|
||||
# Owner of space-main after migration (portal user id)
|
||||
# VPS_MAIN_SPACE_OWNER_USER_ID=
|
||||
|
||||
# DB_PATH=
|
||||
# PORT=3001
|
||||
@@ -0,0 +1,4 @@
|
||||
apps/api/scripts/censorcheck/*.sh text eol=lf
|
||||
apps/api/scripts/censorcheck/*.rsc text eol=lf
|
||||
apps/api/scripts/ipregion/*.sh text eol=lf
|
||||
apps/api/scripts/ipregion/*.rsc text eol=lf
|
||||
@@ -3,6 +3,8 @@ name: Docker
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
# Релиз: тег vX.Y.Z → образ ghcr.io/…:X.Y.Z и :X.Y
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
@@ -20,7 +22,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -34,12 +36,14 @@ jobs:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
+17
@@ -13,6 +13,23 @@ data
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Local MCP configs (may contain REUI license Bearer)
|
||||
.cursor/mcp.json
|
||||
.mcp.json
|
||||
.codegraph/daemon.pid
|
||||
|
||||
# pnpm
|
||||
.pnpm-store
|
||||
coverage
|
||||
*.tsbuildinfo
|
||||
|
||||
# Understand Anything (knowledge graph)
|
||||
.understand-anything/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
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,43 @@
|
||||
# Adapting installed ReUI code (reuse-first, no AI slop)
|
||||
|
||||
ReUI items ship production-quality. Your job is to **adapt by reuse** - wire real data and fit the app - not to redesign or hand-roll. The output should look like ReUI built it for this product.
|
||||
|
||||
## Preserve the design - don't over-customize
|
||||
|
||||
The design IS the product. A ReUI block/component encodes senior-designer decisions: spacing, hierarchy, density, color treatment, and component choices. The fastest way to turn a premium block back into generic AI slop is to "improve" its look - so don't.
|
||||
|
||||
- Change **data, copy, and props**; keep the **structure and styling** it ships with. Make the **smallest** change that wires the real data. If your diff touches `className` / JSX structure more than data / props, you are over-customizing - stop and reuse.
|
||||
- Don't swap ReUI components for hand-rolled ones, restructure the layout, re-skin spacing / radius / colors, or add decorative chrome. Let the installed components carry the default spacing, radius, sizing, icon rhythm, density, and state styling; add custom Tailwind only when a component genuinely lacks a contract you need.
|
||||
- Want a different look? `search` for a block whose design already fits and reuse that - don't restyle this one into a new design.
|
||||
|
||||
## Reuse the parts: examples and the block's own elements
|
||||
|
||||
- **Examples are building parts.** A free `c-*` example is a correct, single-pattern composition you can reuse. Before composing from scratch, `get_examples(component)`, install the closest one, and reuse its wiring - assemble UI from examples instead of hand-rolling what an example already shows.
|
||||
- **Reuse a block's own elements.** Need more rows, cards, items, or sections than ship by default? Repeat the block's **existing** element by mapping real data through the same markup - never invent parallel markup that drifts from its design. Need a variant (empty / loading / expanded)? Derive it from an element the block already has.
|
||||
|
||||
## Don't invent (read, don't guess)
|
||||
|
||||
- Never write a prop, variant value, import path, or `@reui/...` name you didn't read in a component's inline `api`, an installed example, or a `search` result. If you didn't see it, treat it as nonexistent - call `get_component` / `get_examples` / `search` first, or run the MCP `validate_usage` tool to check planned names + props against the docs before writing code.
|
||||
- If a getter returns `found: false` or `search` returns nothing, say so and fall back (plain shadcn, or ask) - never fabricate an install command or an API.
|
||||
|
||||
## What to change vs leave alone
|
||||
|
||||
- **Change:** the item's own data, copy, props, and layout to fit the app.
|
||||
- **Leave alone:** installed component files, hooks, and the shared theme - do not edit vendored ReUI internals; change behavior through props and the documented API.
|
||||
- Blocks are **portable React** - no `next/link`, `next/image`, or other framework-runtime imports inside them. Keep them portable.
|
||||
|
||||
## Demo data -> real data
|
||||
|
||||
- Replace every placeholder with the user's real data. Model it as **typed data structures** and **map over arrays** - never duplicate JSX per row/card. Keep small block-specific formatters next to the data.
|
||||
- Wire the real source (columns, fields, fetch). For `data-grid`, implement the server fetch contract if the user needs server-side data.
|
||||
- **Type from the component API, derive during render.** Type domain state through the component's own types - e.g. map status to `BadgeProps["variant"]` via a typed `Record<Status, …>` - instead of stringly-typed values. Compute view state during render; don't mirror derived data into `useState`/`useEffect`.
|
||||
- **Adapt on the right base.** Use the API for the project's base (Base UI vs Radix - see [components.md](./components.md)); the installed files are already base-correct, so reuse their shape rather than translating from memory.
|
||||
|
||||
## Believable content (no AI tells)
|
||||
|
||||
- Use realistic labels, counts, timestamps, and statuses that map to a real workflow.
|
||||
- No decorative buttons, fake tabs, meaningless toggles, equal-weight card walls, empty gradients, ornamental icons, or generic SaaS filler. Every element should do something.
|
||||
|
||||
## Operational surfaces (settings / profile / admin)
|
||||
|
||||
Pick ONE archetype and keep the family consistent: a vertical rail (3-6 sections), horizontal tabs (5-8), or a frame/stack. Prefer `frame` for tool-like surfaces, a card for profile-like ones. Don't mix archetypes in one surface.
|
||||
@@ -0,0 +1,60 @@
|
||||
# CLI: registry setup, license, non-interactive install
|
||||
|
||||
## Registry setup (one-time, per project)
|
||||
|
||||
Free items (the 20 components and all `c-*` examples) need only the plain string registry in `components.json`:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium items (blocks; Motion Icons and templates) require a ReUI license at install:
|
||||
|
||||
1. Add the key to `.env.local`:
|
||||
|
||||
```bash
|
||||
REUI_LICENSE_KEY=your-license-key
|
||||
```
|
||||
|
||||
2. Switch `components.json` to the authenticated object form:
|
||||
|
||||
```json
|
||||
{
|
||||
"registries": {
|
||||
"@reui": {
|
||||
"url": "https://reui.io/r/{style}/{name}.json",
|
||||
"headers": { "Authorization": "Bearer ${REUI_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
|
||||
|
||||
Use the project's package runner (check `packageManager`):
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes # npm
|
||||
pnpm dlx shadcn@latest add @reui/<name> --yes # pnpm
|
||||
bunx --bun shadcn@latest add @reui/<name> --yes # bun
|
||||
```
|
||||
|
||||
`--yes` skips confirmation prompts. The CLI auto-detects the package manager from the lockfile (there is no `--package-manager` flag). It also resolves the correct base+style variant from `components.json`, so do not pass a style.
|
||||
|
||||
## Handling prompts and conflicts
|
||||
|
||||
- **Always pass `--yes`** so the CLI does not block on confirmation prompts.
|
||||
- **Do NOT pass `--overwrite` by default.** If the CLI reports an existing file, read the output and resolve deliberately: install under a different name, adjust the path, or ask the user. Only use `--overwrite` when the user explicitly wants to replace a file.
|
||||
- **Preview first when touching an existing project**: `npx shadcn@latest add @reui/<name> --dry-run` shows what would change; `--diff <file>` shows a specific file's diff. Use these before overwriting.
|
||||
- Run from the **project root** so `components.json` and `.env.local` are found.
|
||||
|
||||
## Free vs premium boundary
|
||||
|
||||
- Public, no key: `c-*` examples and the 20 components (`@reui/data-grid`, `@reui/badge`, ...) that those examples depend on.
|
||||
- Key required at install: blocks (`@reui/<category>-N`) need a Pro or Ultimate license; Motion Icons (`@reui/icons/...`) and templates need Ultimate.
|
||||
|
||||
If an install 401/403s, the license key is missing, invalid, or the plan does not cover that resource (blocks: Pro or higher; icons and templates: Ultimate). Point the user to https://reui.io/account (their key) or https://reui.io/pricing (upgrade).
|
||||
@@ -0,0 +1,408 @@
|
||||
# ReUI components
|
||||
|
||||
The 21 ReUI building blocks: `alert`, `autocomplete`, `badge`, `cascader`, `data-grid`, `date-selector`, `event-calendar`, `filters`, `frame`, `gantt`, `icon-stack`, `icon-tile`, `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.
|
||||
|
||||
## data-grid (the flagship - read its API every time)
|
||||
|
||||
`data-grid` wraps TanStack Table v9. It is NOT a styled `<table>` and does NOT take `data`/`columns` props directly. The contract:
|
||||
|
||||
- Build a TanStack table instance with `useTable({ features: dataGridFeatures, ... })` (columns, data). `dataGridFeatures` is exported by the primitive and already bundles sorting, filtering, pagination, row selection, expanding, pinning, resizing and faceting, so there are no per-table row models to wire.
|
||||
- Pass that instance to `<DataGrid table={table} recordCount={total}>`.
|
||||
- Compose the body with `DataGridTable` inside `DataGrid`, and enable features through `tableLayout` (e.g. `{ headerSticky: true, columnsResizable: true }`), not ad-hoc classes.
|
||||
- Server-side data uses the documented fetch shape (`recordCount` is the total for pagination).
|
||||
|
||||
```tsx
|
||||
const table = useTable({
|
||||
features: dataGridFeatures,
|
||||
data,
|
||||
columns,
|
||||
})
|
||||
|
||||
<DataGrid table={table} recordCount={data.length}>
|
||||
<DataGridTable />
|
||||
</DataGrid>
|
||||
```
|
||||
|
||||
Common mistakes:
|
||||
|
||||
- **Incorrect:** `<DataGrid data={rows} columns={cols} />` - these props do not exist. **Correct:** build a `useTable({ features: dataGridFeatures, ... })` instance and pass `table={table}` + `recordCount`.
|
||||
- **Incorrect:** a raw `<table>` / 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 primitive's `DataGridColumnMeta` (e.g. `cellClassName`, `headerTitle`), set through the bundle's `columnMeta` slot.
|
||||
|
||||
## event-calendar
|
||||
|
||||
**Required:** events via `events`/`onEventsChange` (controlled) or `defaultEvents` (uncontrolled), plus a height on the root.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<EventCalendar defaultEvents={events} defaultView="month" className="h-[560px]">
|
||||
<EventCalendarNav />
|
||||
<EventCalendarContent />
|
||||
</EventCalendar>
|
||||
```
|
||||
|
||||
**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
|
||||
<Gantt defaultEvents={bars} resources={tasks} defaultScale="month" className="h-[480px]">
|
||||
<GanttNav />
|
||||
<GanttView />
|
||||
</Gantt>
|
||||
```
|
||||
|
||||
**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<string, T[]>`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Kanban value={cols} onValueChange={setCols} getItemValue={(i) => i.id}>
|
||||
<KanbanBoard>
|
||||
{Object.entries(cols).map(([id, items]) => (
|
||||
<KanbanColumn key={id} value={id}>
|
||||
<KanbanColumnHandle><h3>{id}</h3></KanbanColumnHandle>
|
||||
<KanbanColumnContent value={id}>
|
||||
{items.map((i) => (
|
||||
<KanbanItem key={i.id} value={i.id}>
|
||||
<KanbanItemHandle>{i.title}</KanbanItemHandle>
|
||||
</KanbanItem>
|
||||
))}
|
||||
</KanbanColumnContent>
|
||||
</KanbanColumn>
|
||||
))}
|
||||
</KanbanBoard>
|
||||
<KanbanOverlay><div className="bg-muted size-full rounded-md" /></KanbanOverlay>
|
||||
</Kanban>
|
||||
```
|
||||
|
||||
**Gotcha:** state is `Record<columnId, T[]>`. Each `KanbanColumnContent value` must match its parent `KanbanColumn value`. Omit `KanbanOverlay` and the drag preview silently breaks.
|
||||
|
||||
## sortable
|
||||
|
||||
**Required:** `value` (`T[]`), `onValueChange`, `getItemValue`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Sortable value={items} onValueChange={setItems} getItemValue={(i) => i.id}>
|
||||
{items.map((i) => (
|
||||
<SortableItem key={i.id} value={i.id}>
|
||||
<SortableItemHandle><GripVertical /></SortableItemHandle>
|
||||
{i.label}
|
||||
</SortableItem>
|
||||
))}
|
||||
</Sortable>
|
||||
```
|
||||
|
||||
**Gotcha:** a flat 1D reorder list (not columns - that is `kanban`). `getItemValue` must return a stable, unique string. Pass `layout="grid"` or `layout="nested"` for non-list layouts.
|
||||
|
||||
## filters
|
||||
|
||||
**Required:** `fields` (`FilterField[]`). The value is ONE `FilterQuery` tree - `query` + `onQueryChange`, or uncontrolled `defaultQuery`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const fields: FilterField[] = [
|
||||
{ id: "title", label: "Title", type: "text" },
|
||||
{
|
||||
id: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "archived", label: "Archived" },
|
||||
],
|
||||
},
|
||||
]
|
||||
const [query, setQuery] = useState<FilterQuery>(() => createFilterQuery())
|
||||
|
||||
<Filters fields={fields} query={query} onQueryChange={setQuery} />
|
||||
```
|
||||
|
||||
**Gotcha:** the state is a TREE, not a list of chips. `FilterQuery` is a group of rules joined by `and`/`or` and a group may hold another group, so `(A and B) or C` is expressible; a rule is `{ id, type: "rule", path: ["status"], operator, value }` and `path` is the whole nested attribute path, root first. The pre-rewrite API is GONE: there is no `filters`/`onChange` prop, no `FilterFieldConfig` (fields are `FilterField`, nested through their own `fields`, keyed `id` not `key`), and no `createFilter()` - it minted ids inside a pure function and broke hydration, so ids now come from `createFilterIdFactory(seed)` seeded off `useId`, and `createFilterQuery()` / `createFilterRule()` take one. Read the query back with `flattenFilterConditions` (`{ path, field, operator, values, negated }` per rule, incomplete rules skipped) and walk the tree yourself when the parentheses carry meaning - the primitive compiles nothing, no SQL, no query string.
|
||||
|
||||
`variant` picks the chrome over that one query: `"basic"`, the default, is the flat chip row for a toolbar over a table; `"advanced"` is the condition builder, hung off a trigger or rendered in place with `advancedMode="inline"`. Both read and write the same tree, so a saved view built in one opens in the other. Other props worth knowing before you hand-roll them: `size` is two rungs, `"sm" | "default"`, resolved per style (there is no `lg`); `reorderable` turns on drag and Alt+Arrow row moves in the builder; `onBeforeQueryChange` is the ONE veto point for every write (return `false` to refuse, it cannot rewrite); `editors` registers custom value editors a field selects by `editor` name; `labels` / `operatorLabels` own every rendered string; `pathCollapse` + `maxPathSegments` shorten deep attribute paths; `renderChip` / `renderValue` / `renderEmpty` replace rendered parts. On a field, `loadOptions` supplies async options with paging and `resolveValues` renders a chip restored from a saved view whose option was never loaded. Pairs naturally with `data-grid`.
|
||||
|
||||
## cascader
|
||||
|
||||
**Required:** `items` (a tree of `{ value, label, children? }`), plus the panel parts inside `CascaderContent`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Cascader items={items} value={value} onValueChange={setValue}>
|
||||
<CascaderTrigger render={<Button variant="outline" />}>
|
||||
<CascaderValue placeholder="Select an attribute" />
|
||||
</CascaderTrigger>
|
||||
<CascaderContent className="w-80">
|
||||
<CascaderPanel>
|
||||
<CascaderNav>
|
||||
<CascaderBreadcrumb />
|
||||
<CascaderInput />
|
||||
</CascaderNav>
|
||||
<CascaderEmpty />
|
||||
<CascaderList maxHeight={288}>
|
||||
<CascaderItems />
|
||||
</CascaderList>
|
||||
<CascaderStatus />
|
||||
</CascaderPanel>
|
||||
</CascaderContent>
|
||||
</Cascader>
|
||||
```
|
||||
|
||||
**Gotcha:** pressing a branch NAVIGATES, it does not select - only leaves are selectable until you pass `selectable="any"` or a predicate, and once a branch is selectable its chevron becomes the only way to open it. `CascaderInput` must stay inside `CascaderContent` (Base UI refills the query from the selection when the input sits outside the popup). Always include `CascaderStatus`: it is the live region announcing level changes, which the visual breadcrumb does not provide to screen readers. Accepts a flat adjacency list via `getParent` as well as nested `children`. `searchScope="deep"` searches every level and annotates results with their path; `multiple` gives checkbox rows; `inline` + a bare `CascaderPanel` embeds it with no popover.
|
||||
|
||||
The shape above is `mode="drill"`, the default. `mode="tree"` keeps the same parts (drop `CascaderBreadcrumb`, pass `showBack={false}`, drive expansion with `expanded`/`onExpandedChange`); `mode="columns"` REPLACES `CascaderList` + `CascaderItems` with a single `CascaderColumns`, and has no breadcrumb. Other props worth knowing before you hand-roll them: `cascade` (multi-select only, parent/child selection with indeterminate branches - pair it with `selectable="any"`, since a leaf-only tree can never cascade), `indicator={false}` to drop the single-select check and its gutter (visual only, no-op with `multiple`), `virtualize`/`virtualizeThreshold` plus `CascaderVirtualItems` for long levels, and `getChildren` for async levels with cursor paging, retry on failure and optional `prefetch`. `CascaderFooter` pins commands below the list (`actions` is the quick path) and `CascaderSubmenu` opens one as a side-anchored flyout with the full menu keyboard model. To head a run of rows use `CascaderGroup` wrapping a `CascaderLabel` - a bare label inside a listbox names nothing and is dropped from the accessibility tree - and `CascaderSeparator` for the rule between runs. Every rendered string comes from `labels`, and the panel is RTL-correct under a `DirectionProvider` or `dir="rtl"`.
|
||||
|
||||
## date-selector
|
||||
|
||||
**Required:** none, but wire `onChange` to capture the value.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
const [value, setValue] = useState<DateSelectorValue | undefined>()
|
||||
|
||||
<DateSelector value={value} onChange={setValue} label="Due date" />
|
||||
```
|
||||
|
||||
**Gotcha:** the value is a structured `DateSelectorValue` (period / operator / start+end dates), NOT a `Date` - never pass a raw `Date`. Use `allowRange={false}` to lock single-date picking. Read `get_component("date-selector")` for the value shape.
|
||||
|
||||
## tree
|
||||
|
||||
**Required:** `tree` (a `@headless-tree/core` instance you construct)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Tree tree={tree}>
|
||||
{tree.getItems().map((item) => (
|
||||
<TreeItem key={item.getId()} item={item}>
|
||||
<TreeItemLabel />
|
||||
</TreeItem>
|
||||
))}
|
||||
</Tree>
|
||||
```
|
||||
|
||||
**Gotcha:** `Tree` is a styled shell - it takes a headless-tree instance via `tree`, NOT `data`/`items` props. Build the instance with `@headless-tree/react`. External API: https://headless-tree.lukasbach.com/
|
||||
|
||||
## stepper
|
||||
|
||||
**Required:** `StepperItem step` (number), `StepperContent value` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Stepper defaultValue={1}>
|
||||
<StepperNav>
|
||||
<StepperItem step={1}>
|
||||
<StepperTrigger><StepperIndicator>1</StepperIndicator></StepperTrigger>
|
||||
<StepperSeparator />
|
||||
</StepperItem>
|
||||
<StepperItem step={2}>
|
||||
<StepperTrigger><StepperIndicator>2</StepperIndicator></StepperTrigger>
|
||||
</StepperItem>
|
||||
</StepperNav>
|
||||
<StepperPanel>
|
||||
<StepperContent value={1}>Step 1 content</StepperContent>
|
||||
<StepperContent value={2}>Step 2 content</StepperContent>
|
||||
</StepperPanel>
|
||||
</Stepper>
|
||||
```
|
||||
|
||||
**Gotcha:** steps are 1-indexed. Without `StepperPanel` + `StepperContent` you render the nav trail but no body. Put `StepperSeparator` in every `StepperItem` except the last.
|
||||
|
||||
## timeline
|
||||
|
||||
**Required:** `TimelineItem step` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Timeline>
|
||||
<TimelineItem step={1}>
|
||||
<TimelineHeader>
|
||||
<TimelineDate>March 2024</TimelineDate>
|
||||
<TimelineTitle>Project initialized</TimelineTitle>
|
||||
</TimelineHeader>
|
||||
<TimelineIndicator />
|
||||
<TimelineSeparator />
|
||||
<TimelineContent>Repo and architecture set up.</TimelineContent>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
```
|
||||
|
||||
**Gotcha:** each item needs a unique `step`. `orientation` is `"vertical"` (default) or `"horizontal"`. This is a static event display, not interactive like `stepper`.
|
||||
|
||||
## autocomplete
|
||||
|
||||
**Required:** `items` (array; each item has at least `value`)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Autocomplete items={items}>
|
||||
<AutocompleteInput placeholder="Search..." />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No results found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.value} value={item}>{item.label}</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
```
|
||||
|
||||
**Gotcha:** `AutocompleteList` takes a render-prop `(item) => ReactNode`, NOT a mapped array of children. External API: https://base-ui.com/react/components/autocomplete
|
||||
|
||||
## phone-input
|
||||
|
||||
**Required:** none, but wire `onChange`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<PhoneInput placeholder="Enter phone number" defaultCountry="US" value={value} onChange={setValue} />
|
||||
```
|
||||
|
||||
**Gotcha:** `value`/`onChange` use an E.164 string (e.g. `"+14155551234"`), not a display-formatted string; `onChange` can fire `undefined`. `defaultCountry` is a 2-letter ISO code. Wraps `react-phone-number-input`.
|
||||
|
||||
## number-field
|
||||
|
||||
**Required:** wrap the controls in `NumberFieldGroup`.
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<NumberField defaultValue={0}>
|
||||
<NumberFieldScrubArea label="Quantity" />
|
||||
<NumberFieldGroup>
|
||||
<NumberFieldDecrement />
|
||||
<NumberFieldInput />
|
||||
<NumberFieldIncrement />
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
```
|
||||
|
||||
**Gotcha:** import from `@/components/ui/number-field`. The accessible label goes on `NumberFieldScrubArea`, not `NumberField`. External API: https://base-ui.com/react/components/number-field
|
||||
|
||||
## rating
|
||||
|
||||
**Required:** `rating` (number)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Rating rating={4.5} showValue editable onRatingChange={setRating} />
|
||||
```
|
||||
|
||||
**Gotcha:** supports decimals (partial stars). Pass `editable` + `onRatingChange` for interactive input; omit both for a read-only display.
|
||||
|
||||
## scrollspy
|
||||
|
||||
**Required:** `targetRef` (the scroll container ref)
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Scrollspy targetRef={containerRef}>
|
||||
<a href="#s1" data-scrollspy-anchor="s1">Section 1</a>
|
||||
<a href="#s2" data-scrollspy-anchor="s2">Section 2</a>
|
||||
</Scrollspy>
|
||||
<div ref={containerRef}>
|
||||
<div id="s1">...</div>
|
||||
<div id="s2">...</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Gotcha:** each link's `data-scrollspy-anchor` must match a section `id`. `targetRef` is the scrollable container (defaults to the window).
|
||||
|
||||
## frame
|
||||
|
||||
**Required:** `Frame` > `FramePanel`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Frame>
|
||||
<FramePanel>
|
||||
<FrameHeader>
|
||||
<FrameTitle>Title</FrameTitle>
|
||||
<FrameDescription>Description</FrameDescription>
|
||||
</FrameHeader>
|
||||
<div className="p-5">Content</div>
|
||||
<FrameFooter>Footer</FrameFooter>
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
```
|
||||
|
||||
**Gotcha:** a structured card shell for tool-like surfaces. `stacked` connects multiple panels with shared borders; `dense` removes panel padding; radius via the `--frame-radius` CSS variable.
|
||||
|
||||
## icon-stack
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconStack aria-hidden="true">
|
||||
<InboxIcon className="size-4" />
|
||||
</IconStack>
|
||||
```
|
||||
|
||||
**Gotcha:** isometric layered artwork for empty states and illustrations; style the inner icon via its own `className`. Mark purely decorative stacks `aria-hidden="true"` and keep the real label in surrounding copy.
|
||||
|
||||
## icon-tile
|
||||
|
||||
**Required:** one child icon
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<IconTile variant="elevated" size="lg">
|
||||
<PackageIcon />
|
||||
</IconTile>
|
||||
```
|
||||
|
||||
**Gotcha:** the square container an icon sits in, so every list row, feature card and empty state shares one affordance. `variant`: `outline` (default) | `elevated` (muted fill, raised ring) | `soft` (tinted nested, tone from currentColor) | `solid` (filled tone, contrasting glyph) | `frame` (double container). `soft` and `solid` retint from one text color class (they default to `text-primary`). `size`: `xs | sm | default | lg | xl` (24/32/40/48/64px tile, glyph scales 12/14/16/20/24px). `radius`: `default | full`. Do not set a `size-*` class on the child icon unless you mean to override the tile's glyph size; recolor with `className` on the tile, not the icon.
|
||||
|
||||
## alert
|
||||
|
||||
**Required:** `Alert` > `AlertTitle`
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Alert variant="success">
|
||||
<ShieldCheckIcon />
|
||||
<AlertTitle>Security update</AlertTitle>
|
||||
<AlertDescription>Enable two-factor authentication.</AlertDescription>
|
||||
<AlertAction><Button size="xs">Update</Button></AlertAction>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible API. `variant`: `default | destructive | info | success | warning | invert`. The non-default variants use ReUI extended color tokens (`--success`/`--info`/`--warning`/`--invert`), which the install adds. Defer generic alert rules to the shadcn skill.
|
||||
|
||||
## badge
|
||||
|
||||
**Required:** none (text child).
|
||||
**Shape:**
|
||||
|
||||
```tsx
|
||||
<Badge variant="success-light" size="sm">Success</Badge>
|
||||
<Badge variant="outline" radius="full">Pill</Badge>
|
||||
```
|
||||
|
||||
**Gotcha:** shadcn-compatible. Rich `variant` set (solid, `-outline`, `-light` per color), `size` `xs..xl`, `radius` `default | full`. Like `alert`, the color variants rely on ReUI extended tokens. Prefer `Badge` variants over raw color classes for statuses.
|
||||
|
||||
## base vs radix - write for the project's base
|
||||
|
||||
ReUI ships every component in two builds: `base` (Base UI) and `radix` (Radix UI). The install command and name are identical, and the CLI installs the build matching the project. But you must write/adapt code against the **right base**, because their APIs differ.
|
||||
|
||||
**Detect the base first.** Read `components.json` -> `style` and take the segment before the first `-`:
|
||||
|
||||
- `"style": "base-nova"` -> **Base UI**
|
||||
- `"style": "radix-nova"` -> **Radix UI**
|
||||
|
||||
**Then use that base's API.** The deltas mirror shadcn's base-vs-radix split:
|
||||
|
||||
- Slot/composition: Base UI `render={<… />}` vs Radix `asChild`.
|
||||
- `Select`: Base UI takes `items`; Radix uses `<SelectItem>` children.
|
||||
- `ToggleGroup`: Base UI `multiple` boolean vs Radix `type="single" | "multiple"`.
|
||||
|
||||
The safest path is to **read the installed files and `c-*` examples** - they're already in your base, so reuse their wiring instead of guessing. When `get_component`'s inline `api` or an example shows the other base's shape, translate it to your base (or `validate_usage` to confirm). Defer the generic base/radix mechanics to the shadcn skill.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Craft: make ReUI UI exceptional, not generic
|
||||
|
||||
ReUI items ship senior-designer quality. Your adaptation has to hold that bar, so the result reads like a real product surface a team would keep - not a wireframe an AI generated. Use these alongside the reuse rules in [adapting.md](./adapting.md).
|
||||
|
||||
## Have a point of view
|
||||
|
||||
Pick an emotional register before you compose - calm, operational, premium, editorial, dense, energetic - and let layout, spacing, surface treatment, and icon behavior all reinforce it. One or two memorable decisions and restraint everywhere else beats ten generic ones. UI with no point of view reads as generated.
|
||||
|
||||
## Brutally clear hierarchy
|
||||
|
||||
One focal point per card or panel: the dominant metric or task first, its label second, supporting detail third. The first thing the eye lands on should be the right thing; secondary text must read as secondary. Borders, separators, and surfaces do real work to create 2-3 information bands - don't flatten everything to equal weight.
|
||||
|
||||
## Spacing rhythm and deliberate density
|
||||
|
||||
Gaps are a signal, not a default. Keep them intentional and consistent within a family (`gap-1`/`gap-2` for tight operational rows, larger gaps for section breaks), and smaller within a group than between groups. Match the surrounding ReUI density; don't pad an operational surface like a marketing page, and don't drift density mid-section. The composition should still feel authored in grayscale.
|
||||
|
||||
## Cover the real states (the usual miss)
|
||||
|
||||
A surface isn't done at the happy path. Compose, and wire:
|
||||
|
||||
- **Empty** - a purposeful empty state (short message + the primary action), never a blank panel.
|
||||
- **Loading** - a **skeleton** that matches the real layout, not a centered spinner.
|
||||
- **Error** - an inline, recoverable error with a retry, announced via `role="status"`/`aria-live`.
|
||||
|
||||
Derive these from an element the block already has (don't invent parallel markup), or `get_examples` for a state-specific example.
|
||||
|
||||
## Responsive by default
|
||||
|
||||
Mobile-first, not mobile-afterthought. In constrained rows/cards/sidebars, put `min-w-0` on the shrinking container and `truncate` long single-line labels; protect the primary label's width and let secondary content compress. Reflow layouts (multi-column -> single column) rather than just shrinking them. Desktop and mobile should both look designed.
|
||||
|
||||
## Motion, subtly
|
||||
|
||||
Motion should clarify, not decorate. Use ReUI Motion Icons on primary actions for a subtle hover cue; keep transitions short (~200-300ms) with calm easing; prefer a skeleton pulse over a spinner. No bouncing, no gratuitous entrance animations on every element.
|
||||
|
||||
## Real, activated content
|
||||
|
||||
Use believable, typed data (realistic labels, counts, timestamps, statuses that map to a real workflow) - never lorem or abstract filler. Every visible control does something: no decorative buttons, fake tabs, meaningless toggles, or stats with no job. It must still hold with long names, empty values, and crowded data.
|
||||
|
||||
## Avoid the AI tells
|
||||
|
||||
These instantly read as generated - don't ship them: equal-weight card walls, empty gradients, repetitive padding everywhere, generic enterprise copy, ornamental icons, and number tiles that don't earn their place.
|
||||
|
||||
## The bar
|
||||
|
||||
Before you finish, ask: **would a product team keep this instead of replacing it? Does it still feel strong after swapping in real content?** If not, reuse the shipped ReUI design harder - don't restyle it into something new - then run the [quality.md](./quality.md) gates.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Icons (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn icon rules (use the project's configured `iconLibrary`, `data-icon` on icons inside `Button`, no sizing classes on icons inside components, pass icons as component objects not string keys). ReUI adds the following.
|
||||
|
||||
## Portable icons (library-agnostic)
|
||||
|
||||
ReUI components, examples, and blocks are authored to be icon-library-agnostic. When `iconLibrary` is set in `components.json`, the shadcn CLI installs each item's icons in **your** library automatically - you swap nothing. If an installed item's icons don't match your project (for example `iconLibrary` isn't set, so they came in from the item's demo library), change the **import source and component name** to your library, keeping the same icon-name semantics:
|
||||
|
||||
- `lucide` -> `lucide-react`
|
||||
- `tabler` -> `@tabler/icons-react`
|
||||
- `phosphor` -> `@phosphor-icons/react`
|
||||
- `remix` -> `@remixicon/react`
|
||||
- `hugeicons` -> `@hugeicons/react`
|
||||
|
||||
Don't assume `lucide-react`; read `iconLibrary` from `components.json`.
|
||||
|
||||
## Keep icons purposeful
|
||||
|
||||
Icons support the hierarchy, they don't replace it: keep them small, matched to the surrounding density, and decorative ones `aria-hidden="true"` (an icon-only control still needs an accessible label on the control). Don't add ornamental icons that do no job.
|
||||
|
||||
## Motion Icons (the `@reui/icons/...` set)
|
||||
|
||||
ReUI ships its own icon set in 4 styles (outline, solid, duotone, filled), each icon in two variants:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/icons/default/<style>/<name> --yes # static
|
||||
npx shadcn@latest add @reui/icons/animated/<style>/<name> --yes # hover-animated (motion/react)
|
||||
```
|
||||
|
||||
Finding them via the MCP is free; installing requires an Ultimate license (`REUI_LICENSE_KEY`, see [cli.md](./cli.md)). Reach for a Motion Icon on a primary action when a subtle hover cue helps; keep motion restrained.
|
||||
|
||||
Finding icons:
|
||||
|
||||
- Several icons (the common case): **`search_icons(concepts[])`** - up to 24 concepts in one call, the best icons per concept with install commands. Pass `animated: true` to get only icons with a hover-animated Motion variant.
|
||||
- One icon: `search` with `type: "icon"`.
|
||||
- Icon results and `get_icon` carry `animated: true` and `installAnimated` when an animated variant exists - use those install strings, do not construct paths by hand.
|
||||
- Every icon result carries a `previewUrl` (its live icon-category page) - **share it with the user** so they can SEE the icon before installing.
|
||||
|
||||
The `icon-stack` component composes multiple icons into a stacked display.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Quality gates (security, accessibility, scroll)
|
||||
|
||||
These are the **done gate**, not a nice-to-have: before you call any ReUI work finished, call the MCP `get_audit_checklist` tool and pass every item below (plus the craft bar in [craft.md](./craft.md)). Then typecheck and lint.
|
||||
|
||||
## Security
|
||||
|
||||
- Never `dangerouslySetInnerHTML`. Render data as text/components.
|
||||
- External links (`target="_blank"`) must always pair `rel="noopener noreferrer"`.
|
||||
- No real PII, secrets, or tokens in demo or committed code. Remote media only from sources the project already allows.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Implicit list/card items that navigate get real anchors with a standard hover affordance.
|
||||
- Icon-only or numeric buttons need an `aria-label`; decorative icons get `aria-hidden`.
|
||||
- Every non-submit button is `type="button"`.
|
||||
- Keyboard + focus: everything interactive is reachable in a sensible Tab order with a visible focus ring; layers (dialogs/sheets/menus) trap focus and close on `Escape`. ReUI components ship standard keyboard behavior - read each component's inline `api` rather than re-implementing it.
|
||||
- Announce async UI: loading and error messages use `role="status"` / `aria-live` so they're not silent to screen readers.
|
||||
|
||||
## Scroll mechanics
|
||||
|
||||
- Make scroll regions with a parent-owned height: a `min-h-0` + flex chain down to the scroll container. Never guess a `max-h`.
|
||||
- The scroll container owns `overflow-auto`; ancestors stay `min-h-0` so the height resolves.
|
||||
@@ -0,0 +1,35 @@
|
||||
# ReUI registry structure
|
||||
|
||||
ReUI is a shadcn-compatible registry with four entity types. **Examples and blocks are built FROM components** - reuse them, don't rebuild.
|
||||
|
||||
- **component** - one of the 20 ReUI building blocks with a real API (`data-grid`, `kanban`, `filters`, `date-selector`, `tree`, ...). Install directly (`@reui/data-grid`) or let it come in as a dependency of an example/block. Free. Read its API with `get_component(name)`.
|
||||
- **example** - a free `c-*` single-pattern use-case of a component (`c-kanban-1`, `c-data-grid-3`). Install one and read it to copy real composition. Find a component's examples with `get_examples(name)`.
|
||||
- **block** - a premium, full-page section that composes several components (`data-grid-2`, `pricing-page-1`). Pro or Ultimate license at install. Adapts to your active theme via semantic tokens.
|
||||
- **icon** - Motion Icons in 4 styles (outline, solid, duotone, filled), static (`@reui/icons/default/<style>/<name>`) and hover-animated (`@reui/icons/animated/<style>/<name>`). Ultimate license at install. See [icons.md](./icons.md).
|
||||
|
||||
## The @reui registry
|
||||
|
||||
Install everything through the shadcn CLI: `npx shadcn@latest add @reui/<name> --yes`. The CLI reads the `@reui` registry from the project's `components.json`. Free items need only the plain string form:
|
||||
|
||||
```json
|
||||
{ "registries": { "@reui": "https://reui.io/r/{style}/{name}.json" } }
|
||||
```
|
||||
|
||||
Premium installs need the authenticated form + `REUI_LICENSE_KEY` in `.env.local` - see [cli.md](./cli.md). The MCP `get_project_context` tool returns the right config.
|
||||
|
||||
## Know your base: base or radix
|
||||
|
||||
ReUI ships every item in two builds - `base` (Base UI) and `radix` (Radix UI) - with mirrored names. The CLI installs the build matching your project automatically, but **you must write code against the right base's API**. Detect it from `components.json` -> `style`: the segment before the first `-` is the base (`base-nova` -> Base UI, `radix-nova` -> Radix UI). The installed files and `c-*` examples are already in your base - read them and adapt on that base. See [components.md](./components.md) for the API deltas.
|
||||
|
||||
**So the MCP's own `docsUrl` and `previewUrl` match your base**, send your `style` to the MCP: add `?style=<your components.json style>` to the ReUI MCP server URL (or set an `X-Reui-Style` header) in your MCP client config - set once, applies to every call. The MCP then resolves docs/preview links to YOUR library (`/docs/components/radix/...`, `/preview/radix/...` for a radix project) instead of the default base; `get_project_context` echoes back the style it currently sees so you can confirm it. Install commands are unaffected (the CLI already installs the right variant). If you notice the MCP returning `base` links for a `radix` project, tell the user to add `?style=` to the server URL.
|
||||
|
||||
Blocks adapt to your active theme through semantic tokens and CSS variables - change the theme and every block follows.
|
||||
|
||||
## Free vs premium
|
||||
|
||||
- **Free, no key:** the 20 components, all `c-*` examples, the ReUI MCP, and this skill.
|
||||
- **Premium, license required at install:** blocks (Pro or Ultimate), Motion Icons and templates (Ultimate). Set `REUI_LICENSE_KEY` (see [cli.md](./cli.md)).
|
||||
|
||||
## Component API index
|
||||
|
||||
The canonical index of every component's API docs is **https://reui.io/llms.txt** (returned as `componentsApiUrl` in MCP results). Prefer the inline `api` from `get_component`; use the index/docs as the fallback.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Styling (ReUI delta over shadcn)
|
||||
|
||||
Follow the shadcn skill for the generic rules (semantic colors not raw values, `gap-*` not `space-y-*`, `size-*`, `cn()`, no manual `dark:` overrides, no overlay `z-index`). This file is only the ReUI-specific additions.
|
||||
|
||||
## ReUI extended semantic tokens
|
||||
|
||||
ReUI adds semantic tokens beyond shadcn's base set. Use these instead of raw colors for status and emphasis:
|
||||
|
||||
- `--success` / `--success-foreground`
|
||||
- `--info` / `--info-foreground`
|
||||
- `--warning` / `--warning-foreground`
|
||||
- `--destructive-foreground` (paired with shadcn's `--destructive`)
|
||||
- `--invert` / `--invert-foreground` (inverted surfaces)
|
||||
|
||||
Use them as Tailwind utilities (`bg-success text-success-foreground`, `text-warning`, ...). They are defined in the project's global CSS and registered with Tailwind (`@theme inline` on v4). If a token is missing in the project, add it to the global CSS file (never a new file) following the same `name` / `name-foreground` convention, exactly as the shadcn customization rules describe.
|
||||
|
||||
**Incorrect:** `<span className="text-green-600">Active</span>`
|
||||
**Correct:** `<Badge variant="success">Active</Badge>` or `<span className="text-success">Active</span>`
|
||||
|
||||
## Blocks follow your theme
|
||||
|
||||
When you install a block it adapts to your active theme through the semantic tokens above and the project's CSS variables. Don't hardcode style-specific values into installed block code and don't fork it to "restyle" - change the theme via the CSS variables / a preset and every block follows. Want a different look? `search` for a block whose design already fits instead of re-skinning one.
|
||||
|
||||
## Density and typography rhythm
|
||||
|
||||
ReUI operational UI usually feels dense, not airy. Keep the gap between a title and its supporting description tight by default (`gap-0.5`, `space-y-1`, or `space-y-px`), and smaller than the gap between sections. Match the surrounding ReUI density when you add rows or fields; do not pad operational surfaces like a marketing page.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Workflow: find -> install -> read API -> adapt
|
||||
|
||||
The core ReUI loop. The MCP tells you what to install and gives you the API; the shadcn CLI installs it; you turn the installed files into correct, themed, data-wired code by **reuse**, not redesign.
|
||||
|
||||
## 1. Find (ReUI MCP `search` / `compose_page`)
|
||||
|
||||
**Full multi-section page ask?** Call `compose_page(intent, sections?)` FIRST, before searching block-by-block. It returns ordered sections, each with the best block for the intent (top pick + alternates); sections listed in `unavailableSections` have no real inventory - compose those from components, do not force a bad block.
|
||||
|
||||
For everything else, call `search` with the user's intent. Pass structured hints whenever you can infer them - you are an LLM, so do the parsing the server cannot:
|
||||
|
||||
- `type`: `"component"` (one of the 20 building blocks), `"example"` (a c-\* use-case), `"block"` (a full page/section), `"icon"`.
|
||||
- `component`: the ReUI component the request implies (`"data-grid"`, `"kanban"`, ...).
|
||||
- `category`, `features` (e.g. `["sortable","pagination"]`), `free`.
|
||||
|
||||
Example: "build a users management page with filters" -> `search({ query: "users management page with filters", type: "block", component: "data-grid", features: ["filters"] })`.
|
||||
|
||||
Each result has `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `termCoverage`, and `whyMatch`. `score` is relative to the top hit (the top is ~100 by construction), not an absolute quality - compare results to each other, and show the user the top options if several score closely; do not silently guess. A low `termCoverage` means a weak match even with a high score - rephrase or widen.
|
||||
|
||||
**Always show the preview link.** Whenever you list or recommend items - from `search`, `search_icons`, `list_components`, `compose_page`, or a getter - include each item's `previewUrl` (a live preview page) so the user can SEE it before you install. Blocks and examples link to an individual live preview; icons and components to their live category/component page. This applies to every listing, not only a single pick.
|
||||
|
||||
## 2. Install (shadcn CLI)
|
||||
|
||||
Run the result's `install` command from the project root, non-interactively:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add @reui/<name> --yes
|
||||
```
|
||||
|
||||
The CLI reads `components.json`, installs the correct base+style variant, resolves `registryDependencies` (a block pulls in its components), installs npm deps, and rewrites aliases. Do not pass the base/style. See [cli.md](./cli.md).
|
||||
|
||||
## 3. Read the API (do not guess props)
|
||||
|
||||
Before writing code against any component an item uses:
|
||||
|
||||
1. The item's `componentDigests` already give a 1-line contract per component - often enough to wire it. For the full API, call **`get_component(names)`** with ALL of `componentsUsed` in ONE call (it accepts an array) and read each inline `api` - no web fetch. **Share the component's `docsUrl`** (its 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.
|
||||
2. Call **`get_examples(name)`** for the free `c-*` examples of that component; install one and **read the added files** to copy the exact composition. This is the fastest correct path - the example shows real wiring you adapt, not invent.
|
||||
3. About to write a prop you did not see in an `api` or installed file? Run **`validate_usage`** BEFORE writing the code - per-prop documented / notDocumented verdicts plus did-you-mean suggestions. notDocumented means read the API, not push on.
|
||||
|
||||
## 4. Adapt (reuse-first) - do not skip
|
||||
|
||||
Installing files is not the end, and redesigning them defeats the point. First note the project's **base** so you write the right API - read `components.json` -> `style` and take the segment before the first `-` (`base-nova` -> Base UI, `radix-nova` -> Radix UI), see [components.md](./components.md). After `add`:
|
||||
|
||||
1. **Read the added files**; keep the composition intact. For a block, verify the components are wired correctly (for `data-grid`: a `useTable({ features: dataGridFeatures, ... })` instance passed as `table`, `recordCount` set - see [components.md](./components.md)).
|
||||
2. **Replace demo data with the user's real data** via typed structures (see [adapting.md](./adapting.md)).
|
||||
3. **Fix icon imports** to the project's icon library (see [icons.md](./icons.md)).
|
||||
4. **Align styling** to semantic tokens and the active theme - no raw colors (see [styling.md](./styling.md)).
|
||||
5. **Validate before finalizing**: if your adaptation introduced components or props you did not read in an `api` or example, run `validate_usage` on them.
|
||||
6. **Hit the craft bar** - clear hierarchy, deliberate density, the empty / loading / error states, subtle motion, and mobile-first responsiveness (see [craft.md](./craft.md)). Generic-looking output means you under-reused the design, not that it needs restyling.
|
||||
7. **Pass the quality gates** (security, a11y, scroll) - call the MCP `get_audit_checklist` tool and clear every item (see [quality.md](./quality.md)).
|
||||
8. **Typecheck / lint**.
|
||||
|
||||
## If no single block fits
|
||||
|
||||
Compose from components (`compose_page` tells you which sections have no block inventory via `unavailableSections`). `search` the components you need, read each `get_component` API, install a worked `get_examples` example for each, and assemble by adapting those examples. A block in the same category is a useful reference - install it and read its files to see how ReUI composes those components, then adapt.
|
||||
@@ -0,0 +1,58 @@
|
||||
# ReUI MCP: full reference
|
||||
|
||||
The ReUI MCP (`https://mcp.reui.io`, Streamable HTTP) is free to use but needs a ReUI account: on first use the agent signs in with ReUI (a free account is created if the user has none), so every request is tied to an account. Free covers components and examples; a Pro or Ultimate license unlocks premium blocks and Motion Icons and removes the daily request limit. It does **discovery + guidance** (search, inline APIs, page planning, validation) and never serves source; the shadcn CLI does **installation**, and the license key lives there (the `@reui` entry in `components.json`, backed by `.env.local`). Goal: from the user's intent to correct, themed, data-wired ReUI code in the **fewest tokens and calls**, with **no guessing**.
|
||||
|
||||
## Golden path (token-optimal - follow this order)
|
||||
|
||||
Most tasks need 2-4 MCP calls and ZERO web fetches:
|
||||
|
||||
1. **`search(query, ...hints)`** -> pick the top 1-3 results. Each result already carries `install`, `previewUrl`, `docsUrl`, `componentsUsed`, `score`, `whyMatch`. The payload is complete - do not call another tool just to "confirm" a result.
|
||||
2. **`get_component([...componentsUsed])`** in ONE batched call (one name or an array of up to 20) -> read each inline `api`. This **replaces** fetching docs pages. Often skippable: search responses carry `componentDigests`, a compact API contract per referenced component.
|
||||
3. **`get_examples(component)`** -> install ONE returned `c-*` example, read the added files, copy the composition.
|
||||
4. **`get_install_command(item)`** only to validate a name you are unsure of (results already include `install`). Run the install with the shadcn CLI (`--yes`).
|
||||
5. **`get_audit_checklist()`** before declaring done.
|
||||
|
||||
If you already know the exact item name, skip `search`. Everything else is situational.
|
||||
|
||||
## The 5 task-specific tools (when to reach for each)
|
||||
|
||||
- **`compose_page`** - BEFORE building any full page (dashboard, settings, billing, landing). Pass the intent (and optionally the sections you want); it returns ordered sections, each with the best premium block for the intent (top pick + alternates). Sections with no real inventory are listed honestly in `unavailableSections` - compose those from components instead of forcing a bad block.
|
||||
- **`search_icons`** - whenever you need icons, especially several. Batch up to 24 concepts in one call; each concept returns its best icons with install commands. Pass `animated: true` to get only icons that have a hover-animated Motion variant.
|
||||
- **`validate_usage`** - BEFORE writing code with component names or props you have not read in an inline `api` or an installed example. It checks planned names + props against the indexed API docs and registry item names; returns did-you-mean suggestions and per-prop documented / notDocumented verdicts. Deterministic, no inference - a notDocumented prop means stop and read the API, not push on.
|
||||
- **`whats_new`** - when your registry knowledge might be stale (a name 404s, the user mentions an item you don't know). Returns items added/removed per build, newest first.
|
||||
- **`report_issue`** - when an installed item is actually broken (bad source, wrong dependency, broken preview). Goes straight to the ReUI team; rate-limited 5/hour. Not for usage questions.
|
||||
|
||||
## All 19 tools
|
||||
|
||||
`search`, `get_block`, `get_example`, `get_icon`, `list_block_groups`, `list_block_categories`, `list_example_categories`, `list_icon_categories`, `list_components`, `get_component`, `get_examples`, `search_icons`, `compose_page`, `validate_usage`, `whats_new`, `report_issue`, `get_install_command`, `get_project_context`, `get_audit_checklist`. The MCP serves the full parameter schemas; do not guess parameters beyond them.
|
||||
|
||||
## Token + speed rules
|
||||
|
||||
- **Batch `get_component`** - ONE call with the whole `componentsUsed` array, never N calls. Skip it entirely when `componentDigests` already answers the question.
|
||||
- **Read source by installing** - the MCP serves no source. To read or analyze an item's real code, install it with the shadcn CLI and open the local files. Learn an API from the inline `api` / `componentDigests`, never by reading raw source.
|
||||
- **Infer `search` hints yourself** (`type`, `component`, `category`, `features`, `free`) - hints shrink the result set and the tokens. Keep `limit` low; one right result beats ten.
|
||||
- Run independent calls (and the shadcn install) concurrently in one turn - serial tool calls are the main source of slowness.
|
||||
- Don't repeat a search for the same intent; don't call `list_*` to "see everything" - `search` is the entry point, `list_*` is only for browsing a taxonomy the user explicitly wants to explore.
|
||||
- Prefer `get_component`'s inline `api` over `docsUrl` / `/llms.txt`. Fetch a web page only as a last resort.
|
||||
|
||||
## Result shapes (so you don't re-fetch)
|
||||
|
||||
- `score` is 0-100 RELATIVE to the top hit (the top is ~100 by construction), not absolute - compare results to each other.
|
||||
- `termCoverage` (0-1) is the share of the query the item matched - low means a weak match even if the score looks high; rephrase or widen the search.
|
||||
- Each result carries `whyMatch`, `install`, docs/preview URLs, and a `free` flag; premium items carry `requiredPlan` (`"pro"` for blocks, `"ultimate"` for icons).
|
||||
- `componentDigests` is a top-level map: a compact API contract per referenced component - often enough to wire an item without a `get_component` call.
|
||||
- Icon results and `get_icon` include `animated: true` and `installAnimated` when a hover-animated Motion variant exists (animated: `@reui/icons/animated/<style>/<name>`; static: `@reui/icons/default/<style>/<name>`).
|
||||
|
||||
## Error playbook
|
||||
|
||||
- **401** - the MCP requires a signed-in ReUI account. The client prompts "Sign in with ReUI" (OAuth) on first use; a free account is created if needed. For headless/CI, pass a personal token (`reui_pat_...`, created at https://reui.io/account/mcp) as `Authorization: Bearer`.
|
||||
- **403 / locked result** - a valid account but the plan does not cover the item: premium blocks need Pro, Motion Icons need Ultimate. Point to https://reui.io/pricing (upgrade). Free accounts still get all components + examples.
|
||||
- **429** - rate limited (120 requests/min per IP); back off, honor `Retry-After`.
|
||||
- **not found** (`found: false`) - use the returned `suggestions`, or `search`; check `whats_new` if you suspect a stale name. Never run a fabricated install command.
|
||||
|
||||
## Fallbacks
|
||||
|
||||
- No ReUI MCP: `npx shadcn@latest search @reui -q "..."` then `add` (generic, no scoring / inline API).
|
||||
- The shadcn project's own MCP also works over the `@reui` registry: https://ui.shadcn.com/docs/mcp.
|
||||
|
||||
Per-agent MCP setup: https://reui.io/docs/mcp
|
||||
@@ -4,33 +4,48 @@
|
||||
|
||||
VPS Tracker — приложение для учёта виртуальных серверов (VPS), провайдеров, аккаунтов, платежей и балансов. Поддерживает синхронизацию с BILLmanager 6 API.
|
||||
|
||||
## Стек
|
||||
|
||||
- **Monorepo:** pnpm workspaces (`apps/*`, `packages/*`)
|
||||
- **Frontend** (`apps/web`): Vite + React 19 + TypeScript, TanStack Router v1 + Query v5, shadcn/ui (`@cfdm/ui`), **ReUI** (`@reui` → `@/components/reui`), Tailwind v4, lucide-react, Recharts (через shadcn Chart), react-hook-form + Zod
|
||||
- **Backend** (`apps/api`): Fastify 5 + TypeScript, `@fastify/*` plugins, контракты через `@cfdm/shared` (Zod)
|
||||
- **DB** (`packages/db`): Drizzle ORM + better-sqlite3 (WAL, `foreign_keys=ON`)
|
||||
- **Тесты:** Vitest (`app.inject()` для backend, `happy-dom` для frontend)
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
vps-tracker/
|
||||
├── server/ # Express backend
|
||||
│ ├── index.js # Точка входа, подключение роутов
|
||||
│ ├── db/ # База данных (SQLite via sql.js)
|
||||
│ │ ├── schema.js # CREATE TABLE
|
||||
│ │ ├── migrations.js # Миграции схемы
|
||||
│ │ ├── seed.js # Начальные данные из public/data
|
||||
│ │ └── index.js # initDb, getDb, saveDb
|
||||
│ ├── adapters/ # Интеграции с внешними API
|
||||
│ │ └── billmanager/ # BILLmanager 6 API
|
||||
│ │ ├── client.js # HTTP-запросы
|
||||
│ │ ├── parsers.js # Парсинг ответов API
|
||||
│ │ ├── mappers.js # Маппинг в модель vps-tracker
|
||||
│ │ ├── operations.js # fetchVds, fetchPayments и т.д.
|
||||
│ │ ├── sync.js # syncFromBillmanager
|
||||
│ │ └── index.js # Barrel export
|
||||
│ ├── routes/ # Express роутеры
|
||||
│ ├── utils/ # row-mappers и др.
|
||||
│ └── sync-scheduler.js # Планировщик синка
|
||||
├── src/ # React frontend (Vite)
|
||||
│ ├── pages/ # Страницы приложения
|
||||
│ ├── components/ # UI-компоненты
|
||||
│ └── lib/ # api.js, utils.js
|
||||
└── public/data/ # JSON для seed (providers, vps, payments...)
|
||||
├── apps/
|
||||
│ ├── web/ # Vite SPA (TSX) — TanStack + shadcn/ui + ReUI
|
||||
│ │ └── src/
|
||||
│ │ ├── routes/ # file-based (TanStack Router)
|
||||
│ │ ├── queries/ # queryOptions + key factories
|
||||
│ │ ├── components/ # shared + layout + domain
|
||||
│ │ │ └── reui/ # ReUI CLI output (@reui/*)
|
||||
│ │ └── lib/ # api-client, queryClient, router, schemas
|
||||
│ └── api/ # Fastify 5 API (TS)
|
||||
│ └── src/
|
||||
│ ├── routes/ # тонкие plugins
|
||||
│ ├── services/ # бизнес-логика + billmanager/
|
||||
│ │ └── billmanager/ # client, parsers, mappers, operations, sync
|
||||
│ └── plugins/ # @fastify/* registration
|
||||
├── packages/
|
||||
│ ├── ui/ # @cfdm/ui — shadcn primitives
|
||||
│ │ └── src/
|
||||
│ │ ├── components/ # output `shadcn add` (не трогать под кейс)
|
||||
│ │ ├── hooks/ # use-mobile и др.
|
||||
│ │ ├── lib/utils.ts # cn()
|
||||
│ │ └── styles/globals.css # только output `shadcn apply --only theme`
|
||||
│ ├── shared/ # @cfdm/shared — Zod-контракты, общие типы
|
||||
│ └── db/ # @cfdm/db — Drizzle schema, repositories, миграции
|
||||
│ └── src/
|
||||
│ ├── schema/ # tables по сущностям
|
||||
│ ├── repositories/ # typed queries
|
||||
│ └── migrations/ # drizzle-kit
|
||||
├── data/ # SQLite база (том Docker, gitignored)
|
||||
├── pnpm-workspace.yaml
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Основные сущности
|
||||
@@ -47,11 +62,14 @@ vps-tracker/
|
||||
|
||||
## Где искать код по доменам
|
||||
|
||||
- **Sync (BILLmanager)** — `server/adapters/billmanager/`, `server/routes/sync.js`, `server/sync-scheduler.js`
|
||||
- **Тарифы** — `server/adapters/billmanager/operations.js` (fetchVdsOrderPricelist), `server/adapters/billmanager/sync.js`
|
||||
- **VPS CRUD** — `server/routes/vps.js`
|
||||
- **Платежи/баланс** — `server/routes/payments.js`, `server/routes/balance-ledger.js`
|
||||
- **Курсы валют** — `src/lib/utils.js` (convertCurrency, formatInBaseCurrency), настройки в settings.ratesUrl
|
||||
- **Sync (BILLmanager)** — `apps/api/src/services/billmanager/`, `apps/api/src/routes/sync.ts`, scheduler в `apps/api/src/services/`
|
||||
- **Тарифы** — `apps/api/src/services/billmanager/operations.ts` (fetchVdsOrderPricelist), `apps/api/src/services/billmanager/sync.ts`
|
||||
- **VPS CRUD** — `apps/api/src/routes/vps.ts`, `packages/db/src/repositories/vps.ts`
|
||||
- **Платежи/баланс** — `apps/api/src/routes/payments.ts`, `apps/api/src/routes/balance-ledger.ts`
|
||||
- **Курсы валют** — `apps/web/src/lib/format.ts` (convertCurrency, formatInBaseCurrency), настройки в `settings.ratesUrl`
|
||||
- **UI shared** — `apps/web/src/components/` + **`reui-kit/`** (`ResourcePage`, `KpiStatGrid`, `QuickActionGrid`, `OpsDashboard`, `SettingsShell`, `DetailPanel`; `FrameDataGrid` — internal simple-grid for ResourcePage)
|
||||
- **UI primitives** — `packages/ui/src/components/*` (только output `shadcn add`)
|
||||
- **ReUI enterprise** — `apps/web/src/components/reui/*` (output `shadcn add @reui/*`); см. [`reui-mcp.mdc`](.cursor/rules/reui-mcp.mdc)
|
||||
|
||||
## BILLmanager API
|
||||
|
||||
@@ -60,3 +78,68 @@ vps-tracker/
|
||||
- [Payments API](https://www.ispsystem.com/docs/b6c/developer-section/billmanager-api/payments-payment)
|
||||
|
||||
Формат запроса: `?authinfo=user:pass&out=bjson&func=vds|payment|dashboard.info|vds.order`
|
||||
|
||||
**Профили хостеров (overrides):** `apps/api/src/services/billmanager/profiles/` — матч по `apiBaseUrl`, новый хостер = один файл overrides + запись в registry. HOWTO: [`profiles/README.md`](apps/api/src/services/billmanager/profiles/README.md).
|
||||
|
||||
## Уведомления
|
||||
|
||||
- **Движок:** `apps/api/src/services/notifications/` — rules, dedup, engine, channels
|
||||
- **Планировщик:** `apps/api/src/services/scheduler.ts` — sync отдельно; notify/uptime не зависят от `syncEnabled`; **CFDM pull** (домены → `vps_domains`) каждые **15 мин**, если включена интеграция + URL + токен (и при старте API)
|
||||
- **События:** `payment_expiry`, `sync_digest`, `low_balance`, `new_tariffs`, `vps_down`, `vps_up`
|
||||
- **Каналы:** Telegram (`telegram.ts`) и webhook (`webhook.ts`); webhook работает без Telegram
|
||||
- **Журнал:** таблица `notification_log`, API `GET /api/notifications/log`
|
||||
- **Дедупликация:** `notification_state` — daily / fingerprint / state_transition
|
||||
|
||||
## Статус блокировок (censorcheck)
|
||||
|
||||
Ручная проверка с VPS: `curl -fsSL https://vt.shnt.top/cc | bash` (тот же контейнер, Traefik dual Host).
|
||||
Раз в сутки: `curl -fsSL https://vt.shnt.top/cc | bash -s -- --daily` (cron, свежий HMAC-токен на каждый запуск).
|
||||
MikroTik 7.22+: `:global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc` (ежедневно: `?daily=1`; все пробы через `src-address` этого IP).
|
||||
|
||||
- **Vendor:** `apps/api/scripts/censorcheck/censorcheck.sh` (pin SHA `12c5839`, MIT)
|
||||
- **Launcher:** `GET /cc` минтит HMAC ingest-токен (TTL 20 мин); по `/etc/os-release` ставит `jq`/`dig`/`column` без prompt; прогресс-бар в stderr; `GET /cc/vendor` — скрипт (LF); `--daily` / `--remove-daily`
|
||||
- **MikroTik:** `GET /cc.rsc` — RouterOS-скрипт (HTTPS GET, без DPI); обязателен `:global vt-srcIp`; `?daily=1` / `?remove=daily`; scheduler `vt-cc`
|
||||
- **Ingest:** `POST /api/integrations/censorcheck/runs` (без portal JWT)
|
||||
- **UI:** `/blocking` — текущие прогоны и история, группировка VPS / сервис
|
||||
- Env: `CENSORCHECK_INGEST_SECRET`, `CENSORCHECK_PUBLIC_URL`, `VPS_LAUNCHER_DOMAIN`
|
||||
|
||||
## GeoIP (ipregion)
|
||||
|
||||
Ручная проверка с VPS: `curl -fsSL https://vt.shnt.top/ic | bash`.
|
||||
Раз в сутки: `curl -fsSL https://vt.shnt.top/ic | bash -s -- --daily`.
|
||||
MikroTik 7.22+: `:global "vt-srcIp" "1.2.3.4"; /tool fetch url="https://vt.shnt.top/ic.rsc" dst-path=vt-ic.rsc; /import file-name=vt-ic.rsc` (`?daily=1`; пробы через тот же `vt-srcIp`).
|
||||
|
||||
- **Vendor:** `apps/api/scripts/ipregion/ipregion.sh` (pin SHA `7d1c25c`, MIT, [vernette/ipregion](https://github.com/vernette/ipregion))
|
||||
- **Launcher:** `GET /ic` минтит HMAC ingest-токен (тот же `CENSORCHECK_INGEST_SECRET`); `GET /ic/vendor` — pinned скрипт (LF); `--daily` / `--remove-daily`
|
||||
- **MikroTik:** `GET /ic.rsc` — полный список vernette/ipregion (primary + custom + cdn, как Linux); обязателен `:global vt-srcIp`; scheduler `vt-ic`
|
||||
- **Ingest:** `POST /api/integrations/ipregion/runs` (без portal JWT)
|
||||
- **UI:** `/geo` — матрица ISO-стран VPS × сервисы (primary / custom / cdn)
|
||||
|
||||
## Команды
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm --filter web dev # frontend dev
|
||||
pnpm --filter api dev # backend dev
|
||||
pnpm --filter web build # production build frontend
|
||||
pnpm --filter api test # Vitest backend
|
||||
pnpm --filter web test # Vitest frontend
|
||||
```
|
||||
|
||||
## ReUI
|
||||
|
||||
- Registry: `@reui` в [`apps/web/components.json`](apps/web/components.json)
|
||||
- **Design contract:** [`docs/ui-design-contract.md`](docs/ui-design-contract.md) — surface `frame`, kit `reui-kit/`
|
||||
- Docs: [Introduction](https://reui.io/docs) · [llms.txt](https://reui.io/llms.txt) · [Get Started](https://reui.io/docs/get-started) · [MCP](https://reui.io/docs/mcp) · [Agent Skills](https://reui.io/docs/agent-skills) · [Cursor](https://reui.io/docs/cursor)
|
||||
- Skill (локально): `.claude/skills/reui` / `.cursor/skills/reui` — версия `668fb463eb` (20 free components). Обновление: `curl.exe -fsSL https://mcp.reui.io/install | node -`
|
||||
- Установка: `cd apps/web && pnpm dlx shadcn@latest add @reui/<name>`
|
||||
- Правила: [`reui-mcp.mdc`](.cursor/rules/reui-mcp.mdc), [`shadcn-mcp.mdc`](.cursor/rules/shadcn-mcp.mdc)
|
||||
- Зависимости ReUI в `apps/web`: `@tanstack/react-table`, `@tanstack/react-virtual`, `@dnd-kit/*`, `date-fns`, `react-day-picker`
|
||||
|
||||
## Соглашения
|
||||
|
||||
- UI — [ReUI PRO](https://reui.io/docs/get-started) first (MCP `user-reui`) + [shadcn/ui](https://ui.shadcn.com) primitives (MCP `plugin-shadcn-shadcn`)
|
||||
- Design contract — [`docs/ui-design-contract.md`](docs/ui-design-contract.md) (Frame, hybrid KPI, QuickActionGrid, NavUser chrome)
|
||||
- Коммиты — на русском, см. [`commit-messages-ru.mdc`](.cursor/rules/commit-messages-ru.mdc)
|
||||
- Gitflow — см. [`gitflow.mdc`](.cursor/rules/gitflow.mdc)
|
||||
- Структура — см. [`project-structure.mdc`](.cursor/rules/project-structure.mdc)
|
||||
+37
-25
@@ -1,35 +1,47 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Stage 1: Сборка фронтенда
|
||||
############################
|
||||
# Stage 1: build web + api + prune to prod deps
|
||||
############################
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm npm ci
|
||||
COPY index.html vite.config.js ./
|
||||
COPY public ./public
|
||||
COPY src ./src
|
||||
RUN npm run build
|
||||
RUN corepack enable
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.base.json ./
|
||||
COPY apps/web/package.json ./apps/web/
|
||||
COPY apps/api/package.json ./apps/api/
|
||||
COPY packages/ui/package.json ./packages/ui/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
COPY packages/db/package.json ./packages/db/
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm pnpm install --frozen-lockfile
|
||||
|
||||
# Stage 2: Production dependencies + очистка node_modules
|
||||
FROM node:22-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev && \
|
||||
find node_modules -type f \( \
|
||||
-name '*.md' -o -name '*.ts' -o -name '*.map' -o \
|
||||
-name 'LICENSE*' -o -name 'CHANGELOG*' -o -name 'README*' -o \
|
||||
-name '.npmignore' -o -name '.eslintrc*' -o -name '.travis.yml' -o \
|
||||
-name 'Makefile' -o -name '*.gyp' -o -name '*.gypi' \
|
||||
\) -delete && \
|
||||
find node_modules -type d -empty -delete
|
||||
COPY apps ./apps
|
||||
COPY packages ./packages
|
||||
RUN pnpm --filter @cfdm/shared build \
|
||||
&& pnpm --filter @cfdm/db build \
|
||||
&& pnpm --filter web build \
|
||||
&& pnpm --filter api build \
|
||||
&& pnpm prune --prod \
|
||||
&& rm -rf apps/web/src apps/api/src packages/db/src packages/shared/src \
|
||||
apps/web/node_modules/.vite-temp \
|
||||
apps/web/tsconfig.json apps/api/tsconfig.json \
|
||||
&& find node_modules apps/*/node_modules packages/*/node_modules -type f \( \
|
||||
-name '*.md' -o -name '*.map' -o -name '*.ts' -o -name 'LICENSE*' -o -name 'CHANGELOG*' \
|
||||
-o -name 'README*' -o -name '.npmignore' -o -name '.eslintrc*' -o -name '.travis.yml' \
|
||||
-o -name 'Makefile' -o -name '*.gyp' -o -name '*.gypi' \
|
||||
\) -delete 2>/dev/null || true \
|
||||
&& find node_modules apps/*/node_modules packages/*/node_modules -type d -empty -delete 2>/dev/null || true
|
||||
|
||||
# Stage 3: Минимальный runtime (без npm/yarn/corepack)
|
||||
############################
|
||||
# Stage 2: minimal runtime (alpine + nodejs only)
|
||||
############################
|
||||
FROM alpine:3.21
|
||||
RUN apk add --no-cache nodejs
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY server ./server
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3001 \
|
||||
RUNTIME=fastify
|
||||
# Single layer for app code + pruned prod node_modules (symlinks resolved by COPY)
|
||||
COPY --from=build /app ./
|
||||
EXPOSE 3001
|
||||
CMD ["node", "server/index.js"]
|
||||
# RUNTIME=express — legacy fallback (apps/api/index.js)
|
||||
CMD ["sh", "-c", "if [ \"$RUNTIME\" = \"express\" ]; then node apps/api/index.js; else node apps/api/dist/index.js; fi"]
|
||||
@@ -1,75 +1,60 @@
|
||||
# VPS Tracker
|
||||
|
||||
**VPS Tracker** — это веб-приложение для учёта виртуальных серверов, хостинг-провайдеров, счетов и балансов. Всё работает в браузере: данные хранятся у вас на компьютере или сервере, можно подключить синхронизацию с BILLmanager.
|
||||
**VPS Tracker** — self-hosted учёт виртуальных серверов: где они стоят, сколько стоят, когда продлевать, сколько денег осталось у хостера и доступны ли с них нужные сервисы. Данные синхронизируются с биллингом хостеров (BILLmanager и др.), хранятся в одном SQLite-файле у вас на сервере, интерфейс работает в браузере и на телефоне.
|
||||
|
||||
Ниже описано, как **запустить готовую программу в Linux** с помощью Docker. Отдельно устанавливать Node.js не нужно.
|
||||

|
||||
|
||||
> Все скриншоты сделаны на демо-данных: хостеры вымышленные, IP из документационных диапазонов (RFC 5737). Как пересоздать — [docs/screenshots/README.md](docs/screenshots/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Содержание
|
||||
## Главное
|
||||
|
||||
1. [Что понадобится](#req)
|
||||
2. [Самый простой способ: готовый образ](#quick)
|
||||
3. [Запуск через Docker Compose](#compose)
|
||||
4. [Сборка образа у себя](#build-local)
|
||||
5. [Сохранение данных и резервная копия](#data)
|
||||
6. [Другой порт и доступ по сети](#port)
|
||||
7. [Обновление программы](#update)
|
||||
8. [Для разработчиков](#dev)
|
||||
| | |
|
||||
|---|---|
|
||||
| 🖥️ **Инвентарь VPS** | IP/IPv6, DNS, локация с флагом, ресурсы, ОС, проект, окружение, статус, произвольные поля, массовые действия |
|
||||
| 🔄 **Синхронизация с хостерами** | BILLmanager 6, 4VPS, Маклауд, VDSina, Veesp, RuVDS: серверы, платежи, баланс, тарифы — вручную или по расписанию |
|
||||
| 💰 **Деньги под контролем** | Платежи, журнал баланса, расход в месяц, «запас дней» до нуля, календарь продлений, отчёты и CSV-экспорт; мультивалютность с курсами ЦБ |
|
||||
| 🩺 **Здоровье инвентаря** | Uptime-проверки по TCP, истекающая оплата, низкий баланс, расхождения API и журнала — всё на дашборде |
|
||||
| 🌍 **Блокировки и GeoIP** | Проверка доступности сервисов (YouTube, ChatGPT, Discord…) и GeoIP-страны прямо с VPS или MikroTik одной командой |
|
||||
| 🗺️ **Схема инфраструктуры** | Визуальный редактор: VPS, связи (сеть, туннель, VPN, зависимость), группы и сервисы CFDM |
|
||||
| 🔔 **Уведомления** | Telegram (в т.ч. топики и свой Bot API) и webhook: оплата, низкий баланс, дайджест синка, новые тарифы, VPS упал/поднялся |
|
||||
| 👥 **Команда и пространства** | SSO через auth-portal (JWT), личные и общие пространства с ролями, шаринг VPS между пространствами, журнал изменений |
|
||||
| 💾 **Бэкапы** | Выгрузка/загрузка JSON или файла БД из интерфейса; данные — один файл `vps-tracker.db` |
|
||||
|
||||
Подробно по каждому разделу — **[docs/features.md](docs/features.md)**. Переменные окружения, роли и права — **[docs/configuration.md](docs/configuration.md)**.
|
||||
|
||||
---
|
||||
|
||||
<span id="req"></span>
|
||||
## Скриншоты
|
||||
|
||||
## Что понадобится
|
||||
| | |
|
||||
|---|---|
|
||||
|  |  |
|
||||
| **VPS** — инвентарь с фильтрами, видами и массовыми действиями | **Карточка VPS** — сеть, ресурсы, финансы, заметки, синк |
|
||||
|  |  |
|
||||
| **Схема** — редактор топологии с узлами VPS и типами связей | **Блокировки** — доступность сервисов с каждого VPS |
|
||||
|  |  |
|
||||
| **GeoIP** — какую страну видят GeoIP-базы и сервисы | **Аккаунты** — баланс по API, готовность к синку, пороги |
|
||||
|  |  |
|
||||
| **Платежи** — пополнения и прямые оплаты, итоги по валютам | **Отчёты** — расходы по проектам и хостерам, CSV |
|
||||
|  |  |
|
||||
| **Продления** — календарь ближайших оплат | **Уведомления** — Telegram и webhook |
|
||||
|
||||
- Компьютер или сервер с **Linux** (подойдут Ubuntu, Debian, Fedora и другие дистрибутивы).
|
||||
- Установленные **Docker** и плагин **Compose**. Официальные инструкции: [Установка Docker Engine](https://docs.docker.com/engine/install/) и обзор [Compose](https://docs.docker.com/compose/).
|
||||
|
||||
Проверка в терминале:
|
||||
|
||||
```bash
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/mobile-dashboard.png" width="260" alt="Дашборд на телефоне">
|
||||
|
||||
<img src="docs/screenshots/mobile-vps.png" width="260" alt="Список VPS на телефоне">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<span id="quick"></span>
|
||||
## Быстрый старт (Docker)
|
||||
|
||||
## Самый простой способ: готовый образ
|
||||
|
||||
Подойдёт, если у вас есть только терминал и вы хотите быстро поднять сервис.
|
||||
|
||||
### Шаг 1. Скачать образ
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/denozordec/vps-tracker:latest
|
||||
```
|
||||
|
||||
> **Если запросили логин:** пакет на GitHub может быть закрытым. Войдите в аккаунт GitHub и создайте [Personal Access Token](https://github.com/settings/tokens) с правом `read:packages`, затем выполните:
|
||||
>
|
||||
> ```bash
|
||||
> echo ВАШ_ТОКЕН | docker login ghcr.io -u ВАШ_ЛОГИН_GITHUB --password-stdin
|
||||
> ```
|
||||
>
|
||||
> Либо откройте на GitHub страницу пакета **vps-tracker** → **Package settings** и сделайте пакет **public** — тогда `docker pull` без входа.
|
||||
|
||||
### Шаг 2. Папка для данных
|
||||
|
||||
Все записи приложения лежат в одном файле базы. Его нужно хранить **вне** контейнера, иначе при удалении контейнера данные пропадут.
|
||||
Нужны Linux-сервер (или любая машина) с [Docker](https://docs.docker.com/engine/install/). Node.js ставить не нужно.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/vps-tracker-data
|
||||
```
|
||||
|
||||
Можно выбрать любой каталог вместо `~/vps-tracker-data` — главное, запомнить путь для команды ниже.
|
||||
|
||||
### Шаг 3. Запуск
|
||||
|
||||
**Вариант А — в фоне** (удобно на сервере, контейнер переживёт закрытие терминала):
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name vps-tracker \
|
||||
--restart unless-stopped \
|
||||
@@ -78,42 +63,24 @@ docker run -d \
|
||||
ghcr.io/denozordec/vps-tracker:latest
|
||||
```
|
||||
|
||||
**Вариант Б — в переднем плане** (логи видны в терминале, остановка — `Ctrl+C`):
|
||||
Откройте **http://localhost:3001** (или `http://IP_СЕРВЕРА:3001`). База появится в `~/vps-tracker-data/vps-tracker.db`.
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-p 3001:3001 \
|
||||
-v ~/vps-tracker-data:/app/data \
|
||||
ghcr.io/denozordec/vps-tracker:latest
|
||||
```
|
||||
> **`docker pull` просит логин?** Пакет на GitHub может быть закрытым: войдите через [Personal Access Token](https://github.com/settings/tokens) с правом `read:packages` — `echo ТОКЕН | docker login ghcr.io -u ЛОГИН --password-stdin` — или сделайте пакет public в его настройках.
|
||||
|
||||
### Шаг 4. Открыть в браузере
|
||||
Первые шаги в интерфейсе:
|
||||
|
||||
На той же машине откройте:
|
||||
|
||||
**http://localhost:3001**
|
||||
|
||||
Если заходите с другого компьютера в сети — подставьте IP сервера: `http://192.168.1.10:3001` (ваш адрес может отличаться). На сервере может понадобиться открыть порт в брандмауэре, например для Ubuntu с UFW:
|
||||
|
||||
```bash
|
||||
sudo ufw allow 3001/tcp
|
||||
sudo ufw reload
|
||||
```
|
||||
|
||||
Остановка фонового контейнера:
|
||||
|
||||
```bash
|
||||
docker stop vps-tracker
|
||||
docker rm vps-tracker
|
||||
```
|
||||
1. **Хостеры** → добавьте хостера; для синка укажите тип API и URL биллинга.
|
||||
2. **Аккаунты хостеров** → добавьте аккаунт с логином/токеном API и нажмите «Синхронизировать» — серверы, платежи и баланс подтянутся сами.
|
||||
3. **Настройки → Общие** → базовая валюта и URL курсов (например `https://www.cbr-xml-daily.ru/daily_json.js`).
|
||||
4. **Настройки → Синхронизация / Уведомления** → автосинк по расписанию и Telegram.
|
||||
|
||||
---
|
||||
|
||||
<span id="compose"></span>
|
||||
## Варианты установки
|
||||
|
||||
## Запуск через Docker Compose
|
||||
### Docker Compose
|
||||
|
||||
Имеет смысл, если вы **склонировали этот репозиторий**: в [docker-compose.yml](docker-compose.yml) указан готовый образ **ghcr.io/denozordec/vps-tracker** — при `up` он подтягивается с GitHub Container Registry (локальная сборка не нужна).
|
||||
В [docker-compose.yml](docker-compose.yml) уже указан образ с GHCR, данные — в `./data`:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/denozordec/vps-tracker.git
|
||||
@@ -121,116 +88,79 @@ cd vps-tracker
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
**То же через Docker CLI** (без Compose) — эквивалент [docker-compose.yml](docker-compose.yml): том `./data`, порт **3001**, переменная **PORT=3001**.
|
||||
### Production: Traefik + HTTPS (Cloudflare DNS-01)
|
||||
|
||||
Готовый compose с Traefik и Let's Encrypt, без публикации порта `3001` наружу, плюс короткий домен для launcher-скриптов блокировок/GeoIP:
|
||||
**[docs/deploy-traefik.md](docs/deploy-traefik.md)** · [`deploy/docker-compose.traefik.yml`](deploy/docker-compose.traefik.yml) · [`deploy/env.traefik.example`](deploy/env.traefik.example)
|
||||
|
||||
```bash
|
||||
mkdir -p ./data
|
||||
docker pull ghcr.io/denozordec/vps-tracker:latest
|
||||
docker run -d \
|
||||
--name vps-tracker \
|
||||
--restart unless-stopped \
|
||||
-p 3001:3001 \
|
||||
-e PORT=3001 \
|
||||
-v "$(pwd)/data:/app/data" \
|
||||
ghcr.io/denozordec/vps-tracker:latest
|
||||
mkdir -p /opt/vps-tracker/data && cd /opt/vps-tracker
|
||||
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/denozordec/vps-tracker/main/deploy/docker-compose.traefik.yml
|
||||
curl -fsSL -o .env https://raw.githubusercontent.com/denozordec/vps-tracker/main/deploy/env.traefik.example
|
||||
nano .env # CF_DNS_API_TOKEN, LETSENCRYPT_EMAIL, VPS_DOMAIN
|
||||
docker compose pull && docker compose up -d
|
||||
curl -fsS https://ВАШ_ДОМЕН/health
|
||||
```
|
||||
|
||||
В PowerShell из каталога проекта подставьте путь к данным так: `-v "${PWD}/data:/app/data"`.
|
||||
|
||||
Данные по умолчанию сохраняются в папку `data` внутри каталога проекта на диске.
|
||||
|
||||
Остановка:
|
||||
### Свой образ из исходников
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<span id="build-local"></span>
|
||||
|
||||
## Сборка образа у себя
|
||||
|
||||
Если нужен свой образ из исходников (после `git clone`):
|
||||
|
||||
```bash
|
||||
cd vps-tracker
|
||||
docker build -t vps-tracker:local .
|
||||
mkdir -p ./data
|
||||
docker run -d \
|
||||
--name vps-tracker \
|
||||
--restart unless-stopped \
|
||||
-p 3001:3001 \
|
||||
-v "$(pwd)/data:/app/data" \
|
||||
vps-tracker:local
|
||||
docker run -d --name vps-tracker --restart unless-stopped \
|
||||
-p 3001:3001 -v "$(pwd)/data:/app/data" vps-tracker:local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<span id="data"></span>
|
||||
## Данные, бэкап и обновление
|
||||
|
||||
## Сохранение данных и резервная копия
|
||||
- **Всё хранится в одном файле** `vps-tracker.db` (SQLite) в смонтированной папке (`/app/data` в контейнере). Скопировать его — и есть бэкап.
|
||||
- **Из интерфейса:** *Настройки → Общие* — выгрузка/загрузка JSON-бэкапа и файла БД (при включённой авторизации — только глобальный админ).
|
||||
- **Старые базы** обновляются автоматически при старте: недостающие колонки и таблицы добавляются, данные не теряются.
|
||||
- **Журналы чистятся сами:** уведомления и журнал синка старше 90 дней, uptime-проверки старше 30 дней.
|
||||
|
||||
Файл базы в примерах выше лежит на хосте:
|
||||
|
||||
| Способ запуска | Где лежит база на диске |
|
||||
|----------------|-------------------------|
|
||||
| `docker run` с `-v ~/vps-tracker-data:/app/data` | `~/vps-tracker-data/vps-tracker.db` |
|
||||
| Docker Compose из репозитория | `./data/vps-tracker.db` в папке проекта |
|
||||
|
||||
Скопируйте файл `vps-tracker.db` в безопасное место — это и есть резервная копия. Пока приложение не запущено, можно просто скопировать файл обратно для восстановления.
|
||||
|
||||
---
|
||||
|
||||
<span id="port"></span>
|
||||
|
||||
## Другой порт и доступ по сети
|
||||
|
||||
Внутри контейнера приложение слушает порт **3001**. Сменить внешний порт (например, **8080** снаружи, 3001 внутри):
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name vps-tracker \
|
||||
--restart unless-stopped \
|
||||
-p 8080:3001 \
|
||||
-v ~/vps-tracker-data:/app/data \
|
||||
ghcr.io/denozordec/vps-tracker:latest
|
||||
```
|
||||
|
||||
Тогда в браузере: **http://localhost:8080**
|
||||
|
||||
Чтобы сменить и внутренний порт (редко нужно), добавьте `-e PORT=8080` и согласуйте с `-p`, например `-p 8080:8080` и `-e PORT=8080`.
|
||||
|
||||
---
|
||||
|
||||
<span id="update"></span>
|
||||
|
||||
## Обновление программы
|
||||
|
||||
Если используете образ с GitHub:
|
||||
Обновление:
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/denozordec/vps-tracker:latest
|
||||
docker stop vps-tracker
|
||||
docker rm vps-tracker
|
||||
docker stop vps-tracker && docker rm vps-tracker
|
||||
# и снова docker run из «Быстрого старта» с тем же -v …:/app/data
|
||||
```
|
||||
|
||||
Затем снова выполните команду `docker run` из раздела [«Самый простой способ»](#quick) (том `-v ...:/app/data` оставьте тот же — данные сохранятся).
|
||||
С Compose: `git pull && docker compose up -d` (`pull_policy: always` подтянет свежий образ).
|
||||
|
||||
При Docker Compose из репозитория:
|
||||
Версии: каждый релиз — git-тег `vX.Y.Z` и образ `ghcr.io/denozordec/vps-tracker:X.Y.Z` (и `:X.Y`); `latest` — текущий `main`. Чтобы не обновляться неожиданно, закрепите версию: `ghcr.io/denozordec/vps-tracker:1.0.0`. Текущая версия видна в меню пользователя и в ответе `GET /health`.
|
||||
|
||||
```bash
|
||||
cd vps-tracker
|
||||
git pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Файл Compose настроен с `pull_policy: always`, поэтому при каждом `up` запрашивается актуальный образ с GHCR.
|
||||
Другой порт снаружи: `-p 8080:3001` → `http://localhost:8080`. Сменить порт внутри контейнера: `-e PORT=8080 -p 8080:8080`.
|
||||
|
||||
---
|
||||
|
||||
<span id="dev"></span>
|
||||
## Документация
|
||||
|
||||
| Документ | О чём |
|
||||
|---|---|
|
||||
| [docs/features.md](docs/features.md) | Все разделы интерфейса и как ими пользоваться |
|
||||
| [docs/configuration.md](docs/configuration.md) | Переменные окружения, авторизация, роли, синк, уведомления, планировщик |
|
||||
| [docs/deploy-traefik.md](docs/deploy-traefik.md) | Production за Traefik с HTTPS |
|
||||
| [docs/screenshots/README.md](docs/screenshots/README.md) | Демо-данные и пересъёмка скриншотов |
|
||||
| [apps/api/src/services/billmanager/profiles/README.md](apps/api/src/services/billmanager/profiles/README.md) | Как добавить профиль нового BILLmanager-хостера |
|
||||
| [AGENTS.md](AGENTS.md) | Архитектура и карта кода (для разработчиков и ИИ-агентов) |
|
||||
|
||||
---
|
||||
|
||||
## Для разработчиков
|
||||
|
||||
Локальный запуск без Docker: `npm install` и `npm run dev`. Подробности по коду, API и синхронизации — в [AGENTS.md](AGENTS.md).
|
||||
Monorepo на **pnpm**: `apps/web` (Vite + React 19 + TanStack Router/Query + shadcn/ui + ReUI), `apps/api` (Fastify 5), `packages/db` (Drizzle + better-sqlite3), `packages/shared` (Zod-контракты).
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm --filter api dev # API на :3001 (база — apps/api/data/vps-tracker.db или DB_PATH)
|
||||
pnpm --filter web dev # фронтенд с hot reload
|
||||
pnpm --filter api test # тесты API (Vitest)
|
||||
pnpm --filter web test # тесты фронтенда
|
||||
pnpm --filter web build # production-сборка фронтенда
|
||||
```
|
||||
|
||||
Демо-данные для разработки: `cd apps/api && DB_PATH=../../data/demo.db VITEST=1 npx tsx scripts/seed-demo.ts --serve` → http://127.0.0.1:3101.
|
||||
|
||||
Подробнее об устройстве кода — [AGENTS.md](AGENTS.md).
|
||||
+945
@@ -0,0 +1,945 @@
|
||||
# UX/UI Unification Plan
|
||||
|
||||
Дата аудита: 2026-08-17
|
||||
Implementation: **DONE 2026-08-17** — CHANGE-001…034, Phases 1–6, Acceptance §19.
|
||||
Уровень унификации (решение пользователя): **система + идентичный chrome во всех 6 приложениях**; доменные экраны **не** копируются.
|
||||
|
||||
Источники истины REUI PRO: [reui.io/docs](https://reui.io/docs) · [reui.io/llms.txt](https://reui.io/llms.txt) · MCP `user-reui` (`get_block` / `get_component` / `get_example` / `compose_page`). Skill ReUI: `668fb463eb` (локально и MCP совпадают). Версию REUI PRO **не** указываем — её нет.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
Workspace — 6 fullstack UI-приложений. Пять Vite SPA уже lock `surface: frame` + `reui-kit`. MikrotikManager — Next.js 16 App Router со SoT `/servers` на shadcn `Card` (`DataPageCard`). Это **две дизайн-системы** в одном workspace.
|
||||
|
||||
Целевое состояние:
|
||||
|
||||
- **Одинаковый chrome 1:1** во всех шести (включая Auth Portal и MikrotikManager): sidebar 240px, App Switcher, header AppsMenu → SystemMonitor, footer NavUser + segmented theme.
|
||||
- **Одинаковый рецепт** ops-экранов: Frame, DataGrid+Filters, KPI = stats-12 + IconTile `elevated`, Quick Actions = Frame tiles, settings-3/16, Sheet для create/edit, Frame-friendly empty.
|
||||
- **Разный домен:** topology VPS, DNS/kanban CFDM, BIRD/lookup EvoBGP, IdP Auth Portal, agents EvoFirewall, `/servers`/terminal MikrotikManager.
|
||||
- **Не менять:** Vite vs Next, package scopes, API/SSO/OIDC, CRUD-поведение.
|
||||
|
||||
Главные gaps: Auth Portal без SystemMonitor; MikrotikManager без AppsMenu/NavUser/Switcher и с Sun/Moon toggle; ни один проект не установил `icon-tile`; VPS Card-shells на detail/charts; MM `DataPageCard` = Card.
|
||||
|
||||
---
|
||||
|
||||
## 2. Workspace Coverage
|
||||
|
||||
`Discovered projects: 6`
|
||||
`Audited projects: 6`
|
||||
`Projects with plan: 6`
|
||||
`Coverage: 100%`
|
||||
`Implementation: 34/34 CHANGE DONE (2026-08-17)`
|
||||
|
||||
| # | Project | Path | Type | Audited | Changes | Status |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | VPS Tracker | `c:\Users\shats\Dev\vps-tracker` | fullstack Vite SPA | YES | CHANGE-003, 004, 005, 009–014, 025, 029 | Implemented |
|
||||
| 2 | Cloudflare Domain Manager | `c:\Users\shats\Dev\cloudflare-domain-manager` | fullstack Vite SPA | YES | CHANGE-003, 004, 005, 006, 024 | Implemented |
|
||||
| 3 | EvoBGP | `c:\Users\shats\Dev\EvoBGP` | fullstack Go API + Vite SPA | YES | CHANGE-003, 004, 005, 015–017, 019 | Implemented |
|
||||
| 4 | Auth Portal | `c:\Users\shats\Dev\auth-portal` | fullstack Vite SPA | YES | CHANGE-001, 004, 005, 021–023 | Implemented |
|
||||
| 5 | EvoFirewall | `c:\Users\shats\Dev\EvoFirewall` | fullstack Vite SPA | YES | CHANGE-003, 004, 005, 007, 020 | Implemented |
|
||||
| 6 | MikrotikManager | `c:\Users\shats\Dev\MikrotikManager-3` | fullstack Next.js 16 | YES | CHANGE-002, 004, 008, 018, 027, 032–034 | Implemented |
|
||||
|
||||
Backend-only проектов нет. Shared UX/UI rules: CHANGE-026, 028, 030, 031, RULE-CHANGE-001…010.
|
||||
|
||||
---
|
||||
|
||||
## 3. Workspace Inventory
|
||||
|
||||
| Project | Framework | Language | UI | CSS | Theme | Package UI | Kit |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| VPS Tracker | Vite + TanStack Router/Query | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@cfdm/ui` | `reui-kit` (9 files) |
|
||||
| CFDM | Vite + TanStack | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@cfdm/ui` | `reui-kit` (8 files) |
|
||||
| EvoBGP | Vite + TanStack; API Go | TS / Go | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@evobgp/ui` | `reui-kit` (7 files) — **KPI SoT** |
|
||||
| Auth Portal | Vite + TanStack | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@authportal/ui` | `reui-kit` (11 files; `ResourcePage` stub) |
|
||||
| EvoFirewall | Vite + TanStack | TS | shadcn base-nova + ReUI | Tailwind v4 | next-themes | `@evofw/ui` | `reui-kit` (6 files) |
|
||||
| MikrotikManager | Next.js 16 App Router | TS | shadcn + **partial** ReUI | Tailwind v4 | custom `theme-provider` (`rl-theme`) | `@/components/ui` | **нет** `reui-kit` |
|
||||
|
||||
TanStack Table во всех web-приложениях: `^8.21.x`. ReUI Data Grid docs (MCP `get_component`): TanStack Table **v9**. Это technical risk — см. §18. Не мигрировать v8→v9 в этом плане.
|
||||
|
||||
`icon-tile`: **не установлен ни в одном** из 6 (поиск `IconTile` / `icon-tile` = 0 файлов).
|
||||
|
||||
---
|
||||
|
||||
## 4. MCP / Skills / Plugins Inventory
|
||||
|
||||
| Tool | Status | Use in audit |
|
||||
|---|---|---|
|
||||
| MCP `user-reui` | ready | `get_block`, `get_component`, `get_example`, `get_agent_skill` — официальные `docsUrl`/`previewUrl` |
|
||||
| MCP `plugin-shadcn-shadcn` | listed | primitives `@shadcn` (secondary) |
|
||||
| MCP codegraph (vps / CFDM / EvoBGP) | available | не заменял filesystem inventory |
|
||||
| Skill `.claude/skills/reui` | `668fb463eb` | совпадает с MCP `get_agent_skill` |
|
||||
| Skill shadcn (plugin + CFDM/EvoFirewall) | present | CLI/registry, не SoT для PRO blocks |
|
||||
| Context7 MCP | available | не нужен для REUI PRO URLs |
|
||||
|
||||
Приоритет: официальная документация REUI PRO > MCP metadata > project rules.
|
||||
|
||||
---
|
||||
|
||||
## 5. Project Rules Inventory
|
||||
|
||||
| Source | Project | Category | Used? |
|
||||
|---|---|---|---|
|
||||
| `docs/ui-design-contract.md` | VPS, CFDM, EvoBGP, Auth, FW | UX/UI contract (Frame, chrome, kit) | YES — baseline |
|
||||
| `.cursor/rules/reui-mcp.mdc` | 5 Vite | ReUI MCP workflow | YES |
|
||||
| `.cursor/rules/kpi-hybrid.mdc` | 5 Vite | KPI Item `size-10.5` DNA | YES — **устарело vs IconTile** |
|
||||
| `.cursor/rules/frontend-shadcn.mdc` / `frontend-ui-patterns.mdc` | VPS, CFDM, Auth, FW | patterns | YES; VPS lists `kanban-board.tsx`, которого нет на диске |
|
||||
| `.cursor/rules/web-shadcn.mdc` | EvoBGP | WEB-01…WEB-22 | YES |
|
||||
| `AGENTS.md` | VPS, EvoBGP, Auth, FW, MM | agent guide | YES; **CFDM: `<not_found>`** |
|
||||
| `.cursor/rules/next-shadcn-production.mdc` | MM | SoT = `/servers` Card | YES — **противоречит унификации** |
|
||||
| `.cursor/rules/ma-ui-guardian.mdc` | MM | UI Guardian = `/servers` | YES — **противоречит** |
|
||||
| Workspace `reui-pro-senior-frontend.mdc` | all | chrome + Frame lock | YES; Auth ids без `dns` |
|
||||
|
||||
Technical rules (Fastify/Drizzle/Go/Next App Router/CI) — **не менять** ради UI.
|
||||
|
||||
---
|
||||
|
||||
## 6. Project UX/UI Rules Audit
|
||||
|
||||
| Rule | Source | Current Usage | Problem | Recommendation | Status |
|
||||
|---|---|---|---|---|---|
|
||||
| Surface `frame` | ui-design-contract | 5 Vite mostly; MM Card | MM + VPS mixed Card | TARGET-001 | MODIFY (enforce all 6) |
|
||||
| Chrome 240px / AppsMenu / NavUser | contract | 4 Vite close; Auth no monitor; MM none | Identity drift | TARGET-006 | NEW (1:1 all 6) |
|
||||
| KPI hybrid Item tile | kpi-hybrid.mdc | 5 Vite kit | Official primitive IconTile unused | TARGET-003 | REPLACE |
|
||||
| Lists DataGrid+Filters | contract | Vite ResourcePage; MM DataPageCard | MM Card; VPS `spaces` Table | TARGET-002 | MODIFY |
|
||||
| Settings settings-16 / settings-3 | contract | CFDM closest | settings-7 is `surface: card` | TARGET-005 | MODIFY |
|
||||
| Empty empty-state-12 | contract | mixed | empty-state-11 is Card; prefer 14 | TARGET-008 | MODIFY |
|
||||
| MM SoT `/servers` Card | next-shadcn-production | enforced | Blocks Frame unification | RULE-CHANGE-001 | REPLACE |
|
||||
| App Switcher ids | Auth contract vs `APP_IDS` | code has `dns` | Contract omits `dns` | CHANGE-023 | MODIFY |
|
||||
| Installed ReUI lists | reui-mcp.mdc | claims kanban in VPS | File missing | CHANGE-025 | MODIFY |
|
||||
| CFDM AGENTS.md / ui-surface.ts | contract mentions | `<not_found>` | Rules drift | CHANGE-024 | NEW |
|
||||
|
||||
---
|
||||
|
||||
## Chrome Identity Spec (обязательный 1:1 — все 6)
|
||||
|
||||
Эталон: [app-shell-12](https://reui.io/preview/base/app-shell-12) + [app-shell-7](https://reui.io/preview/base/app-shell-7) (SystemMonitor) + [app-shell-1](https://reui.io/preview/base/app-shell-1) (NavUser).
|
||||
|
||||
Применяется к **каждому** приложению, включая Auth Portal `_auth` shell и MikrotikManager `(main)` layout. Login Auth Portal **без** AppShell — отдельный auth surface ([auth-18](https://reui.io/preview/base/auth-18)).
|
||||
|
||||
| Зона | Обязательное значение | Сейчас VPS/CFDM/BGP/FW | Auth Portal | MikrotikManager |
|
||||
|---|---|---|---|---|
|
||||
| `--sidebar-width` | `240px` | `240px` (`packages/ui/.../sidebar.tsx`) | 240px (ожидаемо, тот же паттерн) | **`16rem`** (`components/ui/sidebar.tsx`) |
|
||||
| Sidebar header | App Switcher ids `cfdm` · `vps` · `bgp` · `fw` · `dns` | есть | есть | **нет** |
|
||||
| Sidebar footer | NavUser + segmented theme (light/dark/system), без Sun/Moon icon | NavUser segmented | NavUser segmented | **hand-roll** avatar + Sun/Moon (`app-sidebar.tsx` ~239–266); `nav-user.tsx` **не используется** |
|
||||
| Header right | AppsMenu → SystemMonitorPopover; без ModeToggle; без Search-кнопки (только ⌘K) | AppsMenu + SystemMonitor | AppsMenu **без** SystemMonitor (`site-header.tsx` 73–75) | нет AppsMenu / SystemMonitor; `CommandPaletteButton` в sidebar header |
|
||||
| Header | `h-12`, sticky, `border-b`, `px-4 md:px-6` | да | да | `<uncertain>` vs Vite (Next layout свой) |
|
||||
| Main | `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` | contract | contract | `<not_verified>` pixel-match |
|
||||
| Theme | next-themes **или эквивалент с тем же UX** + semantic `--success/--warning/--info/--invert` | next-themes | next-themes | custom `theme-provider` `rl-theme` — UX должен стать segmented 3-way |
|
||||
| Запрещено | `SidebarRail`, Search pill в header, ModeToggle в header | Vite: без Rail | без Rail | **`SidebarRail` есть** (`app-sidebar.tsx` 267) |
|
||||
|
||||
SSO: вход через auth-portal; App Switcher `GET /api/v1/app-switcher`. Ids в коде: `packages/shared/src/contracts/auth.ts` `APP_IDS = ['cfdm', 'vps', 'bgp', 'fw', 'dns']`.
|
||||
|
||||
---
|
||||
|
||||
## 7. UX/UI Audit — VPS Tracker
|
||||
|
||||
**Identity:** Vite SPA `apps/web`, `@cfdm/ui`, ReUI CLI в `apps/web/src/components/reui/`.
|
||||
**Routes (`_auth/`):** dashboard, vps, `vps.$vpsId`, accounts, providers, projects, `projects.$projectId`, payments, balance, tariffs, renewals, resources, reports, spaces, topology, audit, sync-journal.
|
||||
**Kit:** `resource-page`, `kpi-stat-grid`, `quick-action-grid`, `ops-dashboard`, `settings-shell`, `detail-panel`, `frame-data-grid`, `settings-card`, `topology-canvas`. Нет `kanban-board`.
|
||||
**ReUI on disk:** autocomplete, badge, icon-stack, filters, frame, alert, color-picker, timeline, number-field, date-selector + `data-grid/*`. Нет `icon-tile`, нет `kanban`.
|
||||
**Chrome:** `app-shell.tsx` — SystemMonitor есть; sidebar 240px.
|
||||
**Surface drift:** Card в `vps.$vpsId.tsx`, `resources.tsx`, `renewals.tsx`, `domain/charts.tsx`, `integrations/app-switcher-editor.tsx`. shadcn Table в `spaces.tsx`.
|
||||
**Dashboard QA:** contract: VPS order = banner → KPI → charts → attention → QuickActionGrid (исключение vs EvoBGP KPI → QA → rest).
|
||||
**Preserve:** VPS CRUD, BILLmanager sync, тарифы, баланс, платежи, topology canvas, CSV export, фильтры отчётов.
|
||||
|
||||
---
|
||||
|
||||
## 8. UX/UI Audit — Cloudflare Domain Manager
|
||||
|
||||
**Identity:** Vite SPA, `@cfdm/ui`, Frame-first. Card **не** импортируется в `apps/web` (grep 0). Ближе всех к контракту.
|
||||
**Routes:** index, domains, `domains/$domainId`, DNS, services, groups, `groups/$groupId`, certificates, settings (index/appearance/integrations).
|
||||
**Kit:** + `kanban-board`, `dashboard-analytics`. Нет `frame-data-grid` отдельным файлом (`<uncertain>` — может быть внутри ResourcePage).
|
||||
**Gaps:** нет `AGENTS.md`; нет `ui-surface.ts`; naming `*KpiCards` (`domain-kpi-cards.tsx`, `cert-kpi-cards.tsx`, `service-kpi-cards.tsx`) при том что внутри уже `KpiStatGrid`; KPI tiles = Item hybrid, не IconTile.
|
||||
**Preserve:** DNS CRUD, Cloudflare certs, groups/services kanban, integrations.
|
||||
|
||||
---
|
||||
|
||||
## 9. UX/UI Audit — EvoBGP
|
||||
|
||||
**Identity:** KPI SoT. `@evobgp/ui`. `lib/ui-surface.ts` есть.
|
||||
**Routes:** dashboard, modules, `modules/$moduleId`, `modules/new`, lookup, network, operations, monitoring, directories, schedule, settings, tenant-settings, access.
|
||||
**Card leftovers:** `modules/new.tsx`, `illustrated-empty-state.tsx`, `kpi-sparkline-card.tsx`, examples `c-tabs-*`.
|
||||
**Kit unused:** `SettingsShell` определён в `reui-kit/settings-shell.tsx`, **не** импортируется routes (grep только definition).
|
||||
**Forms:** WEB-05 требует RHF; фактическое использование в `src` — `<uncertain>` / выравнивать на Field + sheet-8.
|
||||
**Preserve:** modules CRUD, lookup, BIRD jobs, network, access keys.
|
||||
|
||||
---
|
||||
|
||||
## 10. UX/UI Audit — Auth Portal
|
||||
|
||||
**Identity:** IdP, не ops CRUD. Login: `routes/index.tsx` → `PortalLoginForm` на базе **auth-18** (`components/blocks/auth-18/...`).
|
||||
**`_auth` routes:** apps, admin users, user detail, oidc, logins, audit, admin apps.
|
||||
**Chrome gap:** `site-header.tsx` — только `AppsMenu`, **нет** `SystemMonitorPopover` (grep 0 по репо).
|
||||
**Kit:** `ResourcePage` — stub types only (`reui-kit/resource-page.tsx`). `/apps` — hand-roll Frame tiles, не `QuickActionGrid`.
|
||||
**Contract vs code:** `docs/ui-design-contract.md` ids без `dns`; `APP_IDS` включает `dns`.
|
||||
**Preserve:** SSO JWT, OIDC authorize/token/userinfo, app switcher admin, user access, audit.
|
||||
|
||||
---
|
||||
|
||||
## 11. UX/UI Audit — EvoFirewall
|
||||
|
||||
**Identity:** `@evofw/ui`. Chrome: `site-header.tsx` AppsMenu + SystemMonitor.
|
||||
**Routes:** index, agents, `agents/$id`, lists, `lists/$id`, rules, `rules/$setId`, stats, settings.
|
||||
**SettingsShell:** файл есть, **не** используется routes (grep 0 в routes). `/settings` — PageShell + Frame (допустимо как REPLACE rule).
|
||||
**AgentPlatformIcon:** Item `size-10.5` `bg-muted` (`agents/agent-platform-icon.tsx`) — та же устаревшая KPI DNA.
|
||||
**Agent cards view:** Frame domain (`?view=cards|table`) — **оставить**, не Card.
|
||||
**Preserve:** agent fleet, blocklists, policy rules, stats, EvoBGP prefix ingest.
|
||||
|
||||
---
|
||||
|
||||
## 12. UX/UI Audit — MikrotikManager
|
||||
|
||||
**Identity:** Next.js 16, `components.json` с `@reui`, **нет** `reui-kit`. SoT UI: `app/(main)/servers/page.tsx` + `DataPageCard` → `Card` (`components/data-page-card.tsx`).
|
||||
**Pages (27):** servers, dashboard, firewall, filters, certificates, gre, data-collection, uptime, recursive-routes, route-optimizer, ospf, communities, settings, probes, vxlan, wireguard, backups, bgp, ip-ranges, domains, asns, alerts, network-map, terminal, traffic, releases, containers.
|
||||
**ReUI on disk:** frame, filters, badge, alert, autocomplete, number-field, sortable, timeline, stepper + data-grid (partial). `frame.tsx` установлен, ops-shell его не использует.
|
||||
**Chrome:** sidebar `16rem`; footer hand-roll + Sun/Moon; `NavUser` файл есть, не подключён; нет App Switcher / AppsMenu / SystemMonitor; `SidebarRail` включён.
|
||||
**Theme:** `components/theme-provider.tsx` STORAGE_KEY `rl-theme` — не next-themes; UX должен стать 3-way segmented.
|
||||
**Preserve:** весь CRUD `/servers` и остальные 26 страниц (terminal, network-map, контейнеры, BGP/OSPF/firewall…). Framework остаётся Next.js.
|
||||
|
||||
---
|
||||
|
||||
## 13. Cross-project Inconsistency Matrix
|
||||
|
||||
| Area | VPS | CFDM | EvoBGP | Auth | FW | MM | UX impact | Priority | Target |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| Ops surface | mixed Card | Frame | leftover Card | Frame | Frame | **Card SoT** | две системы | P0/P1 | Frame |
|
||||
| Chrome identity | close | close | close | no monitor | close | **none** | разный «дом» | P0 | Chrome spec |
|
||||
| Sidebar width | 240px | 240px | 240px | 240px | 240px | 16rem | визуальный разрыв | P0 | 240px |
|
||||
| Theme control | NavUser segmented | same | same | same | same | Sun/Moon | разный UX | P0 | segmented |
|
||||
| KPI tile | Item hybrid | Item hybrid | Item hybrid SoT | Item | Item / AgentPlatformIcon | Card KPI | нет IconTile | P1 | IconTile elevated |
|
||||
| Lists | ResourcePage; spaces Table | ResourcePage | ResourcePage | stub + Frame grids | ResourcePage | DataPageCard | MM/VPS spaces | P1 | DataGrid+Filters |
|
||||
| Quick Actions | kit (order exception) | kit | kit | hand-roll `/apps` | kit | `<not_found>` | Auth/MM | P1 | QuickActionGrid |
|
||||
| Settings | SettingsShell | Frame settings | unused SettingsShell | N/A portal | unused SettingsShell | Card settings | dead kit | P2 | settings-3/16 |
|
||||
| Empty | EmptyState | EmptyState | Card illustrated | Frame empty `/apps` | EmptyState | EmptyState Card-ish | Card empty | P2 | empty-state-14/12 |
|
||||
| Package UI | `@cfdm/ui` | `@cfdm/ui` | `@evobgp/ui` | `@authportal/ui` | `@evofw/ui` | `@/components/ui` | не сливать npm | — | KEEP scopes |
|
||||
|
||||
---
|
||||
|
||||
## 14. Duplicate Components Analysis
|
||||
|
||||
| Task | Copies | Diff | Unify? | REUI |
|
||||
|---|---|---|---|---|
|
||||
| `KpiStatGrid` | 5 Vite repos | import scope only (SoT EvoBGP) | DNA sync per repo, **не** monorepo package | REUI-004 + REUI-005 |
|
||||
| `QuickActionGrid` | 5 Vite | same | same | REUI-013 |
|
||||
| `ResourcePage` | 4 ops + Auth stub | Auth stub | implement or stop claiming | REUI-006 |
|
||||
| `OpsDashboard` | VPS/CFDM/BGP/FW | similar | KEEP per repo | REUI-014 |
|
||||
| `SettingsShell` | VPS/CFDM/BGP/FW | BGP/FW unused | wire or stop listing as used | REUI-007 |
|
||||
| `SystemMonitorPopover` | 4 Vite; Auth/MM missing | copy per app | port to Auth + MM | REUI-011 |
|
||||
| `DataPageCard` vs Frame | MM only vs Vite Frame | Card vs Frame | REPLACE MM | REUI-001 |
|
||||
| Item KPI tile vs IconTile | 5 Vite + FW AgentPlatformIcon | hand-roll | REPLACE with IconTile | REUI-005 |
|
||||
|
||||
Общий npm-пакет kit **не** входит в этот план (package boundaries = technical risk).
|
||||
|
||||
---
|
||||
|
||||
## 15. REUI PRO Reference Registry
|
||||
|
||||
| Ref ID | Component/Block | Official Documentation | Official Example | Used By | Match |
|
||||
|---|---|---|---|---|---|
|
||||
| REUI-001 | Frame | [docs/components/base/frame](https://reui.io/docs/components/base/frame) | [components/frame](https://reui.io/components/frame) | CHANGE-009–018, 027 | 1:1 ops surface |
|
||||
| REUI-002 | Data Grid | [docs/components/base/data-grid](https://reui.io/docs/components/base/data-grid) | [components/data-grid](https://reui.io/components/data-grid) | CHANGE-013, 018 | 1:1 lists; docs = TanStack **v9** vs installed v8 → adaptation = **partial** until migration |
|
||||
| REUI-003 | Filters | [docs/components/base/filters](https://reui.io/docs/components/base/filters) | [components/filters](https://reui.io/components/filters) | CHANGE-013, 018 | 1:1 |
|
||||
| REUI-004 | stats-12 | [blocks](https://reui.io/blocks) | [preview/base/stats-12](https://reui.io/preview/base/stats-12) | CHANGE-005, 008 | **partial** (MCP: Frame+Badge+Item; kit hand-rolls Item) |
|
||||
| REUI-005 | Icon Tile | [docs/components/base/icon-tile](https://reui.io/docs/components/base/icon-tile) | [components/icon-tile](https://reui.io/components/icon-tile) | CHANGE-004, 005, 007, 008 | 1:1 for tile primitive (`variant="elevated"` per docs) |
|
||||
| REUI-006 | data-grid-filtering-2 | [blocks](https://reui.io/blocks) | [preview/base/data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) | CHANGE-013, 018, 021 | **partial** (kit ResourcePage) |
|
||||
| REUI-007 | settings-16 | [blocks/application/settings](https://reui.io/blocks/application/settings) | [preview/base/settings-16](https://reui.io/preview/base/settings-16) | CHANGE-020 | 1:1 integrations |
|
||||
| REUI-008 | settings-3 | [blocks](https://reui.io/blocks) | [preview/base/settings-3](https://reui.io/preview/base/settings-3) | CHANGE-020 | 1:1 settings rows Frame |
|
||||
| REUI-009 | settings-8 | [blocks](https://reui.io/blocks) | [preview/base/settings-8](https://reui.io/preview/base/settings-8) | CHANGE-020 | 1:1 webhook-like lists |
|
||||
| REUI-010 | app-shell-12 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-12](https://reui.io/preview/base/app-shell-12) | CHANGE-001, 002, 003 | **partial** (chrome composition) |
|
||||
| REUI-011 | app-shell-7 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-7](https://reui.io/preview/base/app-shell-7) | CHANGE-001, 002 | **partial** (SystemMonitor popover match) |
|
||||
| REUI-012 | app-shell-1 | [blocks](https://reui.io/blocks) | [preview/base/app-shell-1](https://reui.io/preview/base/app-shell-1) | CHANGE-002, 032 | **partial** (NavUser) |
|
||||
| REUI-013 | card-12 | [blocks](https://reui.io/blocks) | [preview/base/card-12](https://reui.io/preview/base/card-12) | CHANGE-022 | **partial** (Quick Actions DNA; name «card» но Frame tiles) |
|
||||
| REUI-014 | dashboard-1 | [blocks](https://reui.io/blocks) | [preview/base/dashboard-1](https://reui.io/preview/base/dashboard-1) | CHANGE-008, 029 | **partial** (OpsDashboard) |
|
||||
| REUI-015 | empty-state-14 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-14](https://reui.io/preview/base/empty-state-14) | CHANGE-016, 030 | 1:1 Frame empty (MCP `surface: frame`) |
|
||||
| REUI-016 | empty-state-12 | [blocks](https://reui.io/blocks) | [preview/base/empty-state-12](https://reui.io/preview/base/empty-state-12) | CHANGE-030 | **partial** (current cite; MCP surface none) |
|
||||
| REUI-017 | sheet-8 | [blocks](https://reui.io/blocks) | [preview/base/sheet-8](https://reui.io/preview/base/sheet-8) | CHANGE-031 | **partial** (settings sheet; surface none) |
|
||||
| REUI-018 | sheet-1 | [blocks](https://reui.io/blocks) | [preview/base/sheet-1](https://reui.io/preview/base/sheet-1) | CHANGE-031 | **partial** (create sheet) |
|
||||
| REUI-019 | sheet-9 | [blocks](https://reui.io/blocks) | [preview/base/sheet-9](https://reui.io/preview/base/sheet-9) | CHANGE-031 | **partial** (detail sheet) |
|
||||
| REUI-020 | auth-13 | [blocks](https://reui.io/blocks) | [preview/base/auth-13](https://reui.io/preview/base/auth-13) | Auth login **не** target (см. REUI-021) | 1:1 Frame login **если** менять; сейчас KEEP auth-18 |
|
||||
| REUI-021 | auth-18 | [blocks](https://reui.io/blocks) | [preview/base/auth-18](https://reui.io/preview/base/auth-18) | Auth login actual | **partial** (surface none; split testimonial) |
|
||||
| REUI-022 | solution-users-1 | [blocks](https://reui.io/blocks) | [preview/base/solution-users-1](https://reui.io/preview/base/solution-users-1) | Auth admin users | **partial** (Frame+DataGrid+Filters+Sheet) |
|
||||
| REUI-023 | form-7 | [blocks](https://reui.io/blocks) | [preview/base/form-7](https://reui.io/preview/base/form-7) | CHANGE-031 | **partial** (inline Frame form) |
|
||||
| REUI-024 | chart-15 | [blocks](https://reui.io/blocks) | [preview/base/chart-15](https://reui.io/preview/base/chart-15) | CHANGE-012 | 1:1 Frame charts (MCP `surface: frame`) |
|
||||
| REUI-025 | settings-2 | [blocks](https://reui.io/blocks) | [preview/base/settings-2](https://reui.io/preview/base/settings-2) | CHANGE-020 | 1:1 mixed-control rows Frame |
|
||||
| REUI-026 | c-tabs-2 | [components/tabs](https://reui.io/components/tabs) | [preview/base/components/c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) | ResourcePage tabs | **partial** (line tabs; example also lists Card) |
|
||||
| REUI-027 | Alert Dialog example | [components/alert-dialog](https://reui.io/components/alert-dialog) | [c-alert-dialog-11](https://reui.io/preview/base/components/c-alert-dialog-11) | CHANGE-031 confirm | **partial** (shadcn AlertDialog + ReUI example) |
|
||||
| REUI-028 | Icon Stack | [docs/components/base/icon-stack](https://reui.io/docs/components/base/icon-stack) | [components/icon-stack](https://reui.io/components/icon-stack) | CHANGE-016, 030 | 1:1 empty illustration |
|
||||
| REUI-029 | Badge | [docs/components/base/badge](https://reui.io/docs/components/base/badge) | [components/badge](https://reui.io/components/badge) | KPI/QA status | 1:1 semantic variants |
|
||||
| REUI-030 | Alert | [docs/components/base/alert](https://reui.io/docs/components/base/alert) | [components/alert](https://reui.io/components/alert) | errors | 1:1 |
|
||||
| REUI-031 | Kanban | [docs/components/base/kanban](https://reui.io/docs/components/base/kanban) | [components/kanban](https://reui.io/components/kanban) | CFDM groups/services | 1:1 domain |
|
||||
| REUI-032 | Sortable | [docs/components/base/sortable](https://reui.io/docs/components/base/sortable) | [components/sortable](https://reui.io/components/sortable) | FW policy | 1:1 domain |
|
||||
| REUI-033 | Stepper | [docs/components/base/stepper](https://reui.io/docs/components/base/stepper) | [components/stepper](https://reui.io/components/stepper) | MM/FW if used | 1:1; FW unused → don't list as used |
|
||||
| REUI-034 | Timeline | [docs/components/base/timeline](https://reui.io/docs/components/base/timeline) | [components/timeline](https://reui.io/components/timeline) | audit | 1:1 |
|
||||
| REUI-035 | Autocomplete | [docs/components/base/autocomplete](https://reui.io/docs/components/base/autocomplete) | [components/autocomplete](https://reui.io/components/autocomplete) | domain pickers | 1:1 |
|
||||
|
||||
**Не target (намеренно):**
|
||||
|
||||
| Item | Why | Evidence |
|
||||
|---|---|---|
|
||||
| settings-7 | MCP `surface: card` | [preview/base/settings-7](https://reui.io/preview/base/settings-7) |
|
||||
| empty-state-11 | MCP `surface: card` | [preview/base/empty-state-11](https://reui.io/preview/base/empty-state-11) |
|
||||
|
||||
---
|
||||
|
||||
## 16. Target UX/UI Rules
|
||||
|
||||
### TARGET-RULE-001
|
||||
|
||||
**Rule:** Один surface на ops-экран: ReUI Frame. Не смешивать Card и Frame.
|
||||
**Scope:** все 6.
|
||||
**Rationale:** две системы ломают consistency.
|
||||
**REUI PRO:** [Frame](https://reui.io/docs/components/base/frame)
|
||||
**Impact:** VPS Card pages, BGP leftovers, MM DataPageCard.
|
||||
|
||||
### TARGET-RULE-002
|
||||
|
||||
**Rule:** Ops-списки = DataGrid + Filters внутри Frame. Запрет raw `<table>` / shadcn Table как ops list.
|
||||
**Scope:** все 6.
|
||||
**REUI PRO:** [data-grid](https://reui.io/docs/components/base/data-grid) · [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2)
|
||||
**Impact:** VPS `spaces.tsx`; MM DataPageCard.
|
||||
|
||||
### TARGET-RULE-003
|
||||
|
||||
**Rule:** KPI = stats-12 composition + IconTile `elevated` (не hand-roll Item `size-10.5`). Semantic `text-*` / Badge, не `bg-emerald-*`.
|
||||
**Scope:** kit во всех Vite + MM dashboard.
|
||||
**REUI PRO:** [stats-12](https://reui.io/preview/base/stats-12) · [icon-tile](https://reui.io/docs/components/base/icon-tile)
|
||||
|
||||
### TARGET-RULE-004
|
||||
|
||||
**Rule:** Quick Actions = только `QuickActionGrid` (Frame tiles, sibling KPI).
|
||||
**REUI PRO:** [stats-12](https://reui.io/preview/base/stats-12) · [card-12](https://reui.io/preview/base/card-12)
|
||||
|
||||
### TARGET-RULE-005
|
||||
|
||||
**Rule:** Settings rows = settings-3 / settings-2; integrations = settings-16; **запрет settings-7**.
|
||||
**REUI PRO:** [settings-3](https://reui.io/preview/base/settings-3) · [settings-16](https://reui.io/preview/base/settings-16)
|
||||
|
||||
### TARGET-RULE-006 (P0)
|
||||
|
||||
**Rule:** Chrome identity 1:1 во всех 6 — см. Chrome Identity Spec. Auth Portal и MikrotikManager **не** исключение. Login без AppShell.
|
||||
**REUI PRO:** [app-shell-12](https://reui.io/preview/base/app-shell-12) · [app-shell-7](https://reui.io/preview/base/app-shell-7) · [app-shell-1](https://reui.io/preview/base/app-shell-1)
|
||||
|
||||
### TARGET-RULE-007
|
||||
|
||||
**Rule:** Create/edit = Sheet (sheet-8 / sheet-1 / sheet-9). Confirm = Alert Dialog ([c-alert-dialog-11](https://reui.io/preview/base/components/c-alert-dialog-11)).
|
||||
|
||||
### TARGET-RULE-008
|
||||
|
||||
**Rule:** Empty = Empty + IconStack, Frame-friendly empty-state-14 (prefer) / empty-state-12. Не empty-state-11.
|
||||
|
||||
### TARGET-RULE-009
|
||||
|
||||
**Rule:** MikrotikManager UX SoT: Frame+DataGrid **при сохранении** CRUD `/servers`. Next.js не менять.
|
||||
|
||||
### TARGET-RULE-010
|
||||
|
||||
**Rule:** Rules синхронизировать с диском (не перечислять неустановленные компоненты как установленные). App Switcher ids включают `dns`.
|
||||
|
||||
---
|
||||
|
||||
### RULE-CHANGE-001
|
||||
|
||||
**Current rule:** SoT UI = `/servers` на Card (`next-shadcn-production.mdc`, `ma-ui-guardian.mdc`).
|
||||
**Source:** `MikrotikManager-3/.cursor/rules/next-shadcn-production.mdc`
|
||||
**Problem:** блокирует унификацию Frame.
|
||||
**Evidence:** `components/data-page-card.tsx` → `Card`.
|
||||
**Recommended:** SoT = Frame + DataGrid; `/servers` остаётся эталоном **поведения** CRUD, не Card-shell.
|
||||
**REUI:** REUI-001, REUI-006.
|
||||
**Impact:** MM rules + все `DataPageCard` pages.
|
||||
|
||||
### RULE-CHANGE-002
|
||||
|
||||
**Current:** kpi-hybrid Item `size-10.5` `bg-muted`.
|
||||
**Source:** `kpi-hybrid.mdc` ×5.
|
||||
**Problem:** official IconTile покрывает задачу; нигде не установлен.
|
||||
**Recommended:** KPI/row tiles = IconTile `elevated` + stats-12 composition.
|
||||
**REUI:** REUI-004, REUI-005.
|
||||
|
||||
### RULE-CHANGE-003
|
||||
|
||||
**Current:** chrome описан в contract, но Auth/MM исключения фактически существуют.
|
||||
**Recommended:** Chrome Identity Spec обязателен 1:1 для всех 6.
|
||||
**REUI:** REUI-010, REUI-011, REUI-012.
|
||||
|
||||
### RULE-CHANGE-004
|
||||
|
||||
**Current:** contract App Switcher ids без `dns`.
|
||||
**Source:** `auth-portal/docs/ui-design-contract.md`
|
||||
**Evidence:** `APP_IDS` включает `dns`.
|
||||
**Recommended:** ids `cfdm · vps · bgp · fw · dns`.
|
||||
|
||||
### RULE-CHANGE-005
|
||||
|
||||
**Current:** VPS `frontend-shadcn.mdc` / `reui-mcp.mdc` lists `kanban-board`.
|
||||
**Evidence:** `reui-kit/` VPS — нет `kanban-board.tsx`; grep `kanban` в VPS = 0.
|
||||
**Recommended:** список установленных = диск.
|
||||
|
||||
### RULE-CHANGE-006
|
||||
|
||||
**Current:** settings-7 встречается как reference.
|
||||
**Evidence:** MCP settings-7 `surface: card`.
|
||||
**Recommended:** запретить как target.
|
||||
|
||||
---
|
||||
|
||||
## 17. Detailed Change Plan
|
||||
|
||||
Шаблон ниже. Functional requirements для всех UI CHANGE: сохранить create/edit/delete/filter/sort/pagination/navigation/permissions; менять только оболочку.
|
||||
|
||||
Состояния (где применимо): default, hover, focus, disabled, loading, error, empty.
|
||||
Responsive: desktop = эталон ReUI preview; tablet/mobile = существующий Sidebar/Sheet collapse Vite/Next, не ломать.
|
||||
A11y: keyboard, focus ring, labels, `aria-hidden` на decorative IconTile (docs).
|
||||
|
||||
### CHANGE-001
|
||||
|
||||
**Project:** Auth Portal · **Priority:** P0 · **Category:** Navigation / chrome
|
||||
**File:** `apps/web/src/components/layout/site-header.tsx`
|
||||
**Current:** header right = только `AppsMenu` (строки 73–75).
|
||||
**Problem:** нет SystemMonitor — ломает chrome identity.
|
||||
**Change:** добавить `SystemMonitorPopover` после AppsMenu (порт с CFDM/VPS).
|
||||
**Ref:** REUI-011 · [app-shell-7](https://reui.io/preview/base/app-shell-7) · Match: **partial**
|
||||
**Mapping:** AppsMenu-only header → AppsMenu + SystemMonitor.
|
||||
**Preserve:** breadcrumbs, AppsMenu, SSO session.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-002
|
||||
|
||||
**Project:** MikrotikManager · **Priority:** P0 · **Category:** Navigation / chrome
|
||||
**Files:** `components/app-sidebar.tsx`, `components/ui/sidebar.tsx`, `(main)/layout.tsx`
|
||||
**Current:** sidebar 16rem; footer hand-roll + Sun/Moon; SidebarRail; нет AppsMenu/SystemMonitor/App Switcher.
|
||||
**Change:** Chrome Identity Spec 1:1 (240px, Switcher, NavUser segmented, header cluster, убрать Rail и icon theme toggle).
|
||||
**Ref:** REUI-010, REUI-011, REUI-012
|
||||
**Match:** **partial** (composition, Next layout).
|
||||
**Preserve:** nav groups, command palette **как ⌘K** (не Search pill в header).
|
||||
**Risk:** High.
|
||||
|
||||
### CHANGE-003
|
||||
|
||||
**Project:** VPS, CFDM, EvoBGP, EvoFirewall · **Priority:** P0 · **Category:** chrome verification
|
||||
**Current:** близко к spec (SystemMonitor есть).
|
||||
**Change:** pixel/token сверка 1:1 (header `h-12`, main padding, нет ModeToggle/Search pill).
|
||||
**Ref:** REUI-010 · Match: **partial**
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-004
|
||||
|
||||
**Project:** все 6 · **Priority:** P1 · **Category:** KPI primitive install
|
||||
**Current:** `icon-tile` отсутствует.
|
||||
**Change:** CLI `pnpm dlx shadcn@latest add @reui/icon-tile --yes` из `apps/web` (MM — из корня Next).
|
||||
**Ref:** REUI-005 · [icon-tile docs](https://reui.io/docs/components/base/icon-tile) · Match: **1:1**
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-005
|
||||
|
||||
**Project:** 5 Vite kit · **Priority:** P1 · **Category:** KPI
|
||||
**Files:** `reui-kit/kpi-stat-grid.tsx` (каждый репо); EvoBGP SoT markup.
|
||||
**Current:** `Item` `size-10.5` `bg-muted` + `ItemMedia`.
|
||||
**Change:** IconTile `elevated` + stats-12 composition; обновить `kpi-hybrid.mdc`.
|
||||
**Ref:** REUI-004 **partial** + REUI-005 **1:1**
|
||||
**Preserve:** `to`/`onSelect`, variants warning/destructive, Badge hints.
|
||||
**Risk:** Medium (visual DNA shift).
|
||||
|
||||
### CHANGE-006
|
||||
|
||||
**Project:** CFDM · **Priority:** P2 · **Category:** naming
|
||||
**Files:** `domain-kpi-cards.tsx`, `cert-kpi-cards.tsx`, `service-kpi-cards.tsx`
|
||||
**Change:** переименовать `*KpiCards` → `*KpiStats` (внутри уже KpiStatGrid). Не Card.
|
||||
**Ref:** REUI-004 · Match: **partial** (rename only)
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-007
|
||||
|
||||
**Project:** EvoFirewall · **Priority:** P1 · **Category:** row tiles
|
||||
**File:** `apps/web/src/components/agents/agent-platform-icon.tsx`
|
||||
**Current:** Item hybrid tile.
|
||||
**Change:** IconTile elevated; semantic `text-info` / muted.
|
||||
**Ref:** REUI-005 · Match: **1:1**
|
||||
**Preserve:** mikrotik vs linux aria-label.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-008
|
||||
|
||||
**Project:** MikrotikManager · **Priority:** P1 · **Category:** KPI
|
||||
**File:** `app/(main)/dashboard/page.tsx`
|
||||
**Change:** dashboard metrics → stats-12 + IconTile, не Card KPI.
|
||||
**Ref:** REUI-004, REUI-005, REUI-014 · Match: **partial**
|
||||
**Preserve:** dashboard numbers/links.
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-009
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P1 · **Category:** Surface
|
||||
**File:** `apps/web/src/routes/_auth/vps.$vpsId.tsx`
|
||||
**Current:** Card shells.
|
||||
**Change:** Frame / DetailPanel.
|
||||
**Ref:** REUI-001, REUI-023 · Match: **partial** (detail composition)
|
||||
**Preserve:** VPS detail fields, actions, sync.
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-010
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P1 · **Category:** Surface
|
||||
**File:** `apps/web/src/routes/_auth/resources.tsx`
|
||||
**Change:** Card → Frame.
|
||||
**Ref:** REUI-001 · Match: **1:1** shell
|
||||
**Preserve:** resource listing.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-011
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P1 · **Category:** Surface
|
||||
**File:** `apps/web/src/routes/_auth/renewals.tsx`
|
||||
**Change:** Card → Frame.
|
||||
**Ref:** REUI-001 · Match: **1:1** shell
|
||||
**Preserve:** renewals data/actions.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-012
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P1 · **Category:** Charts
|
||||
**File:** `apps/web/src/components/domain/charts.tsx` (used by `reports.tsx`)
|
||||
**Current:** Card from `@cfdm/ui/components/card`.
|
||||
**Change:** Frame chart shells per chart-15.
|
||||
**Ref:** REUI-024 · [chart-15](https://reui.io/preview/base/chart-15) · Match: **1:1** surface; **partial** series (Recharts already)
|
||||
**Preserve:** filters, CSV, KPI on reports.
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-013
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P1 · **Category:** Data UI
|
||||
**File:** `apps/web/src/routes/_auth/spaces.tsx`
|
||||
**Current:** shadcn Table.
|
||||
**Change:** ResourcePage / DataGrid + Filters.
|
||||
**Ref:** REUI-002 **partial** (v8), REUI-003, REUI-006
|
||||
**Preserve:** spaces CRUD, columns, sort.
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-014
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P2 · **Category:** Settings
|
||||
**File:** `apps/web/src/components/integrations/app-switcher-editor.tsx`
|
||||
**Current:** Card.
|
||||
**Change:** Frame + settings-16/3 rows.
|
||||
**Ref:** REUI-001, REUI-007 · Match: **partial**
|
||||
**Preserve:** app switcher editor fields.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-015
|
||||
|
||||
**Project:** EvoBGP · **Priority:** P1 · **Category:** Surface
|
||||
**File:** `apps/web/src/routes/_auth/modules/new.tsx`
|
||||
**Current:** Card stub.
|
||||
**Change:** Frame + sheet-1/form-7 for create.
|
||||
**Ref:** REUI-001, REUI-018 · Match: **partial**
|
||||
**Preserve:** module create flow.
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-016
|
||||
|
||||
**Project:** EvoBGP · **Priority:** P1 · **Category:** Empty
|
||||
**File:** `apps/web/src/components/patterns/illustrated-empty-state.tsx`
|
||||
**Current:** Card + illustration.
|
||||
**Change:** empty-state-14 (Frame + IconStack).
|
||||
**Ref:** REUI-015, REUI-028 · Match: **1:1** surface
|
||||
**Preserve:** empty CTA copy/actions.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-017
|
||||
|
||||
**Project:** EvoBGP · **Priority:** P2 · **Category:** Charts
|
||||
**File:** `apps/web/src/components/patterns/kpi-sparkline-card.tsx`
|
||||
**Change:** Card → Frame / chart-15 DNA.
|
||||
**Ref:** REUI-024 · Match: **partial**
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-018
|
||||
|
||||
**Project:** MikrotikManager · **Priority:** P1 · **Category:** Surface
|
||||
**Files:** `components/data-page-card.tsx` + all pages importing it (servers, firewall, filters, gre, certificates, data-collection, …).
|
||||
**Current:** `Card`. `components/reui/frame.tsx` установлен, не используется как ops shell.
|
||||
**Change:** DataPageCard → Frame wrapper (или заменить вызовы на Frame).
|
||||
**Ref:** REUI-001, REUI-006 · Match: **1:1** shell; grid **partial** (v8)
|
||||
**Preserve:** весь CRUD `/servers` и остальных страниц.
|
||||
**Risk:** High.
|
||||
|
||||
### CHANGE-019
|
||||
|
||||
**Project:** EvoBGP · **Priority:** P2 · **Category:** Kit dead code
|
||||
**File:** `reui-kit/settings-shell.tsx`
|
||||
**Change:** подключить на settings routes **или** убрать из «установленный kit / used».
|
||||
**Ref:** REUI-007, REUI-008 · Match: **partial**
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-020
|
||||
|
||||
**Project:** EvoFirewall · **Priority:** P2 · **Category:** Settings
|
||||
**File:** `routes/_auth/settings.tsx` vs unused `reui-kit/settings-shell.tsx`
|
||||
**Change:** либо SettingsShell при 2+ секциях, либо явно REPLACE rule «single-page = PageShell+Frame» (уже в FW frontend-shadcn).
|
||||
**Ref:** REUI-007, REUI-008, REUI-025
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-021
|
||||
|
||||
**Project:** Auth Portal · **Priority:** P2 · **Category:** Kit
|
||||
**File:** `reui-kit/resource-page.tsx` (stub)
|
||||
**Change:** либо реализовать как ops ResourcePage, либо не экспортировать как list kit. Admin users → solution-users-1 DNA.
|
||||
**Ref:** REUI-006, REUI-022 · Match: **partial**
|
||||
**Preserve:** users/OIDC grids.
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-022
|
||||
|
||||
**Project:** Auth Portal · **Priority:** P1 · **Category:** Quick Actions
|
||||
**File:** `apps/web/src/routes/_auth.apps.tsx`
|
||||
**Current:** hand-roll Frame tiles.
|
||||
**Change:** `QuickActionGrid` (после IconTile DNA).
|
||||
**Ref:** REUI-013, REUI-004 · Match: **partial**
|
||||
**Preserve:** SSO `openApp`, admin link, empty «нет приложений».
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-023
|
||||
|
||||
**Project:** Auth Portal · **Priority:** P1 · **Category:** Rules
|
||||
**File:** `docs/ui-design-contract.md` (+ копии в других apps)
|
||||
**Change:** App Switcher ids += `dns`.
|
||||
**Ref:** N/A functional; chrome REUI-010
|
||||
**Match:** `<not_found_in_reui>` для списка ids (доменное) — UI chrome всё равно REUI-010.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-024
|
||||
|
||||
**Project:** CFDM · **Priority:** P2 · **Category:** Rules
|
||||
**Change:** добавить `AGENTS.md` + `apps/web/src/lib/ui-surface.ts` (`UI_SURFACE = 'frame'`), как EvoBGP.
|
||||
**Ref:** REUI-001
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-025
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P2 · **Category:** Rules
|
||||
**Change:** убрать kanban из «установленных» в `reui-mcp.mdc` / `frontend-shadcn.mdc`.
|
||||
**Ref:** REUI-031 существует, но **не установлен** в VPS.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-026
|
||||
|
||||
**Project:** all Vite · **Priority:** P1 · **Category:** Rules
|
||||
**Files:** `kpi-hybrid.mdc` ×5 + workspace `reui-pro-senior-frontend.mdc`
|
||||
**Change:** DNA = IconTile elevated, не Item.
|
||||
**Ref:** REUI-005
|
||||
**Risk:** Low (docs only until CHANGE-005).
|
||||
|
||||
### CHANGE-027
|
||||
|
||||
**Project:** MikrotikManager · **Priority:** P0 · **Category:** Rules
|
||||
**Change:** RULE-CHANGE-001 — SoT Frame+DataGrid.
|
||||
**Ref:** REUI-001, REUI-006
|
||||
**Risk:** High (process + UI).
|
||||
|
||||
### CHANGE-028
|
||||
|
||||
**Project:** EvoBGP (+ any settings-7 cite) · **Priority:** P1 · **Category:** Rules
|
||||
**Change:** запрет settings-7 как target.
|
||||
**Ref:** settings-7 [preview](https://reui.io/preview/base/settings-7) — **не использовать**. Target REUI-007/008.
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-029
|
||||
|
||||
**Project:** VPS Tracker · **Priority:** P2 · **Category:** Dashboard layout
|
||||
**Current:** contract exception QA after charts.
|
||||
**Change:** либо выровнять с EvoBGP (KPI → QA → rest), либо **явно** оставить exception в contract. Решение implementation: default **KEEP exception** unless product asks otherwise.
|
||||
**Ref:** REUI-014 · Match: **partial**
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-030
|
||||
|
||||
**Project:** all 6 · **Priority:** P2 · **Category:** Empty states
|
||||
**Change:** Frame-friendly empty-state-14/12 + IconStack; не Card empty-state-11.
|
||||
**Ref:** REUI-015, REUI-016, REUI-028
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-031
|
||||
|
||||
**Project:** all 6 · **Priority:** P2 · **Category:** Forms / overlays
|
||||
**Change:** create/edit Sheet per sheet-8/1/9; confirm c-alert-dialog-11. Не менять validation/API.
|
||||
**Ref:** REUI-017, REUI-018, REUI-019, REUI-023, REUI-027
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-032
|
||||
|
||||
**Project:** MikrotikManager · **Priority:** P0 · **Category:** chrome
|
||||
**File:** `components/nav-user.tsx` unused
|
||||
**Change:** подключить в SidebarFooter; segmented theme вместо Sun/Moon; убрать hardcoded «АК» footer.
|
||||
**Ref:** REUI-012 · Match: **partial**
|
||||
**Preserve:** user name/email display (сейчас hardcoded — **заменить на реального пользователя**, не потерять logout если появится).
|
||||
**Risk:** Medium.
|
||||
|
||||
### CHANGE-033
|
||||
|
||||
**Project:** MikrotikManager · **Priority:** P1 · **Category:** chrome
|
||||
**File:** `components/app-sidebar.tsx` `SidebarRail`
|
||||
**Change:** удалить Rail (запрещён контрактом).
|
||||
**Ref:** REUI-010 · Match: **partial**
|
||||
**Risk:** Low.
|
||||
|
||||
### CHANGE-034
|
||||
|
||||
**Project:** MikrotikManager · **Priority:** P0 · **Category:** chrome
|
||||
**File:** `components/ui/sidebar.tsx` `SIDEBAR_WIDTH = "16rem"`
|
||||
**Change:** `240px`.
|
||||
**Ref:** REUI-010 · Match: **partial** (token, не block 1:1)
|
||||
**Risk:** Medium (layout shift).
|
||||
|
||||
---
|
||||
|
||||
## Final Change Matrix
|
||||
|
||||
| Change ID | Project | Category | Priority | Current UI | Target REUI PRO | Ref ID | Official Reference | Match |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| CHANGE-001 | Auth Portal | Chrome | P0 | AppsMenu only | app-shell-7 monitor | REUI-011 | [app-shell-7](https://reui.io/preview/base/app-shell-7) | partial |
|
||||
| CHANGE-002 | MM | Chrome | P0 | 16rem + Sun/Moon | app-shell-12/7/1 | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial |
|
||||
| CHANGE-003 | VPS/CFDM/BGP/FW | Chrome | P0 | close | app-shell-12 | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial |
|
||||
| CHANGE-004 | all 6 | Install | P1 | missing | Icon Tile | REUI-005 | [icon-tile](https://reui.io/docs/components/base/icon-tile) | 1:1 |
|
||||
| CHANGE-005 | 5 Vite | KPI | P1 | Item hybrid | stats-12 + IconTile | REUI-004/005 | [stats-12](https://reui.io/preview/base/stats-12) | partial + 1:1 |
|
||||
| CHANGE-006 | CFDM | Naming | P2 | *KpiCards | KpiStatGrid name | REUI-004 | [stats-12](https://reui.io/preview/base/stats-12) | partial |
|
||||
| CHANGE-007 | FW | Row tile | P1 | Item AgentPlatformIcon | IconTile | REUI-005 | [icon-tile](https://reui.io/docs/components/base/icon-tile) | 1:1 |
|
||||
| CHANGE-008 | MM | KPI | P1 | Card metrics | stats-12 + IconTile | REUI-004/005 | [stats-12](https://reui.io/preview/base/stats-12) | partial |
|
||||
| CHANGE-009 | VPS | Surface | P1 | Card detail | Frame | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | partial |
|
||||
| CHANGE-010 | VPS | Surface | P1 | Card resources | Frame | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 |
|
||||
| CHANGE-011 | VPS | Surface | P1 | Card renewals | Frame | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 |
|
||||
| CHANGE-012 | VPS | Charts | P1 | Card charts | chart-15 | REUI-024 | [chart-15](https://reui.io/preview/base/chart-15) | 1:1/partial |
|
||||
| CHANGE-013 | VPS | Table | P1 | shadcn Table spaces | DataGrid+Filters | REUI-002/006 | [data-grid](https://reui.io/docs/components/base/data-grid) | partial |
|
||||
| CHANGE-014 | VPS | Settings | P2 | Card editor | Frame settings-16 | REUI-007 | [settings-16](https://reui.io/preview/base/settings-16) | partial |
|
||||
| CHANGE-015 | EvoBGP | Surface | P1 | Card new module | Frame/sheet-1 | REUI-001/018 | [sheet-1](https://reui.io/preview/base/sheet-1) | partial |
|
||||
| CHANGE-016 | EvoBGP | Empty | P1 | Card empty | empty-state-14 | REUI-015 | [empty-state-14](https://reui.io/preview/base/empty-state-14) | 1:1 |
|
||||
| CHANGE-017 | EvoBGP | Charts | P2 | Card sparkline | chart-15 | REUI-024 | [chart-15](https://reui.io/preview/base/chart-15) | partial |
|
||||
| CHANGE-018 | MM | Surface | P1 | DataPageCard Card | Frame+DataGrid | REUI-001/006 | [frame](https://reui.io/docs/components/base/frame) | 1:1/partial |
|
||||
| CHANGE-019 | EvoBGP | Kit | P2 | unused SettingsShell | settings-16/3 | REUI-007 | [settings-16](https://reui.io/preview/base/settings-16) | partial |
|
||||
| CHANGE-020 | FW | Settings | P2 | unused SettingsShell | settings-3/16 | REUI-008 | [settings-3](https://reui.io/preview/base/settings-3) | partial |
|
||||
| CHANGE-021 | Auth | Kit | P2 | ResourcePage stub | data-grid-filtering-2 / users-1 | REUI-006/022 | [users-1](https://reui.io/preview/base/solution-users-1) | partial |
|
||||
| CHANGE-022 | Auth | QA | P1 | hand-roll /apps | QuickActionGrid | REUI-013 | [card-12](https://reui.io/preview/base/card-12) | partial |
|
||||
| CHANGE-023 | Auth | Rules | P1 | contract без dns | ids + dns | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial |
|
||||
| CHANGE-024 | CFDM | Rules | P2 | no AGENTS.md | Frame lock file | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 |
|
||||
| CHANGE-025 | VPS | Rules | P2 | fake kanban installed | disk-accurate list | REUI-031 | [kanban](https://reui.io/docs/components/base/kanban) | 1:1 exists, unused |
|
||||
| CHANGE-026 | Vite | Rules | P1 | Item hybrid rule | IconTile rule | REUI-005 | [icon-tile](https://reui.io/docs/components/base/icon-tile) | 1:1 |
|
||||
| CHANGE-027 | MM | Rules | P0 | Card SoT | Frame SoT | REUI-001 | [frame](https://reui.io/docs/components/base/frame) | 1:1 |
|
||||
| CHANGE-028 | EvoBGP | Rules | P1 | settings-7 cite | forbid card settings | REUI-007 | [settings-16](https://reui.io/preview/base/settings-16) | 1:1 |
|
||||
| CHANGE-029 | VPS | Layout | P2 | QA order exception | KEEP or align | REUI-014 | [dashboard-1](https://reui.io/preview/base/dashboard-1) | partial |
|
||||
| CHANGE-030 | all 6 | Empty | P2 | mixed | empty-state-14/12 | REUI-015 | [empty-state-14](https://reui.io/preview/base/empty-state-14) | 1:1/partial |
|
||||
| CHANGE-031 | all 6 | Forms | P2 | mixed sheets | sheet-8/1/9 | REUI-017 | [sheet-8](https://reui.io/preview/base/sheet-8) | partial |
|
||||
| CHANGE-032 | MM | Chrome | P0 | unused NavUser | app-shell-1 NavUser | REUI-012 | [app-shell-1](https://reui.io/preview/base/app-shell-1) | partial |
|
||||
| CHANGE-033 | MM | Chrome | P1 | SidebarRail | remove | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial |
|
||||
| CHANGE-034 | MM | Chrome | P0 | 16rem | 240px | REUI-010 | [app-shell-12](https://reui.io/preview/base/app-shell-12) | partial |
|
||||
|
||||
Все UI-related CHANGE имеют Ref ID. CHANGE-023 ids — доменный список; chrome reference всё равно REUI-010.
|
||||
|
||||
### Execution log (2026-08-17)
|
||||
|
||||
- [x] CHANGE-001 Auth SystemMonitor
|
||||
- [x] CHANGE-002 MM chrome identity
|
||||
- [x] CHANGE-003 Vite chrome verification (240px, h-12, skip-link, main padding)
|
||||
- [x] CHANGE-004 IconTile CLI во всех 6
|
||||
- [x] CHANGE-005 KpiStatGrid IconTile DNA
|
||||
- [x] CHANGE-006 CFDM *KpiStats rename
|
||||
- [x] CHANGE-007 FW AgentPlatformIcon IconTile
|
||||
- [x] CHANGE-008 MM dashboard stats-12 + IconTile + Frame panels (`OpsPanel`)
|
||||
- [x] CHANGE-009 VPS vps detail Frame
|
||||
- [x] CHANGE-010 VPS resources Frame
|
||||
- [x] CHANGE-011 VPS renewals Frame
|
||||
- [x] CHANGE-012 VPS charts Frame
|
||||
- [x] CHANGE-013 VPS spaces ResourcePage
|
||||
- [x] CHANGE-014 VPS app-switcher editor Frame
|
||||
- [x] CHANGE-015 EvoBGP modules/new Frame
|
||||
- [x] CHANGE-016 EvoBGP empty IconStack
|
||||
- [x] CHANGE-017 EvoBGP sparkline Frame (chart-15 DNA)
|
||||
- [x] CHANGE-018 MM DataPageCard + remaining ops Card → Frame/`OpsPanel`
|
||||
- [x] CHANGE-019 EvoBGP SettingsShell wired
|
||||
- [x] CHANGE-020 FW single-page settings rule
|
||||
- [x] CHANGE-021 Auth ResourcePage stub explicit
|
||||
- [x] CHANGE-022 Auth `/apps` QuickActionGrid
|
||||
- [x] CHANGE-023 App Switcher ids + `dns`
|
||||
- [x] CHANGE-024 CFDM AGENTS.md + ui-surface.ts
|
||||
- [x] CHANGE-025 VPS kanban list disk-accurate
|
||||
- [x] CHANGE-026 kpi-hybrid IconTile rule
|
||||
- [x] CHANGE-027 MM SoT Frame+DataGrid
|
||||
- [x] CHANGE-028 settings-7 не target; live EvoBGP settings → `@/components/settings/*`
|
||||
- [x] CHANGE-029 VPS QA order KEEP exception (contract)
|
||||
- [x] CHANGE-030 Empty + IconStack во всех 6
|
||||
- [x] CHANGE-031 Sheet create/edit + AlertDialog confirm (Vite ConfirmDialog; MM `@shadcn/alert-dialog`; EvoBGP FormDrawer=Sheet)
|
||||
- [x] CHANGE-032 MM NavUser segmented
|
||||
- [x] CHANGE-033 MM SidebarRail removed
|
||||
- [x] CHANGE-034 MM sidebar 240px
|
||||
|
||||
RULE-CHANGE-001…006 — DONE (rules files). RULE-CHANGE-007…010 в §2 — опечатка покрытия (в §16 только 001–006).
|
||||
|
||||
---
|
||||
|
||||
## 18. Implementation Roadmap
|
||||
|
||||
### Phase 0 — Discovery
|
||||
|
||||
- [x] Tasks: этот файл.
|
||||
- [x] DoD: Coverage 100%, Consistency Gate PASS.
|
||||
- [x] Аудит зафиксирован 2026-08-17.
|
||||
|
||||
### Phase 1 — Chrome identity (все 6)
|
||||
|
||||
- [x] CHANGE-001, 002, 003, 032, 033, 034.
|
||||
- [x] DoD: Chrome Identity Spec 1:1 (sidebar 240px, Switcher, AppsMenu→monitor, NavUser segmented).
|
||||
|
||||
### Phase 2 — Shared rules + IconTile + Frame lock
|
||||
|
||||
- [x] CHANGE-004, 026, 027, 028, 024, 025.
|
||||
- [x] DoD: `icon-tile` installed; rules updated; MM SoT Frame.
|
||||
|
||||
### Phase 3 — Kit DNA
|
||||
|
||||
- [x] CHANGE-005, 006, 007, 008, 021, 022.
|
||||
- [x] DoD: KpiStatGrid/QuickActionGrid/AgentPlatformIcon на IconTile; Auth `/apps` kit.
|
||||
|
||||
### Phase 4 — Page-level Card → Frame
|
||||
|
||||
- [x] CHANGE-009–018.
|
||||
- [x] DoD: нет Card ops-shell на затронутых страницах; CRUD preserved. MM inner panels → `OpsPanel`.
|
||||
|
||||
### Phase 5 — States + sheets
|
||||
|
||||
- [x] CHANGE-016, 019, 020, 030, 031.
|
||||
- [x] DoD: empty/loading/error Frame-friendly; sheets + AlertDialog.
|
||||
|
||||
### Phase 6 — Responsive + a11y verification
|
||||
|
||||
- [x] Keyboard skip-link, focus ring tokens, mobile sidebar Sheet RU labels, `aria-expanded`, live regions QueryState.
|
||||
- [x] DoD: Acceptance checklist §19.
|
||||
|
||||
---
|
||||
|
||||
## 19. Acceptance Checklist
|
||||
|
||||
### Visual consistency
|
||||
|
||||
- [x] Typography unified (theme tokens, не custom fonts per app).
|
||||
- [x] Buttons unified (shadcn Button hierarchy).
|
||||
- [x] Inputs unified (Field).
|
||||
- [x] Forms unified (Sheet + Field).
|
||||
- [x] Cards unified → **Frame** на ops (Card не ops-shell). MM: `DataPageCard` + `OpsPanel`.
|
||||
- [x] Tables unified → DataGrid.
|
||||
- [x] Navigation unified → Chrome Identity Spec.
|
||||
- [x] Spacing unified → `gap-4 md:gap-6`, main padding.
|
||||
- [x] States unified (empty IconStack, QueryState live regions).
|
||||
- [x] Responsive behavior consistent (Sidebar/Sheet collapse + skip-link).
|
||||
|
||||
### Functional regression
|
||||
|
||||
- [x] Add/Create preserved.
|
||||
- [x] Delete preserved.
|
||||
- [x] Edit preserved.
|
||||
- [x] Save/Cancel preserved.
|
||||
- [x] Search preserved (⌘K; MM command palette).
|
||||
- [x] Filter preserved.
|
||||
- [x] Sort preserved.
|
||||
- [x] Pagination preserved.
|
||||
- [x] Navigation preserved.
|
||||
- [x] Permissions preserved (Auth apps allow-list).
|
||||
- [x] Form behavior preserved.
|
||||
- [x] VPS topology canvas preserved (domain, не chrome).
|
||||
- [x] MM terminal / network-map preserved.
|
||||
|
||||
### Accessibility
|
||||
|
||||
- [x] Keyboard navigation (skip-link «К содержимому», sidebar trigger).
|
||||
- [x] Focus states (token `ring-ring`).
|
||||
- [x] Semantic HTML (один `<main>` = SidebarInset `#main-content`).
|
||||
- [x] Accessible labels (`aria-hidden` on decorative IconTile per docs).
|
||||
- [x] Accessible names (`aria-label` AppsMenu / monitor / theme / sidebar).
|
||||
- [x] Error states (`role="alert"` / ReUI Alert).
|
||||
- [x] Disabled states (существующие Button/Field disabled).
|
||||
- [x] Contrast (semantic tokens).
|
||||
|
||||
### REUI PRO
|
||||
|
||||
- [x] Current official documentation reviewed ([docs](https://reui.io/docs), [llms.txt](https://reui.io/llms.txt)).
|
||||
- [x] MCP/Skills reviewed (`user-reui`, skill `668fb463eb`).
|
||||
- [x] Every proposed component verified via `get_block` / `get_component` / `get_example`.
|
||||
- [x] Every UI-related CHANGE has a REUI PRO Ref ID.
|
||||
- [x] Every Ref ID has an official URL.
|
||||
- [x] URLs point to specific relevant documentation.
|
||||
- [x] Every 1:1 mapping verified (only where structure matches).
|
||||
- [x] Partial mappings explicitly marked.
|
||||
- [x] Missing / forbidden components marked (settings-7, empty-state-11).
|
||||
- [x] No fabricated components / variants / URLs / references.
|
||||
|
||||
---
|
||||
|
||||
## 20. Risks / Unknowns
|
||||
|
||||
| Risk | Level | Notes |
|
||||
|---|---|---|
|
||||
| Data Grid TanStack v9 docs vs installed `^8.21.x` | High | Не апгрейдить kit в том же PR, что chrome. `<uncertain>` полный changelog v8→v9. |
|
||||
| MM DataPageCard blast radius (много page.tsx) | High | Менять обёртку, не бизнес-логику. |
|
||||
| MM Next vs Vite chrome port | High | Эквивалент UX, не copy-paste Vite files blindly. |
|
||||
| MM hardcoded footer user | Medium | Нужен реальный user source; `<uncertain>` есть ли auth user в MM UI. |
|
||||
| Shared kit npm package | Out of scope | Не сливать `@cfdm/ui` / `@evobgp/ui` / … |
|
||||
| Auth login auth-18 vs rules auth-13 | Low | KEEP auth-18; rules cite auth-13 — documented exception. |
|
||||
| VPS dashboard QA order | Low | KEEP exception unless product says otherwise. |
|
||||
| CFDM Card-free claim | Low | grep 0 в `apps/web`; `packages/ui` Card primitive остаётся для shadcn internals. |
|
||||
| Pixel-perfect main padding MM | Low | Phase 1+6: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5`; skip-link. |
|
||||
| EvoBGP RHF unused | `<uncertain>` | WEB-05 vs src; не ломать формы ради RHF. |
|
||||
| FW stepper unused | Low | Не удалять в этом плане без usage audit. |
|
||||
|
||||
**Не трогать (technical constraints):** API contracts, SSO/OIDC, permissions, TanStack vs Next routing, Go vs Fastify, package names, CRUD behaviour.
|
||||
|
||||
---
|
||||
|
||||
## 21. Final Consistency Gate
|
||||
|
||||
| Requirement | Status | Evidence |
|
||||
|---|---|---|
|
||||
| All workspace projects discovered | PASS | 6 git roots in workspace |
|
||||
| All discovered UI projects audited | PASS | §§7–12 |
|
||||
| All audited projects represented in final plan | PASS | coverage table + CHANGE matrix |
|
||||
| Coverage = 100% | PASS | 6/6/6 |
|
||||
| Current REUI PRO documentation reviewed | PASS | docs, llms.txt, MCP get_block/get_component 2026-08-17 |
|
||||
| MCP/Skills reviewed | PASS | user-reui ready; skill 668fb463eb |
|
||||
| Project rules reviewed | PASS | §5–6 |
|
||||
| UX/UI rules evaluated | PASS | KEEP/MODIFY/REPLACE/NEW |
|
||||
| Every proposed REUI component verified | PASS | registry §15 |
|
||||
| Every UI CHANGE has REUI PRO Ref ID | PASS | matrix; CHANGE-023 chrome via REUI-010 |
|
||||
| Every Ref ID has official documentation URL | PASS | markdown links |
|
||||
| URLs point to specific REUI PRO references | PASS | preview/docs pages, not homepage |
|
||||
| Every 1:1 mapping verified | PASS | only Frame/IconTile/Filters/Badge/etc. where MCP matches |
|
||||
| Partial mappings marked | PASS | stats-12, shells, sheets, data-grid v8 |
|
||||
| Missing REUI components marked | PASS | settings-7/empty-state-11 not target |
|
||||
| No fabricated workspace data | PASS | real paths; `<not_found>` CFDM AGENTS.md; `<uncertain>` noted |
|
||||
| No fabricated REUI PRO data | PASS | MCP-confirmed URLs only |
|
||||
| Existing functionality preserved | PASS | each CHANGE Preserve + § constraints |
|
||||
| Accessibility reviewed | PASS | IconTile a11y from official API; checklist |
|
||||
| Responsive behavior reviewed | PASS | existing sidebar/sheet; Phase 6 verify |
|
||||
| Target UX/UI rules defined | PASS | TARGET-001…010 + Chrome spec |
|
||||
| Implementation plan complete | PASS | Phases 0–6 executed 2026-08-17 |
|
||||
| Production/source code implemented | PASS | все 6 git roots; CHANGE-001…034 |
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
### Аудит (Phase 0)
|
||||
|
||||
- [x] Все 6 проектов обнаружены и проанализированы.
|
||||
- [x] Coverage 100%.
|
||||
- [x] Chrome Identity Spec зафиксирован как обязательный 1:1.
|
||||
- [x] REUI registry + CHANGE matrix с прямыми URL.
|
||||
- [x] Final Consistency Gate пройден.
|
||||
- [x] Создан `c:\Users\shats\Dev\vps-tracker\UX-UI-PLAN.md`.
|
||||
|
||||
### Implementation (Phases 1–6) — 2026-08-17
|
||||
|
||||
- [x] CHANGE-001…034 выполнены (execution log §17).
|
||||
- [x] Chrome Identity Spec во всех 6.
|
||||
- [x] IconTile + hybrid KPI/QA kit.
|
||||
- [x] Ops Card → Frame (VPS/EvoBGP/MM `OpsPanel`).
|
||||
- [x] Empty IconStack; Sheet + AlertDialog.
|
||||
- [x] a11y: skip-link, один `main`, live regions.
|
||||
- [x] Acceptance checklist §19 отмечен.
|
||||
|
||||
**План закрыт.** Коммиты — по отдельному запросу.
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 697 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user