Based on 3-expert UX/UI review: fixes critical --bg-base bug, migrates AuditLog to DataTable, replaces admin nav with Tabs, reworks user creation with provider-aware flow, adds password management, toast feedback, and accessibility improvements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.1 KiB
Admin Section Redesign Spec
Date: 2026-03-18 Scope: UX/UI consistency overhaul of AuditLog, OidcConfig, UserManagement admin pages
Overview
Three expert reviews identified critical bugs, consistency gaps, and usability issues in the admin section. This spec covers all changes organized by priority tier.
Tier 1: Critical Bugs
1.1 Replace nonexistent --bg-base token
--bg-base is referenced 3 times but does not exist in tokens.css. Dark mode is broken.
Files:
Admin.module.cssline 6:.adminNavUserManagement.module.csslines 13, 19:.listPane,.detailPane
Fix: Replace all var(--bg-base) with var(--bg-surface).
1.2 Change AuditEvent id to string
DataTable requires T extends { id: string }. Current AuditEvent.id is number.
Files:
auditMocks.ts: changeid: numbertoid: string, update all IDs to'audit-1','audit-2', etc.AuditLog.tsx: updateexpandedIdstate fromnumber | nulltostring | null
Tier 2: High-Impact Consistency
2.1 Replace admin nav with Tabs composite
The hand-rolled admin nav in Admin.tsx lacks ARIA roles and has subtle color differences from the Tabs composite.
Fix: Replace the custom <nav> block with:
<Tabs
tabs={[
{ label: 'User Management', value: '/admin/rbac' },
{ label: 'Audit Log', value: '/admin/audit' },
{ label: 'OIDC', value: '/admin/oidc' },
]}
active={location.pathname}
onChange={(path) => navigate(path)}
/>
Delete .adminNav, .adminTab, .adminTabActive from Admin.module.css.
2.2 Remove duplicate page titles
Breadcrumb + active tab + h2 heading all show the same label. Remove the h2.
Files:
AuditLog.tsx: remove the.headerdiv with<h2>Audit Log</h2>. Move the event count badge into a toolbar row.OidcConfig.tsx: remove the.headerdiv with<h2>. Keep Save/Test buttons in a compact toolbar row below the tabs.
Delete .header, .title CSS from both AuditLog.module.css and OidcConfig.module.css.
2.3 Migrate AuditLog to DataTable
Replace the hand-built <table> with the DataTable composite.
Column definitions:
- Timestamp: render with
MonoText, width'170px' - User: render with bold text
- Category: render with
Badge color="auto" - Action: plain text
- Target: render with ellipsis style
- Result: render with
Badge color={row.result === 'SUCCESS' ? 'success' : 'error'}
Features to enable:
sortableon Timestamp, User, Category, Result columnsrowAccent={(row) => row.result === 'FAILURE' ? 'error' : undefined}— red left-border on failuresexpandedContent={(row) => <detail block with IP, user agent, CodeBlock>}pageSize={10}flushprop (table sits inside a card wrapper)
Card wrapper: Wrap DataTable in a section with background: var(--bg-surface), border: 1px solid var(--border-subtle), border-radius: var(--radius-lg), box-shadow: var(--shadow-card). Add a header row with title + event count badge, matching Dashboard's .tableSection pattern.
Delete from AuditLog.module.css: .tableWrap, .table, .th, .row, .td, .userCell, .target, .empty, .detailRow, .detailCell, .detailGrid, .detailField, .detailLabel, .detailValue, .detailJson. Also remove the separate Pagination import — DataTable handles pagination internally.
2.4 Fix content padding
Admin.module.css .adminContent: change padding: 20px to padding: 20px 24px 40px.
2.5 Center OIDC form
OidcConfig.module.css .page: add margin: 0 auto to center the 640px max-width form.
2.6 Replace inline style
UserManagement.tsx line 20: replace style={{ marginTop: 16 }} with a CSS class .tabContent { margin-top: 16px; } in UserManagement.module.css.
Tier 3: Usability Improvements
3.1 Add toast notifications to RBAC mutations
Import useToast into UsersTab.tsx, GroupsTab.tsx, RolesTab.tsx. Fire toasts on:
- Create user/group/role:
variant: 'success' - Delete user/group/role:
variant: 'warning' - Role assigned/removed:
variant: 'success' - Group added/removed:
variant: 'success'
3.2 Rework user creation form
Replace the flat inline form with a provider-aware two-step form.
Form structure:
- Provider selection — RadioGroup with "Local" and "OIDC" options. Default: "Local".
- Fields (always shown): Username (required), Display name (optional), Email (optional)
- Fields (Local only): Password (required)
- OIDC info callout: When OIDC selected, show an InfoCallout: "OIDC users authenticate via the configured identity provider. Pre-register to assign roles/groups before their first login."
Components used: RadioGroup + RadioItem (existing primitive), Input, InfoCallout (existing primitive), Button.
Create handler: Set provider based on RadioGroup selection. Only validate password when provider is 'local'.
The form should use the existing inline pattern (appears at the top of the list pane), but use a proper card-like treatment (the existing .createForm background is fine).
3.3 Add password management to user detail pane
Add a "Security" section (using SectionHeader) in the user detail pane, below the metadata grid.
For local users:
- Show "Password: ••••••••" with a "Reset password" Button
- Clicking "Reset password" reveals an inline form: Input (type=password, placeholder "New password") + Cancel/Set buttons
- Setting fires a success toast: "Password updated"
For OIDC users:
- Show "Authentication: OIDC ({provider})"
- Show InfoCallout: "Password managed by the identity provider."
- No password reset option
3.4 Add ConfirmDialog to cascading removals
When removing a group from a user (which may strip inherited roles), show a confirmation dialog if the group grants roles.
When removing a role from a group (which affects all members), show a confirmation dialog.
Direct role removal from a user does not need confirmation (low risk).
3.5 Make entity list items keyboard accessible
Add to each .entityItem div:
role="option"tabIndex={0}aria-selected={selectedId === item.id}onKeyDown: Enter/Space to select, ArrowUp/ArrowDown to navigate
Add role="listbox" and aria-label to each .entityList container.
3.6 Add expand/collapse affordance to AuditLog
After DataTable migration (2.3), add a first column with a chevron indicator (> / v) that rotates when the row is expanded. Width: '40px'. This makes the expandable row pattern discoverable.
3.7 Add duplicate name validation
Before creating, check for existing names:
- Users:
users.some(u => u.username === newUsername.trim()) - Groups:
groups.some(g => g.name.toLowerCase() === newName.trim().toLowerCase()) - Roles:
roles.some(r => r.name === newName.trim().toUpperCase())
Show inline error using state + red text below the name field. Disable Create button.
3.8 Partial FilterBar migration for AuditLog
After DataTable migration, use FilterBar for search + category filters:
- Search input maps to FilterBar's built-in search
- Categories (INFRA, AUTH, USER_MGMT, CONFIG) become FilterPill toggles
- Keep DateRangePicker and user filter Input alongside FilterBar in a row
3.9 Add empty-search states to entity lists
When search returns no results in Users/Groups/Roles lists, show centered muted text: "No users match your search" (etc.) inside the .entityList area.
Tier 4: Polish
4.1 Replace lock emoji with Badge
RolesTab.tsx: replace 🔒 with <Badge label="system" color="auto" variant="outlined" />.
4.2 Fix split-pane border radius
UserManagement.module.css: change border-radius: var(--radius-md) to var(--radius-lg) on .splitPane, .listPane, .detailPane.
4.3 Add shadow to split-pane
UserManagement.module.css: add box-shadow: var(--shadow-card) to .splitPane.
Out of Scope
- Replacing split-pane with DataTable+DetailPanel (not appropriate for dense editing)
- EventFeed as alternative audit view (future enhancement)
- Tabs inside user detail pane (not needed until more sections are added)
- FilterBar extension to support DateRangePicker slots (separate design system ticket)