Add UX polish design spec with comprehensive audit findings

Playwright-driven audit of the live UI (build 69dcce2, 60+ screenshots)
covering all pages, CRUD lifecycles, design consistency, and interaction
patterns. Spec defines 8 batches of work: critical bugs, layout
consistency, interaction consistency, contrast/readability, data
formatting, chart fixes, admin polish, and nice-to-have items.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-09 18:00:50 +02:00
parent f24a5e5ff0
commit 4ea8bb368a
5 changed files with 2054 additions and 0 deletions

View File

@@ -0,0 +1,531 @@
# UX Polish & Bug Fixes — Design Spec
**Date:** 2026-04-09
**Scope:** Bug fixes, design consistency, interaction consistency, contrast/readability, data formatting, chart fixes, admin polish
**Out of scope:** Feature work (onboarding, alerting, shareable links, latency investigation) — tracked in Epic #100
## Context
Comprehensive Playwright-driven audit of the live Cameleer3 UI (build 69dcce2, 60+ screenshots) combined with the existing UI_FINDINGS.md visual audit (2026-03-25) and 16 open Gitea issues. Three code-level audits performed: layout consistency (CSS modules), interaction patterns (dialogs, buttons, toasts), and design system adoption.
Audit artifacts in `audit/`:
- `monitoring-pages-findings.md` — exchanges, dashboard, runtime, deployments, command palette, dark mode
- `admin-lifecycle-findings.md` — RBAC CRUD, audit log, OIDC, environments, database, ClickHouse, platform
- `design-consistency-findings.md` — per-page CSS module usage, container padding, card patterns
- `interaction-patterns-findings.md` — confirmation dialogs, button order, edit/save flows, toasts, loading/empty states
- 60+ screenshots
## Implementation Strategy
8 theme-based batches, ordered by impact. Each batch groups related fixes that share code paths.
---
## Batch 1: Critical Bug Fixes
**Effort:** 1 day
### 1.1 SSE Navigation Bug
**Problem:** Admin pages sporadically redirect to `/server/exchanges` during form editing. The SSE exchange data stream triggers React state updates that cause route changes, losing unsaved work.
**Fix:** Guard SSE-driven state updates so they never trigger navigation when the current route is outside the exchanges scope. The exchange list polling/SSE should update data stores without pushing route state. Likely in `LayoutShell.tsx` or the SSE connection manager — the exchange data subscription must be decoupled from route navigation.
### 1.2 User Creation in OIDC Mode
**Problem:** `UserAdminController.createUser()` returns `ResponseEntity.badRequest().build()` (empty body) when OIDC is enabled. The UI still shows "+ Add user" and the full creation form. Toast says "Failed to create user" with no explanation.
**Fix (backend):** Return `{ "error": "Local user creation is disabled when OIDC is enabled" }` as the response body.
**Fix (frontend):** When OIDC is enabled, show an inline banner replacing the create form: "Local user creation is disabled when OIDC is active. Users are provisioned automatically via SSO." Hide or disable the "+ Add user" button.
**Files:** `UserAdminController.java:92-93`, `UsersTab.tsx` (create form section)
### 1.3 `/server/deployments` 404
**Problem:** Direct URL shows unhandled React Router dev error. The Deployments tab lives at `/server/apps`.
**Fix:** Add redirect route in `router.tsx`: `{ path: "deployments", element: <Navigate to="/server/apps" replace /> }`
**Files:** `ui/src/router.tsx`
### 1.4 GC Pauses Chart X-axis
**Problem:** Renders ~60 full ISO-8601 timestamps overlapping into an unreadable block. All other agent charts (CPU, Memory, Throughput, Error Rate, Thread Count) use concise time labels.
**Fix:** Use the same X-axis time formatter as the other 5 agent charts. The GC Pauses chart likely passes raw ISO strings to the chart library instead of using the shared time axis configuration.
**Files:** `AgentInstance.tsx` or `AgentInstance.module.css` (GC Pauses chart config)
---
## Batch 2a: Design/Layout Consistency
**Effort:** 2 days
Reference: `audit/design-consistency-findings.md`
### 2a.1 Exchanges Table Containment
**Problem:** Only full-bleed table in the app. `Dashboard.tsx` rolls its own `.tableHeader` with `padding: 8px 12px` instead of using `table-section.module.css` (shared: `12px 16px`). No card wrapper.
**Fix:** Import and use `tableStyles.tableSection` wrapper. Replace custom `.tableHeader`, `.tableTitle`, `.tableRight`, `.tableMeta` with the shared module classes. The exchange table should look like every other table (Audit Log, ClickHouse, Dashboard L2/L3).
**Files:** `pages/Dashboard/Dashboard.tsx`, `pages/Dashboard/Dashboard.module.css`
### 2a.2 App Detail/Create App Flat Controls
**Problem:** `AppsTab.tsx` renders all form controls flat against the background. Config tabs (Monitoring, Resources, Variables, Traces & Taps, Route Recording) have labels and controls but no card wrappers. Controls "mesh into background."
**Fix:** Wrap each configuration group in `sectionStyles.section` from `section-card.module.css`, matching the OIDC page pattern (`OidcConfigPage.tsx`) and AppConfigDetail pattern (`AppConfigDetailPage.tsx`). Each sub-tab's content gets a section card.
**Files:** `pages/AppsTab/AppsTab.tsx`, `pages/AppsTab/AppsTab.module.css`
### 2a.3 Apps Deployment Table
**Problem:** Uses raw HTML `<table>` instead of `DataTable` with no `tableStyles.tableSection` wrapper.
**Fix:** Replace manual `<table>` with `DataTable` inside `tableStyles.tableSection`, matching the Audit Log pattern.
**Files:** `pages/AppsTab/AppsTab.tsx`
### 2a.4 Container Padding Normalization
**Problem:** Three different strategies: Exchanges/Dashboard = no padding, Runtime/Admin = `20px 24px 40px`, Apps = `16px`.
**Fix:**
- Standardize on `20px 24px 40px` for all scrollable content pages
- Apps: change from `16px` to `20px 24px 40px`
- Dashboard: add `padding: 0 24px 20px` for side margins (keep gap-based vertical spacing)
- Exchanges: exception — split-view height-filling layout needs no padding
**Files:** `pages/AppsTab/AppsTab.module.css` (`.container`), `pages/DashboardTab/DashboardTab.module.css` (`.content`)
### 2a.5 Deduplicate Card CSS
**Problem:** The `bg-surface + border + border-radius + box-shadow` card pattern is copy-pasted in 8+ locations instead of importing shared modules.
**Fix:** Replace custom card declarations with imports from `section-card.module.css` or `table-section.module.css`:
- `DashboardTab.module.css``.errorsSection`, `.diagramSection` -> `tableStyles.tableSection`
- `AgentHealth.module.css``.configBar`, `.eventCard` -> `sectionStyles.section`
- `AgentInstance.module.css``.processCard`, `.timelineCard` -> `sectionStyles.section`
- `ClickHouseAdminPage.module.css``.pipelineCard` -> `sectionStyles.section`
### 2a.6 Admin Detail Pages Flat Forms
**Problem:** RBAC detail (Users/Groups) and Environments detail render form controls without section cards.
**Fix:** Wrap detail sections in `sectionStyles.section`. For master-detail pages, the detail panel should use section cards to group related fields (Identity, Roles, Group Membership, etc.).
**Files:** `pages/Admin/UsersTab.tsx`, `pages/Admin/GroupsTab.tsx`, `pages/Admin/EnvironmentsPage.tsx`
### 2a.7 Database Admin Table
**Problem:** Uses `DataTable` without `tableStyles.tableSection` wrapper.
**Fix:** Wrap in `tableStyles.tableSection` like Audit Log and ClickHouse.
**Files:** `pages/Admin/DatabaseAdminPage.tsx`
---
## Batch 2b: Interaction Pattern Consistency
**Effort:** 2 days
Reference: `audit/interaction-patterns-findings.md`
### 2b.1 AppConfigDetailPage Button Order Reversed (HIGH)
**Problem:** Save|Cancel order (lines 311-315), reversed from every other form. Save button has no `variant="primary"`.
**Fix:** Swap to Cancel (ghost, left) | Save (primary, right).
**Files:** `pages/Admin/AppConfigDetailPage.tsx:311-315`
### 2b.2 Deployment Stop Needs Confirmation (HIGH)
**Problem:** `AppsTab.tsx:672` — stopping a running deployment is immediate. Route Stop/Suspend uses ConfirmDialog.
**Fix:** Add `ConfirmDialog`: "Stop deployment for {appName}? This will take the service offline." Type-to-confirm with app slug.
**Files:** `pages/AppsTab/AppsTab.tsx:672`
### 2b.3 Tap Deletion Inconsistency (HIGH)
**Problem:** `TapConfigModal.tsx:117` — delete is immediate. `RouteDetail.tsx:992` — shows ConfirmDialog.
**Fix:** Add ConfirmDialog to TapConfigModal delete, matching RouteDetail.
**Files:** `components/TapConfigModal.tsx:117`
### 2b.4 Kill Query Unguarded (HIGH)
**Problem:** `DatabaseAdminPage.tsx:30` — no confirmation, no toast.
**Fix:** Add AlertDialog ("Kill query {pid}?") and success/error toast.
**Files:** `pages/Admin/DatabaseAdminPage.tsx:30`
### 2b.5 Role Removal From User Unguarded (MEDIUM)
**Problem:** `UsersTab.tsx:504-528` — role removal is immediate. Group removal shows AlertDialog.
**Fix:** Add AlertDialog for role removal: "Remove role {name}? This may revoke access. Continue?" matching group removal pattern.
**Files:** `pages/Admin/UsersTab.tsx:504-528`
### 2b.6 Cancel Button Variant Standardization (MEDIUM)
**Problem:** Create forms use `variant="ghost"`. Modal dialogs (TapConfigModal, RouteDetail) use `variant="secondary"`.
**Fix:** Standardize Cancel = `ghost` everywhere.
**Files:** `components/TapConfigModal.tsx:255`, `pages/Routes/RouteDetail.tsx` (tap modal footer)
### 2b.7 OIDC Always-Editable Deviation (MEDIUM)
**Problem:** Only admin form without Edit mode toggle or Cancel button. No way to discard changes.
**Fix:** Add explicit Edit mode with Cancel/Save, matching Environment resource editing and AppConfigDetail patterns. Show read-only view by default, Edit button to enter edit mode.
**Files:** `pages/Admin/OidcConfigPage.tsx`
### 2b.8 ConfirmDialog Missing `loading` Prop (MEDIUM)
**Problem:** `OidcConfigPage.tsx:258` and `RouteDetail.tsx:992` — no `loading` prop (all others have it).
**Fix:** Add `loading={mutation.isPending}` to both.
**Files:** `pages/Admin/OidcConfigPage.tsx:258`, `pages/Routes/RouteDetail.tsx:992`
### 2b.9 Loading State Standardization (MEDIUM)
**Problem:** Mix of `Spinner size="md"`, `Spinner size="lg"`, `PageLoader`, and `null`.
**Fix:** Use `PageLoader` for all full-page loading states. Replace bare `<Spinner size="md" />` returns in UsersTab, GroupsTab, RolesTab, EnvironmentsPage, AppListView, AppDetailView with `<PageLoader />`. Fix OidcConfigPage returning `null`.
**Files:** All admin page files, `pages/Admin/OidcConfigPage.tsx`
### 2b.10 Error Toast Title Format (MEDIUM)
**Problem:** RBAC: "Failed to create user" / AppsTab: "Save failed" — two patterns.
**Fix:** Standardize on "Failed to [verb] [noun]" (more descriptive). Update AppsTab and AppConfigDetailPage error toasts.
**Files:** `pages/AppsTab/AppsTab.tsx`, `pages/Admin/AppConfigDetailPage.tsx`
### 2b.11 Empty State Standardization (LOW)
**Problem:** 5 different approaches. DS `EmptyState` component only used in AgentInstance.
**Fix:** Replace all `<p className={emptyNote}>`, `<span className={inheritedNote}>`, `<div className={emptyText}>`, and plain text empty states with DS `EmptyState` component or a consistent shared `emptyNote` class with centered, muted styling.
**Files:** `pages/AppsTab/AppsTab.tsx`, `pages/Admin/*.tsx`, `pages/Routes/RouteDetail.tsx`
### 2b.12 Unsaved Changes Indicator (LOW)
**Problem:** Only AppsTab ConfigSubTab has the banner pattern.
**Fix:** Add unsaved-changes indicator to:
- `AppConfigDetailPage.tsx` — reuse banner pattern from ConfigSubTab
- `EnvironmentsPage.tsx` — resource editing and JAR retention sections
- `OidcConfigPage.tsx` — after adding Edit mode (2b.7)
### 2b.13 Save Button Loading State (LOW)
**Problem:** `AppConfigDetailPage.tsx:313` shows "Saving..." text instead of Button's `loading` prop.
**Fix:** Use `loading={isPending}` on Button (shows spinner, matches all other save buttons).
**Files:** `pages/Admin/AppConfigDetailPage.tsx:313`
### 2b.14 OIDC Dual Error Display (LOW)
**Problem:** Shows both toast AND inline Alert on error. No other page does this.
**Fix:** Remove the inline Alert. Use toast only, matching all other pages. Or remove the toast and keep only the inline Alert (useful for form context). Pick one — don't show both.
**Files:** `pages/Admin/OidcConfigPage.tsx:92,139`
---
## Batch 3: Contrast & Readability
**Effort:** 1 day
Reference: `UI_FINDINGS.md` cross-cutting issues
### 3.1 WCAG AA Fix for `--text-muted`
**Problem:** Fails WCAG AA in both modes. Light: #9C9184 on #FFFFFF = ~3.0:1. Dark: #7A7068 on #242019 = ~2.9:1.
**Fix:** Change design system token values:
- Light mode: `--text-muted: #766A5E` (achieves 4.5:1)
- Dark mode: `--text-muted: #9A9088` (achieves 4.5:1)
Single highest-impact fix — affects every page.
**Files:** Design system CSS variables (light and dark theme definitions)
### 3.2 WCAG AA Fix for `--text-faint`
**Problem:** Dark mode #4A4238 on #242019 = 1.4:1 — essentially invisible.
**Fix:** Restrict `--text-faint` to decorative use only (borders, dividers), or change dark mode value to `#6A6058` (achieves 3:1 minimum). Audit all usages and replace any `--text-faint` on readable text with `--text-muted`.
**Files:** Design system CSS variables, all files using `--text-faint`
### 3.3 Font Size Floor
**Problem:** 10px text used for StatCard labels, overview labels, chain labels, section meta, sidebar tree labels. 11px for table meta, error messages, pagination, toggle buttons, chart titles.
**Fix:** Establish `--font-size-min: 12px`. Update all 10px and 11px instances to 12px minimum. Grep for `font-size: 10px` and `font-size: 11px` across all CSS modules.
**Files:** All CSS modules containing sub-12px font sizes
---
## Batch 4: Data Formatting & Terminology
**Effort:** 2 days
### 4.1 Exchange ID Truncation
**Problem:** 33-char hex dominates the table (e.g., `96E395B0088AA6D-000000000001E75C`).
**Fix:**
- Show last 8 chars with ellipsis: `...0001E75C`
- Full ID on hover tooltip
- Copy-to-clipboard on click (show brief "Copied" indicator)
- Apply to: exchange table, detail breadcrumb, command palette results
### 4.2 Attributes Column
**Problem:** Always shows "---" for every row.
**Fix:** Hide the column when all rows in the current result set have no attributes. Show as colored badges when populated (infrastructure exists in `attribute-color.ts`).
### 4.3 Status Terminology
**Problem:** "OK/ERR" in exchange table vs "COMPLETED/FAILED" in detail panel.
**Fix:** Standardize on the table convention (OK/WARN/ERR) everywhere. Update the detail panel's status display.
### 4.4 Agent Name Truncation
**Problem:** Raw K8s pod names like `8c0affadb860-1` or `cameleer3-backend-7c778f488c-2c2pc-1`.
**Fix:**
- Use agent `displayName` if set at registration
- Otherwise extract a short identifier (strip common prefix, show unique suffix)
- Full name in hover tooltip
- Apply to: exchange table Agent column, Runtime agent list
### 4.5 Duration Formatting
**Problem:** P99 "6695ms" should be "6.7s"; raw "321s" should be "5m 21s".
**Fix:** Create/extend shared duration formatter:
- `< 1000ms` -> `Xms` (e.g., "178ms")
- `1000ms-59999ms` -> `X.Xs` (e.g., "6.7s")
- `>= 60000ms` -> `Xm Ys` (e.g., "5m 21s")
Apply to: KPI strip, exchange table, exchange detail, dashboard route table, agent detail.
### 4.6 Number Formatting
**Problem:** Locale-specific decimals ("1.050" ambiguous), inconsistent unit spacing.
**Fix:** Use `Intl.NumberFormat` with explicit locale handling. Always put space before unit: "6.7 s", "1.9 %", "7.1 msg/s". Use K/M suffixes consistently for large numbers.
---
## Batch 5: Chart & Visualization Fixes
**Effort:** 1 day
### 5.1 Agent Throughput Y-axis
**Problem:** 2 msg/s data on 1.2k scale — flat line.
**Fix:** Auto-scale Y-axis to data range with ~20% headroom. Apply to all 6 agent charts.
**Files:** `pages/AgentInstance/AgentInstance.tsx`
### 5.2 Agent Error Rate Unit Mismatch
**Problem:** Chart shows "err/h", KPI shows "%".
**Fix:** Standardize units. If KPI shows %, chart should show %.
### 5.3 Agent Memory Reference Line
**Problem:** Y-axis goes to 68 MB but max heap is 124 MB.
**Fix:** Add a reference/threshold line at max heap so users see how close memory is to the limit.
### 5.4 Agent State "UNKNOWN"
**Problem:** Shown alongside "LIVE" — confusing dual state.
**Fix:** If UNKNOWN is a secondary state field, either hide when primary state is LIVE, or label clearly: "Container: Unknown".
### 5.5 Dashboard Table Pointer Events
**Problem:** `_tableSection` and `_chartGrid` divs intercept pointer events on Application Health table rows.
**Fix:** Fix z-index/pointer-events so table rows receive click events correctly.
**Files:** `pages/DashboardTab/DashboardTab.module.css`
---
## Batch 6: Admin Polish
**Effort:** 2 days
### 6.1 Error Toasts Surface API Details
**Problem:** All API error handlers show generic "Failed to X" without the response body message.
**Fix:** Extract response body message in all API error handlers and include in toast description. Fallback to generic only when body is empty.
### 6.2 Unicode Escape in Roles
**Problem:** Role descriptions show `\u00b7` literally.
**Fix:** Decode unicode escapes in the role description strings. Either fix at the backend (return actual character) or frontend (decode before render).
**Files:** `pages/Admin/RolesTab.tsx`, potentially backend `RoleAdminController`
### 6.3 Password Confirmation Field
**Problem:** User creation form has no password confirmation.
**Fix:** Add "Confirm Password" field below Password. Inline validation error if mismatch. Show password policy hint: "Min 12 characters, 3 of 4: uppercase, lowercase, number, special".
**Files:** `pages/Admin/UsersTab.tsx`
### 6.4 Platform Label/Value Spacing
**Problem:** "Slugdefault", "Max Agents3", "Issued8. April 2026" — missing separators.
**Fix:** Fix the SaaS platform components that render key-value pairs. Add proper layout (colon + space, or definition list).
**Files:** SaaS platform UI (likely in `cameleer-saas` repo)
### 6.5 License Badge Colors
**Problem:** DISABLED features use red badges — looks like errors.
**Fix:** Change from `--error` (red) to neutral gray/muted for "not included in plan". Reserve red for "broken"/"failed".
**Files:** SaaS platform UI (license page component)
### 6.6 OIDC Client Secret Masking
**Problem:** Plain text input for sensitive field.
**Fix:** Change to `type="password"` with a show/hide toggle button.
**Files:** `pages/Admin/OidcConfigPage.tsx`
### 6.7 Audit Log Export
**Problem:** No export functionality for compliance.
**Fix:** Add export button to audit log header: CSV (current page, client-side) and CSV (all matching, server-side streaming). Filename: `cameleer-audit-YYYY-MM-DDTHH-MM.csv`.
**Files:** `pages/Admin/AuditLogPage.tsx`, new backend endpoint with `Accept: text/csv`
---
## Batch 7: Nice-to-Have Polish
**Effort:** 1-2 days
Lower priority, implement time-permitting:
| # | Item | File(s) |
|---|------|---------|
| 7.1 | Breadcrumb update when exchange selected | `ExchangesPage.tsx` |
| 7.2 | Explicit close/back button on exchange detail panel | `ExchangesPage.tsx` |
| 7.3 | Command palette: update category counts when search filters | Command palette component |
| 7.4 | Command palette: truncate exchange IDs | Command palette component |
| 7.5 | 7-Day Pattern heatmap: show "Insufficient data" when < 2 days | `DashboardTab` |
| 7.6 | Deployment list: show status badges per row | `AppsTab.tsx` |
| 7.7 | SplitPane empty state placeholders: add icon + centered styling | DS `SplitPane` or per-page |
| 7.8 | App deletion: make "Delete App" button more discoverable | `AppsTab.tsx` |
| 7.9 | Audit log: expandable rows for event detail | `AuditLogPage.tsx` |
| 7.10 | ConfirmDialog `confirmText` convention: standardize on display name | All ConfirmDialog usages |
---
## Implementation Order
| Order | Batch | Items | Effort | Impact |
|-------|-------|-------|--------|--------|
| 1 | **Batch 1: Critical Bugs** | 4 | 1 day | Fixes broken functionality |
| 2 | **Batch 2a: Layout Consistency** | 7 | 2 days | Visual coherence |
| 3 | **Batch 2b: Interaction Consistency** | 14 | 2 days | Behavioral coherence |
| 4 | **Batch 3: Contrast & Readability** | 3 | 1 day | WCAG compliance |
| 5 | **Batch 4: Data Formatting** | 6 | 2 days | Exchange table transformation |
| 6 | **Batch 5: Chart Fixes** | 5 | 1 day | Agent detail usability |
| 7 | **Batch 6: Admin Polish** | 7 | 2 days | Form UX and error handling |
| 8 | **Batch 7: Nice-to-Have** | 10 | 1-2 days | Final polish |
**Total: ~52 items across 8 batches, ~12-14 days of work.**
---
## Related Issues
| Issue | Relevance |
|-------|-----------|
| #100 | Epic: UX Audit PMF Readiness — this spec covers polish/bugs only, not feature work |
| #105 | Exchange table readability — covered by Batch 4 (4.1-4.4) |
| #110 | Time/locale formatting — covered by Batch 4 (4.5-4.6) |
| #107 | Data export — partially covered by Batch 6 (6.7 audit log export) |
| #82 | Non-admin user experience — not covered (feature work) |
| #90 | Backend gaps — not covered (feature work) |
## Appendix: Design System Reference Patterns
**Correct table pattern** (Audit Log, ClickHouse, Dashboard L2/L3):
```tsx
import tableStyles from '../../styles/table-section.module.css';
<div className={tableStyles.tableSection}>
<div className={tableStyles.tableHeader}>
<span className={tableStyles.tableTitle}>Title</span>
<span className={tableStyles.tableMeta}>meta</span>
</div>
<DataTable ... />
</div>
```
**Correct form section pattern** (OIDC, AppConfigDetail):
```tsx
import sectionStyles from '../../styles/section-card.module.css';
<div className={sectionStyles.section}>
<SectionHeader>Section Title</SectionHeader>
{/* form controls */}
</div>
```
**Correct button order** (all create/edit forms):
```tsx
<div className={styles.actions}>
<Button variant="ghost" size="sm" onClick={onCancel}>Cancel</Button>
<Button variant="primary" size="sm" onClick={onSubmit}>Save</Button>
</div>
```
**Correct loading state:**
```tsx
import { PageLoader } from '../../components/PageLoader';
if (isLoading) return <PageLoader />;
```