Skip to content

Commit f8d8ea7

Browse files
aksOpsclaude
andcommitted
feat(ui): redesign UI with hierarchical service map
Consolidate 7 pages to 4 views (Service Map, Traces, Logs, MCP Console). Service Map is the hero view with tier-based hierarchical layout, side panel on click, progressive edge disclosure, search, and zoom controls. Handles 100-200 services performantly via canvas rendering and edge threshold culling. - Add tier-based layout algorithm (BFS) with 16 tests - Add ServiceSidePanel with KPIs, health score, connections, alerts - Add ServiceMap with ECharts canvas, toolbar, search, zoom controls - Add global stats bar to TopNav (services, traces, logs, error rate, DB size) - Add cross-view navigation with service filter - Add polling: 30s dashboard stats, 60s service map - Remove Overview, Metrics, Archive pages - Set up Vitest test infrastructure (23 tests total) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ce7903d commit f8d8ea7

30 files changed

Lines changed: 4289 additions & 853 deletions

docs/superpowers/plans/2026-03-22-ui-redesign.md

Lines changed: 1257 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# OtelContext UI Redesign — Design Spec
2+
3+
## Goal
4+
5+
Redesign the OtelContext UI to be more intuitive and professional. The service map becomes the hero view, showing stats and flow across all services. Must handle 100-200 services performantly and neatly. Remove low-value pages, consolidate stats into fewer, richer views.
6+
7+
## Navigation (4 Views)
8+
9+
| View | Purpose | Replaces |
10+
|------|---------|----------|
11+
| **Service Map** | Hero view — hierarchical topology with per-service stats, flow edges, side panel detail | Overview, Services, Metrics |
12+
| **Traces** | Trace list + waterfall span detail | (kept, minor cleanup) |
13+
| **Logs** | Live log stream + search + similarity | (kept, minor cleanup) |
14+
| **MCP Console** | JSON-RPC tool surface | (kept as-is) |
15+
16+
### Removed Pages
17+
18+
- **Overview** — KPI stats merged into Service Map top bar
19+
- **Metrics** — traffic/latency/service-map metrics merged into Service Map side panel
20+
- **Archive** — dedicated page removed; archive stats shown in top bar; archive search available via MCP Console tools
21+
22+
## Service Map View (Primary)
23+
24+
### Layout Structure
25+
26+
```
27+
┌─────────────────────────────────────────────────────────────────┐
28+
│ TopNav: Logo │ [Service Map] [Traces] [Logs] [MCP] │ Stats Bar │
29+
├─────────────────────────────────────────────────────────────────┤
30+
│ Search │ Edge threshold slider │ Zoom controls (+/−/Fit) │
31+
├──────────────────────────────────────────────┬──────────────────┤
32+
│ │ Side Panel │
33+
│ Hierarchical Service Map │ (on click) │
34+
│ (ECharts canvas) │ │
35+
│ │ - KPI cards │
36+
│ GATEWAY tier │ - Health score │
37+
│ API LAYER tier │ - Connections │
38+
│ SERVICES tier │ - Recent errors │
39+
│ DATA tier │ - Action links │
40+
│ │ │
41+
│ [minimap] │ │
42+
└──────────────────────────────────────────────┴──────────────────┘
43+
```
44+
45+
### Top Stats Bar (Global)
46+
47+
Always-visible horizontal strip in the top nav showing:
48+
- Total services (with healthy count)
49+
- Total traces
50+
- Total logs
51+
- Error rate (colored red if >5%)
52+
- DB size (`DBSizeMB` from RepoStats) — serves as storage indicator since archive size is not tracked separately
53+
- WebSocket connection indicator
54+
55+
Data sources: `GET /api/metrics/dashboard` + `GET /api/stats`
56+
57+
**Auto-refresh:** Dashboard stats poll every 30 seconds to keep top bar current.
58+
59+
### Hierarchical Layout (Tier-based)
60+
61+
Services arranged in tiers from top to bottom, representing request flow direction:
62+
63+
| Tier | Criteria | Visual |
64+
|------|----------|--------|
65+
| **Gateway** | Nodes with no inbound edges (entry points) | Top row |
66+
| **API Layer** | Nodes called directly by gateway | Second row |
67+
| **Services** | Internal services (middle of graph) | Third row |
68+
| **Data** | Leaf nodes with no outbound edges (databases, caches) | Bottom row |
69+
70+
**Tier assignment algorithm:**
71+
1. Build adjacency maps (inbound and outbound) from edges
72+
2. BFS from all root nodes (no inbound edges), assigning each node tier = longest path from any root
73+
3. Identify leaf nodes (no outbound edges) — override their tier to max tier (Data layer)
74+
4. Bucket tiers into 4 display rows: tier 0 = Gateway, tier 1 = API Layer, tier 2..max-1 = Services, tier max = Data
75+
5. If a graph has no root nodes (cycles only), fall back to sorting by `span_count` descending and distributing evenly across 4 tiers
76+
77+
**Layout engine:** Compute tier positions in pure JS (BFS), then pass fixed `(x, y)` coordinates to ECharts graph nodes. Layout is computed once on data load (not animated per-frame). No external layout library needed.
78+
79+
### Service Nodes (Compact)
80+
81+
Each node renders as a small card:
82+
83+
```
84+
┌──────────────────┐
85+
│ ● service-name │
86+
│ 340 rps 0.2% err│
87+
└──────────────────┘
88+
```
89+
90+
- **Health dot**: green (#22c55e), orange (#fb923c), red (#ef4444) based on `status` field
91+
- **Service name**: truncated at ~16 chars, full name on hover tooltip
92+
- **Two metrics**: RPS (from `request_rate_rps`) + error rate (from `error_rate`)
93+
- **Background tint**: subtle color matching health status
94+
- **Border**: colored by health status
95+
96+
Node sizing:
97+
- Normal (< 70 services): ~120x46px
98+
- Dense (70+ services): ~90x36px, smaller font
99+
100+
### Edge Rendering (Progressive Disclosure)
101+
102+
Edges represent service-to-service calls. With 200 services, there could be 500-1000+ edges.
103+
104+
**Default behavior:**
105+
- Show only edges with `call_count` above a configurable threshold
106+
- Default threshold: `max(median call_count, 10)` — ensures meaningful filtering even in sparse graphs where median is very low
107+
- User can adjust via a slider in the toolbar (range: 1 to max call_count)
108+
109+
**Edge visual properties:**
110+
- Width: scaled by `log(call_count)` — range 1px to 4px
111+
- Color: matches health status of the edge (green/yellow/red based on `error_rate`)
112+
- Opacity: higher traffic = more opaque
113+
- Direction: arrows pointing from source to target (top to bottom flow)
114+
115+
**Edge culling for performance:**
116+
- Maximum 500 rendered edges at any time
117+
- Below threshold edges are hidden, not removed from data
118+
- Hovering a node temporarily shows all its edges regardless of threshold
119+
120+
### Side Panel (Service Detail)
121+
122+
Slides in from the right when a service node is clicked. Width: 280px. Contents:
123+
124+
**Header:**
125+
- Health dot + service name + status badge (HEALTHY/DEGRADED/CRITICAL)
126+
127+
**KPI Cards (2x2 grid):**
128+
- RPS (`request_rate_rps`)
129+
- Error Rate (`error_rate`) — colored red if > 5%
130+
- Avg Latency (`avg_latency_ms`)
131+
- P99 Latency (`p99_latency_ms`)
132+
133+
**Health Score Bar:**
134+
- Visual bar 0-1.0 with numeric label
135+
- Color gradient: red → yellow → green
136+
137+
**Connected Services:**
138+
- **Upstream** — services that call this one (inbound edges), with call counts
139+
- **Downstream** — services this one calls (outbound edges), with call counts
140+
- Each entry is clickable → pans the map to that node and opens its side panel (replacing the current one)
141+
142+
**Alerts / Recent Errors:**
143+
- Display the `alerts` array from the `SystemNode` (already available in graph response — e.g., "error rate above 5%", "avg latency above 500ms")
144+
- These are computed server-side during graph generation, no additional API call needed
145+
146+
**Action Links:**
147+
- "View Traces →" — navigates to Traces view, pre-filtered by `service_name`
148+
- "View Logs →" — navigates to Logs view, pre-filtered by `service_name`
149+
150+
### Search
151+
152+
Text input that filters/highlights services by name. As user types:
153+
- Matching nodes get highlighted (bright border/glow)
154+
- Non-matching nodes dim (lower opacity)
155+
- Map pans to center on first match
156+
157+
### Minimap
158+
159+
Small overview in bottom-right corner (~100x80px). Implemented as a second ECharts instance with `silent: true` (no interactions except click-to-navigate), rendering the same graph data at thumbnail scale with simplified nodes (dots only, no labels). A semi-transparent rectangle overlay shows the current viewport position. On click, the main map pans to the corresponding position.
160+
161+
### Zoom Controls
162+
163+
Three buttons in toolbar:
164+
- **+** Zoom in
165+
- **** Zoom out
166+
- **** Fit all (reset zoom to show entire graph)
167+
168+
ECharts built-in zoom/pan via mouse scroll and drag.
169+
170+
## Traces View
171+
172+
Keep current two-column layout with minor cleanup:
173+
- Left: trace list (25 limit, click to select)
174+
- Right: selected trace waterfall + detail
175+
- Add service filter support (pre-filter when navigating from Service Map)
176+
- Remove JsonViewer at bottom (raw JSON adds clutter, not useful for most users)
177+
178+
## Logs View
179+
180+
Keep current two-column layout with minor cleanup:
181+
- Left: search + severity filter + similarity results
182+
- Right: live log stream (WebSocket)
183+
- Add service filter support (pre-filter when navigating from Service Map)
184+
185+
## MCP Console
186+
187+
Keep as-is. No changes needed — it's already well-designed for its purpose.
188+
189+
## Cross-View Navigation
190+
191+
When clicking "View Traces →" or "View Logs →" from the Service Map side panel, the service name is passed via React state in `App.tsx`:
192+
193+
```typescript
194+
// App.tsx state
195+
const [activeView, setActiveView] = useState<OtelView>('services');
196+
const [serviceFilter, setServiceFilter] = useState<string | null>(null);
197+
198+
// Navigation from side panel
199+
const navigateToTraces = (serviceName: string) => {
200+
setServiceFilter(serviceName);
201+
setActiveView('traces');
202+
};
203+
```
204+
205+
`TracesPage` and `LogsPage` receive `serviceFilter` as a prop and use it to pre-filter their initial data fetch. A "clear filter" chip is shown when a filter is active, allowing the user to remove it.
206+
207+
## Data Refresh Strategy
208+
209+
| View | Refresh Mechanism | Interval |
210+
|------|-------------------|----------|
211+
| Top Stats Bar | Polling `useDashboard()` | 30 seconds |
212+
| Service Map | Polling `useSystemGraph()` | 60 seconds (matches backend GraphRAG refresh loop) |
213+
| Logs | WebSocket real-time stream | Continuous |
214+
| Traces | Manual refresh button | On demand |
215+
| MCP Console | On demand | Per tool call |
216+
217+
Service Map refresh preserves current zoom/pan position and selected service. Layout is only recomputed if the node set changes (new/removed services), not on metric updates.
218+
219+
## Technical Implementation
220+
221+
### Dependencies
222+
223+
No new dependencies required. Continue using:
224+
- **ECharts** — canvas rendering for the service map (already installed)
225+
- **Radix UI** — dialogs, tooltips (already installed)
226+
- **Lucide React** — icons (already installed)
227+
228+
Layout is computed in pure JS via BFS tier assignment — no external layout library needed.
229+
230+
### Performance Strategy (100-200 Services)
231+
232+
| Technique | Purpose |
233+
|-----------|---------|
234+
| ECharts canvas renderer | All nodes/edges rendered to single canvas, not DOM |
235+
| Tier layout computed once | No per-frame physics simulation (BFS runs once on data load) |
236+
| Edge threshold culling | Max 500 visible edges |
237+
| Semantic zoom | At zoom < 0.6: hide metric text on nodes, show only health dot + name. At zoom < 0.3: collapse nodes to colored dots only. Implemented via ECharts `graphScaleLimit` + zoom event listener toggling label visibility |
238+
| Dense mode auto-detection | Smaller nodes when count > 70 |
239+
| `React.memo` on side panel | Prevent re-render on map pan/zoom |
240+
| Debounced search | Filter during typing, not on every keystroke |
241+
242+
### Data Flow
243+
244+
```
245+
App.tsx
246+
├── useSystemGraph() → Service Map nodes + edges
247+
├── useDashboard() → Top bar stats (traces, logs, errors, services)
248+
├── useTraces() → Traces view
249+
├── useLogs() → Logs view
250+
├── useWebSocket() → Real-time log stream
251+
└── useMCP() → MCP Console
252+
```
253+
254+
The `useMetrics()` hook can be simplified or removed — the Service Map gets its data from `useSystemGraph()` which already includes per-service metrics and edge stats.
255+
256+
### Files to Modify
257+
258+
| File | Change |
259+
|------|--------|
260+
| `App.tsx` | Remove Overview/Metrics/Archive views, update nav |
261+
| `TopNav.tsx` | 4 nav items instead of 7, add global stats bar |
262+
| `ServicesPage.tsx` | Complete rewrite — hierarchical layout, side panel, search, edge threshold |
263+
| `TracesPage.tsx` | Add service filter prop, remove JsonViewer |
264+
| `LogsPage.tsx` | Add service filter prop |
265+
| `useSystemGraph.ts` | Add tier computation logic |
266+
| `types/api.ts` | Add `db_size_mb` to `RepoStats` interface, remove unused types |
267+
268+
### Files to Delete
269+
270+
| File | Reason |
271+
|------|--------|
272+
| `OverviewPage.tsx` | Merged into Service Map + top bar |
273+
| `MetricsPage.tsx` | Merged into Service Map side panel |
274+
| `ArchivePage.tsx` | Stats in top bar, search via MCP |
275+
| `useArchive.ts` | No longer needed |
276+
| `useMetrics.ts` | No longer needed (data comes from useSystemGraph) |
277+
| `JsonViewer.tsx` | No remaining consumers after removing OverviewPage and TracesPage usage |
278+
279+
### Color System
280+
281+
Health status colors (consistent across nodes, edges, badges):
282+
- **Healthy**: `#22c55e` (green) — background tint `#0f2618`, border `#166534`
283+
- **Degraded**: `#fb923c` (orange) — background tint `#1a1207`, border `#854d0e` (matches existing codebase convention)
284+
- **Critical**: `#ef4444` (red) — background tint `#1c0707`, border `#991b1b`
285+
- **Data stores**: `#818cf8` (indigo) — background tint `#0c0c1f`, border `#312e81`
286+
287+
### Tier Label Styling
288+
289+
Vertical text labels on the left edge of the map canvas:
290+
- Font: 9px uppercase
291+
- Color: `#3f3f46` (subtle zinc)
292+
- Labels: GATEWAY, API LAYER, SERVICES, DATA

0 commit comments

Comments
 (0)