@nextsparkjs/ai-workflow 0.1.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: permissions-system
|
|
3
|
+
description: |
|
|
4
|
+
Three-layer permission system (Team Roles + Plans + Quotas) for this Next.js application.
|
|
5
|
+
Covers user roles, team roles, theme extensions, permission checking, and RLS integration.
|
|
6
|
+
Use this skill when implementing or modifying access control features.
|
|
7
|
+
allowed-tools: Read, Glob, Grep
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Permissions System Skill
|
|
12
|
+
|
|
13
|
+
Three-layer permission architecture for role-based access control, feature gating, and usage limits.
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
THREE-LAYER PERMISSION SYSTEM:
|
|
19
|
+
|
|
20
|
+
Layer 1: Team Roles (RBAC)
|
|
21
|
+
├── Core roles: owner (100), admin (50), member (10), viewer (1)
|
|
22
|
+
├── Theme can extend: editor, contributor, moderator, etc.
|
|
23
|
+
└── Permissions defined in permissions.config.ts
|
|
24
|
+
|
|
25
|
+
Layer 2: Plans (Feature Gating)
|
|
26
|
+
├── Subscription-based feature access
|
|
27
|
+
├── Plan features defined in plans.config.ts
|
|
28
|
+
└── Checked via membership.hasFeature()
|
|
29
|
+
|
|
30
|
+
Layer 3: Quotas (Usage Limits)
|
|
31
|
+
├── Per-plan usage tracking
|
|
32
|
+
├── Enforced via membership.checkQuota()
|
|
33
|
+
└── Tracked in usage table
|
|
34
|
+
|
|
35
|
+
core/lib/permissions/
|
|
36
|
+
├── types.ts # Permission type definitions
|
|
37
|
+
├── check.ts # Server-side permission checks
|
|
38
|
+
├── hooks.ts # Frontend permission hooks
|
|
39
|
+
├── system.ts # Core system permissions
|
|
40
|
+
└── merge.ts # Configuration merging
|
|
41
|
+
|
|
42
|
+
core/lib/services/
|
|
43
|
+
├── permission.service.ts # O(1) permission lookups
|
|
44
|
+
└── membership.service.ts # Complete membership context
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## When to Use This Skill
|
|
48
|
+
|
|
49
|
+
- Implementing role-based access control
|
|
50
|
+
- Adding entity permissions
|
|
51
|
+
- Extending team roles in themes
|
|
52
|
+
- Checking permissions in API routes
|
|
53
|
+
- Rendering UI based on permissions
|
|
54
|
+
- Working with feature flags and quotas
|
|
55
|
+
|
|
56
|
+
## User Roles vs Team Roles
|
|
57
|
+
|
|
58
|
+
### CRITICAL: Understanding the Two Role Systems
|
|
59
|
+
|
|
60
|
+
This application uses **two completely separate role systems** for different purposes:
|
|
61
|
+
|
|
62
|
+
| Aspect | User Roles | Team Roles |
|
|
63
|
+
|--------|------------|------------|
|
|
64
|
+
| **Storage** | `user.role` column | `teamMembers.role` column |
|
|
65
|
+
| **Scope** | Global (entire app) | Per-team membership |
|
|
66
|
+
| **Extensible** | ❌ **NO** - Hardcoded in core | ✅ **YES** - Themes add custom roles |
|
|
67
|
+
| **Purpose** | Route access control | Entity permissions |
|
|
68
|
+
| **Checked by** | Middleware, roleHelpers | MembershipService |
|
|
69
|
+
|
|
70
|
+
### User Roles (App-Level, FIXED - NOT Extensible)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// 3 fixed system roles - CANNOT be extended by themes
|
|
74
|
+
// Defined in: core/types/user.types.ts
|
|
75
|
+
type UserRole = 'member' | 'superadmin' | 'developer'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Role | Hierarchy | Access | Routes |
|
|
79
|
+
|------|-----------|--------|--------|
|
|
80
|
+
| `member` | 1 | Standard authenticated user | `/dashboard/*` |
|
|
81
|
+
| `superadmin` | 99 | System admin, bypasses team permissions | `/superadmin/*` |
|
|
82
|
+
| `developer` | 100 | Full access, debugging APIs | `/devtools/*` |
|
|
83
|
+
|
|
84
|
+
**Why NOT extensible:**
|
|
85
|
+
- Security: Route protection must be predictable
|
|
86
|
+
- Core functionality: Middleware relies on fixed set
|
|
87
|
+
- System integrity: Themes cannot grant system-level access
|
|
88
|
+
|
|
89
|
+
**Check user role:**
|
|
90
|
+
```typescript
|
|
91
|
+
import { roleHelpers } from '@/core/lib/role-helpers'
|
|
92
|
+
|
|
93
|
+
// CORRECT: Use roleHelpers for user roles
|
|
94
|
+
if (roleHelpers.isDeveloper(user.role)) {
|
|
95
|
+
// Access to /devtools/*
|
|
96
|
+
}
|
|
97
|
+
if (roleHelpers.isSuperAdmin(user.role)) {
|
|
98
|
+
// Access to /superadmin/*, bypasses team checks
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// WRONG: Never check user roles via membership
|
|
102
|
+
if (membership.hasRole('superadmin')) {} // This checks TEAM role, not user role!
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Team Roles (Team-Level, EXTENSIBLE by Themes)
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Core team roles (protected, always available)
|
|
109
|
+
type CoreTeamRole = 'owner' | 'admin' | 'member' | 'viewer'
|
|
110
|
+
|
|
111
|
+
// Theme can add custom roles
|
|
112
|
+
type TeamRole = CoreTeamRole | 'editor' | 'contributor' | 'moderator' | string
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Role | Hierarchy | Description | Extensible |
|
|
116
|
+
|------|-----------|-------------|------------|
|
|
117
|
+
| `owner` | 100 | Team creator, all permissions, cannot be removed | ❌ Core |
|
|
118
|
+
| `admin` | 50 | Team management, member roles, billing | ❌ Core |
|
|
119
|
+
| `member` | 10 | Standard entity access | ❌ Core |
|
|
120
|
+
| `viewer` | 1 | Read-only access | ❌ Core |
|
|
121
|
+
| `editor` | 5 | Theme-defined: Edit without delete | ✅ Theme |
|
|
122
|
+
| `contributor` | 3 | Theme-defined: Limited create/edit | ✅ Theme |
|
|
123
|
+
| `moderator` | 7 | Theme-defined: Content moderation | ✅ Theme |
|
|
124
|
+
|
|
125
|
+
### Complete Example: Adding Custom Team Roles
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// contents/themes/your-theme/config/permissions.config.ts
|
|
129
|
+
import type { ThemePermissionsConfig } from '@/core/lib/permissions/types'
|
|
130
|
+
|
|
131
|
+
export const PERMISSIONS_CONFIG_OVERRIDES: ThemePermissionsConfig = {
|
|
132
|
+
// 1. Define custom team roles
|
|
133
|
+
roles: {
|
|
134
|
+
additionalRoles: ['editor', 'contributor', 'moderator'] as const,
|
|
135
|
+
hierarchy: {
|
|
136
|
+
editor: 5, // Between viewer (1) and member (10)
|
|
137
|
+
contributor: 3, // Above viewer, below editor
|
|
138
|
+
moderator: 7, // Above editor, below member
|
|
139
|
+
},
|
|
140
|
+
displayNames: {
|
|
141
|
+
editor: 'common.teamRoles.editor', // i18n key
|
|
142
|
+
contributor: 'common.teamRoles.contributor',
|
|
143
|
+
moderator: 'common.teamRoles.moderator',
|
|
144
|
+
},
|
|
145
|
+
descriptions: {
|
|
146
|
+
editor: 'Can view and edit content without delete access',
|
|
147
|
+
contributor: 'Can create and edit own content only',
|
|
148
|
+
moderator: 'Can moderate content and manage comments',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// 2. Define which roles can perform which actions (object format, not array)
|
|
153
|
+
entities: {
|
|
154
|
+
products: [
|
|
155
|
+
{ action: 'read', roles: ['owner', 'admin', 'member', 'editor', 'contributor', 'moderator', 'viewer'] },
|
|
156
|
+
{ action: 'create', roles: ['owner', 'admin', 'member', 'editor', 'contributor'] },
|
|
157
|
+
{ action: 'update', roles: ['owner', 'admin', 'member', 'editor'] },
|
|
158
|
+
{ action: 'delete', roles: ['owner', 'admin'] },
|
|
159
|
+
{ action: 'moderate', roles: ['owner', 'admin', 'moderator'] },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// 3. Define team-level permissions
|
|
164
|
+
teams: [
|
|
165
|
+
{ action: 'team.view', roles: ['owner', 'admin', 'member', 'editor', 'contributor', 'moderator', 'viewer'] },
|
|
166
|
+
{ action: 'team.edit', roles: ['owner', 'admin'] },
|
|
167
|
+
{ action: 'team.members.invite', roles: ['owner', 'admin'] },
|
|
168
|
+
{ action: 'team.members.remove', roles: ['owner', 'admin'] },
|
|
169
|
+
{ action: 'team.members.changeRole', roles: ['owner', 'admin'] },
|
|
170
|
+
{ action: 'team.delete', roles: ['owner'], dangerous: true },
|
|
171
|
+
],
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Translations for Custom Roles
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
// contents/themes/your-theme/messages/en.json
|
|
179
|
+
{
|
|
180
|
+
"common": {
|
|
181
|
+
"teamRoles": {
|
|
182
|
+
"editor": "Editor",
|
|
183
|
+
"contributor": "Contributor",
|
|
184
|
+
"moderator": "Moderator"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// contents/themes/your-theme/messages/es.json
|
|
190
|
+
{
|
|
191
|
+
"common": {
|
|
192
|
+
"teamRoles": {
|
|
193
|
+
"editor": "Editor",
|
|
194
|
+
"contributor": "Colaborador",
|
|
195
|
+
"moderator": "Moderador"
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**IMPORTANT:** Core roles (`owner`, `admin`, `member`, `viewer`) translations are in `core/messages/`. Theme MUST NOT redefine them.
|
|
202
|
+
|
|
203
|
+
## Permission Format
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Pattern: "[scope].[action]"
|
|
207
|
+
type Permission = `${string}.${string}`
|
|
208
|
+
|
|
209
|
+
// Entity permissions
|
|
210
|
+
'customers.create'
|
|
211
|
+
'customers.read'
|
|
212
|
+
'customers.update'
|
|
213
|
+
'customers.delete'
|
|
214
|
+
|
|
215
|
+
// Team permissions
|
|
216
|
+
'team.view'
|
|
217
|
+
'team.edit'
|
|
218
|
+
'team.members.invite'
|
|
219
|
+
'team.members.remove'
|
|
220
|
+
'team.delete'
|
|
221
|
+
|
|
222
|
+
// Feature permissions
|
|
223
|
+
'page-builder.access'
|
|
224
|
+
'page-builder.custom-css'
|
|
225
|
+
'api-keys.manage'
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Theme Extension Pattern
|
|
229
|
+
|
|
230
|
+
### permissions.config.ts
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// contents/themes/default/config/permissions.config.ts
|
|
234
|
+
import type { ThemePermissionsConfig } from '@/core/lib/permissions/types'
|
|
235
|
+
|
|
236
|
+
export const PERMISSIONS_CONFIG_OVERRIDES: ThemePermissionsConfig = {
|
|
237
|
+
// 1. Add custom team roles
|
|
238
|
+
roles: {
|
|
239
|
+
additionalRoles: ['editor'] as const,
|
|
240
|
+
hierarchy: { editor: 5 }, // Between viewer (1) and member (10)
|
|
241
|
+
displayNames: {
|
|
242
|
+
editor: 'common.teamRoles.editor', // i18n key
|
|
243
|
+
},
|
|
244
|
+
descriptions: {
|
|
245
|
+
editor: 'Can view and edit content with limited access',
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
// 2. Team-level permissions
|
|
250
|
+
teams: [
|
|
251
|
+
{ action: 'team.view', roles: ['owner', 'admin', 'member', 'viewer', 'editor'] },
|
|
252
|
+
{ action: 'team.edit', roles: ['owner', 'admin'] },
|
|
253
|
+
{ action: 'team.members.invite', roles: ['owner', 'admin'] },
|
|
254
|
+
{ action: 'team.members.remove', roles: ['owner', 'admin'] },
|
|
255
|
+
{ action: 'team.delete', roles: ['owner'], dangerous: true },
|
|
256
|
+
],
|
|
257
|
+
|
|
258
|
+
// 3. Entity permissions (entity.action format)
|
|
259
|
+
entities: {
|
|
260
|
+
customers: [
|
|
261
|
+
{ action: 'create', roles: ['owner', 'admin'], label: 'Create customers' },
|
|
262
|
+
{ action: 'read', roles: ['owner', 'admin', 'member', 'editor'] },
|
|
263
|
+
{ action: 'update', roles: ['owner', 'admin', 'member'] },
|
|
264
|
+
{ action: 'delete', roles: ['owner'], dangerous: true },
|
|
265
|
+
],
|
|
266
|
+
tasks: [
|
|
267
|
+
{ action: 'create', roles: ['owner', 'admin', 'member'] },
|
|
268
|
+
{ action: 'read', roles: ['owner', 'admin', 'member'] },
|
|
269
|
+
{ action: 'update', roles: ['owner', 'admin', 'member'] },
|
|
270
|
+
{ action: 'delete', roles: ['owner', 'admin'] },
|
|
271
|
+
// Note: 'editor' intentionally excluded from tasks
|
|
272
|
+
],
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// 4. Feature permissions
|
|
276
|
+
features: [
|
|
277
|
+
{
|
|
278
|
+
action: 'page-builder.access',
|
|
279
|
+
roles: ['owner', 'admin', 'editor', 'member'],
|
|
280
|
+
label: 'Access Page Builder',
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
action: 'page-builder.custom-css',
|
|
284
|
+
roles: ['owner', 'admin'],
|
|
285
|
+
dangerous: true,
|
|
286
|
+
label: 'Use custom CSS',
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
|
|
290
|
+
// 5. Override or disable core permissions (optional)
|
|
291
|
+
overrides: {
|
|
292
|
+
// Override specific permission config
|
|
293
|
+
},
|
|
294
|
+
disabled: [
|
|
295
|
+
// Disable specific core permissions
|
|
296
|
+
],
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## PermissionService
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// core/lib/services/permission.service.ts
|
|
304
|
+
|
|
305
|
+
export class PermissionService {
|
|
306
|
+
// Check single permission - O(1)
|
|
307
|
+
static hasPermission(role: string, permission: Permission): boolean
|
|
308
|
+
|
|
309
|
+
// Check any action format (teams.*, entities.*, features.*)
|
|
310
|
+
static canDoAction(role: string, action: string): boolean
|
|
311
|
+
|
|
312
|
+
// Get all permissions for role - O(1)
|
|
313
|
+
static getRolePermissions(role: string): Permission[]
|
|
314
|
+
|
|
315
|
+
// Check multiple permissions
|
|
316
|
+
static hasAnyPermission(role: string, permissions: Permission[]): boolean
|
|
317
|
+
static hasAllPermissions(role: string, permissions: Permission[]): boolean
|
|
318
|
+
|
|
319
|
+
// Get permission configuration
|
|
320
|
+
static getConfig(permission: Permission): ResolvedPermission | undefined
|
|
321
|
+
|
|
322
|
+
// Full matrix for admin UI
|
|
323
|
+
static getMatrix(): { permissions, matrix, sections, roles }
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Usage:**
|
|
328
|
+
```typescript
|
|
329
|
+
import { PermissionService } from '@/core/lib/services/permission.service'
|
|
330
|
+
|
|
331
|
+
// Check if admin can create customers
|
|
332
|
+
PermissionService.hasPermission('admin', 'customers.create') // true
|
|
333
|
+
|
|
334
|
+
// Check if editor can access tasks
|
|
335
|
+
PermissionService.canDoAction('editor', 'tasks.read') // false
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## MembershipService
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// core/lib/services/membership.service.ts
|
|
342
|
+
|
|
343
|
+
// TeamMembership class - returned by MembershipService.get()
|
|
344
|
+
export class TeamMembership {
|
|
345
|
+
// Role checks
|
|
346
|
+
hasMinHierarchy(level: number): boolean
|
|
347
|
+
hasRole(role: string): boolean
|
|
348
|
+
hasAnyRole(roles: string[]): boolean
|
|
349
|
+
|
|
350
|
+
// Permission checks
|
|
351
|
+
hasPermission(permission: Permission): boolean
|
|
352
|
+
hasFeature(feature: string): boolean
|
|
353
|
+
|
|
354
|
+
// Quota checks
|
|
355
|
+
checkQuota(limitSlug: string, increment?: number): { allowed, remaining }
|
|
356
|
+
|
|
357
|
+
// Comprehensive action check (combines all layers)
|
|
358
|
+
canPerformAction(action: string, options?): ActionResult
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export class MembershipService {
|
|
362
|
+
// Get complete membership context
|
|
363
|
+
static async get(userId: string, teamId: string): Promise<TeamMembership>
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Usage:**
|
|
368
|
+
```typescript
|
|
369
|
+
import { MembershipService } from '@/core/lib/services/membership.service'
|
|
370
|
+
|
|
371
|
+
const membership = await MembershipService.get(userId, teamId)
|
|
372
|
+
|
|
373
|
+
// Layer 1: RBAC
|
|
374
|
+
if (membership.hasPermission('customers.delete')) {
|
|
375
|
+
// Can delete customers
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Layer 2: Plan features
|
|
379
|
+
if (membership.hasFeature('advanced_analytics')) {
|
|
380
|
+
// Show analytics dashboard
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Layer 3: Quotas
|
|
384
|
+
const quota = membership.checkQuota('projects', 1)
|
|
385
|
+
if (!quota.allowed) {
|
|
386
|
+
throw new Error('Project limit reached')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Comprehensive check
|
|
390
|
+
const result = membership.canPerformAction('customers.create')
|
|
391
|
+
if (!result.allowed) {
|
|
392
|
+
// result.reason: 'permission_denied' | 'quota_exceeded' | 'feature_disabled'
|
|
393
|
+
throw new Error(result.message)
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Server-Side Permission Checks
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// core/lib/permissions/check.ts
|
|
401
|
+
|
|
402
|
+
// Single permission check
|
|
403
|
+
export async function checkPermission(
|
|
404
|
+
userId: string,
|
|
405
|
+
teamId: string,
|
|
406
|
+
permission: Permission
|
|
407
|
+
): Promise<boolean>
|
|
408
|
+
|
|
409
|
+
// Multiple permissions (AND - all required)
|
|
410
|
+
export async function checkPermissions(
|
|
411
|
+
userId: string,
|
|
412
|
+
teamId: string,
|
|
413
|
+
permissions: Permission[]
|
|
414
|
+
): Promise<boolean>
|
|
415
|
+
|
|
416
|
+
// Multiple permissions (OR - any sufficient)
|
|
417
|
+
export async function checkAnyPermission(
|
|
418
|
+
userId: string,
|
|
419
|
+
teamId: string,
|
|
420
|
+
permissions: Permission[]
|
|
421
|
+
): Promise<boolean>
|
|
422
|
+
|
|
423
|
+
// Synchronous check (when role is known)
|
|
424
|
+
export function hasPermissionSync(
|
|
425
|
+
teamRole: TeamRole,
|
|
426
|
+
permission: Permission
|
|
427
|
+
): boolean
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**API Route Usage:**
|
|
431
|
+
```typescript
|
|
432
|
+
import { checkPermission } from '@/core/lib/permissions/check'
|
|
433
|
+
import { createApiError } from '@/core/lib/api/response'
|
|
434
|
+
|
|
435
|
+
export async function DELETE(request: NextRequest, { params }) {
|
|
436
|
+
const { userId, teamId } = await getAuthContext(request)
|
|
437
|
+
|
|
438
|
+
// Check permission before action
|
|
439
|
+
const canDelete = await checkPermission(userId, teamId, 'customers.delete')
|
|
440
|
+
if (!canDelete) {
|
|
441
|
+
return createApiError('Permission denied', 403)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Proceed with deletion
|
|
445
|
+
await CustomerService.delete(params.id, userId)
|
|
446
|
+
return createApiResponse({ success: true })
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Frontend Permission Hooks
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
// core/lib/permissions/hooks.ts
|
|
454
|
+
'use client'
|
|
455
|
+
|
|
456
|
+
// Single permission check
|
|
457
|
+
export function usePermission(permission: Permission): boolean
|
|
458
|
+
|
|
459
|
+
// Multiple permissions (returns object)
|
|
460
|
+
export function usePermissions<T extends Record<string, Permission>>(
|
|
461
|
+
permissions: T
|
|
462
|
+
): Record<keyof T, boolean>
|
|
463
|
+
|
|
464
|
+
// Get all user permissions in team
|
|
465
|
+
export function useAllPermissions(): Permission[]
|
|
466
|
+
|
|
467
|
+
// Get current team role
|
|
468
|
+
export function useTeamRole(): TeamRole | null
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Component Usage:**
|
|
472
|
+
```typescript
|
|
473
|
+
import { usePermission, usePermissions, useTeamRole } from '@/core/lib/permissions/hooks'
|
|
474
|
+
|
|
475
|
+
function CustomerActions({ customerId }) {
|
|
476
|
+
// Single permission
|
|
477
|
+
const canDelete = usePermission('customers.delete')
|
|
478
|
+
|
|
479
|
+
// Multiple permissions
|
|
480
|
+
const { canEdit, canExport } = usePermissions({
|
|
481
|
+
canEdit: 'customers.update',
|
|
482
|
+
canExport: 'customers.export',
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
// Role-based rendering
|
|
486
|
+
const role = useTeamRole()
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<>
|
|
490
|
+
{canEdit && <EditButton id={customerId} />}
|
|
491
|
+
{canDelete && <DeleteButton id={customerId} />}
|
|
492
|
+
{role === 'owner' && <OwnerOnlySettings />}
|
|
493
|
+
</>
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Entity Permission Actions
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// Standard entity actions
|
|
502
|
+
type EntityAction =
|
|
503
|
+
| 'create' // Create new record
|
|
504
|
+
| 'read' // View individual record
|
|
505
|
+
| 'list' // List records
|
|
506
|
+
| 'update' // Edit record
|
|
507
|
+
| 'delete' // Delete record
|
|
508
|
+
| 'export' // Export data
|
|
509
|
+
| 'import' // Import data
|
|
510
|
+
| 'assign' // Assign to user
|
|
511
|
+
| 'publish' // Publish record
|
|
512
|
+
| 'archive' // Archive record
|
|
513
|
+
|
|
514
|
+
// Permission action definition
|
|
515
|
+
interface EntityPermissionAction {
|
|
516
|
+
action: EntityAction | string
|
|
517
|
+
label: string
|
|
518
|
+
description?: string
|
|
519
|
+
roles: TeamRole[]
|
|
520
|
+
dangerous?: boolean
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## RLS Integration
|
|
525
|
+
|
|
526
|
+
Permissions work with Row Level Security at the database level:
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// RLS policies use team membership
|
|
530
|
+
CREATE POLICY "users_can_view_own_team_data"
|
|
531
|
+
ON customers
|
|
532
|
+
FOR SELECT
|
|
533
|
+
USING (
|
|
534
|
+
"teamId" IN (
|
|
535
|
+
SELECT "teamId" FROM "teamMembers"
|
|
536
|
+
WHERE "userId" = current_setting('app.user_id')
|
|
537
|
+
)
|
|
538
|
+
);
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Service layer sets RLS context:**
|
|
542
|
+
```typescript
|
|
543
|
+
import { queryWithRLS } from '@/core/lib/db'
|
|
544
|
+
|
|
545
|
+
// userId passed to set app.user_id for RLS
|
|
546
|
+
const customers = await queryWithRLS(
|
|
547
|
+
'SELECT * FROM customers WHERE status = $1',
|
|
548
|
+
['active'],
|
|
549
|
+
userId // Sets app.user_id
|
|
550
|
+
)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## Build-Time Registry
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
// core/lib/registries/permissions-registry.ts (AUTO-GENERATED)
|
|
557
|
+
|
|
558
|
+
// Pre-computed at build time for O(1) lookups
|
|
559
|
+
export const ALL_PERMISSIONS: Permission[]
|
|
560
|
+
export const PERMISSIONS_BY_ROLE: Record<TeamRole, Set<Permission>>
|
|
561
|
+
export const PERMISSIONS_BY_CATEGORY: Record<string, ResolvedPermission[]>
|
|
562
|
+
export const TEAM_PERMISSIONS_BY_ROLE: Record<TeamRole, Permission[]>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Rebuild registry:**
|
|
566
|
+
```bash
|
|
567
|
+
node core/scripts/build/registry.mjs
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## Anti-Patterns
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
// NEVER: Hardcode role checks
|
|
574
|
+
if (user.role === 'admin') { /* access */ }
|
|
575
|
+
|
|
576
|
+
// CORRECT: Use permission checks
|
|
577
|
+
if (await checkPermission(userId, teamId, 'customers.delete')) {}
|
|
578
|
+
|
|
579
|
+
// NEVER: Skip permission check on destructive actions
|
|
580
|
+
await CustomerService.delete(id, userId) // Missing permission check!
|
|
581
|
+
|
|
582
|
+
// CORRECT: Always check before destructive actions
|
|
583
|
+
const canDelete = await checkPermission(userId, teamId, 'customers.delete')
|
|
584
|
+
if (!canDelete) throw new Error('Permission denied')
|
|
585
|
+
await CustomerService.delete(id, userId)
|
|
586
|
+
|
|
587
|
+
// NEVER: Mix user roles and team roles
|
|
588
|
+
if (membership.hasRole('superadmin')) {} // Wrong context!
|
|
589
|
+
|
|
590
|
+
// CORRECT: Use appropriate role type
|
|
591
|
+
if (roleHelpers.isSuperAdmin(user.role)) {} // User role
|
|
592
|
+
if (membership.hasRole('admin')) {} // Team role
|
|
593
|
+
|
|
594
|
+
// NEVER: Check permissions client-side only
|
|
595
|
+
// Client checks are for UI, server MUST re-validate
|
|
596
|
+
|
|
597
|
+
// CORRECT: Always validate on server
|
|
598
|
+
// Client: usePermission() for UI
|
|
599
|
+
// Server: checkPermission() for action
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## Checklist
|
|
603
|
+
|
|
604
|
+
Before finalizing permission implementation:
|
|
605
|
+
|
|
606
|
+
- [ ] Uses three-layer check when appropriate (permission + feature + quota)
|
|
607
|
+
- [ ] Server-side checks on all protected routes
|
|
608
|
+
- [ ] Frontend hooks for conditional UI rendering
|
|
609
|
+
- [ ] Entity permissions defined in theme's permissions.config.ts
|
|
610
|
+
- [ ] Custom roles include hierarchy and display names
|
|
611
|
+
- [ ] Dangerous actions marked with `dangerous: true`
|
|
612
|
+
- [ ] RLS policies align with permission model
|
|
613
|
+
- [ ] Registry rebuilt after config changes
|
|
614
|
+
|
|
615
|
+
## Related Skills
|
|
616
|
+
|
|
617
|
+
- `better-auth` - Authentication patterns
|
|
618
|
+
- `entity-api` - API endpoint patterns
|
|
619
|
+
- `service-layer` - Service patterns with RLS
|