Developer Docs

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 indeterminate attribute)
  • 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