# UI Mock Alignment Design

**Date:** 2026-03-23
**Status:** Reviewed
**Scope:** Close all gaps between `@cameleer/design-system` mocks and the cameleer-server UI

## Context

The `@cameleer/design-system` package (v0.0.2) contains fully realized mock pages demonstrating the target UX for the Cameleer monitoring platform. The current server UI was built as a first pass and has significant deviations from these mocks across every page. This spec defines the work to align them.

**Out of scope:**
- Business context columns (Order ID, Customer) — not applicable to current data model
- Application log streaming — agent does not send logs; placeholder only

## 1. Backend — New Endpoints

### 1a. Processor Stats Endpoint

**`GET /api/v1/routes/metrics/processors`**

Exposes per-processor statistics. The current `stats_1m_processor` continuous aggregate groups by `(bucket, group_name, route_id, processor_type)` and lacks `processor_id`. TimescaleDB continuous aggregates cannot be ALTERed to add GROUP BY columns.

**Migration:** Add `V7__processor_stats_by_id.sql` creating a new continuous aggregate `stats_1m_processor_detail`:
```sql
CREATE MATERIALIZED VIEW stats_1m_processor_detail
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 minute', start_time) AS bucket,
  group_name,
  route_id,
  processor_id,
  processor_type,
  COUNT(*) AS total_count,
  COUNT(*) FILTER (WHERE status = 'FAILED') AS failed_count,
  SUM(duration_ms) AS duration_sum,
  MAX(duration_ms) AS duration_max,
  approx_percentile(0.99, percentile_agg(duration_ms)) AS p99_duration
FROM processor_executions
GROUP BY bucket, group_name, route_id, processor_id, processor_type;
```
Leave the original `stats_1m_processor` intact (used elsewhere).

**Controller:** Add new method in existing `RouteMetricsController.java` (shares `/api/v1/routes/metrics` base path) rather than a separate controller.

**Query params:**
- `routeId` (required) — filter by route
- `appId` (optional) — filter by application
- `from` / `to` (optional) — time window, defaults to last 24h

**Response:** `List<ProcessorMetrics>`
```java
record ProcessorMetrics(
    String processorId,     // unique processor ID within the route
    String processorType,   // e.g. "to", "process", "choice"
    String routeId,
    String appId,
    long totalCount,
    long failedCount,
    double avgDurationMs,
    double p99DurationMs,
    double errorRate        // failedCount / totalCount
)
```

**Security:** VIEWER+ role. Already covered by existing `GET /api/v1/routes/**` wildcard in `SecurityConfig`.

### 1b. Agent Metrics Query Endpoint

**`GET /api/v1/agents/{agentId}/metrics`**

Queries the `agent_metrics` hypertable and returns time-bucketed series.

**Query params:**
- `names` (required) — comma-separated metric names (e.g. `jvm.cpu.process,jvm.memory.heap.used`)
- `from` / `to` (optional) — time window, defaults to last 1h
- `buckets` (optional, default 60) — number of time buckets

**Response:** `AgentMetricsResponse`
```java
record AgentMetricsResponse(
    Map<String, List<MetricBucket>> metrics
)

record MetricBucket(
    Instant time,
    double value    // avg within bucket
)
```

**Implementation:** Use `time_bucket()` on `agent_metrics.collected_at`, grouped by `metric_name`, averaged by `metric_value`. Filter by `agent_id` and optional `tags` if needed.

**Security:** VIEWER+ role. Requires new `SecurityConfig` rule: `GET /api/v1/agents/*/metrics` (existing `/api/v1/agents` rule is exact-match only, does not cover sub-paths).

### 1c. Enrich AgentInstanceResponse

Add fields to existing `AgentInstanceResponse`:
```java
// existing fields...
String version,           // from AgentInfo.version in registry
Map<String, Object> capabilities  // from AgentInfo.capabilities
```

These values are already stored in the `AgentRegistry`'s `AgentInfo` objects. The `AgentRegistrationController.listAgents()` method just needs to map them into the response DTO.

### 1d. Password Reset Endpoint

**`POST /api/v1/admin/users/{userId}/password`**

The current `UpdateUserRequest` has no password field. Add a dedicated endpoint for admin password reset.

**Request body:** `SetPasswordRequest`
```java
record SetPasswordRequest(
    @NotBlank String password
)
```

**Response:** 204 No Content

**Implementation:** Hash password with same BCrypt encoder used in `createUser`, update `users.password_hash` column.

**Security:** ADMIN role required (same as other user management endpoints).

**New files:** `cameleer-server-app/.../dto/SetPasswordRequest.java`; new method in `UserAdminController`.

## 2. Dashboard Enhancements

### 2a. DetailPanel — Errors Section

When the selected execution has a non-null `errorMessage`:
- Insert an "Errors" section between Overview and Processors in the DetailPanel
- Display:
  - Error class: parsed from `errorMessage` (text before `:` or first line)
  - Error message: remainder of `errorMessage`
  - Stack trace: `errorStackTrace` in a collapsible `CodeBlock` (content prop)
- Use `Alert` variant="error" for the error class/message, `Collapsible` + `CodeBlock` for the stack trace

### 2b. DetailPanel — Route Flow Tab

Add a third tab to the DetailPanel tabs: **Overview | Processors | Route Flow**

- Fetch diagram via `useDiagramByRoute(execution.groupName, execution.routeId)`
- Render `RouteFlow` component from design system
- Overlay execution data: map each `ProcessorNode` status onto diagram nodes using `diagramNodeId`
- Color nodes by status: success (green), failed (red), running (blue)
- Show duration labels on nodes
- **RouteFlow overlay API:** The `RouteFlow` component accepts execution data to color nodes. During implementation, read the `RouteFlow.tsx` source in the design system to confirm the exact props interface (likely an `overlays` or `nodeStates` prop mapping node IDs to status/duration). Map `ProcessorNode.diagramNodeId` → `PositionedNode.id` to connect execution data to diagram nodes.

### 2c. Stat Card Alignment

Change the 5 stat cards to match mock semantics:

| Position | Label | Value | Source |
|----------|-------|-------|--------|
| 1 | Throughput | exchanges/s | `totalCount / timeWindowSeconds` from stats |
| 2 | Error Rate | % | `failedCount / totalCount * 100` from stats |
| 3 | Avg Latency | ms | `avgDurationMs` from stats |
| 4 | P99 Latency | ms | `p99LatencyMs` from stats |
| 5 | In-Flight | count | `activeCount` from stats |

Each card includes:
- `Sparkline` from timeseries buckets (existing)
- Trend arrow: compare current vs `prev*` fields, show up/down indicator

## 3. Exchange Detail Enhancements

### 3a. Correlation Chain

Below the exchange header card, add a "Correlation Chain" section:

- **Data source:** `POST /search/executions` with filter `{ correlationId: execution.correlationId }`
- **Rendering:** Horizontal chain of small cards connected by arrows
  - Each card: route name, `StatusDot`, duration, relative timestamp
  - Current exchange highlighted
  - Click navigates to that exchange (`/exchanges/:id`)
- **Conditional:** Only show section when correlationId is present and search returns > 1 result
- **Limit:** Request with `limit: 20` to prevent excessive results. If more exist, show "+N more" link
- **Hook:** `useCorrelationChain(correlationId)` — new query hook wrapping the search call

### 3b. Timeline / Flow Toggle

Above the processor timeline section:

- Add `SegmentedTabs` with options: **Timeline** | **Flow**
- **Timeline** (default): existing `ProcessorTimeline` component (Gantt view)
- **Flow**: `RouteFlow` component with execution overlay
  - Fetch diagram via `useDiagramByRoute(execution.groupName, execution.routeId)` (same as 2b)
  - Color nodes by processor status, show duration labels
  - Clicking a processor node in either view selects it and loads its snapshot

### 3c. Header Enrichment

Add to the exchange header:
- Processor count: `execution.processors.length` (or recursive count for nested trees)
- Display as a stat in the header's right section alongside duration

## 4. Route Detail Page (NEW)

**New page** at `/routes/:appId/:routeId`. Currently this path renders a filtered `RoutesMetrics`; replace with a dedicated route detail page. The filtered table/chart view from `RoutesMetrics` is not lost — it is subsumed by the Performance and Recent Executions tabs in the new page, which provide the same data in a richer context alongside the route diagram.

Update `router.tsx`: the `/routes/:appId/:routeId` route imports a new `RouteDetail` component instead of `RoutesMetrics`. The `/routes` and `/routes/:appId` routes remain unchanged (continue to render `RoutesMetrics`).

### 4a. Route Header Card

Card displaying:
- Route name (`routeId`) and application name (`appId`)
- Health status from route catalog (`useRouteCatalog()` filtered)
- Exchange count (last 24h)
- Last seen timestamp
- Back link to `/routes/:appId`

### 4b. Route Diagram + Processor Stats (Side-by-Side)

Two-column grid:
- **Left:** `RouteFlow` component rendering the route diagram
  - Data from `useDiagramByRoute(appId, routeId)` or `useDiagramLayout(contentHash)`
- **Right:** Processor stats table from new endpoint (1a)
  - `DataTable` columns: Processor ID, Type, Executions, Avg Duration, P99 Duration, Error Rate
  - Data from `useProcessorMetrics(routeId, appId)`

### 4c. Tabbed Section

`Tabs` component with three tabs:

**Performance tab:**
- 2x2 chart grid (same pattern as RoutesMetrics) filtered to this specific route
- Data from `useStatsTimeseries(from, to, routeId, appId)`

**Recent Executions tab:**
- `DataTable` showing recent executions for this route
- Data from `useSearchExecutions({ routeId, group: appId, limit: 20, sortField: 'startTime', sortDir: 'desc' })`
- Columns: Status, Execution ID, Duration, Start Time, Error Message
- Row click navigates to `/exchanges/:id`

**Error Patterns tab:**
- Group failed executions by `errorMessage`
- Display: error message (truncated), count, last occurrence timestamp, link to sample execution
- Data from `useSearchExecutions({ routeId, group: appId, status: 'FAILED', limit: 100 })` — client-side grouping by `errorMessage`

### 4d. CSS Module

`RouteDetail.module.css` with classes:
- `.headerCard` — surface card, padding, margin-bottom
- `.diagramStatsGrid` — 2-column grid
- `.diagramPane` / `.statsPane` — surface cards
- `.tabSection` — margin-top
- `.chartGrid` — 2x2 grid (reuse pattern from RoutesMetrics)

## 5. Agent Health Enhancements

### 5a. DetailPanel (Slide-In)

Add a `DetailPanel` from the design system, triggered by clicking an instance row in a GroupCard.

**Overview tab:**
- Status with `StatusDot` and `Badge`
- Application name, version (from enriched AgentInstanceResponse, section 1c)
- Uptime (formatted), last heartbeat (relative time)
- TPS, error rate
- Active routes / total routes
- Memory usage: `ProgressBar` — data from `GET /agents/{id}/metrics?names=jvm.memory.heap.used,jvm.memory.heap.max&buckets=1` (single latest bucket gives the most recent averaged value)
- CPU usage: `ProgressBar` — data from `GET /agents/{id}/metrics?names=jvm.cpu.process&buckets=1` (single latest bucket)

**Performance tab:**
- Two `LineChart` components:
  - Throughput over time (from timeseries stats filtered by agentId, or from agent metrics)
  - Error rate over time

### 5b. Instance Table Enrichment

Add columns to the instance rows within each `GroupCard`:

| Column | Source |
|--------|--------|
| Status dot | `agent.status` (existing) |
| Instance name | `agent.name` (existing) |
| State badge | `agent.status` (existing) |
| Uptime | `agent.uptimeSeconds` → formatted "2d 4h" / "15m" |
| TPS | `agent.tps` (existing) |
| Error rate | `agent.errorRate` → percentage |
| Last heartbeat | `agent.lastHeartbeat` → relative "2m ago" |
| Link | Icon button → `/agents/:appId/:instanceId` |

### 5c. Alert Banners

When a `GroupCard` contains instances with status `DEAD`:
- Show `Alert` variant="error" at the top of the card body
- Message: `"N instance(s) unreachable"` where N is the count of DEAD instances

### 5d. Stat Card Alignment

Replace current 4 cards (Total, Live, Stale, Dead) with 5 cards matching mock:

| Label | Value | Accent |
|-------|-------|--------|
| Total Agents | count (subtitle: "N live / N stale / N dead") | default |
| Applications | count of unique appIds | default |
| Active Routes | sum of activeRoutes across live agents | default |
| Total TPS | sum of tps across live agents | default |
| Dead | count of dead agents | error |

### 5e. Scope Trail

Add breadcrumb below stat cards:
- All agents view: `Agents` with live `Badge` showing "N live"
- Filtered by app: `Agents` > `{appName}` with health `Badge` (live/stale/dead color)

## 6. Agent Instance Enhancements

### 6a. JVM Metrics Charts (3x2 Grid)

Replace current 2-column chart grid with 3x2 grid. All data from new endpoint (1b).

| Chart | Type | Metric Name(s) |
|-------|------|----------------|
| CPU Usage | AreaChart | `jvm.cpu.process` (0-1 scale, display as %) |
| Memory (Heap) | AreaChart | `jvm.memory.heap.used` + `jvm.memory.heap.max` (two series) |
| Throughput | AreaChart | from `useStatsTimeseries` filtered by agent (existing) |
| Error Rate | LineChart | from `useStatsTimeseries` filtered by agent (existing) |
| Thread Count | LineChart | `jvm.threads.count` |
| GC Pauses | BarChart | `jvm.gc.time` |

**Hook:** `useAgentMetrics(agentId, metricNames[], from, to, buckets)` — wraps endpoint 1b.

### 6b. Process Information Card

Card with key-value pairs:

| Key | Source |
|-----|--------|
| JVM Version | `agent.capabilities.jvmVersion` or parse from registration |
| Camel Version | `agent.capabilities.camelVersion` |
| Spring Boot | `agent.capabilities.springBootVersion` |
| Started | `agent.registeredAt` formatted |
| Capabilities | render as tags: tracing, metrics, diagrams, replay |

Data from enriched `AgentInstanceResponse` (section 1c). If version details aren't in current capabilities, they can be added to agent registration in a future iteration — show what's available.

### 6c. Stat Card Alignment

Replace current 4 cards with 5 cards matching mock:

| Label | Value | Source |
|-------|-------|--------|
| CPU | % | latest `jvm.cpu.process` from agent metrics |
| Memory | % | latest `heap.used / heap.max * 100` |
| Throughput | req/s | `agent.tps` |
| Errors | % | `agent.errorRate` |
| Uptime | formatted | `agent.uptimeSeconds` |

CPU and Memory require a small fetch from endpoint 1b (latest single value).

### 6d. Application Log Placeholder

Below the EventFeed card, add an `EmptyState` component:
- Title: "Application Logs"
- Description: "Application log streaming is not yet available"
- No action button

### 6e. Version Badge in Scope Trail

Breadcrumb: `Agents` > `{appName}` > `{instanceName}`
- Add `Badge` next to instance name showing version (from enriched response)
- Add `StatusDot` + status `Badge` for visual state

## 7. Admin & Miscellaneous

### 7a. OIDC Config — Default Roles

Add a "Default Roles" section to the OIDC config page:
- Display current default roles as `Tag` components (removable, click X to remove)
- `Input` + "Add" `Button` to add a role
- Validate against existing roles from `useRoles()` query
- Persist via existing OIDC config save endpoint

### 7b. OIDC Config — ConfirmDialog on Delete

Replace direct delete button with `ConfirmDialog`:
- Message: "Delete OIDC configuration? All OIDC users will lose access."
- Require typing "DELETE" to confirm

### 7c. Design System Update

Update `@cameleer/design-system` from `^0.0.1` to `^0.0.2` in `ui/package.json`.

**TopBar `onLogout` prop:** Replace the custom `Dropdown` + `Avatar` logout hack in `LayoutShell.tsx` with the TopBar's new `onLogout` prop:
```tsx
<TopBar
  breadcrumb={breadcrumb}
  user={{ name: username }}
  onLogout={handleLogout}
/>
```

Remove the manual Avatar/Dropdown logout code.

**Verification needed during implementation:** Confirm that the TopBar v0.0.2 renders a user avatar/menu internally when `user` + `onLogout` are provided. If it only renders a bare logout button without the "Signed in as" display, keep the custom Avatar/Dropdown and just wire up the TopBar's `onLogout` as an additional trigger.

### 7d. Regenerate schema.d.ts

After backend endpoints are added, regenerate types from the running server:
```bash
npm run generate-api:live
```

This ensures all new DTOs (`ProcessorMetrics`, `AgentMetricsResponse`, `MetricBucket`, enriched `AgentInstanceResponse`) are accurately typed.

## 8. RBAC / User Management Overhaul

The current RBAC page is a basic DataTable + Modal CRUD interface. The design system mock implements a split-pane detail-oriented admin panel with rich interactions. This section describes the full rebuild.

### 8a. Layout — Split-Pane Replaces DataTable

All three tabs (Users, Groups, Roles) adopt the same layout pattern:

```
┌─────────────────────┬────────────────────┐
│    List Pane (52%)   │  Detail Pane (48%) │
│                      │                    │
│  [Search input]      │  [Selected entity  │
│  [+ Create button]   │   detail view]     │
│                      │                    │
│  [Inline create form]│                    │
│                      │                    │
│  [Scrollable entity  │                    │
│   list with avatars, │                    │
│   badges, tags]      │                    │
│                      │                    │
└─────────────────────┴────────────────────┘
```

**New file:** `ui/src/pages/Admin/UserManagement.module.css`
- `.splitPane` — CSS grid `52fr 48fr`, full height
- `.listPane` — scrollable, border-right
- `.detailPane` — scrollable, padding
- `.entityItem` / `.entityItemSelected` — list items with hover/selected states
- `.entityInfo`, `.entityName`, `.entityMeta`, `.entityTags` — list item layout
- `.createForm`, `.createFormActions` — inline form styling
- `.metaGrid` — key-value metadata layout
- `.sectionTags` — tag group with wrap
- `.inheritedNote` — small italic annotation text
- `.securitySection` / `.resetForm` — password management styling

**Keep existing stat cards** above tabs — these are a useful addition not present in the mock.

### 8b. Users Tab

**List pane:**
- **Search:** `Input` with search icon, filters across username, displayName, email (client-side)
- **Create button:** "+ Add User" opens inline form (not modal)
- **Inline create form:**
  - `Input`: Username (required), Display Name, Email
  - `Input`: Password (required)
  - Client-side validation: duplicate username check, required fields
  - Cancel + Create buttons
  - **Note:** Admin-created users are always local. OIDC users are auto-provisioned on first login (no admin creation needed). The create form does not include a provider selector.
- **Entity list:** `role="listbox"`, each item `role="option"` with `tabIndex={0}`
  - `Avatar` (initials, size sm)
  - Display name + provider `Badge` (if not local)
  - Email + group path in meta line
  - Direct roles and groups as small `Badge` tags
  - Click or Enter/Space to select → populates detail pane

**Detail pane (when user selected):**
- **Header:** `Avatar` (lg) + Display name (`InlineEdit` for rename) + Email + Delete button
- **Status:** "Active" `Tag`
- **Metadata grid:** User ID (`MonoText`), Created (formatted date+time), Provider
- **Security section:**
  - Local users: masked password display + "Reset password" button → toggles inline form (new password `Input` + Cancel/Set)
  - OIDC users: `InfoCallout` "Password managed by identity provider"
- **Group membership:**
  - Current groups as removable `Tag` components
  - `MultiSelect` dropdown to add groups
  - Warning on removal if inherited roles would be revoked
- **Effective roles:**
  - Direct roles: removable `Tag` (warning color)
  - Inherited roles: dashed `Badge` with "↑ groupName" source notation (opacity 0.65, non-removable)
  - `MultiSelect` to add direct roles
  - Note: "Roles with ↑ are inherited through group membership"
- **Delete:** `ConfirmDialog` requiring username to be typed. Self-delete guard (can't delete own account).

**API hooks used:** `useUsers`, `useUser`, `useCreateUser`, `useUpdateUser`, `useDeleteUser`, `useAssignRoleToUser`, `useRemoveRoleFromUser`, `useAddUserToGroup`, `useRemoveUserFromGroup`, `useGroups`, `useRoles`

### 8c. Groups Tab

**List pane:**
- **Search:** filter by group name
- **Create form:** inline with name + parent group `Select` dropdown (options: "Top-level" + all existing groups)
- **Entity list:**
  - `Avatar` + group name
  - Meta: "Child of {parent}" or "Top-level" + child count + member count
  - Role tags

**Detail pane:**
- **Header:** Group name (`InlineEdit` for non-built-in) + parent info + Delete button (disabled for built-in Admins group)
- **Metadata:** Group ID (`MonoText`)
- **Parent group:** display current parent
- **Members:** removable `Tag` list + `MultiSelect` to add users. Note: "+ all members of child groups" if applicable
- **Child groups:** removable `Tag` list + `MultiSelect` to add existing groups as children. Circular reference prevention (can't add ancestor as child)
- **Assigned roles:** removable `Tag` list + `MultiSelect` to add roles. Warning on removal: "Removing {role} from {group} will affect N member(s). Continue?"
- **Delete:** `ConfirmDialog`. Guard: built-in Admins group cannot be deleted.

**API hooks used:** `useGroups`, `useGroup`, `useCreateGroup`, `useUpdateGroup`, `useDeleteGroup`, `useAssignRoleToGroup`, `useRemoveRoleFromGroup`

### 8d. Roles Tab

**List pane:**
- **Search:** filter by role name
- **Create form:** inline with name (auto-uppercase) + description
- **Entity list:**
  - `Avatar` + role name + "system" `Badge` (if system role)
  - Meta: description + assignment count
  - Tags: assigned groups (success color) + direct users

**Detail pane:**
- **Header:** Role name + description + Delete button (disabled for system roles)
- **Metadata:** Role ID (`MonoText`), scope, type (system/custom — "System role (read-only)")
- **Assigned to groups:** view-only `Tag` list (shows which groups have this role)
- **Assigned to users (direct):** view-only `Tag` list
- **Effective principals:** filled `Badge` (direct assignment) + dashed `Badge` (inherited via group). Note: "Dashed entries inherit this role through group membership"

**API hooks used:** `useRoles`, `useRole`, `useCreateRole`, `useUpdateRole`, `useDeleteRole`

### 8e. Shared Patterns

- **Toast notifications** for all mutations (create, update, delete, assign, remove) — use `useToast` from design system
- **Cascade warnings** when actions affect other entities (removing role from group, removing user from group with roles)
- **Keyboard accessibility:** Enter/Space to select, ARIA roles (`listbox`, `option`), `aria-selected`
- **Mutation button states:** disable while in-flight, show spinner
- **ToastProvider:** Add `ToastProvider` from design system to `LayoutShell.tsx` (or app root in `main.tsx`) to enable `useToast()` hook across admin pages
- **Graceful empty states:** When agent metrics are unavailable (agent not sending a particular metric), show per-chart empty state rather than crashing. Check metric name existence in response before rendering.

## File Impact Summary

### New files:
- `ui/src/pages/Routes/RouteDetail.tsx` + `RouteDetail.module.css`
- `ui/src/pages/Admin/UserManagement.module.css`
- `ui/src/pages/Admin/UsersTab.tsx`
- `ui/src/pages/Admin/GroupsTab.tsx`
- `ui/src/pages/Admin/RolesTab.tsx`
- `ui/src/api/queries/agent-metrics.ts` (useAgentMetrics hook)
- `ui/src/api/queries/processor-metrics.ts` (useProcessorMetrics hook)
- `ui/src/api/queries/correlation.ts` (useCorrelationChain hook)
- `cameleer-server-app/.../controller/AgentMetricsController.java`
- `cameleer-server-app/.../dto/ProcessorMetrics.java`
- `cameleer-server-app/.../dto/AgentMetricsResponse.java`
- `cameleer-server-app/.../dto/MetricBucket.java`
- `cameleer-server-app/.../dto/SetPasswordRequest.java`
- `cameleer-server-app/src/main/resources/db/migration/V7__processor_stats_by_id.sql`

### Modified files:
- `ui/package.json` — design system `^0.0.2`
- `ui/src/router.tsx` — add RouteDetail route
- `ui/src/components/LayoutShell.tsx` — TopBar `onLogout` prop, remove Dropdown/Avatar
- `ui/src/pages/Dashboard/Dashboard.tsx` — error section, RouteFlow tab, stat card changes
- `ui/src/pages/Dashboard/Dashboard.module.css` — new classes
- `ui/src/pages/ExchangeDetail/ExchangeDetail.tsx` — correlation chain, flow toggle, processor count
- `ui/src/pages/ExchangeDetail/ExchangeDetail.module.css` — new classes
- `ui/src/pages/Routes/RoutesMetrics.tsx` — stat card adjustments
- `ui/src/pages/AgentHealth/AgentHealth.tsx` — DetailPanel, table enrichment, alert banners, stat cards, scope trail
- `ui/src/pages/AgentHealth/AgentHealth.module.css` — new classes
- `ui/src/pages/AgentInstance/AgentInstance.tsx` — 3x2 charts, process info, stat cards, log placeholder, version badge
- `ui/src/pages/AgentInstance/AgentInstance.module.css` — new classes
- `ui/src/pages/Admin/RbacPage.tsx` — restructured to container with split-pane tabs
- `ui/src/pages/Admin/OidcConfigPage.tsx` — default roles, ConfirmDialog
- `ui/src/api/schema.d.ts` — regenerated with new types
- `cameleer-server-app/.../dto/AgentInstanceResponse.java` — add version, capabilities
- `cameleer-server-app/.../controller/AgentRegistrationController.java` — map version/capabilities
- `cameleer-server-app/.../controller/RouteMetricsController.java` — add processor stats method
- `cameleer-server-app/.../controller/UserAdminController.java` — add password reset method
- `cameleer-server-app/.../SecurityConfig.java` — add rule for `GET /api/v1/agents/*/metrics`
- `ui/src/main.tsx` or `ui/src/components/LayoutShell.tsx` — add `ToastProvider`
- `cameleer-server-app/.../OpenApiConfig.java` — register new DTOs

### Backend migration:
- `V7__processor_stats_by_id.sql` — new `stats_1m_processor_detail` continuous aggregate with `processor_id` grouping
