From 9fa6269174ecaf047d2f9dbafd090af94cffdb3d Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 26 Oct 2026 13:10:58 +0700 Subject: [PATCH] feat: harden firewall policies telemetry and operations Implement reliable agent apply, telemetry v2, analytics retention, guarded rollouts, enrollment, labels and webhooks. Improve ReUI frontend data safety and bounded list loading, and add Docker smoke, recovery and resource checks. Document local verification and pending native acceptance. --- .agents/skills/reui/SKILL.md | 103 +- .agents/skills/reui/rules/adapting.md | 7 +- .agents/skills/reui/rules/anti-patterns.md | 117 + .agents/skills/reui/rules/cli.md | 20 +- .agents/skills/reui/rules/color.md | 74 + .agents/skills/reui/rules/components.md | 183 +- .agents/skills/reui/rules/compose.md | 62 + .agents/skills/reui/rules/icons.md | 28 +- .agents/skills/reui/rules/layout.md | 56 + .agents/skills/reui/rules/motion.md | 80 + .agents/skills/reui/rules/quality.md | 45 +- .agents/skills/reui/rules/registry.md | 23 +- .agents/skills/reui/rules/theming.md | 37 + .agents/skills/reui/rules/workflow.md | 26 +- .agents/skills/reui/tools.md | 24 +- .gitea/workflows/cd.yaml | 12 +- .gitea/workflows/quality.yaml | 63 +- .serena/.gitignore | 2 + .serena/project.yml | 187 + AGENTS.md | 2 +- apps/api/package.json | 2 +- apps/api/src/agent-scripts/evofw-firewall.sh | 1509 ++++--- apps/api/src/agent-scripts/install.sh | 97 +- .../src/agent-scripts/mikrotik-install.rsc | 631 ++- apps/api/src/app.ts | 35 +- apps/api/src/config.ts | 15 + apps/api/src/plugins/auth.ts | 26 +- apps/api/src/plugins/error-handler.ts | 3 +- apps/api/src/plugins/operational.ts | 105 + apps/api/src/routes/agent-labels.ts | 46 + apps/api/src/routes/agent.ts | 296 +- apps/api/src/routes/agents.ts | 89 +- apps/api/src/routes/control.ts | 4 + apps/api/src/routes/dashboard.ts | 7 +- apps/api/src/routes/enrollment.ts | 37 + apps/api/src/routes/install-links.ts | 5 +- apps/api/src/routes/integrations-evobgp.ts | 41 +- apps/api/src/routes/lists.ts | 72 +- apps/api/src/routes/policy-rollouts.ts | 68 + apps/api/src/routes/policy-sets.ts | 2 +- apps/api/src/routes/settings.ts | 14 +- apps/api/src/routes/stats.ts | 53 +- apps/api/src/routes/ui-summary.ts | 32 + apps/api/src/routes/webhooks.ts | 28 + .../services/agent-hardening-budget.test.ts | 64 + .../services/agent-hardening-cache.test.ts | 63 + .../agent-hardening-credentials.test.ts | 148 + .../agent-hardening-dualstack.test.ts | 123 + .../services/agent-hardening-mikrotik.test.ts | 141 + .../agent-hardening-telemetry.test.ts | 317 ++ apps/api/src/services/agent-hardening.test.ts | 213 + apps/api/src/services/analytics.test.ts | 217 + apps/api/src/services/analytics.ts | 144 + apps/api/src/services/audit-outbox.ts | 99 + apps/api/src/services/audit.test.ts | 6 +- apps/api/src/services/audit.ts | 79 +- .../src/services/backend-hardening.test.ts | 192 + apps/api/src/services/detail-stats.test.ts | 99 + apps/api/src/services/enrollment.test.ts | 108 + apps/api/src/services/enrollment.ts | 100 + apps/api/src/services/evobgp.ts | 41 + apps/api/src/services/install-links.test.ts | 10 +- apps/api/src/services/install-links.ts | 20 +- apps/api/src/services/ip-block-stats.test.ts | 7 +- apps/api/src/services/labels.test.ts | 117 + apps/api/src/services/labels.ts | 66 + .../src/services/list-import-summary.test.ts | 108 + apps/api/src/services/lists/entries.ts | 256 +- apps/api/src/services/lists/refresh.ts | 156 +- apps/api/src/services/operational.test.ts | 187 + .../services/policy/cache-approval.test.ts | 121 + .../src/services/policy/cache-budget.test.ts | 198 + apps/api/src/services/policy/cache.ts | 74 + apps/api/src/services/policy/evaluate.ts | 130 +- apps/api/src/services/policy/explain.test.ts | 166 + apps/api/src/services/policy/explain.ts | 92 + .../src/services/policy/mikrotik-rsc.test.ts | 130 +- apps/api/src/services/policy/mikrotik-rsc.ts | 200 +- .../src/services/policy/resolve-hostname.ts | 59 +- apps/api/src/services/policy/rollouts.test.ts | 212 + apps/api/src/services/policy/rollouts.ts | 166 + apps/api/src/services/port-acl.test.ts | 4 +- apps/api/src/services/row-mappers.ts | 39 + apps/api/src/services/safe-fetch.ts | 121 + .../api/src/services/settings-secrets.test.ts | 31 + apps/api/src/services/source-dns.test.ts | 45 + .../api/src/services/source-hardening.test.ts | 175 + apps/api/src/services/telemetry.test.ts | 196 + apps/api/src/services/telemetry.ts | 158 + apps/api/src/services/ui-summary.test.ts | 135 + apps/api/src/services/uniq.ts | 10 +- .../src/services/webhook-transport.test.ts | 52 + apps/api/src/services/webhook-transport.ts | 43 + apps/api/src/services/webhooks-routes.test.ts | 30 + apps/api/src/services/webhooks.test.ts | 114 + apps/api/src/services/webhooks.ts | 161 + .../src/components/agents/add-agent-sheet.tsx | 10 +- .../components/agents/agent-blocked-ips.tsx | 3 + .../components/agents/agent-blocked-ports.tsx | 3 + .../agents/agent-credentials-sheet.tsx | 278 ++ .../components/agents/agent-detail-page.tsx | 86 +- .../agents/agent-diagnostics-panel.test.tsx | 37 + .../agents/agent-diagnostics-panel.tsx | 69 + .../components/agents/agent-explain-sheet.tsx | 83 + .../components/agents/agent-host-firewall.tsx | 5 +- .../components/agents/agent-labels-sheet.tsx | 608 +++ .../agents/agent-policy-sets-sortable.tsx | 33 +- .../agents/agent-port-acl-forms.tsx | 66 + .../src/components/agents/agent-port-acl.tsx | 436 +- .../agents/agent-settings-sheets.tsx | 93 +- .../components/agents/agents-fleet-chart.tsx | 97 +- .../components/agents/explain-validation.ts | 18 + .../components/agents/f1-validation.test.tsx | 46 + .../components/agents/override-validation.ts | 27 + .../agents/port-acl-validation.test.ts | 24 + .../components/agents/port-acl-validation.ts | 58 + .../web/src/components/f2-validation.test.tsx | 144 + apps/web/src/components/form-sheet.tsx | 8 +- .../src/components/layout/portal-shell.tsx | 72 +- .../src/components/lists/lists-columns.tsx | 15 +- .../web/src/components/named-multi-select.tsx | 92 + apps/web/src/components/portal/chrome.tsx | 46 +- apps/web/src/components/query-state.test.tsx | 34 + apps/web/src/components/query-state.tsx | 35 +- .../src/components/reui-kit/resource-page.tsx | 30 +- .../reui/data-grid/data-grid-table.tsx | 15 +- apps/web/src/components/reui/sortable.tsx | 13 +- .../rules/policy-rollouts-sheet.tsx | 596 +++ .../rules/policy-rules-sortable.tsx | 7 +- .../components/stats/stats-agent-select.tsx | 39 + .../stats/traffic-timeseries-chart.tsx | 72 + apps/web/src/components/webhooks-sheet.tsx | 474 +++ apps/web/src/hooks/use-stats-window.ts | 12 + apps/web/src/lib/api.test.ts | 19 + apps/web/src/lib/api.ts | 26 +- apps/web/src/lib/list-import-draft.ts | 27 + apps/web/src/lib/stats-csv.test.ts | 28 + apps/web/src/lib/stats-csv.ts | 27 + apps/web/src/lib/timeseries.test.ts | 48 + apps/web/src/lib/timeseries.ts | 86 + apps/web/src/main.tsx | 3 +- apps/web/src/portal-theme.css | 9 + apps/web/src/queries/app-switcher.ts | 5 +- apps/web/src/queries/index.ts | 120 +- apps/web/src/queries/invalidation.test.ts | 15 + apps/web/src/queries/invalidation.ts | 15 + apps/web/src/queries/list-pages.test.ts | 30 + apps/web/src/queries/timeseries.ts | 14 + apps/web/src/routes/_auth/agents/index.tsx | 47 +- apps/web/src/routes/_auth/index.tsx | 11 +- apps/web/src/routes/_auth/lists/$id.tsx | 92 +- apps/web/src/routes/_auth/lists/index.tsx | 7 +- apps/web/src/routes/_auth/rules/$setId.tsx | 19 +- apps/web/src/routes/_auth/rules/index.tsx | 16 +- apps/web/src/routes/_auth/settings.tsx | 46 +- apps/web/src/routes/_auth/stats.tsx | 197 +- apps/web/test/f1.html | 1 + apps/web/test/f1.tsx | 112 + apps/web/test/f2.html | 1 + apps/web/test/f2.tsx | 515 +++ apps/web/test/interaction.html | 10 + apps/web/test/interaction.tsx | 192 + deploy/compose/docker-compose.example.yaml | 6 + deploy/docker/README.md | 10 +- deploy/docker/backup.sh | 11 + deploy/docker/buildkitd.toml | 17 + deploy/docker/docker-bake.hcl | 30 +- deploy/docker/evofw/Dockerfile | 28 +- deploy/docker/evofw/sqlite-backup.mjs | 41 + deploy/docker/mirror-base-images.sh | 98 +- deploy/docker/restore.sh | 13 + docs/agent-credentials.md | 33 + docs/agent-hardening-checkpoint.md | 72 + docs/agent-labels.md | 17 + docs/agents.md | 14 +- docs/architecture.md | 2 +- docs/backend-hardening.md | 69 + docs/frontend-validation.md | 96 + docs/implementation-status.md | 27 + docs/list-import-summary.md | 21 + docs/openapi.schemas.json | 3719 +++++++++++++++++ docs/openapi.yaml | 253 +- docs/operational-hardening.md | 55 + docs/policy-budget-cache.md | 19 + docs/policy-explain.md | 45 + docs/policy-rollouts.md | 31 + docs/runtime-validation.md | 86 + docs/source-hardening.md | 31 + docs/ui-design-contract.md | 16 + packages/db/migrations/012_telemetry_v2.sql | 41 + .../db/migrations/013_stats_analytics.sql | 27 + packages/db/migrations/014_audit_outbox.sql | 9 + .../db/migrations/015_override_expiry.sql | 5 + packages/db/migrations/016_webhooks.sql | 22 + .../db/migrations/017_policy_snapshots.sql | 42 + .../db/migrations/018_agent_credentials.sql | 19 + packages/db/migrations/019_agent_labels.sql | 13 + packages/db/src/client.ts | 24 +- packages/db/src/repositories/agents.ts | 6 +- packages/db/src/repositories/analytics.ts | 82 + packages/db/src/repositories/index.ts | 33 +- packages/db/src/repositories/install-links.ts | 4 +- packages/db/src/repositories/labels.ts | 21 + packages/db/src/repositories/list-pages.ts | 54 + packages/db/src/repositories/lists.ts | 36 +- packages/db/src/repositories/policy.ts | 155 +- packages/db/src/repositories/port-acl.ts | 6 + packages/db/src/repositories/rollouts.ts | 69 + packages/db/src/repositories/stats.ts | 265 +- packages/db/src/repositories/telemetry.ts | 25 + packages/db/src/schema.ts | 67 +- packages/shared/src/contracts.ts | 64 +- packages/shared/src/contracts/enrollment.ts | 33 + packages/shared/src/contracts/explain.ts | 30 + packages/shared/src/contracts/labels.ts | 16 + packages/shared/src/contracts/list-import.ts | 26 + .../shared/src/contracts/policy-response.ts | 7 + packages/shared/src/contracts/rollouts.ts | 37 + packages/shared/src/contracts/telemetry.ts | 57 + packages/shared/src/contracts/timeseries.ts | 20 + packages/shared/src/contracts/ui-summary.ts | 14 + packages/shared/src/contracts/webhooks.ts | 16 + packages/shared/src/index.ts | 12 + packages/shared/src/list-entries.ts | 61 +- packages/shared/src/network.test.ts | 21 + packages/shared/src/network.ts | 92 + packages/shared/src/permissions.ts | 28 +- packages/shared/src/policy-budget.ts | 3 + scripts/bench/bundle.mjs | 18 + scripts/bench/fixture.mjs | 33 + scripts/bench/load.mjs | 147 + scripts/bench/runtime.sh | 39 + scripts/ci/benchmark-fixture.test.mjs | 23 + scripts/ci/generate-openapi-schemas.mjs | 53 + scripts/ci/image-smoke.sh | 100 + scripts/ci/linux-agent-fixture.py | 60 + scripts/ci/linux-agent-fixture.test.py | 21 + scripts/ci/linux-netns-smoke.sh | 80 + scripts/ci/manifest-platform.mjs | 12 + scripts/ci/manifest-platform.test.mjs | 23 + scripts/ci/prepare-builder.sh | 15 + scripts/ci/sqlite-backup.test.mjs | 50 + 242 files changed, 19872 insertions(+), 3057 deletions(-) create mode 100644 .agents/skills/reui/rules/anti-patterns.md create mode 100644 .agents/skills/reui/rules/color.md create mode 100644 .agents/skills/reui/rules/compose.md create mode 100644 .agents/skills/reui/rules/layout.md create mode 100644 .agents/skills/reui/rules/motion.md create mode 100644 .agents/skills/reui/rules/theming.md create mode 100644 .serena/.gitignore create mode 100644 .serena/project.yml create mode 100644 apps/api/src/plugins/operational.ts create mode 100644 apps/api/src/routes/agent-labels.ts create mode 100644 apps/api/src/routes/enrollment.ts create mode 100644 apps/api/src/routes/policy-rollouts.ts create mode 100644 apps/api/src/routes/ui-summary.ts create mode 100644 apps/api/src/routes/webhooks.ts create mode 100644 apps/api/src/services/agent-hardening-budget.test.ts create mode 100644 apps/api/src/services/agent-hardening-cache.test.ts create mode 100644 apps/api/src/services/agent-hardening-credentials.test.ts create mode 100644 apps/api/src/services/agent-hardening-dualstack.test.ts create mode 100644 apps/api/src/services/agent-hardening-mikrotik.test.ts create mode 100644 apps/api/src/services/agent-hardening-telemetry.test.ts create mode 100644 apps/api/src/services/agent-hardening.test.ts create mode 100644 apps/api/src/services/analytics.test.ts create mode 100644 apps/api/src/services/analytics.ts create mode 100644 apps/api/src/services/audit-outbox.ts create mode 100644 apps/api/src/services/backend-hardening.test.ts create mode 100644 apps/api/src/services/detail-stats.test.ts create mode 100644 apps/api/src/services/enrollment.test.ts create mode 100644 apps/api/src/services/enrollment.ts create mode 100644 apps/api/src/services/evobgp.ts create mode 100644 apps/api/src/services/labels.test.ts create mode 100644 apps/api/src/services/labels.ts create mode 100644 apps/api/src/services/list-import-summary.test.ts create mode 100644 apps/api/src/services/operational.test.ts create mode 100644 apps/api/src/services/policy/cache-approval.test.ts create mode 100644 apps/api/src/services/policy/cache-budget.test.ts create mode 100644 apps/api/src/services/policy/cache.ts create mode 100644 apps/api/src/services/policy/explain.test.ts create mode 100644 apps/api/src/services/policy/explain.ts create mode 100644 apps/api/src/services/policy/rollouts.test.ts create mode 100644 apps/api/src/services/policy/rollouts.ts create mode 100644 apps/api/src/services/safe-fetch.ts create mode 100644 apps/api/src/services/settings-secrets.test.ts create mode 100644 apps/api/src/services/source-dns.test.ts create mode 100644 apps/api/src/services/source-hardening.test.ts create mode 100644 apps/api/src/services/telemetry.test.ts create mode 100644 apps/api/src/services/telemetry.ts create mode 100644 apps/api/src/services/ui-summary.test.ts create mode 100644 apps/api/src/services/webhook-transport.test.ts create mode 100644 apps/api/src/services/webhook-transport.ts create mode 100644 apps/api/src/services/webhooks-routes.test.ts create mode 100644 apps/api/src/services/webhooks.test.ts create mode 100644 apps/api/src/services/webhooks.ts create mode 100644 apps/web/src/components/agents/agent-credentials-sheet.tsx create mode 100644 apps/web/src/components/agents/agent-diagnostics-panel.test.tsx create mode 100644 apps/web/src/components/agents/agent-diagnostics-panel.tsx create mode 100644 apps/web/src/components/agents/agent-explain-sheet.tsx create mode 100644 apps/web/src/components/agents/agent-labels-sheet.tsx create mode 100644 apps/web/src/components/agents/agent-port-acl-forms.tsx create mode 100644 apps/web/src/components/agents/explain-validation.ts create mode 100644 apps/web/src/components/agents/f1-validation.test.tsx create mode 100644 apps/web/src/components/agents/override-validation.ts create mode 100644 apps/web/src/components/agents/port-acl-validation.test.ts create mode 100644 apps/web/src/components/agents/port-acl-validation.ts create mode 100644 apps/web/src/components/f2-validation.test.tsx create mode 100644 apps/web/src/components/named-multi-select.tsx create mode 100644 apps/web/src/components/query-state.test.tsx create mode 100644 apps/web/src/components/rules/policy-rollouts-sheet.tsx create mode 100644 apps/web/src/components/stats/stats-agent-select.tsx create mode 100644 apps/web/src/components/stats/traffic-timeseries-chart.tsx create mode 100644 apps/web/src/components/webhooks-sheet.tsx create mode 100644 apps/web/src/hooks/use-stats-window.ts create mode 100644 apps/web/src/lib/api.test.ts create mode 100644 apps/web/src/lib/list-import-draft.ts create mode 100644 apps/web/src/lib/stats-csv.test.ts create mode 100644 apps/web/src/lib/stats-csv.ts create mode 100644 apps/web/src/lib/timeseries.test.ts create mode 100644 apps/web/src/lib/timeseries.ts create mode 100644 apps/web/src/queries/invalidation.test.ts create mode 100644 apps/web/src/queries/invalidation.ts create mode 100644 apps/web/src/queries/list-pages.test.ts create mode 100644 apps/web/src/queries/timeseries.ts create mode 100644 apps/web/test/f1.html create mode 100644 apps/web/test/f1.tsx create mode 100644 apps/web/test/f2.html create mode 100644 apps/web/test/f2.tsx create mode 100644 apps/web/test/interaction.html create mode 100644 apps/web/test/interaction.tsx create mode 100644 deploy/docker/backup.sh create mode 100644 deploy/docker/buildkitd.toml create mode 100644 deploy/docker/evofw/sqlite-backup.mjs create mode 100644 deploy/docker/restore.sh create mode 100644 docs/agent-credentials.md create mode 100644 docs/agent-hardening-checkpoint.md create mode 100644 docs/agent-labels.md create mode 100644 docs/backend-hardening.md create mode 100644 docs/frontend-validation.md create mode 100644 docs/implementation-status.md create mode 100644 docs/list-import-summary.md create mode 100644 docs/openapi.schemas.json create mode 100644 docs/operational-hardening.md create mode 100644 docs/policy-budget-cache.md create mode 100644 docs/policy-explain.md create mode 100644 docs/policy-rollouts.md create mode 100644 docs/runtime-validation.md create mode 100644 docs/source-hardening.md create mode 100644 packages/db/migrations/012_telemetry_v2.sql create mode 100644 packages/db/migrations/013_stats_analytics.sql create mode 100644 packages/db/migrations/014_audit_outbox.sql create mode 100644 packages/db/migrations/015_override_expiry.sql create mode 100644 packages/db/migrations/016_webhooks.sql create mode 100644 packages/db/migrations/017_policy_snapshots.sql create mode 100644 packages/db/migrations/018_agent_credentials.sql create mode 100644 packages/db/migrations/019_agent_labels.sql create mode 100644 packages/db/src/repositories/analytics.ts create mode 100644 packages/db/src/repositories/labels.ts create mode 100644 packages/db/src/repositories/list-pages.ts create mode 100644 packages/db/src/repositories/rollouts.ts create mode 100644 packages/db/src/repositories/telemetry.ts create mode 100644 packages/shared/src/contracts/enrollment.ts create mode 100644 packages/shared/src/contracts/explain.ts create mode 100644 packages/shared/src/contracts/labels.ts create mode 100644 packages/shared/src/contracts/list-import.ts create mode 100644 packages/shared/src/contracts/policy-response.ts create mode 100644 packages/shared/src/contracts/rollouts.ts create mode 100644 packages/shared/src/contracts/telemetry.ts create mode 100644 packages/shared/src/contracts/timeseries.ts create mode 100644 packages/shared/src/contracts/ui-summary.ts create mode 100644 packages/shared/src/contracts/webhooks.ts create mode 100644 packages/shared/src/network.test.ts create mode 100644 packages/shared/src/network.ts create mode 100644 packages/shared/src/policy-budget.ts create mode 100644 scripts/bench/bundle.mjs create mode 100644 scripts/bench/fixture.mjs create mode 100644 scripts/bench/load.mjs create mode 100644 scripts/bench/runtime.sh create mode 100644 scripts/ci/benchmark-fixture.test.mjs create mode 100644 scripts/ci/generate-openapi-schemas.mjs create mode 100644 scripts/ci/image-smoke.sh create mode 100644 scripts/ci/linux-agent-fixture.py create mode 100644 scripts/ci/linux-agent-fixture.test.py create mode 100644 scripts/ci/linux-netns-smoke.sh create mode 100644 scripts/ci/manifest-platform.mjs create mode 100644 scripts/ci/manifest-platform.test.mjs create mode 100644 scripts/ci/prepare-builder.sh create mode 100644 scripts/ci/sqlite-backup.test.mjs diff --git a/.agents/skills/reui/SKILL.md b/.agents/skills/reui/SKILL.md index 4e024d6..b2ae48c 100644 --- a/.agents/skills/reui/SKILL.md +++ b/.agents/skills/reui/SKILL.md @@ -1,68 +1,83 @@ --- name: reui -description: Use the ReUI registry from your AI agent - find, install, and correctly use ReUI components (the 22 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. +description: "ReUI design and component rules for React apps on shadcn/ui and Tailwind. Use for any UI work in a project with @reui in components.json or a components/reui folder, and in any shadcn project that asks for data grids, kanban boards, dashboards or full pages: building or restyling pages, forms and tables; colors, theme tokens and dark mode; spacing, typography and layout; animation and hover motion; installing ReUI components, examples, Pro blocks or Motion Icons. Works with the ReUI MCP." allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) metadata: user-invocable: "false" --- -> **ReUI skill version `d9f4a302f4`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer (see `get_agent_skill` -> `install.recommended`) to update this skill. Cloud/tools-only agents have no local file and always read the latest - they can ignore this. +> **ReUI skill version `68f0e6fa91`.** If the ReUI MCP's `get_agent_skill` reports a newer `version`, re-run the ReUI installer in the mode it was installed with to update this skill: `get_agent_skill` -> `install.recommended` from the project root, with `REUI_GLOBAL=1` before `node` when this file is `~/.claude/skills/reui/SKILL.md` or `~/.agents/skills/reui/SKILL.md` (a global install). Cloud/tools-only agents have no local file and always read the latest - they can ignore this. -# ReUI for Agents +# ReUI -ReUI is a shadcn-compatible registry. It ships four things you **reuse** - never redesign: +ReUI is an existing design system on shadcn/ui. Keep its look. Generic frontend advice (vary fonts or themes, gradients, page-load or staggered reveals, "2-3 intentional motions", Framer Motion) does not apply here. +It ships free components, free `c-*` examples, Pro blocks and Ultimate Motion Icons through the shadcn CLI; the ReUI MCP finds them and serves their APIs. -- **components** - the 22 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-base-2`, `settings-2`); Pro or Ultimate license at install -- **icons** - Motion Icons in 4 styles, static + hover-animated variants; Ultimate license at install +## Order of authority -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. +1. The installed style (`components.json` `style`, baked into `components/ui` and `components/reui`): it owns radius, height, padding, shadow and overlay motion. +2. The installed component's variants and props (`size`, `variant`, `spacing`). +3. These rules and the topic files. +4. Other design skills, then your taste: where they disagree with 1 to 3, ReUI wins. Never redefine `--ease-out` or any Tailwind built-in. -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. +## Before you write UI code, write this plan in your reply -## The core loop (MCP-native) +1. Surface: operational, marketing or flow. +2. Archetype: 1 from [compose.md](./rules/compose.md). +3. Container family: Frame-led, Card-led, or Cards for the KPIs and a Frame for the table. +4. Density: dense (`size="sm"` on controls that take it) or default. +5. Regions: top to bottom, each with the item that fills it. +6. States: empty, loading and error for each data region. +7. Motion: none, or the 1 entrance. -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/ --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). +Then build exactly that plan; if you deviate, say where. -**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. +## The loop -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. +1. **Find**: `compose_page` for a full page, else `search` with `type` and `component` hints. +2. **Install**: `npx shadcn@latest add @reui/ --yes`, with `` exactly as a tool returned it (shadcn/ui parts install by bare name: `npx shadcn@latest add button --yes`). +3. **Read the API on your base**: `components.json` style `base-nova` is Base UI, `radix-nova` is Radix. Call `get_component([...componentsUsed])` once, then install 1 `get_examples` example and read its files. +4. **Adapt**: change data, copy and props; keep the structure and classes. +5. **Check**, after every UI edit, follow-ups included: `validate_usage` on any name you did not read, the self-checks C1 to C4 in [quality.md](./rules/quality.md), [anti-patterns.md](./rules/anti-patterns.md), `get_audit_checklist`, typecheck, lint. Hand back in the 5 lines quality.md gives. -## Commands +Show every item's `previewUrl` whenever you list, recommend or install it. -Run ReUI as explicit slash commands (via the ReUI MCP) **or** just ask in plain language - both run the same workflow. +## Rules that always apply -| Command | Invoke | Does | -| ----------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------ | -| **build** | `/mcp__reui__build ` | Compose a page/section/feature from ReUI: plan → install → read API → adapt → craft → audit. | -| **add** | `/mcp__reui__add ` | 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). | +- **Owners**: tables `data-grid`, filtering `filters`, boards `kanban`, modals `Dialog` (destructive confirms `AlertDialog`), toasts `sonner`; never hand-roll. +- **Color**: semantic tokens for meaning; state colors through Badge or Alert variants (`@reui/badge` and `@reui/alert`, not shadcn's); raw palette only for categorical identity, always with its `dark:` pair. +- **Contrast**: never `text-success` or `text-warning` as body text in light mode, and never `text--foreground` on a solid `bg-` fill. +- **Shape**: no `rounded-*` except `rounded-full`, and no `shadow-*`, `h-*` or `px-*` on Button, Input, SelectTrigger, Card or Frame; pick `size` and `variant`. A panel is a Card or Frame, never a div with `rounded-lg border`. +- **Spacing**: `gap-*` on the parent, not `space-y-*`; `gap-1` to `gap-2` in a row, `gap-3` to `gap-4` between groups, `gap-6` to `gap-8` between sections; title to description `gap-0.5`. +- **Type**: `text-sm` body, `text-xs` meta (controls keep their style's size), `font-medium` or `font-semibold`, headings `tracking-tight`, numbers `tabular-nums`; nothing below `text-xs` except illustrative mini-UI. +- **Motion**: installed components own their motion. Your own code uses `transition-colors` for hover and press, `duration-200 ease-[cubic-bezier(0.22,1,0.36,1)]` to reveal, `duration-300` with the same curve at most (media hover zoom: 500), and `SPRING` (`{ type: "spring", visualDuration: 0.25, bounce: 0 }`) for drag and reorder. At most 1 entrance animation per view (a stagger counts as 1). Animate only `transform` and `opacity`, CSS before motion/react, never set state per frame; `` once at the root plus `motion-reduce:` on CSS, where reduced motion means a calm fade, not nothing. +- **Surfaces**: prefer 1 container family per screen. With a KPI row and a table, use FramePanels for both, or Cards for the KPIs and a Frame for the table; never a Card inside a Frame or a Frame inside a Card. Icons come from 1 library, the `iconLibrary` in `components.json` (on Ultimate, 1 Motion Icon on the primary action is the exception). +- **States**: each data region has empty (1 action), loading (a `Skeleton` in the real layout) and error (Retry) states; light and dark work. +- **Never invent**: never write a prop, variant or `@reui/` name you did not read in an `api`, an installed file or a tool result. -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. +## Topics -## When to reach for ReUI vs plain shadcn +Always read `compose` before building a screen, and `quality` plus `anti-patterns` at the Check step. Besides those, read only the topic files the task touches, at most 4 (for example `color`, `motion`, `layout` and `components`). -| 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 ``) | -| 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** | +Installed skill: open the file. MCP only: call `get_agent_skill` with the topic, or fetch `https://mcp.reui.io/skill/reui/`. -## Detailed references +| Topic | File | Read when | +| --------------- | -------------------------------------------------- | --------------------------------------------- | +| `compose` | [rules/compose.md](./rules/compose.md) | before building any screen or section | +| `workflow` | [rules/workflow.md](./rules/workflow.md) | `compose_page`, search hints, installing | +| `adapting` | [rules/adapting.md](./rules/adapting.md) | changing an installed block or example | +| `components` | [rules/components.md](./rules/components.md) | `data-grid`, `filters`, `kanban` and the rest | +| `color` | [rules/color.md](./rules/color.md) | any color, status, chart or dark mode choice | +| `theming` | [rules/theming.md](./rules/theming.md) | the styles, radius, density and sizes | +| `motion` | [rules/motion.md](./rules/motion.md) | any transition, animation or motion/react | +| `layout` | [rules/layout.md](./rules/layout.md) | house defaults, hierarchy, surfaces, states | +| `icons` | [rules/icons.md](./rules/icons.md) | icon imports, Motion Icons | +| `quality` | [rules/quality.md](./rules/quality.md) | the done gate: self-checks, a11y, hand-back | +| `anti-patterns` | [rules/anti-patterns.md](./rules/anti-patterns.md) | the Check step: drift pairs, stale habits | +| `registry` | [rules/registry.md](./rules/registry.md) | free vs Pro vs Ultimate, base vs radix | +| `cli` | [rules/cli.md](./rules/cli.md) | `components.json`, license key, install flags | +| `tools` | [tools.md](./tools.md) | MCP result shapes, errors, quota, commands | -- [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 22 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 +## Plans + +Free covers components, `c-*` examples, this skill and the MCP (a ReUI account and a daily allowance). Pro adds blocks. Ultimate adds Motion Icons. A plan miss is a normal answer with `locked: true`; keep building with what the plan covers. diff --git a/.agents/skills/reui/rules/adapting.md b/.agents/skills/reui/rules/adapting.md index b0c1a16..367539c 100644 --- a/.agents/skills/reui/rules/adapting.md +++ b/.agents/skills/reui/rules/adapting.md @@ -30,14 +30,11 @@ The design IS the product. A ReUI block/component encodes senior-designer decisi - 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. +- Never read `window`, `localStorage`, `location`, `Date.now()` or `Math.random()` in render or in a `useState` initializer; read them in an effect. - **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` - 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. +- No decorative buttons, fake tabs, meaningless toggles, equal-weight card walls, empty gradients, ornamental icons, or generic SaaS filler. Every element should do something, and the layout must still hold with long names, empty values and crowded data. diff --git a/.agents/skills/reui/rules/anti-patterns.md b/.agents/skills/reui/rules/anti-patterns.md new file mode 100644 index 0000000..f6a8e5f --- /dev/null +++ b/.agents/skills/reui/rules/anti-patterns.md @@ -0,0 +1,117 @@ +# Anti-patterns + +Read this at the Check step, after the self-checks in [quality.md](./quality.md). Each entry: an `Incorrect:` line, then its `Correct:` fix. Detector: the self-check that prints it (C1 to C4), or `visual` when reading finds it. Source: the pilot run that wrote it, or `default` (a training-data habit). + +## Pairs + +### 1. Stagger on every row (Detector: C2; source: run-08) + +Incorrect: `transition={{ delay: i * 0.05 }}` on all 50 rows of a table +Correct: 1 stagger of at most 6 items, `staggerChildren: 0.05`, never on table rows + +### 2. Transition everything on a card (Detector: C1; source: runs 10, 15) + +Incorrect: `className="rounded-xl border transition-all duration-300 hover:shadow-lg"` +Correct: `transition-colors` for a hover color; a lift is `transform` over a static shadow + +### 3. Raw palette for a state (Detector: C3; source: runs 08, 15) + +Incorrect: `Active` +Correct: `Active` + +### 4. Animated table rows (Detector: C1; source: runs 01, 08, 17) + +Incorrect: `` +Correct: `data-grid` rows, which already hover; never animate rows with motion/react + +### 5. Hover scale on the default spring (Detector: C1; source: runs 01, 10) + +Incorrect: `` with no `transition` +Correct: CSS first: `transition-colors` for hover, `transition-transform active:scale-[0.98] motion-reduce:transition-none` for press + +### 6. Entrance from scale 0 (Detector: C1; source: runs 10, 15) + +Incorrect: `` +Correct: `initial={{ opacity: 0, scale: 0.95 }}`: start at scale 0.95 or more and move 8px or less + +### 7. Hand-built modal or toast (Detector: C4; source: run-15) + +Incorrect: `
` as a modal +Correct: `Dialog`, `AlertDialog` for a destructive confirm, `sonner` for toasts; a palette: ``, as `CommandDialog` holds no `Command` + +### 8. Native select (Detector: C4; source: run-15) + +Incorrect: `
` with mapped `` rows +Correct: `data-grid`: a `useTable({ features: dataGridFeatures })` instance passed as `table={table}`, columns typed `ColumnDef[]` + +### 10. Button inside a clickable wrapper (Detector: visual; source: run-02) + +Incorrect: `` +Correct: the `Button` alone: 1 interactive element, and it already has a press state + +### 11. Radix asChild on Base UI (Detector: C4; source: run-17, judge) + +Incorrect: `` +Correct: `}>Open` on base-nova; a link: `
` 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. +- Build a TanStack table with `useTable({ features: dataGridFeatures, ... })`. `dataGridFeatures` already bundles sorting, filtering, pagination, selection, expanding, pinning, resizing and faceting: no row models to wire. - Pass that instance to ``. - 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). +- Server-side data: `manualPagination: true` and `rowCount: total` in `useTable`, `recordCount={total}`, and fetch with the table's `pagination` state (`DataGridPagination` changes `pageSize`). +- Columns are TanStack v9, `ColumnDef[]`, never v8's `ColumnDef[]`; a sortable column's `header` renders `DataGridColumnHeader` (a string header cannot sort). +- Imports, each from `@/components/reui/data-grid/`: `DataGrid`, `DataGridFeatures` and `dataGridFeatures` from `data-grid`, `DataGridColumnHeader` from `data-grid-column-header`, `DataGridTable` from `data-grid-table`, `DataGridPagination` from `data-grid-pagination`, `DataGridScrollArea` from `data-grid-scroll-area`; `useTable` and `ColumnDef` from `@tanstack/react-table`. +- States: `isLoading` renders skeleton rows from each column's `meta.skeleton` (a `ReactNode`: ``); `emptyMessage` (a `ReactNode`) fills a grid with no rows: after a failed fetch, pass it the error with a Retry. +- Numeric column: `meta: { headerClassName: "text-end [&>div]:justify-end", cellClassName: "text-end tabular-nums" }`. +- Rows edited in place: `autoResetPageIndex: false` in `useTable`, or each write jumps back to page 1. +- In a Frame: `` around `DataGridScrollArea` and `DataGridTable`, plus `tableClassNames={{ base: "min-w-[720px]" }}` so on a phone the table scrolls inside the panel instead of squeezing. ```tsx -const table = useTable({ - features: dataGridFeatures, - data, - columns, -}) +const columns = useMemo[]>(() => [ + { accessorKey: "total", header: ({ column }) => }, +], []) +const table = useTable({ features: dataGridFeatures, data, columns }) ``` -Common mistakes: - -- **Incorrect:** `` - these props do not exist. **Correct:** build a `useTable({ features: dataGridFeatures, ... })` instance and pass `table={table}` + `recordCount`. -- **Incorrect:** a raw `
` / hand-rolled pagination. **Correct:** use `data-grid`; read its API for sticky header, pagination, virtualization, row selection. -- **Incorrect:** styling rows/cells with arbitrary classes. **Correct:** drive layout via `tableLayout` and the primitive's `DataGridColumnMeta` (e.g. `cellClassName`, `headerTitle`), set through the bundle's `columnMeta` slot. +Never a raw `
` or hand-rolled pagination: the API covers sticky header, virtualization and row selection. ## event-calendar @@ -69,7 +70,10 @@ Common mistakes: {Object.entries(cols).map(([id, items]) => ( -

{id}

+
+

{id}

+ +
{items.map((i) => ( @@ -84,7 +88,7 @@ Common mistakes: ``` -**Gotcha:** state is `Record`. Each `KanbanColumnContent value` must match its parent `KanbanColumn value`. Omit `KanbanOverlay` and the drag preview silently breaks. +**Gotcha:** state is `Record`. Each `KanbanColumnContent value` must match its parent `KanbanColumn value`. Omit `KanbanOverlay` and the drag preview silently breaks. `KanbanColumnHandle` is hidden until its column is hovered or it has keyboard focus, so keep the column title outside it. ## sortable @@ -127,9 +131,9 @@ const [query, setQuery] = useState(() => createFilterQuery()) ``` -**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. +**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()`: ids come from `createFilterIdFactory(seed)` seeded off `useId` (never minted in render), and `createFilterRule({ id })` takes one, `createFilterQuery()` none. 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`. +`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, and `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. ## cascader @@ -157,9 +161,9 @@ const [query, setQuery] = useState(() => createFilterQuery()) ``` -**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. +**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 the current level and everything under it, `searchScope="global"` the whole tree from any level, and both annotate 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"`. +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), `virtualize` plus `CascaderVirtualItems` for long levels, and `getChildren` for async levels (paging, retry, `prefetch`). `CascaderFooter` pins commands below the list and `CascaderSubmenu` opens one as a side flyout. 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. ## date-selector @@ -285,7 +289,7 @@ const [value, setValue] = useState() ``` -**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 +**Gotcha:** import from `@/components/reui/number-field`. The accessible label goes on `NumberFieldScrubArea`, not `NumberField`. External API: https://base-ui.com/react/components/number-field ## rating @@ -314,7 +318,7 @@ const [value, setValue] = useState() ``` -**Gotcha:** each link's `data-scrollspy-anchor` must match a section `id`. `targetRef` is the scrollable container (defaults to the window). +**Gotcha:** each link's `data-scrollspy-anchor` must match a section `id`. `targetRef` is the scrollable container (defaults to the window). `smooth` defaults to true: pass `smooth={!reduceMotion}` from motion/react's `useReducedMotion()`. Mark the active link `aria-current="location"` (track it with `onUpdate`); `"page"` is for route links. ## frame @@ -323,18 +327,16 @@ const [value, setValue] = useState() ```tsx - - - Title - Description - -
Content
- Footer -
+ + Title + Description + + Content + Footer ``` -**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. +**Gotcha:** the shell for data and tool-like surfaces. `spacing` (`xs` | `sm` | `default` | `lg`) sets panel padding and the gap between panels, so pick it instead of padding classes (the 1 exception: `` around a `data-grid`); `variant` is `default` | `inverse` | `ghost`; `stacked` joins panels with shared borders; `dense` removes the frame's own inset and gap (panels sit flush), and panel padding stays; `--frame-radius` is the one radius knob (panels derive `--frame-panel-radius`). ## icon-stack @@ -380,7 +382,7 @@ const [value, setValue] = useState() ``` -**Gotcha:** the one-liner is already a complete block; every child (header, title, language label, copy button, wrap toggle, expand button, line actions) is optional chrome, at any depth. Shiki is the only npm dependency and loads lazily, one chunk per language, on first highlight; `lines` (from `highlightCode` in `code-block-highlight`, which is server-safe and has no `"use client"`) or `highlight={false}` loads nothing at all. `maxLines` caps the height AND marks the block collapsible, which is what makes `CodeBlockExpandButton` appear. Use `variant="ghost"` when the block sits inside a surface that already has a border. +**Gotcha:** the one-liner is already a complete block; every child is optional chrome, at any depth. Shiki is the only npm dependency and loads lazily on first highlight; `lines` (from `highlightCode` in `code-block-highlight`, which is server-safe and has no `"use client"`) or `highlight={false}` loads nothing at all. `maxLines` caps the height AND marks the block collapsible, which is what makes `CodeBlockExpandButton` appear. Use `variant="ghost"` when the block sits inside a surface that already has a border. ## alert @@ -396,7 +398,7 @@ const [value, setValue] = useState() ``` -**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. +**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. ## badge @@ -404,25 +406,116 @@ const [value, setValue] = useState() **Shape:** ```tsx -Success -Pill +Success +Draft ``` -**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. +**Gotcha:** shadcn-compatible. Rich `variant` set (solid, `-outline`, `-light` per color), `size` `xs..xl` (keep the default), `radius` `default | full`. Like `alert`, the color variants rely on ReUI extended tokens. Prefer `Badge` variants over raw color classes: `-light` for a state, `outline` for a neutral label; an omitted `variant` is the solid primary fill. + +## color-picker + +**Required:** none (`` alone is complete); wire `value` + `onValueChange`. +**Shape:** + +```tsx + + + + + + + + + + + +``` + +**Gotcha:** the value is a CSS color string in `format` (`"hex"` default, `"rgb"`, `"hsl"`, `"hsb"` or `"oklch"`), `null` when empty, never an object. `onValueChange` fires on every drag move, so save in `onValueCommit`. Without `alpha` the value is always opaque. A bare `ColorPickerPanel` inside the root is an inline picker; `ColorPickerInput` replaces the trigger with a typable field. + +## emoji-picker + +**Required:** none; wire `onEmojiSelect` to insert a pick, or `value` + `onValueChange` to use it as a field. +**Shape:** + +```tsx + insert(value)}> + + + + + + + + + + + + + +``` + +**Gotcha:** the emoji data is fetched at runtime from a pinned jsDelivr path (`EMOJI_PICKER_DATA_URL`): under a strict Content Security Policy allow it in `connect-src`, self-host with `dataUrl`, or pass `data` (from `normalizeEmojibaseData`). Store the pick's `value` (the native emoji in its skin tone, or `:shortcode:` for a custom one). `multiple` makes the value a list, capped by `max`. + +## file-type + +**Required:** none; pass `type` (an extension, a family name or any string). +**Shape:** + +```tsx + +