Permission Matrix Editor
The Permission Matrix is the core interactive element of the Custom RBAC system's frontend. It renders 35 platform permissions, organized into 8 visual categories, as a checkbox grid with category-level "select all" toggles. This doc covers the data model, interaction patterns, and reuse guidelines.
Data Model
PERMISSION_CATEGORIES
Defined in src/lib/queries/customRoles.ts, the PERMISSION_CATEGORIES constant structures all 35 backend Permission enum values into 8 display groups:
export interface PermissionCategory {
label: string; // e.g. "Restaurant"
icon: string; // e.g. "๐ด"
permissions: { value: Permission; label: string }[]; // checkbox entries
}
export const PERMISSION_CATEGORIES: PermissionCategory[] = [
{
label: 'Common',
icon: 'โ๏ธ',
permissions: [
{ value: 'VIEW_ANALYTICS', label: 'View Analytics' },
{ value: 'MANAGE_PRODUCTS', label: 'Manage Products' },
// ...
]
},
// 7 more categories...
];
Categories
| Category | Icon | Count | Permissions |
|---|---|---|---|
| Common | โ๏ธ | 4 | VIEW_ANALYTICS, MANAGE_PRODUCTS, MANAGE_SERVICES, MANAGE_ORDERS |
| Administrative | ๐ | 6 | MANAGE_MEMBERS, MANAGE_ROLES, MANAGE_TEAMS, UPDATE_ORG, DELETE_ORG, VIEW_AUDIT_LOGS |
| Tour Operator | ๐บ๏ธ | 5 | VIEW_BOOKINGS, VIEW_MANIFESTS, UPDATE_STATUS, MANAGE_BOOKINGS, CONTACT_GUESTS |
| Restaurant | ๐ด | 4 | VIEW_ORDERS, CREATE_ORDERS, UPDATE_ORDER_STATUS, ACCESS_KDS |
| Photography | ๐ท | 3 | UPLOAD_IMAGES, MANAGE_GALLERIES, ACCESS_PROOFING |
| Author | โ๏ธ | 2 | EDIT_BLOGS, EDIT_PRODUCTS |
| Platform & Security | ๐ก๏ธ | 7 | MANAGE_SEARCH, MANAGE_SSO, MANAGE_USERS, MANAGE_ANALYTICS, MANAGE_WEBHOOKS, MANAGE_TAGS, MANAGE_BILLING |
| Payouts & Revenue | ๐ฐ | 4 | VIEW_PAYOUTS, MANAGE_PAYOUTS, VIEW_REVENUE, MANAGE_COMMISSIONS |
| Total | โ | 35 | โ |
Interaction Patterns
Category Toggle
Each category header has a checkbox that controls all permissions in that group:
function toggleCategory(categoryPerms: Permission[]) {
const allChecked = categoryPerms.every((p) => formPermissions.has(p));
const next = new Set(formPermissions);
if (allChecked) {
categoryPerms.forEach((p) => next.delete(p));
} else {
categoryPerms.forEach((p) => next.add(p));
}
formPermissions = next;
}
The checkbox displays three states:
- Checked: All permissions in the category are selected
- Indeterminate: Some permissions are selected (uses native
indeterminateattribute) - Unchecked: No permissions in the category are selected
Individual Toggle
function togglePermission(perm: Permission) {
const next = new Set(formPermissions);
if (next.has(perm)) {
next.delete(perm);
} else {
next.add(perm);
}
formPermissions = next;
}
Validation
The matrix enforces a single constraint: at least one permission must be selected. This is validated client-side before submission and enforced server-side by the createCustomRole / updateCustomRole resolvers.
Component Architecture
The permission matrix is embedded inline within CustomRbacPanel.svelte rather than extracted as a separate component. This is intentional โ the matrix state (formPermissions) is tightly coupled to the role editor's form lifecycle (create/edit mode, save/cancel actions).
State Management
// Set-based tracking for O(1) lookup during rendering
let formPermissions = $state<Set<Permission>>(new Set());
// Selected count is derived reactively
// Rendered inline: ({formPermissions.size} selected)
Rendering Strategy
{#each PERMISSION_CATEGORIES as category}
{@const catPerms = category.permissions.map((p) => p.value)}
{@const allChecked = catPerms.every((p) => formPermissions.has(p))}
{@const someChecked = catPerms.some((p) => formPermissions.has(p))}
<div class="perm-category">
<!-- Category header with toggle-all checkbox -->
<!-- Responsive grid of individual permission checkboxes -->
</div>
{/each}
The {@const} blocks compute derived state per-category within the template โ avoiding the need for a separate $derived per category. This is the recommended Svelte 5 pattern for loop-scoped derivations.
Reuse Guidelines
If a future feature needs a permission picker (e.g., Collaborator Permissions, API key scopes), extract the matrix into a standalone PermissionMatrixPicker.svelte component with:
interface Props {
categories: PermissionCategory[];
selected: Set<Permission>;
onchange: (selected: Set<Permission>) => void;
disabled?: boolean;
}
File Reference
| File | Purpose |
|---|---|
src/lib/queries/customRoles.ts |
Permission type, PERMISSION_CATEGORIES, GraphQL operations |
src/lib/components/rbac/CustomRbacPanel.svelte |
Role editor + embedded permission matrix |
tests/custom-rbac.test.ts |
Validates category structure, count (35), uniqueness |
Related
- Custom RBAC โ Backend architecture: entity schema, 3-layer permission resolution, GraphQL API, audit events
- Organization & Malet Management โ Parent dashboard hosting the Roles & Permissions tab
- Advanced Audit Logging UI โ Custom role lifecycle events (CUSTOM_ROLE_CREATED/UPDATED/DELETED) appear in the Audit Log