UI Consistency Audit — cameleer3-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 |
| 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
EnvironmentSelector.tsx — zero DS imports, entire component is a bare <select>. Used globally in sidebar.
ExecutionDiagram/tabs/LogTab.tsx — reimplements LogViewer from scratch (raw table + input + button). AgentInstance and AgentHealth already use DS LogViewer correctly.
AppsTab.tsx sub-tabs — 3 instances of homegrown <button> tab bars. DS provides SegmentedTabs and Tabs.
AppConfigDetailPage.tsx — 4x <select>, 4x <label>, 2x <input type="checkbox">, 4x <button> — all have DS equivalents already used elsewhere.
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)
- DS
<EmptyState> component (AgentInstance — correct)
EntityList emptyMessage prop (EnvironmentsPage, RbacPage)
.logEmpty CSS class, 12px, var(--text-faint) (AgentHealth, AgentInstance)
.emptyNote CSS class, 12px, italic (AppsTab)
- Inline
0.875rem, var(--text-muted) (ExchangesPage)
Loading state patterns (3 different approaches)
<Spinner size="lg"> in flex div with inline padding: 4rem — copy-pasted 3 times
<Spinner size="md"> returned directly, no centering (EnvironmentsPage)
- 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)
- Add missing DS variables:
--error-bg, --success-bg, --amber-bg, --bg-inverse, --text-inverse
- Fix undefined variables:
--accent -> --amber, --bg-base -> --bg-body, --surface -> --bg-surface
Phase 2: Eliminate CSS duplication (~22% of all classes)
- Extract shared
tableSection pattern to shared CSS module (saves ~140 duplicate lines across 5 files)
- Extract shared log viewer CSS to shared module (saves ~50 lines across 2 files)
- Remove duplicate tap modal CSS from RouteDetail (saves ~40 lines)
- Extract shared rate/refresh/chart patterns
Phase 3: Fix hardcoded colors
- Replace all hex in
ProcessDiagram/*.tsx SVG components (~45 values)
- Replace all hex in
ExecutionDiagram.module.css (~17 naked + strip ~40 fallbacks)
- Fix remaining CSS hex violations (Dashboard, AppsTab, AgentHealth)
Phase 4: Replace native HTML with DS components
EnvironmentSelector -> DS Select
LogTab -> DS LogViewer
AppsTab sub-tabs -> DS SegmentedTabs
AppConfigDetailPage form elements -> DS Select/Toggle/FormField/Button
- Remaining
<button> -> DS Button
Phase 5: Eliminate inline styles
- Create CSS modules for AdminLayout, DatabaseAdminPage, OidcCallback
- Extract shared
<PageLoader> component
- Move AppsTab fixed-width inputs to CSS module size classes
- Move remaining inline margins/flex patterns to CSS classes
Phase 6: Standardize layout patterns
- Unify page padding to
20px 24px 40px
- Standardize section gaps to
gap: 20px on flex containers
- Normalize font units to
px throughout
- Standardize empty state to DS
<EmptyState>
- Standardize loading state to shared
<PageLoader>