Files
cameleer-server/audit/interaction-patterns-findings.md
hsiegeln cb3ebfea7c
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 18s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
chore: rename cameleer3 to cameleer
Rename Java packages from com.cameleer3 to com.cameleer, module
directories from cameleer3-* to cameleer-*, and all references
throughout workflows, Dockerfiles, docs, migrations, and pom.xml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:28:42 +02:00

30 KiB

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:

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)

  1. 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.

  2. 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.

  3. 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.

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

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

  6. 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)

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

  2. 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.

  3. 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).

  4. 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.

  5. 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