> **Status: RESOLVED** — All phases (1-5) executed on 2026-04-09. Remaining: responsive design (separate initiative).

# UI Consistency Audit — cameleer-server

**Date:** 2026-04-09
**Scope:** All files under `ui/src/` (26 CSS modules, ~45 TSX components, ~15 pages)
**Verdict:** ~55% design system adoption for interactive UI. Significant duplication and inline style debt.

---

## Executive Summary

| Dimension | Score | Key Issue |
|-----------|-------|-----------|
| Design system component adoption | 55% | 32 raw `<button>`, 12 raw `<select>`, 8 raw `<input>` should use DS |
| Color consistency | Poor | ~140 violations: 45 hardcoded hex in TSX, 13 naked hex in CSS, ~55 fallback hex in `var()` |
| Inline styles | Poor | 55 RED (static inline styles), 8 YELLOW, 14 GREEN (justified) |
| Layout consistency | Mixed | 3 different page padding values, mixed gap/margin approaches |
| CSS module duplication | 22% | ~135 of 618 classes are copy-pasted across files |
| Responsive design | None | Zero `@media` queries in entire UI |

---

## 1. Critical: Hardcoded Colors (CLAUDE.md violation)

The project rule states: *"Always use `@cameleer/design-system` CSS variables for colors — never hardcode hex values."*

### Worst offenders

| File | Violations | Severity |
|------|-----------|----------|
| `ProcessDiagram/DiagramNode.tsx` | ~20 hex values in SVG fill/stroke | Critical |
| `ExecutionDiagram/ExecutionDiagram.module.css` | 17 naked hex + ~40 hex fallbacks in `var()` | Critical |
| `ProcessDiagram/CompoundNode.tsx` | 8 hex values | Critical |
| `ProcessDiagram/DiagramEdge.tsx` | 3 hex values | High |
| `ProcessDiagram/ConfigBadge.tsx` | 3 hex values | High |
| `ProcessDiagram/ErrorSection.tsx` | 2 hex values | High |
| `ProcessDiagram/NodeToolbar.tsx` | 2 hex values | High |
| `ProcessDiagram/Minimap.tsx` | 3 hex values | High |
| `Dashboard/Dashboard.module.css` | `#5db866` (not even a DS color) | High |
| `AppsTab/AppsTab.module.css` | `var(--accent, #6c7aff)` (undefined DS variable) | Medium |

### Undefined CSS variables (not in design system)

| Variable | Files | Should be |
|----------|-------|-----------|
| `--accent` | EnvironmentSelector, AppsTab | `--amber` (or define in DS) |
| `--bg-base` | LoginPage | `--bg-body` |
| `--surface` | ContentTabs, ExchangeHeader | `--bg-surface` |
| `--bg-surface-raised` | AgentHealth | `--bg-raised` |

### Missing DS tokens needed

Several tint/background colors are used repeatedly but have no DS variable:
- `--error-bg` (used as `#FDF2F0`, `#F9E0DC`)
- `--success-bg` (used as `#F0F9F1`)
- `--amber-bg` / `--warning-bg` (used as `#FFF8F0`)
- `--bg-inverse` / `--text-inverse` (used as `#1A1612` / `#E4DFD8`)

---

## 2. Critical: CSS Module Duplication (~22%)

~135 of 618 class definitions are copy-pasted across files.

### Table section pattern — 5 files, ~35 duplicate classes

`.tableSection`, `.tableHeader`, `.tableTitle`, `.tableMeta`, `.tableRight` are **identical** in:
- `DashboardTab.module.css`
- `AuditLogPage.module.css`
- `ClickHouseAdminPage.module.css`
- `RoutesMetrics.module.css`
- `RouteDetail.module.css`

### Log viewer panel — 2 files, ~50 lines identical

`.logCard`, `.logHeader`, `.logToolbar`, `.logSearchWrap`, `.logSearchInput`, `.logSearchClear`, `.logClearFilters`, `.logEmpty`, `.sortBtn`, `.refreshBtn`, `.headerActions` — byte-for-byte identical in `AgentHealth.module.css` and `AgentInstance.module.css`.

### Tap modal form — 2 files, ~40 lines identical

`.typeSelector`, `.typeOption`, `.typeOptionActive`, `.testSection`, `.testTabs`, `.testTabBtn`, `.testTabBtnActive`, `.testBody`, `.testResult`, `.testSuccess`, `.testError` — identical in `TapConfigModal.module.css` and `RouteDetail.module.css`.

### Other duplicates

| Pattern | Files | Lines |
|---------|-------|-------|
| Rate color classes (`.rateGood/.rateWarn/.rateBad/.rateNeutral`) | DashboardTab, RouteDetail, RoutesMetrics | ~12 each |
| Refresh indicator + `@keyframes pulse` | DashboardTab, RoutesMetrics | ~15 each |
| Chart card (`.chartCard`) | AgentInstance, RouteDetail | ~6 each |
| Section card (`.section`) | AppConfigDetailPage, OidcConfigPage | ~7 each |
| Meta grid (`.metaGrid/.metaLabel/.metaValue`) | AboutMeDialog, UserManagement | ~9 each |

---

## 3. High: Inline Styles (55 RED violations)

### Files with zero CSS modules (all inline)

| File | Issue |
|------|-------|
| `pages/Admin/AdminLayout.tsx` | Entire layout wrapper is inline styled |
| `pages/Admin/DatabaseAdminPage.tsx` | All layout, typography, spacing inline — no CSS module |
| `auth/OidcCallback.tsx` | Full-page layout inline — no CSS module |

### Most inline violations

| File | RED count | Primary patterns |
|------|-----------|-----------------|
| `pages/AppsTab/AppsTab.tsx` | ~25 | Fixed-width inputs (`width: 50-90px` x18), visually-hidden pattern x2, table cell layouts |
| `components/LayoutShell.tsx` | 6 | StarredList sub-component, sidebar layout |
| `pages/Admin/EnvironmentsPage.tsx` | 8 | Raw `<select>` fully styled inline, save/cancel button rows |
| `pages/Routes/RouteDetail.tsx` | 5 | Heading styles, tab panel margins |

### Repeated inline patterns that need extraction

| Pattern | Occurrences | Fix |
|---------|-------------|-----|
| `style={{ display: 'flex', justifyContent: 'center', padding: '4rem' }}` (loading fallback) | 3 files | Create shared `<PageLoader>` |
| `style={{ position: 'absolute', width: 1, height: 1, clip: 'rect(0,0,0,0)' }}` (visually hidden) | 2 in AppsTab | Create `.visuallyHidden` utility class |
| `style={{ width: N }}` on `<Input>`/`<Select>` (fixed widths) | 18+ in AppsTab | Size classes or CSS module rules |
| `style={{ marginTop: 8, display: 'flex', gap: 8, justifyContent: 'flex-end' }}` (action row) | 3+ in EnvironmentsPage | Shared `.editActions` class |

---

## 4. High: Design System Component Adoption Gaps

### Native HTML that should use DS components

| Element | Instances | Files | DS Replacement |
|---------|-----------|-------|---------------|
| `<button>` | 32 | 8 files | `Button`, `SegmentedTabs` |
| `<select>` | 12 | 4 files | `Select` |
| `<input>` | 8 | 4 files | `Input`, `Toggle`, `Checkbox` |
| `<label>` | 9 | 2 files | `FormField`, `Label` |
| `<table>` (data) | 2 | 2 files | `DataTable`, `LogViewer` |

### Highest-priority replacements

1. **`EnvironmentSelector.tsx`** — zero DS imports, entire component is a bare `<select>`. Used globally in sidebar.
2. **`ExecutionDiagram/tabs/LogTab.tsx`** — reimplements LogViewer from scratch (raw table + input + button). AgentInstance and AgentHealth already use DS `LogViewer` correctly.
3. **`AppsTab.tsx` sub-tabs** — 3 instances of homegrown `<button>` tab bars. DS provides `SegmentedTabs` and `Tabs`.
4. **`AppConfigDetailPage.tsx`** — 4x `<select>`, 4x `<label>`, 2x `<input type="checkbox">`, 4x `<button>` — all have DS equivalents already used elsewhere.
5. **`AgentHealth.tsx`** — config bar uses `Toggle` (correct) alongside raw `<select>` and `<button>` (incorrect).

### Cross-page inconsistencies

| Pattern | Correct usage | Incorrect usage |
|---------|--------------|-----------------|
| Log viewer | AgentInstance, AgentHealth use DS `LogViewer` | LogTab rebuilds from scratch |
| Config edit form | Both pages render same 4 fields | AgentHealth uses `Toggle`, AppConfigDetail uses `<input type="checkbox">` |
| Sub-tabs | RbacPage uses DS `Tabs` | AppsTab uses homegrown `<button>` tabs with non-DS `--accent` color |
| Select dropdowns | AppsTab uses DS `Select` for some fields | Same file uses raw `<select>` for other fields |

---

## 5. Medium: Layout Inconsistencies

### Page padding (3 different values)

| Pages | Padding |
|-------|---------|
| AgentHealth, AgentInstance, AdminLayout | `20px 24px 40px` |
| AppsTab | `16px` (all sides) |
| DashboardTab, Dashboard | No padding (full-bleed) |

### Section gap spacing (mixed approaches)

| Approach | Pages |
|----------|-------|
| CSS `gap: 20px` on flex container | DashboardTab, RoutesMetrics |
| `margin-bottom: 20px` | AgentInstance |
| Mixed `margin-bottom: 16px` and `20px` on same page | AgentHealth, ClickHouseAdminPage |

### Typography inconsistencies

| Issue | Details |
|-------|---------|
| Card title weight | Most use `font-weight: 600`, RouteDetail `.paneTitle` uses `700` |
| Chart title style | RouteDetail: `12px/700/uppercase`, AgentHealth: `12px/600/uppercase` |
| Font units | ExchangeHeader + TabKpis use `rem`, everything else uses `px` |
| Raw headings | DatabaseAdminPage uses `<h2>`/`<h3>` with inline styles; all others use DS `SectionHeader` or CSS classes |
| Table header padding | Most: `12px 16px`, Dashboard: `8px 12px`, AgentHealth eventCard: `10px 16px` |

### Stat strip layouts

| Page | Layout | Gap |
|------|--------|-----|
| AgentHealth, AgentInstance, RbacPage | CSS grid `repeat(N, 1fr)` | `10px` |
| ClickHouseAdminPage | Flexbox (unequal widths) | `10px` |
| DatabaseAdminPage | Inline flex | `1rem` (16px) |

### Empty state patterns (4 different approaches)

1. DS `<EmptyState>` component (AgentInstance — correct)
2. `EntityList emptyMessage` prop (EnvironmentsPage, RbacPage)
3. `.logEmpty` CSS class, `12px`, `var(--text-faint)` (AgentHealth, AgentInstance)
4. `.emptyNote` CSS class, `12px`, `italic` (AppsTab)
5. Inline `0.875rem`, `var(--text-muted)` (ExchangesPage)

### Loading state patterns (3 different approaches)

1. `<Spinner size="lg">` in flex div with inline `padding: 4rem` — copy-pasted 3 times
2. `<Spinner size="md">` returned directly, no centering (EnvironmentsPage)
3. No loading UI, data simply absent (DashboardL1/L2/L3)

---

## 6. Low: Other Findings

- **`!important`**: 1 use in `RouteControlBar.module.css` — works around specificity conflict
- **Zero responsive design**: no `@media` queries anywhere
- **Z-index**: only 4 uses, all in diagram components (5 and 10), consistent
- **Naming convention**: all camelCase — consistent, no issues
- **Unused CSS classes**: ~11 likely unused in AppsTab (old create-modal classes) and TapConfigModal

---

## Recommended Fix Order

### Phase 1: Design system tokens (unblocks everything else)

1. Add missing DS variables: `--error-bg`, `--success-bg`, `--amber-bg`, `--bg-inverse`, `--text-inverse`
2. Fix undefined variables: `--accent` -> `--amber`, `--bg-base` -> `--bg-body`, `--surface` -> `--bg-surface`

### Phase 2: Eliminate CSS duplication (~22% of all classes)

3. Extract shared `tableSection` pattern to shared CSS module (saves ~140 duplicate lines across 5 files)
4. Extract shared log viewer CSS to shared module (saves ~50 lines across 2 files)
5. Remove duplicate tap modal CSS from RouteDetail (saves ~40 lines)
6. Extract shared rate/refresh/chart patterns

### Phase 3: Fix hardcoded colors

7. Replace all hex in `ProcessDiagram/*.tsx` SVG components (~45 values)
8. Replace all hex in `ExecutionDiagram.module.css` (~17 naked + strip ~40 fallbacks)
9. Fix remaining CSS hex violations (Dashboard, AppsTab, AgentHealth)

### Phase 4: Replace native HTML with DS components

10. `EnvironmentSelector` -> DS `Select`
11. `LogTab` -> DS `LogViewer`
12. `AppsTab` sub-tabs -> DS `SegmentedTabs`
13. `AppConfigDetailPage` form elements -> DS `Select`/`Toggle`/`FormField`/`Button`
14. Remaining `<button>` -> DS `Button`

### Phase 5: Eliminate inline styles

15. Create CSS modules for AdminLayout, DatabaseAdminPage, OidcCallback
16. Extract shared `<PageLoader>` component
17. Move AppsTab fixed-width inputs to CSS module size classes
18. Move remaining inline margins/flex patterns to CSS classes

### Phase 6: Standardize layout patterns

19. Unify page padding to `20px 24px 40px`
20. Standardize section gaps to `gap: 20px` on flex containers
21. Normalize font units to `px` throughout
22. Standardize empty state to DS `<EmptyState>`
23. Standardize loading state to shared `<PageLoader>`
