# Cameleer UI Interaction Patterns Audit

Audit date: 2026-04-09
Scope: All `.tsx` files under `ui/src/pages/` and `ui/src/components/`

---

## 1. Delete / Destructive Operations

### 1.1 Delete User
- **File**: `ui/src/pages/Admin/UsersTab.tsx` (lines 155-172, 358-365, 580-587)
- **Button location**: Detail pane header, top-right, inline with avatar and name
- **Button**: `<Button size="sm" variant="danger">Delete</Button>`
- **Confirmation**: `ConfirmDialog` (type-to-confirm)
  - Message: `Delete user "${name}"? This cannot be undone.`
  - Confirm text: user's `displayName`
  - Has `loading` prop bound to mutation
- **Self-delete guard**: Button is `disabled={isSelf}` (cannot delete yourself)
- **Toast on success**: `variant: 'warning'`, title: "User deleted"
- **Toast on error**: `variant: 'error'`, `duration: 86_400_000`

### 1.2 Remove User From Group (via User detail)
- **File**: `ui/src/pages/Admin/UsersTab.tsx` (lines 588-613)
- **Button location**: Tag `onRemove` handler on group tags in detail pane
- **Confirmation**: `AlertDialog` (simple confirm, no type-to-confirm)
  - Title: "Remove group membership"
  - Description: "Removing this group may also revoke inherited roles. Continue?"
  - Confirm label: "Remove"
  - Variant: `warning`
- **Toast on success**: `variant: 'success'`, title: "Group removed"

### 1.3 Remove Role From User (via User detail)
- **File**: `ui/src/pages/Admin/UsersTab.tsx` (lines 504-528)
- **Button location**: Tag `onRemove` handler on role tags in detail pane
- **Confirmation**: NONE -- immediate mutation on tag remove click
- **Toast on success**: `variant: 'success'`, title: "Role removed"

**INCONSISTENCY**: Removing a group shows an AlertDialog confirmation but removing a role does not, even though both can have cascading effects.

### 1.4 Delete Group
- **File**: `ui/src/pages/Admin/GroupsTab.tsx` (lines 140-155, 340-347, 434-441)
- **Button location**: Detail pane header, top-right
- **Button**: `<Button size="sm" variant="danger">Delete</Button>`
- **Confirmation**: `ConfirmDialog` (type-to-confirm)
  - Message: `Delete group "${name}"? This cannot be undone.`
  - Confirm text: group's `name`
  - Has `loading` prop
- **Built-in guard**: Button is `disabled={isBuiltinAdmins}`
- **Toast on success**: `variant: 'warning'`, title: "Group deleted"

### 1.5 Remove Role From Group
- **File**: `ui/src/pages/Admin/GroupsTab.tsx` (lines 404-427, 442-455)
- **Button location**: Tag `onRemove` handler on role tags in group detail
- **Confirmation**: `AlertDialog` shown ONLY when the group has members (conditional)
  - Title: "Remove role from group"
  - Description: `Removing this role will affect ${members.length} member(s) who inherit it. Continue?`
  - Confirm label: "Remove"
  - Variant: `warning`
- **If group has no members**: Immediate mutation, no confirmation
- **Toast on success**: `variant: 'success'`, title: "Role removed"

### 1.6 Remove Member From Group
- **File**: `ui/src/pages/Admin/GroupsTab.tsx` (lines 366-372)
- **Button location**: Tag `onRemove` handler on member tags in group detail
- **Confirmation**: NONE -- immediate mutation on tag remove click
- **Toast on success**: `variant: 'success'`, title: "Member removed"

### 1.7 Delete Role
- **File**: `ui/src/pages/Admin/RolesTab.tsx` (lines 93-110, 261-265, 223-231)
- **Button location**: Detail pane header, top-right
- **Button**: `<Button size="sm" variant="danger">Delete</Button>`
- **Confirmation**: `ConfirmDialog` (type-to-confirm)
  - Message: `Delete role "${name}"? This cannot be undone.`
  - Confirm text: role's `name`
  - Has `loading` prop
- **System role guard**: Button hidden for system roles (`!role.system`)
- **Toast on success**: `variant: 'warning'`, title: "Role deleted"

### 1.8 Delete Environment
- **File**: `ui/src/pages/Admin/EnvironmentsPage.tsx` (lines 101-112, 245-252, 319-327)
- **Button location**: Detail pane header, top-right
- **Button**: `<Button size="sm" variant="danger">Delete</Button>`
- **Confirmation**: `ConfirmDialog` (type-to-confirm)
  - Message: `Delete environment "${displayName}"? All apps and deployments in this environment will be removed. This cannot be undone.`
  - Confirm text: environment's `slug` (NOT the display name)
  - Has `loading` prop
- **Default guard**: Button is `disabled={isDefault}` (cannot delete default environment)
- **Toast on success**: `variant: 'warning'`, title: "Environment deleted"

**NOTE**: The confirm text requires the slug but the message shows the display name. This is intentional (slug is the unique identifier) but differs from Users/Groups/Roles which use the display name.

### 1.9 Delete OIDC Configuration
- **File**: `ui/src/pages/Admin/OidcConfigPage.tsx` (lines 113-124, 253-264)
- **Button location**: Bottom of page in a "Danger Zone" section
- **Button**: `<Button size="sm" variant="danger">Delete OIDC Configuration</Button>`
- **Confirmation**: `ConfirmDialog` (type-to-confirm)
  - Message: `Delete OIDC configuration? All users signed in via OIDC will lose access.`
  - Confirm text: `"delete oidc"` (static string)
  - NO `loading` prop
- **Toast on success**: `variant: 'warning'`, title: "Configuration deleted"

**INCONSISTENCY**: No `loading` prop on this ConfirmDialog, unlike all other delete confirmations.

### 1.10 Delete App
- **File**: `ui/src/pages/AppsTab/AppsTab.tsx` (lines 533-539, 565, 589-596)
- **Button location**: App detail header, top-right, in `detailActions` div alongside "Upload JAR"
- **Button**: `<Button size="sm" variant="danger">Delete App</Button>`
- **Confirmation**: `ConfirmDialog` (type-to-confirm)
  - Message: `Delete app "${displayName}"? All versions and deployments will be removed. This cannot be undone.`
  - Confirm text: app's `slug`
  - Has `loading` prop
- **Toast on success**: `variant: 'warning'`, title: "App deleted"
- **Post-delete**: Navigates to `/apps`

### 1.11 Stop Deployment
- **File**: `ui/src/pages/AppsTab/AppsTab.tsx` (lines 526-531, 672)
- **Button location**: Inline in deployments table, right-aligned actions column
- **Button**: `<Button size="sm" variant="danger">Stop</Button>`
- **Confirmation**: NONE -- immediate mutation on click
- **Toast on success**: `variant: 'warning'`, title: "Deployment stopped"

**INCONSISTENCY**: Stopping a deployment is a destructive operation that affects live services but has NO confirmation dialog. Route stop/suspend in RouteControlBar uses a ConfirmDialog, but deployment stop does not.

### 1.12 Stop/Suspend Route
- **File**: `ui/src/pages/Exchanges/RouteControlBar.tsx` (lines 43-154)
- **Button location**: Route control bar (segmented button group)
- **Button**: Custom segmented `<button>` elements (not design system Button)
- **Confirmation**: `ConfirmDialog` (type-to-confirm) -- only for `stop` and `suspend` actions
  - Title: `"Stop route?"` or `"Suspend route?"`
  - Message: `This will ${action} route "${routeId}" on ${application}. This affects all live agents.`
  - Confirm text: the action name (e.g., `"stop"` or `"suspend"`)
  - Confirm label: `"Stop Route"` or `"Suspend Route"`
  - Variant: `danger` for stop, `warning` for suspend
  - Has `loading` prop
- **Start and Resume**: No confirmation (immediate action)
- **Toast patterns match others**

### 1.13 Delete Tap (Route Detail page)
- **File**: `ui/src/pages/Routes/RouteDetail.tsx` (lines 991-1001)
- **Button location**: Inline delete icon button in taps table row
- **Confirmation**: `ConfirmDialog` (type-to-confirm)
  - Title: "Delete Tap"
  - Message: `This will remove the tap "${attributeName}" from the configuration.`
  - Confirm text: tap's `attributeName`
  - Confirm label: "Delete"
  - Variant: `danger`
- **No `loading` prop on this dialog**

**INCONSISTENCY**: No `loading` prop, unlike entity delete confirmations.

### 1.14 Delete Tap (TapConfigModal)
- **File**: `ui/src/components/TapConfigModal.tsx` (lines 117-122, 249-253)
- **Button location**: Inside the modal footer, left-aligned (only shown when editing)
- **Button**: `<Button variant="danger">Delete</Button>`
- **Confirmation**: NONE -- immediate call to `onDelete` then `onClose`
- **Toast**: Handled by parent component (ExchangesPage)

**INCONSISTENCY**: Deleting a tap from the TapConfigModal has no confirmation, but deleting from the RouteDetail table shows a ConfirmDialog.

### 1.15 Kill Database Query
- **File**: `ui/src/pages/Admin/DatabaseAdminPage.tsx` (line 30)
- **Button location**: Inline in active queries table
- **Button**: `<Button variant="danger" size="sm">Kill</Button>`
- **Confirmation**: NONE -- immediate mutation
- **Toast**: None visible

**INCONSISTENCY**: Killing a database query is a destructive action with no confirmation and no toast feedback.

---

## 2. Button Placement & Order

### 2.1 Create Forms (Users, Groups, Roles, Environments)

All four entity create forms use an identical pattern:

| Page | File | Line | Left Button | Right Button |
|------|------|------|-------------|--------------|
| Users | `UsersTab.tsx` | 254-274 | Cancel (ghost) | Create (primary) |
| Groups | `GroupsTab.tsx` | 251-268 | Cancel (ghost) | Create (primary) |
| Roles | `RolesTab.tsx` | 142-159 | Cancel (ghost) | Create (primary) |
| Environments | `EnvironmentsPage.tsx` | 181-194 | Cancel (ghost) | Create (primary) |

- **Position**: Bottom of inline create form in the list pane
- **Container class**: `styles.createFormActions`
- **Order**: Cancel (left) | Create (right) -- **CONSISTENT**
- **Variants**: Cancel = `ghost`, Create = `primary` -- **CONSISTENT**
- **Size**: Both `sm` -- **CONSISTENT**

### 2.2 App Creation Page

- **File**: `ui/src/pages/AppsTab/AppsTab.tsx` (lines 282-287)
- **Position**: Top of page in `detailActions` header area
- **Order**: Cancel (ghost, left) | Create & Deploy / Create (primary, right)
- **Size**: Both `sm`
- **CONSISTENT** with the pattern (Cancel left, Submit right)

### 2.3 OIDC Config Page (Toolbar)

- **File**: `ui/src/pages/Admin/OidcConfigPage.tsx` (lines 130-137)
- **Position**: Top toolbar
- **Order**: Test Connection (secondary, left) | Save (primary, right)
- **No Cancel button** -- form is always editable

**NOTE**: This is the only admin page without a Cancel button or Edit mode toggle.

### 2.4 App Detail Header

- **File**: `ui/src/pages/AppsTab/AppsTab.tsx` (lines 560-566)
- **Position**: Top-right header area in `detailActions`
- **Order**: Upload JAR (primary) | Delete App (danger)

**NOTE**: The primary action (Upload) is on the LEFT and the destructive action (Delete) is on the RIGHT.

### 2.5 App Config Detail Page (AppConfigDetailPage)

- **File**: `ui/src/pages/Admin/AppConfigDetailPage.tsx` (lines 308-319)
- **Position**: Top toolbar
- **Read mode**: Back (ghost) ... Edit (secondary)
- **Edit mode**: Back (ghost) ... Save (default/no variant specified!) | Cancel (secondary)
- **Order when editing**: Save (left) | Cancel (right)

**INCONSISTENCY #1**: Save button has NO `variant` prop set -- it renders as default, not `primary`. Every other Save button uses `variant="primary"`.

**INCONSISTENCY #2**: Button order is REVERSED from every other form. Here it is Save (left) | Cancel (right). Everywhere else it is Cancel (left) | Save (right).

### 2.6 App Config Sub-Tab (AppsTab ConfigSubTab)

- **File**: `ui/src/pages/AppsTab/AppsTab.tsx` (lines 922-936)
- **Position**: Top banner bar (editBanner)
- **Read mode**: Banner text + Edit (secondary)
- **Edit mode**: Banner text + Cancel (ghost) | Save Configuration (primary)
- **Order when editing**: Cancel (left) | Save (right) -- **CONSISTENT**

### 2.7 Environment Default Resources / JAR Retention Sections

- **File**: `ui/src/pages/Admin/EnvironmentsPage.tsx` (lines 437-446, 505-514)
- **Position**: Bottom of section, right-aligned (`justifyContent: 'flex-end'`)
- **Read mode**: Edit Defaults / Edit Policy (secondary)
- **Edit mode**: Cancel (ghost) | Save (primary) -- **CONSISTENT**
- **Size**: Both `sm`

### 2.8 User Password Reset

- **File**: `ui/src/pages/Admin/UsersTab.tsx` (lines 407-431)
- **Position**: Inline in Security section
- **Order**: Cancel (ghost) | Set (primary)
- **CONSISTENT** pattern (Cancel left, Submit right)

### 2.9 Tap Modal (TapConfigModal)

- **File**: `ui/src/components/TapConfigModal.tsx` (lines 249-257)
- **Position**: Modal footer
- **Order (edit mode)**: Delete (danger, left, in `footerLeft`) | Cancel (secondary) | Save (primary)
- **Order (create mode)**: Cancel (secondary) | Save (primary)
- **No `size` prop specified** -- renders at default size

**NOTE**: Uses `variant="secondary"` for Cancel, not `variant="ghost"` like create forms.

### 2.10 Tap Modal (RouteDetail inline version)

- **File**: `ui/src/pages/Routes/RouteDetail.tsx` (lines 984-986)
- **Position**: Modal footer (`tapModalFooter`)
- **Order**: Cancel (secondary) | Save (primary)
- **No `size` prop specified**
- **CONSISTENT** with TapConfigModal

### 2.11 About Me Dialog

- **File**: `ui/src/components/AboutMeDialog.tsx` (lines 14, 72)
- **Uses `Modal` with built-in close button** (no explicit action buttons)
- **Close via**: Modal `onClose` handler (X button and backdrop click)

### 2.12 Login Page

- **File**: `ui/src/auth/LoginPage.tsx` (lines 176-184)
- **Single button**: Sign in (primary, full width, submit type)
- **Optional SSO button above**: Sign in with SSO (secondary)

### Summary of Button Order Patterns

| Location | Cancel Side | Submit Side | Consistent? |
|----------|------------|-------------|-------------|
| User create form | Left (ghost) | Right (primary) | YES |
| Group create form | Left (ghost) | Right (primary) | YES |
| Role create form | Left (ghost) | Right (primary) | YES |
| Env create form | Left (ghost) | Right (primary) | YES |
| App create page | Left (ghost) | Right (primary) | YES |
| Env Default Resources edit | Left (ghost) | Right (primary) | YES |
| Env JAR Retention edit | Left (ghost) | Right (primary) | YES |
| AppsTab config sub-tab edit | Left (ghost) | Right (primary) | YES |
| User password reset | Left (ghost) | Right (primary) | YES |
| TapConfigModal | Left (secondary) | Right (primary) | Variant mismatch |
| RouteDetail tap modal | Left (secondary) | Right (primary) | Variant mismatch |
| **AppConfigDetailPage** | **Left (NO variant)** | **Right (secondary)** | **REVERSED** |

---

## 3. Edit / Save Patterns

### 3.1 Users (UsersTab)
- **Edit mode**: No explicit toggle. Display name uses `InlineEdit` (click-to-edit). Everything else is managed via tag add/remove.
- **No Save/Cancel for the detail view** -- all changes are immediate mutations.
- **Unsaved changes indicator**: N/A (no batched editing)
- **On success**: Toast with `variant: 'success'`
- **On error**: Toast with `variant: 'error'`, `duration: 86_400_000` (effectively permanent)

### 3.2 Groups (GroupsTab)
- **Edit mode**: Name uses `InlineEdit`. All other changes (members, roles) are immediate mutations.
- **Pattern**: Same as Users -- no batched edit mode.

### 3.3 Roles (RolesTab)
- **Edit mode**: Read-only detail panel. No editing of role fields.
- **Only action**: Delete

### 3.4 Environments (EnvironmentsPage)
- **Edit mode (name)**: `InlineEdit`
- **Edit mode (production/enabled toggles)**: Immediate mutations per toggle change
- **Edit mode (Default Resources)**: Explicit Edit toggle (`setEditing(true)`)
  - Cancel/Save buttons appear at bottom-right
  - Resets form on cancel
  - No unsaved changes indicator
  - On success: Toast `variant: 'success'`
- **Edit mode (JAR Retention)**: Same pattern as Default Resources
- **On environment switch**: Both sub-sections auto-reset to read mode

### 3.5 OIDC Config (OidcConfigPage)
- **Edit mode**: ALWAYS editable (no toggle)
- **Save button**: Always visible in top toolbar
- **No Cancel button** -- cannot discard changes
- **No unsaved changes indicator**
- **On success**: Toast `variant: 'success'`
- **On error**: Toast `variant: 'error'` + inline `<Alert variant="error">` both shown

**INCONSISTENCY**: Only page that is always editable with no way to discard changes. Also the only page that shows BOTH a toast AND an inline alert on error.

### 3.6 App Config Detail (AppConfigDetailPage)
- **Edit mode**: Explicit toggle via `Edit` button (Pencil icon) in toolbar
- **Toolbar in edit mode**: Save (unstyled!) | Cancel (secondary)
- **Save button text**: Shows "Saving..." while pending
- **No unsaved changes indicator**
- **On success**: Toast `variant: 'success'`, exits edit mode
- **On error**: Toast `variant: 'error'`, stays in edit mode

### 3.7 App Config Sub-Tab (AppsTab ConfigSubTab)
- **Edit mode**: Explicit toggle via banner + Edit button
- **Banner in read mode**: "Configuration is read-only. Enter edit mode to make changes."
- **Banner in edit mode**: "Editing configuration. Changes are not saved until you click Save." (styled differently with `editBannerActive`)
- **This IS an unsaved changes indicator** (the banner text changes)
- **Cancel/Save in edit banner**: Cancel (ghost) | Save Configuration (primary)
- **On success**: Toast `variant: 'success'`, exits edit mode, shows redeploy notice
- **On error**: Toast `variant: 'error'`, stays in edit mode

### 3.8 App Create Page
- **Edit mode**: N/A (always a creation form)
- **Multi-step indicator**: Shows step text like "Creating app...", "Uploading JAR..." during submission
- **On success**: Toast `variant: 'success'`, navigates to app detail page
- **On error**: Toast `variant: 'error'` with step context

### 3.9 Tap Editing (TapConfigModal + RouteDetail inline)
- **Edit mode**: Modal opens for edit or create
- **Save/Cancel**: In modal footer
- **On success**: Modal closes, parent handles toast
- **On error**: Parent handles toast

### Summary of Edit Patterns

| Page | Explicit Edit Toggle? | Unsaved Changes Indicator? | Consistent? |
|------|----------------------|---------------------------|-------------|
| Users | No (inline edits) | N/A | N/A |
| Groups | No (inline edits) | N/A | N/A |
| Roles | No (read-only) | N/A | N/A |
| Environments - name | No (InlineEdit) | N/A | OK |
| Environments - resources | YES | No | Missing |
| Environments - JAR retention | YES | No | Missing |
| OIDC Config | No (always editable) | No | Deviation |
| AppConfigDetailPage | YES | No | Missing |
| AppsTab ConfigSubTab | YES (banner) | YES (banner text) | Best pattern |

**INCONSISTENCY**: The AppsTab ConfigSubTab is the only one with a proper unsaved-changes indicator. AppConfigDetailPage (which edits the same data for a different entry point) has no such indicator.

---

## 4. Toast / Notification Patterns

### 4.1 Toast Provider
- **File**: `ui/src/components/LayoutShell.tsx` (line 783)
- **Provider**: `<ToastProvider>` from `@cameleer/design-system` wraps the entire app layout
- **Hook**: `useToast()` returns `{ toast }` function

### 4.2 Toast Call Signature
All toast calls use the same shape:
```typescript
toast({
  title: string,
  description?: string,
  variant: 'success' | 'error' | 'warning',
  duration?: number
})
```

### 4.3 Toast Variants Used

| Variant | Used For | Duration |
|---------|----------|----------|
| `success` | Successful operations | Default (auto-dismiss) |
| `error` | Failed operations | `86_400_000` (24 hours = effectively permanent) |
| `warning` | Destructive successes (delete, stop) AND partial failures | Mixed (see below) |

### 4.4 Duration Patterns

- **Success toasts**: No explicit duration (uses design system default) -- **CONSISTENT**
- **Error toasts**: Always `duration: 86_400_000` -- **CONSISTENT** (49 occurrences across 10 files)
- **Warning toasts for deletion success** (user/group/role/env/OIDC/app deleted): No explicit duration (auto-dismiss) -- **CONSISTENT**
- **Warning toasts for partial push failures**: `duration: 86_400_000` -- **CONSISTENT**

### 4.5 Naming Conventions for Toast Titles

**Success pattern**: Action-noun format
- "User created", "Group created", "Role created", "Environment created"
- "Display name updated", "Password updated", "Group renamed"
- "Config saved", "Configuration saved", "Tap configuration saved"

**Error pattern**: "Failed to [action]" format
- "Failed to create user", "Failed to delete group", "Failed to update password"
- "Save failed", "Upload failed", "Deploy failed" (shorter form)

**INCONSISTENCY**: Error messages mix two patterns:
1. "Failed to [verb] [noun]" (e.g., "Failed to create user") -- used in RBAC pages
2. "[Noun] failed" (e.g., "Save failed", "Upload failed") -- used in AppsTab, AppConfigDetailPage

### 4.6 Warning Variant for Deletions

Successful deletions use `variant: 'warning'` consistently:
- "User deleted" (UsersTab:162)
- "Group deleted" (GroupsTab:147)
- "Role deleted" (RolesTab:100)
- "Environment deleted" (EnvironmentsPage:105)
- "Configuration deleted" (OidcConfigPage:119)
- "App deleted" (AppsTab:536)
- "Deployment stopped" (AppsTab:529)

**CONSISTENT** -- all destructive-but-successful operations use warning.

---

## 5. Loading / Empty States

### 5.1 Full-Page Loading States

| Page | Component | Size | Wrapper |
|------|-----------|------|---------|
| UsersTab | `<Spinner size="md" />` | md | Bare return |
| GroupsTab | `<Spinner size="md" />` | md | Bare return |
| RolesTab | `<Spinner size="md" />` | md | Bare return |
| EnvironmentsPage | `<Spinner size="md" />` | md | Bare return |
| AppListView | `<Spinner size="md" />` | md | Bare return |
| AppDetailView | `<Spinner size="md" />` | md | Bare return |
| AgentInstance | `<Spinner size="lg" />` | **lg** | Bare return |
| AppConfigDetailPage | `<Spinner size="lg" />` | **lg** | Wrapped in `div.loading` |
| DashboardPage | `<PageLoader />` | lg | Centered container |
| RuntimePage | `<PageLoader />` | lg | Centered container |
| OidcConfigPage | `return null` | N/A | Returns nothing |

**INCONSISTENCY #1**: Most admin pages use `<Spinner size="md" />` as a bare return. AgentInstance and AppConfigDetailPage use `size="lg"`. DashboardPage and RuntimePage use the `<PageLoader />` component which wraps `<Spinner size="lg" />` in a centered container.

**INCONSISTENCY #2**: OidcConfigPage returns `null` while loading (shows a blank page), unlike every other page.

**INCONSISTENCY #3**: SplitPane detail loading (GroupsTab line 317, RolesTab line 212) uses `<Spinner size="md" />` -- consistent within that context.

### 5.2 Section Loading States

- **RouteDetail charts**: `<Spinner size="sm" />` inline in chart containers (lines 713, 804)
- **AboutMeDialog**: `<Spinner size="md" />` in a `div.loading` wrapper

### 5.3 Empty States

| Context | Pattern | Component Used |
|---------|---------|----------------|
| SplitPane list (no search match) | `emptyMessage="No X match your search"` | EntityList built-in |
| SplitPane detail (nothing selected) | `emptyMessage="Select a X to view details"` | SplitPane built-in |
| Deployments table (none) | `<p className={styles.emptyNote}>No deployments yet.</p>` | Plain `<p>` |
| Versions list (none) | `<p className={styles.emptyNote}>No versions uploaded yet.</p>` | Plain `<p>` |
| Env vars (none, not editing) | `<p className={styles.emptyNote}>No environment variables configured.</p>` | Plain `<p>` |
| Traces/Taps (none) | `<p className={styles.emptyNote}>No processor traces or taps configured.</p>` | Plain `<p>` |
| Route recording (none) | `<p className={styles.emptyNote}>No routes found for this application.</p>` | Plain `<p>` |
| AgentInstance metrics | `<EmptyState title="No data" description="No X available" />` | EmptyState (DS component) |
| Log/Event panels | `<div className={logStyles.logEmpty}>No events...</div>` | Styled `<div>` |
| OIDC default roles | `<span className={styles.noRoles}>No default roles configured</span>` | `<span>` |
| Group members (none) | `<span className={styles.inheritedNote}>(no members)</span>` | `<span>` |
| AppConfigDetailPage (not found) | `<div>No configuration found for "{appId}".</div>` | Plain `<div>` |
| RouteDetail error patterns | `<div className={styles.emptyText}>No error patterns found...</div>` | Styled `<div>` |
| RouteDetail taps (none) | `<div className={styles.emptyState}>No taps configured...</div>` | Styled `<div>` |

**INCONSISTENCY**: Empty states use at least 5 different approaches:
1. Design system `EmptyState` component (only in AgentInstance)
2. `<p className={styles.emptyNote}>` (AppsTab)
3. `<span className={styles.inheritedNote}>` with parenthetical format "(none)" (RBAC pages)
4. `<div className={styles.emptyText}>` (RouteDetail)
5. Unstyled inline text (AppConfigDetailPage)

The design system provides an `EmptyState` component but it is only used in one place (AgentInstance).

---

## 6. Inconsistency Summary

### HIGH Priority (User-facing confusion)

1. **AppConfigDetailPage button order is reversed** (Save|Cancel instead of Cancel|Save) and Save button has no `variant="primary"`. File: `ui/src/pages/Admin/AppConfigDetailPage.tsx`, lines 311-315.

2. **Deployment Stop has no confirmation dialog**. Stopping a running deployment immediately executes with no confirmation, while stopping/suspending a route shows a ConfirmDialog. File: `ui/src/pages/AppsTab/AppsTab.tsx`, line 672.

3. **Tap deletion is inconsistent**. Deleting from TapConfigModal: no confirmation. Deleting from RouteDetail table: ConfirmDialog. File: `ui/src/components/TapConfigModal.tsx` line 117 vs `ui/src/pages/Routes/RouteDetail.tsx` line 992.

4. **Kill Query has no confirmation and no feedback**. File: `ui/src/pages/Admin/DatabaseAdminPage.tsx`, line 30.

### MEDIUM Priority (Pattern deviations)

5. **Cancel button variant inconsistency**. Create forms use `variant="ghost"` for Cancel. Modal dialogs (TapConfigModal, RouteDetail tap modal) use `variant="secondary"`. File: `ui/src/components/TapConfigModal.tsx` line 255, vs `ui/src/pages/Admin/UsersTab.tsx` line 258.

6. **Removing a role from a user has no confirmation** but removing a group from a user shows an AlertDialog. Both can cascade. File: `ui/src/pages/Admin/UsersTab.tsx`, lines 504-528 vs 588-613.

7. **OIDC Config is always editable with no Cancel/discard**. Every other editable form either has inline-edit (immediate save) or explicit edit mode with Cancel. File: `ui/src/pages/Admin/OidcConfigPage.tsx`.

8. **OIDC Config delete ConfirmDialog missing `loading` prop**. All other delete ConfirmDialogs pass `loading={mutation.isPending}`. File: `ui/src/pages/Admin/OidcConfigPage.tsx`, line 258.

9. **Loading state size inconsistency**. Most pages use `Spinner size="md"`, some use `size="lg"`, some use `PageLoader`, and OidcConfigPage returns `null`. No single standard.

10. **Error toast title format inconsistency**. RBAC pages use "Failed to [verb] [noun]" while AppsTab/AppConfigDetailPage use "[Noun] failed". Should pick one.

### LOW Priority (Minor deviations)

11. **Empty state presentation varies widely**. Five different approaches used. Should standardize on the design system `EmptyState` component or at least a consistent CSS class.

12. **ConfirmDialog confirmText varies between display name and slug**. Users/Groups/Roles use display name; Environments and Apps use slug. This is arguably intentional (slug is the technical identifier) but may confuse users.

13. **OIDC Config shows both toast and inline Alert on error**. No other page shows both simultaneously. File: `ui/src/pages/Admin/OidcConfigPage.tsx`, line 92 (toast) + line 139 (inline Alert).

14. **AppConfigDetailPage Save button text changes to "Saving..."** using string interpolation, while every other page uses the `loading` prop on Button (which shows a spinner). File: `ui/src/pages/Admin/AppConfigDetailPage.tsx`, line 313.

15. **Unsaved changes indicator** only present on AppsTab ConfigSubTab (banner text). AppConfigDetailPage, Environment resource sections, and JAR retention section have no indicator even though they use explicit edit mode.

---

## 7. ConfirmDialog Usage Matrix

| Object | File | Line | confirmText Source | Has `loading`? | Has `variant`? | Has `confirmLabel`? |
|--------|------|------|-------------------|----------------|----------------|---------------------|
| User | UsersTab.tsx | 580 | displayName | YES | No (default) | No (default) |
| Group | GroupsTab.tsx | 434 | name | YES | No (default) | No (default) |
| Role | RolesTab.tsx | 223 | name | YES | No (default) | No (default) |
| Environment | EnvironmentsPage.tsx | 319 | slug | YES | No (default) | No (default) |
| OIDC Config | OidcConfigPage.tsx | 258 | "delete oidc" | **NO** | No (default) | No (default) |
| App | AppsTab.tsx | 589 | slug | YES | No (default) | No (default) |
| Tap (RouteDetail) | RouteDetail.tsx | 992 | attributeName | **NO** | `danger` | `"Delete"` |
| Route Stop | RouteControlBar.tsx | 139 | action name | YES | `danger`/`warning` | `"Stop Route"` / `"Suspend Route"` |

**NOTE**: RouteControlBar and RouteDetail set explicit `variant` and `confirmLabel` on ConfirmDialog while all RBAC/admin pages use defaults. This creates visual differences in the confirmation dialogs.

---

## 8. AlertDialog Usage Matrix

| Context | File | Line | Title | Confirm Label | Variant |
|---------|------|------|-------|---------------|---------|
| Remove group from user | UsersTab.tsx | 588 | "Remove group membership" | "Remove" | `warning` |
| Remove role from group | GroupsTab.tsx | 442 | "Remove role from group" | "Remove" | `warning` |

AlertDialog is used consistently where present (both use `warning` variant and "Remove" label).

---

## 9. Files Examined

All `.tsx` files under `ui/src/pages/` and `ui/src/components/`:

- `ui/src/pages/Admin/UsersTab.tsx`
- `ui/src/pages/Admin/GroupsTab.tsx`
- `ui/src/pages/Admin/RolesTab.tsx`
- `ui/src/pages/Admin/EnvironmentsPage.tsx`
- `ui/src/pages/Admin/OidcConfigPage.tsx`
- `ui/src/pages/Admin/AppConfigDetailPage.tsx`
- `ui/src/pages/Admin/DatabaseAdminPage.tsx`
- `ui/src/pages/Admin/ClickHouseAdminPage.tsx`
- `ui/src/pages/Admin/AuditLogPage.tsx`
- `ui/src/pages/AppsTab/AppsTab.tsx`
- `ui/src/pages/Routes/RouteDetail.tsx`
- `ui/src/pages/Exchanges/ExchangesPage.tsx`
- `ui/src/pages/Exchanges/RouteControlBar.tsx`
- `ui/src/pages/AgentHealth/AgentHealth.tsx`
- `ui/src/pages/AgentInstance/AgentInstance.tsx`
- `ui/src/pages/DashboardTab/DashboardPage.tsx`
- `ui/src/pages/RuntimeTab/RuntimePage.tsx`
- `ui/src/components/TapConfigModal.tsx`
- `ui/src/components/AboutMeDialog.tsx`
- `ui/src/components/PageLoader.tsx`
- `ui/src/components/LayoutShell.tsx`
- `ui/src/auth/LoginPage.tsx`
