# RBAC CRUD Gaps — Design Specification

## Goal

Add missing CRUD and assignment UI to the RBAC management page, fix date formatting, seed a built-in Admins group, and fix dashboard diagram ordering.

## References

- Parent spec: `docs/superpowers/specs/2026-03-17-rbac-management-design.md`
- Visual prototype: `examples/RBAC/rbac_management_ui.html`

---

## Changes

### 1. Users Tab — Delete + Assignments

Users cannot be created manually (they arrive via login). The detail pane gains:

- **Delete button** in the detail header area. Uses existing `ConfirmDeleteDialog` with the user's `displayName` as the confirmation string. Calls `useDeleteUser()`. **Guard:** the currently authenticated user (from `useAuthStore`) cannot delete themselves — button disabled with tooltip "Cannot delete your own account".
- **Group membership section** — "+ Add" chip opens a **multi-select dropdown** listing all groups the user is NOT already a member of. Checkboxes for batch selection, "Apply" button to commit. Calls are batched via `Promise.allSettled()` — if any fail, show an inline error, invalidate queries regardless to refresh. Existing group chips gain an "x" remove button calling `useRemoveUserFromGroup()`.
- **Direct roles section** — the existing "Effective roles" section renders both direct and inherited roles. The "+ Add" multi-select dropdown lists roles not yet directly assigned. Calls `useAssignRoleToUser()` (batched via `Promise.allSettled()`). Direct role chips gain an "x" button calling `useRemoveRoleFromUser()`. Inherited role chips (dashed border) do NOT get remove buttons — they can only be removed by changing group membership or group role assignments.
- **Created field** — change from date-only to full date+time: `new Date(createdAt).toLocaleString()`.
- **Mutation button states** — all action buttons (delete, remove chip "x") disable while their mutation is in-flight to prevent double-clicks.

### 2. Groups Tab — CRUD + Assignments

- **"+ Add group" button** in the panel header (`.btnAdd` style exists). Opens an inline form below the search bar with: name text input, optional parent group dropdown, "Create" button. Calls `useCreateGroup()`. Form clears and closes on success. On error: shows error message inline.
- **Delete button** in detail pane header. Uses `ConfirmDeleteDialog` with group name. Calls `useDeleteGroup()`. Resets selected group. **Guard:** the built-in Admins group (`SystemRole.ADMINS_GROUP_ID`) cannot be deleted — button disabled with tooltip "Built-in group cannot be deleted".
- **Assigned roles section** — "+ Add" multi-select dropdown listing roles not yet assigned to this group. Batched via `Promise.allSettled()`. Calls `useAssignRoleToGroup()`. Role chips gain "x" for `useRemoveRoleFromGroup()`.
- **Parent group** — shown as a dropdown in the detail header area, allowing re-parenting. Calls `useUpdateGroup()`. The dropdown excludes the group itself and its transitive descendants (cycle prevention — requires recursive traversal of `childGroups` on each `GroupDetail`). Setting to empty/none makes it top-level.

### 3. Roles Tab — CRUD

- **"+ Add role" button** in panel header. Opens an inline form: name (required), description (optional), scope (optional, free-text, defaults to "custom"). Calls `useCreateRole()`.
- **Delete button** in detail pane header. **Disabled for system roles** (lock icon + tooltip "System roles cannot be deleted"). Custom roles use `ConfirmDeleteDialog` with role name → `useDeleteRole()`.
- No assignment UI on the roles tab — assignments are managed from the User and Group detail panes.

### 4. Multi-Select Dropdown Component

A reusable component used across all assignment actions:

```
Props:
  items: { id: string; label: string }[]   — available items to pick from
  onApply: (selectedIds: string[]) => void  — called with all checked IDs
  placeholder?: string                      — search filter placeholder
```

Behavior:
- Opens as a positioned dropdown below the "+ Add" chip
- Search/filter input at top
- Checkbox list of items (max-height with scroll)
- "Apply" button at bottom (disabled when nothing selected)
- Closes on Apply, Escape, or click-outside
- Shows count badge on Apply button: "Apply (3)"

Styling: background `var(--bg-raised)`, border `var(--border)`, border-radius `var(--radius-md)`, items with `var(--bg-hover)` on hover, checkboxes with `var(--amber)` accent.

### 5. Inline Create Form

A reusable pattern for "Add group" and "Add role":

- Appears below the search bar in the list pane, pushing content down
- Input fields with labels
- "Create" and "Cancel" buttons
- On success: closes form, clears inputs, new entity appears in list
- On error: shows error message inline
- "Create" button disabled while mutation is in-flight

### 6. Built-in Admins Group Seed

**Database migration** — new `V2__admin_group_seed.sql` (V1 is already deployed, V2-V10 were deleted in the migration consolidation so V2 is safe):

```sql
-- Built-in Admins group
INSERT INTO groups (id, name) VALUES
    ('00000000-0000-0000-0000-000000000010', 'Admins');

-- Assign ADMIN role to Admins group
INSERT INTO group_roles (group_id, role_id) VALUES
    ('00000000-0000-0000-0000-000000000010', '00000000-0000-0000-0000-000000000004');
```

**SystemRole.java** — add constants:
```java
public static final UUID ADMINS_GROUP_ID = UUID.fromString("00000000-0000-0000-0000-000000000010");
```

**UiAuthController.login()** — after upserting the user and assigning ADMIN role, also add to Admins group:
```java
rbacService.addUserToGroup(subject, SystemRole.ADMINS_GROUP_ID);
```

**Frontend guard:** The Admins group UUID is hardcoded as a constant in the frontend to disable deletion. Alternatively, check if a group's ID matches a known system group ID.

### 7. Dashboard Diagram Ordering

The inheritance diagram's three columns (Groups → Roles → Users) must show items in a consistent, matching order:

- **Groups column**: alphabetical by name, children indented under parents
- **Roles column**: iterate groups top-to-bottom, collect their direct roles, deduplicate preserving first-seen order. Roles not assigned to any group are omitted from the diagram.
- **Users column**: alphabetical by display name

Sort explicitly in `DashboardTab.tsx` before rendering.

---

## Files Changed

### Frontend — Modified
| File | Change |
|---|---|
| `ui/src/pages/admin/rbac/UsersTab.tsx` | Delete button, group/role assignment dropdowns, date format fix, self-delete guard |
| `ui/src/pages/admin/rbac/GroupsTab.tsx` | Add group form, delete button, role assignment dropdown, parent group dropdown, Admins guard |
| `ui/src/pages/admin/rbac/RolesTab.tsx` | Add role form, delete button (disabled for system) |
| `ui/src/pages/admin/rbac/DashboardTab.tsx` | Sort diagram columns consistently |
| `ui/src/pages/admin/rbac/RbacPage.module.css` | Styles for multi-select dropdown, inline create form, delete button, action chips, remove buttons |

### Frontend — New
| File | Responsibility |
|---|---|
| `ui/src/pages/admin/rbac/components/MultiSelectDropdown.tsx` | Reusable multi-select picker with search, checkboxes, batch apply |

### Backend — Modified
| File | Change |
|---|---|
| `cameleer-server-core/.../rbac/SystemRole.java` | Add `ADMINS_GROUP_ID` constant |
| `cameleer-server-app/.../security/UiAuthController.java` | Add admin user to Admins group on login |

### Backend — New Migration
| File | Change |
|---|---|
| `cameleer-server-app/src/main/resources/db/migration/V2__admin_group_seed.sql` | Seed Admins group + ADMIN role assignment |

---

## Out of Scope

- Editing user profile fields (name, email) — users are managed by their identity provider
- Drag-and-drop group hierarchy management
- Role permission editing (custom roles have no effect on Spring Security yet)
