From 3a41e1f1d3ea0541e97adae0ef7c897046e51abe Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:25:47 +0100 Subject: [PATCH] docs: add execution overlay implementation plan (sub-project 2) 12 tasks covering backend prerequisites (iteration fields, snapshot-by-id endpoint), ProcessDiagram overlay props, node/edge visual states, compound iteration stepper, detail panel with 7 tabs, ExecutionDiagram wrapper, and ExchangeDetail page integration. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plans/2026-03-27-execution-overlay.md | 1121 +++++++++++++++++ 1 file changed, 1121 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-27-execution-overlay.md diff --git a/docs/superpowers/plans/2026-03-27-execution-overlay.md b/docs/superpowers/plans/2026-03-27-execution-overlay.md new file mode 100644 index 00000000..357a6b29 --- /dev/null +++ b/docs/superpowers/plans/2026-03-27-execution-overlay.md @@ -0,0 +1,1121 @@ +# Execution Overlay & Debugger — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Overlay real execution data onto the ProcessDiagram component, turning it into an after-the-fact debugger with node status visualization, iteration stepping, and a tabbed detail panel. + +**Architecture:** An `ExecutionDiagram` wrapper component composes the existing `ProcessDiagram` with execution data from `useExecutionDetail`. ProcessDiagram gains optional overlay props that tint nodes, color edges, and add iteration steppers to compound nodes. A resizable bottom panel shows per-processor details across 7 tabs. Backend prerequisites add iteration fields to the storage layer and a snapshot-by-processorId endpoint. + +**Tech Stack:** Java 17 / Spring Boot (backend), React / TypeScript / CSS Modules (frontend), PostgreSQL (storage), React Query (data fetching) + +**Design spec:** `docs/superpowers/specs/2026-03-27-execution-overlay-design.md` + +--- + +## File Structure + +### Backend Changes + +| Action | File | Responsibility | +|--------|------|---------------| +| Create | `cameleer3-server-app/src/main/resources/db/migration/V8__processor_iteration_fields.sql` | Add iteration columns to processor_executions | +| Modify | `cameleer3-server-core/.../storage/ExecutionStore.java` | Extend ProcessorRecord with iteration fields | +| Modify | `cameleer3-server-app/.../storage/PostgresExecutionStore.java` | Update SQL queries for new columns | +| Modify | `cameleer3-server-core/.../ingestion/IngestionService.java` | Store iteration fields during ingestion | +| Modify | `cameleer3-server-core/.../detail/ProcessorNode.java` | Add iteration fields to detail model | +| Modify | `cameleer3-server-core/.../detail/DetailService.java` | Pass iteration fields through tree builder | +| Modify | `cameleer3-server-app/.../controller/DetailController.java` | Add snapshot-by-processorId endpoint | + +### Frontend Changes + +| Action | File | Responsibility | +|--------|------|---------------| +| Modify | `ui/src/api/openapi.json` | Regenerate with new fields/endpoints | +| Modify | `ui/src/api/schema.d.ts` | Regenerate TypeScript types | +| Modify | `ui/src/api/queries/executions.ts` | Add useProcessorSnapshotById hook | +| Modify | `ui/src/components/ProcessDiagram/types.ts` | Add overlay props + NodeExecutionState | +| Modify | `ui/src/components/ProcessDiagram/DiagramNode.tsx` | Execution overlay visuals (tint, badges, duration) | +| Modify | `ui/src/components/ProcessDiagram/DiagramEdge.tsx` | Traversed/not-traversed edge styling | +| Modify | `ui/src/components/ProcessDiagram/CompoundNode.tsx` | Iteration stepper in compound header | +| Modify | `ui/src/components/ProcessDiagram/ErrorSection.tsx` | Pass overlay props to child nodes | +| Modify | `ui/src/components/ProcessDiagram/ProcessDiagram.tsx` | Pass overlay props through to children | +| Modify | `ui/src/components/ProcessDiagram/ProcessDiagram.module.css` | Overlay-related styles | +| Create | `ui/src/components/ExecutionDiagram/ExecutionDiagram.tsx` | Root wrapper: exchange bar + diagram + detail panel | +| Create | `ui/src/components/ExecutionDiagram/ExecutionDiagram.module.css` | Layout styles (splitter, exchange bar, panel) | +| Create | `ui/src/components/ExecutionDiagram/useExecutionOverlay.ts` | Maps execution data → node overlay state | +| Create | `ui/src/components/ExecutionDiagram/useIterationState.ts` | Per-compound iteration tracking | +| Create | `ui/src/components/ExecutionDiagram/DetailPanel.tsx` | Bottom panel: tabs container + processor header | +| Create | `ui/src/components/ExecutionDiagram/tabs/InfoTab.tsx` | Processor metadata + attributes | +| Create | `ui/src/components/ExecutionDiagram/tabs/HeadersTab.tsx` | Input/output headers side-by-side | +| Create | `ui/src/components/ExecutionDiagram/tabs/BodyTab.tsx` | Formatted message body (shared by Input/Output) | +| Create | `ui/src/components/ExecutionDiagram/tabs/ErrorTab.tsx` | Exception details + stack trace | +| Create | `ui/src/components/ExecutionDiagram/tabs/ConfigTab.tsx` | Placeholder (TODO: agent data) | +| Create | `ui/src/components/ExecutionDiagram/tabs/TimelineTab.tsx` | Gantt-style processor duration chart | +| Create | `ui/src/components/ExecutionDiagram/types.ts` | Overlay-specific types | +| Create | `ui/src/components/ExecutionDiagram/index.ts` | Public exports | +| Modify | `ui/src/pages/ExchangeDetail/ExchangeDetail.tsx` | Replace RouteFlow with ExecutionDiagram | + +--- + +## Prerequisites + +Before starting, verify that the `cameleer3-common` dependency (in the agent repo) exposes iteration getters on `ProcessorExecution`: `getLoopIndex()`, `getLoopSize()`, `getSplitIndex()`, `getSplitSize()`, `getMulticastIndex()`. If these methods do not exist in the current published version, they must be added to `cameleer3-common` and a new SNAPSHOT or release published before Task 1 Step 4 can work. Check `cameleer3/cameleer3-common/src/main/java/com/cameleer3/common/model/ProcessorExecution.java`. + +Note on migration versioning: The next migration is V8. If other work merges before this plan executes, bump the version number accordingly. + +--- + +## Tasks + +### Task 1: Add iteration fields to backend storage + +Add `loop_index`, `loop_size`, `split_index`, `split_size`, `multicast_index` columns to the database and thread them through the storage → ingestion → detail pipeline. + +**Files:** +- Create: `cameleer3-server-app/src/main/resources/db/migration/V8__processor_iteration_fields.sql` +- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java` +- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java` +- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java` +- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ProcessorNode.java` +- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java` + +- [ ] **Step 1: Create Flyway migration V8** + +```sql +-- V8__processor_iteration_fields.sql +ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS loop_index INTEGER; +ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS loop_size INTEGER; +ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS split_index INTEGER; +ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS split_size INTEGER; +ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS multicast_index INTEGER; +``` + +- [ ] **Step 2: Extend ProcessorRecord in ExecutionStore.java** + +Add five new fields to the `ProcessorRecord` record. Current signature has 18 fields; add after `attributes`: + +```java +record ProcessorRecord( + String executionId, String processorId, String processorType, + String applicationName, String routeId, + int depth, String parentProcessorId, String status, + Instant startTime, Instant endTime, Long durationMs, + String errorMessage, String errorStacktrace, + String inputBody, String outputBody, String inputHeaders, String outputHeaders, + String attributes, + // NEW iteration fields: + Integer loopIndex, Integer loopSize, + Integer splitIndex, Integer splitSize, + Integer multicastIndex +) {} +``` + +- [ ] **Step 3: Update PostgresExecutionStore SQL and row mapper** + +In `upsertProcessors()`: add the 5 new columns to the INSERT and ON CONFLICT SET clauses. In the `ProcessorRecord` RowMapper: read the new columns using `rs.getObject("loop_index") != null ? rs.getInt("loop_index") : null` pattern. In `findProcessors()`: add the new columns to the SELECT list. + +- [ ] **Step 4: Update IngestionService.flattenProcessors()** + +In the `flattenProcessors` method, extract iteration fields from `ProcessorExecution` and pass them to `ProcessorRecord`: + +```java +flat.add(new ProcessorRecord( + executionId, p.getProcessorId(), p.getProcessorType(), + applicationName, routeId, + depth, parentProcessorId, + p.getStatus() != null ? p.getStatus().name() : "RUNNING", + p.getStartTime() != null ? p.getStartTime() : execStartTime, + p.getEndTime(), + p.getDurationMs(), + p.getErrorMessage(), p.getErrorStackTrace(), + truncateBody(p.getInputBody()), truncateBody(p.getOutputBody()), + toJson(p.getInputHeaders()), toJson(p.getOutputHeaders()), + toJson(p.getAttributes()), + p.getLoopIndex(), p.getLoopSize(), + p.getSplitIndex(), p.getSplitSize(), + p.getMulticastIndex() +)); +``` + +- [ ] **Step 5: Add iteration fields to ProcessorNode.java** + +Add 5 private fields, update constructor, add getters: + +```java +private final Integer loopIndex; +private final Integer loopSize; +private final Integer splitIndex; +private final Integer splitSize; +private final Integer multicastIndex; +``` + +Constructor adds these after `attributes`. Add corresponding getters. + +- [ ] **Step 6: Update DetailService.buildTree() to pass iteration fields** + +In the node creation inside `buildTree()`: + +```java +ProcessorNode node = new ProcessorNode( + p.processorId(), p.processorType(), p.status(), + p.startTime(), p.endTime(), + p.durationMs() != null ? p.durationMs() : 0L, + p.errorMessage(), p.errorStacktrace(), + parseAttributes(p.attributes()), + p.loopIndex(), p.loopSize(), + p.splitIndex(), p.splitSize(), + p.multicastIndex() +); +``` + +- [ ] **Step 7: Verify backend compiles** + +Run: `mvn clean compile -DskipTests` +Expected: BUILD SUCCESS + +- [ ] **Step 8: Commit** + +```bash +git add cameleer3-server-app/src/main/resources/db/migration/V8__processor_iteration_fields.sql \ + cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java \ + cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java \ + cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java \ + cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ProcessorNode.java \ + cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java +git commit -m "feat: add iteration fields to processor storage and detail model" +``` + +--- + +### Task 2: Add snapshot-by-processorId endpoint + +Add a REST endpoint that fetches processor snapshot data by processorId instead of positional index. + +**Files:** +- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java` +- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java` +- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java` +- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java` + +- [ ] **Step 1: Add findProcessorById to ExecutionStore interface** + +```java +Optional findProcessorById(String executionId, String processorId); +``` + +- [ ] **Step 2: Implement in PostgresExecutionStore** + +```java +@Override +public Optional findProcessorById(String executionId, String processorId) { + String sql = "SELECT * FROM processor_executions WHERE execution_id = ? AND processor_id = ? LIMIT 1"; + List results = jdbc.query(sql, processorRowMapper, executionId, processorId); + return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0)); +} +``` + +- [ ] **Step 3: Add getProcessorSnapshot method to DetailService** + +```java +public Optional> getProcessorSnapshot(String executionId, String processorId) { + return executionStore.findProcessorById(executionId, processorId) + .map(p -> { + Map snapshot = new LinkedHashMap<>(); + if (p.inputBody() != null) snapshot.put("inputBody", p.inputBody()); + if (p.outputBody() != null) snapshot.put("outputBody", p.outputBody()); + if (p.inputHeaders() != null) snapshot.put("inputHeaders", p.inputHeaders()); + if (p.outputHeaders() != null) snapshot.put("outputHeaders", p.outputHeaders()); + return snapshot; + }); +} +``` + +- [ ] **Step 4: Add endpoint to DetailController** + +```java +@GetMapping("/{executionId}/processors/by-id/{processorId}/snapshot") +public ResponseEntity> processorSnapshotById( + @PathVariable String executionId, + @PathVariable String processorId) { + return detailService.getProcessorSnapshot(executionId, processorId) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); +} +``` + +- [ ] **Step 5: Verify backend compiles** + +Run: `mvn clean compile -DskipTests` +Expected: BUILD SUCCESS + +- [ ] **Step 6: Commit** + +```bash +git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java \ + cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java \ + cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java \ + cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DetailController.java +git commit -m "feat: add snapshot-by-processorId endpoint" +``` + +--- + +### Task 3: Regenerate OpenAPI spec and TypeScript types + +Update `openapi.json` with the new fields and endpoint, then regenerate `schema.d.ts`. + +**Files:** +- Modify: `ui/src/api/openapi.json` +- Modify: `ui/src/api/schema.d.ts` + +- [ ] **Step 1: Start the server and regenerate openapi.json** + +Run the server locally, then fetch the OpenAPI spec: + +```bash +# Build and run +cd cameleer3-server-app && mvn spring-boot:run -DskipTests & +# Wait for startup, then fetch +curl -s http://localhost:8080/v3/api-docs | python3 -m json.tool > ui/src/api/openapi.json +``` + +If the server cannot start (no database), manually add the following to `openapi.json`: + +1. Add `loopIndex`, `loopSize`, `splitIndex`, `splitSize`, `multicastIndex` (all `integer`, nullable) to the `ProcessorNode` schema +2. Add the new endpoint path `/api/v1/executions/{executionId}/processors/by-id/{processorId}/snapshot` + +- [ ] **Step 2: Regenerate schema.d.ts** + +Run the project's type generation script, or manually add the new fields to the `ProcessorNode` interface in `ui/src/api/schema.d.ts`: + +```typescript +// Add to ProcessorNode in schema.d.ts +loopIndex?: number; +loopSize?: number; +splitIndex?: number; +splitSize?: number; +multicastIndex?: number; +``` + +**Note:** The current `schema.d.ts` has a stale `diagramNodeId` field on `ProcessorNode` — this was dropped in V6 migration and never existed in the Java model. Remove it during regeneration to avoid confusion. The `processorId` field IS the diagram node ID. + +- [ ] **Step 3: Verify frontend types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` +Expected: No errors + +- [ ] **Step 4: Commit** + +```bash +git add ui/src/api/openapi.json ui/src/api/schema.d.ts +git commit -m "chore: regenerate openapi spec with iteration fields and snapshot-by-id endpoint" +``` + +--- + +### Task 4: Add overlay types, extend ProcessDiagramProps, and add diagramLayout prop + +Define the execution overlay type system and extend ProcessDiagram to accept optional overlay props. Critically, add a `diagramLayout` prop so ExecutionDiagram can load the diagram by `diagramContentHash` and pass the pre-fetched layout — ensuring the diagram version matches the execution, not the latest. + +**Files:** +- Create: `ui/src/components/ExecutionDiagram/types.ts` +- Modify: `ui/src/components/ProcessDiagram/types.ts` +- Modify: `ui/src/components/ProcessDiagram/useDiagramData.ts` +- Modify: `ui/src/components/ProcessDiagram/ProcessDiagram.tsx` + +- [ ] **Step 1: Create ExecutionDiagram types** + +Create `ui/src/components/ExecutionDiagram/types.ts`: + +```typescript +import type { components } from '../../api/schema'; + +export type ExecutionDetail = components['schemas']['ExecutionDetail']; +export type ProcessorNode = components['schemas']['ProcessorNode']; + +export interface NodeExecutionState { + status: 'COMPLETED' | 'FAILED'; + durationMs: number; + /** True if this node's target sub-route failed (DIRECT/SEDA) */ + subRouteFailed?: boolean; + /** True if trace data is available for this processor */ + hasTraceData?: boolean; +} + +export interface IterationInfo { + /** Current iteration index (0-based) */ + current: number; + /** Total number of iterations */ + total: number; + /** Type of iteration (determines label) */ + type: 'loop' | 'split' | 'multicast'; +} + +export type DetailTab = 'info' | 'headers' | 'input' | 'output' | 'error' | 'config' | 'timeline'; +``` + +**Note:** The spec defines `iterationState` as `Map`. This plan uses `Map` instead, which carries `total` and `type` in addition to `current` — needed by the CompoundNode stepper to render "3/5" and label the type. This is a deliberate improvement over the spec. + +- [ ] **Step 2: Extend ProcessDiagramProps with overlay and diagramLayout props** + +Add to `ui/src/components/ProcessDiagram/types.ts`: + +```typescript +import type { DiagramLayout } from '../../api/queries/diagrams'; +import type { NodeExecutionState, IterationInfo } from '../ExecutionDiagram/types'; + +// Add to ProcessDiagramProps: +/** Pre-fetched diagram layout (bypasses internal fetch by application/routeId) */ +diagramLayout?: DiagramLayout; +/** Execution overlay: maps diagram node ID → execution state */ +executionOverlay?: Map; +/** Per-compound iteration info: maps compound node ID → iteration info */ +iterationState?: Map; +/** Called when user changes iteration on a compound stepper */ +onIterationChange?: (compoundNodeId: string, iterationIndex: number) => void; +``` + +- [ ] **Step 3: Update useDiagramData to accept pre-fetched layout** + +Modify `ui/src/components/ProcessDiagram/useDiagramData.ts` to accept an optional `DiagramLayout` parameter. When provided, skip the `useDiagramByRoute` fetch and process the pre-fetched layout directly: + +```typescript +export function useDiagramData( + application: string, routeId: string, direction: 'LR' | 'TB', + preloadedLayout?: DiagramLayout, // NEW +) { + const { data: fetchedLayout, isLoading, error } = useDiagramByRoute( + preloadedLayout ? undefined : application, // disable fetch when preloaded + preloadedLayout ? undefined : routeId, + direction, + ); + const layout = preloadedLayout ?? fetchedLayout; + // ... rest of section/edge separation logic uses `layout` +} +``` + +- [ ] **Step 4: Update ProcessDiagram to pass diagramLayout through** + +In `ProcessDiagram.tsx`, pass the new `diagramLayout` prop to `useDiagramData`: + +```typescript +const { sections, totalWidth, totalHeight, isLoading, error } = useDiagramData( + application, currentRouteId, direction, diagramLayout, +); +``` + +- [ ] **Step 3: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` +Expected: No errors + +- [ ] **Step 4: Commit** + +```bash +git add ui/src/components/ExecutionDiagram/types.ts \ + ui/src/components/ProcessDiagram/types.ts +git commit -m "feat: add execution overlay types and ProcessDiagram overlay props" +``` + +--- + +### Task 5: DiagramNode overlay visual states + +Update DiagramNode to render execution status: green tint + checkmark for completed, red tint + ! for failed, dimmed for skipped, duration badge. + +**Files:** +- Modify: `ui/src/components/ProcessDiagram/DiagramNode.tsx` +- Modify: `ui/src/components/ProcessDiagram/ProcessDiagram.module.css` + +**Reference:** Design spec Section 3 — Node Visual States + +- [ ] **Step 1: Add overlay prop to DiagramNode** + +Add `executionState?: NodeExecutionState` to the `DiagramNodeProps` interface. Import `NodeExecutionState` from `ExecutionDiagram/types`. + +- [ ] **Step 2: Implement overlay visuals** + +When `executionState` is present: + +**Completed state:** +- Card background fill: `#F0F9F1` (green tint) instead of white +- Border stroke: `#3D7C47` at 1.5px + left accent bar 4px green +- Checkmark badge: green circle (16px, `#3D7C47`) with white `✓` at top-right corner (x: node.width - 8, y: -8) +- Duration text: green `#3D7C47` at bottom-right (e.g., "5ms") + +**Failed state:** +- Card background fill: `#FDF2F0` (red tint) +- Border stroke: `#C0392B` at 2px +- Error badge: red circle (16px, `#C0392B`) with white `!` at top-right +- Duration text: red `#C0392B` at bottom-right +- Label text color: `#C0392B` + +**Sub-route failure** (`subRouteFailed` flag): +- Same as failed, plus a drill-down arrow icon (↳) at bottom-left + +**No execution state and overlay is active (skipped):** +- Set group opacity to 0.35 +- This requires knowing whether overlay mode is active. Add an `overlayActive?: boolean` prop. When true and no `executionState`, dim the node. + +- [ ] **Step 3: Add CSS for overlay elements** + +Add to `ProcessDiagram.module.css`: styles for duration badge text positioning and any overlay-specific hover states. + +- [ ] **Step 4: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 5: Commit** + +```bash +git add ui/src/components/ProcessDiagram/DiagramNode.tsx \ + ui/src/components/ProcessDiagram/ProcessDiagram.module.css +git commit -m "feat: DiagramNode execution overlay visuals (tint, badges, duration)" +``` + +--- + +### Task 6: DiagramEdge traversed/not-traversed states + +Update DiagramEdge to show green solid edges for traversed paths and dashed gray for not-traversed. + +**Files:** +- Modify: `ui/src/components/ProcessDiagram/DiagramEdge.tsx` +- Modify: `ui/src/components/ProcessDiagram/ProcessDiagram.tsx` + +**Reference:** Design spec Section 3 — Edge States + +- [ ] **Step 1: Add traversal prop to DiagramEdge** + +Add `traversed?: boolean | undefined` to DiagramEdgeProps. When `undefined` (no overlay), render as current default. When `true`, render green solid. When `false`, render gray dashed. + +```typescript +// Traversed: green solid +stroke={traversed === true ? '#3D7C47' : traversed === false ? '#9CA3AF' : '#9CA3AF'} +strokeWidth={traversed === true ? 1.5 : 1} +strokeDasharray={traversed === false ? '4,3' : undefined} +markerEnd={traversed === false ? undefined : 'url(#arrowhead)'} +``` + +- [ ] **Step 2: Add a green arrowhead marker** + +In ProcessDiagram.tsx ``, add a second marker with green fill: +```xml + + + +``` + +Use `url(#arrowhead-green)` for traversed edges. + +- [ ] **Step 3: Compute edge traversal in ProcessDiagram** + +In ProcessDiagram.tsx, when `executionOverlay` is present, determine if an edge is traversed by checking if both its `sourceId` and `targetId` have entries in the overlay map. Pass this as `traversed` prop to each `DiagramEdge`. + +- [ ] **Step 4: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 5: Commit** + +```bash +git add ui/src/components/ProcessDiagram/DiagramEdge.tsx \ + ui/src/components/ProcessDiagram/ProcessDiagram.tsx +git commit -m "feat: DiagramEdge traversed/not-traversed overlay states" +``` + +--- + +### Task 7: CompoundNode iteration stepper + +Add an iteration stepper widget to compound node headers (LOOP, SPLIT, MULTICAST) when overlay iteration data is present. + +**Files:** +- Modify: `ui/src/components/ProcessDiagram/CompoundNode.tsx` +- Modify: `ui/src/components/ProcessDiagram/ProcessDiagram.module.css` + +**Reference:** Design spec Section 4 — Per-Compound Iteration Stepper + +- [ ] **Step 1: Add overlay props to CompoundNode** + +Add to CompoundNodeProps: +```typescript +executionOverlay?: Map; +overlayActive?: boolean; +iterationState?: Map; +onIterationChange?: (compoundNodeId: string, iterationIndex: number) => void; +``` + +- [ ] **Step 2: Render iteration stepper in compound header** + +When `iterationState` has an entry for this compound node's ID, render a stepper widget inside the colored header bar (right-aligned): + +```tsx +{iterationInfo && ( + +
+ + {iterationInfo.current + 1} / {iterationInfo.total} + +
+
+)} +``` + +- [ ] **Step 3: Add CSS for iteration stepper** + +```css +.iterationStepper { + display: flex; + align-items: center; + gap: 2px; + background: rgba(255, 255, 255, 0.15); + border-radius: 3px; + padding: 1px 3px; + font-size: 10px; + color: white; +} +.iterationStepper button { + width: 16px; + height: 16px; + border: none; + background: rgba(255, 255, 255, 0.2); + color: white; + border-radius: 2px; + cursor: pointer; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + padding: 0; +} +.iterationStepper button:disabled { + opacity: 0.3; + cursor: default; +} +``` + +- [ ] **Step 4: Pass overlay props through from ProcessDiagram** + +In ProcessDiagram.tsx, pass `executionOverlay`, `overlayActive` (= `!!executionOverlay`), `iterationState`, and `onIterationChange` down to CompoundNode instances and ErrorSection instances. Both components render child nodes that need overlay state. + +- [ ] **Step 5: Update ErrorSection to accept and forward overlay props** + +`ErrorSection.tsx` internally renders `DiagramNode` and `CompoundNode`. Add `executionOverlay`, `overlayActive`, `iterationState`, and `onIterationChange` to its props interface, and pass them through to its child node renderers. Without this, error/completion handler section nodes will render without overlay styling. + +- [ ] **Step 6: Apply overlay dimming to compound children** + +When overlay is active: compound children without execution state should be dimmed (opacity 0.35). Pass `overlayActive` and `executionOverlay` down to child DiagramNode and recursive CompoundNode calls. + +**Compound node overlay behavior:** Compound nodes (LOOP, SPLIT, CHOICE) appear as processors in the execution tree but should NOT get status badges. The `useExecutionOverlay` hook (Task 9) should either skip compound-type processor IDs or mark them with a flag. Compound nodes derive their visual from their children — if all children are green, the compound looks normal; if a child failed, the child shows red. + +- [ ] **Step 7: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 8: Commit** + +```bash +git add ui/src/components/ProcessDiagram/CompoundNode.tsx \ + ui/src/components/ProcessDiagram/ErrorSection.tsx \ + ui/src/components/ProcessDiagram/ProcessDiagram.tsx \ + ui/src/components/ProcessDiagram/ProcessDiagram.module.css +git commit -m "feat: CompoundNode iteration stepper and overlay prop threading" +``` + +--- + +### Task 8: useProcessorSnapshotById hook + +Add a frontend hook for the new snapshot-by-processorId endpoint. + +**Files:** +- Modify: `ui/src/api/queries/executions.ts` + +- [ ] **Step 1: Add the hook** + +**Important:** This project uses `openapi-fetch` with typed `api.GET()` calls (uppercase, path template parameters), not axios-style `api.get()`. Follow the existing pattern in `executions.ts`: + +```typescript +export function useProcessorSnapshotById( + executionId: string | null, + processorId: string | null, +) { + return useQuery({ + queryKey: ['processor-snapshot-by-id', executionId, processorId], + queryFn: async () => { + const { data, error } = await api.GET( + '/executions/{executionId}/processors/by-id/{processorId}/snapshot', + { params: { path: { executionId: executionId!, processorId: processorId! } } }, + ); + if (error) throw error; + return data as Record; + }, + enabled: !!executionId && !!processorId, + }); +} +``` + +**Note:** This endpoint path must exist in `openapi.json` (added in Task 3) for the typed path to work. If the openapi spec was manually updated rather than regenerated, ensure the path template matches exactly. + +- [ ] **Step 2: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 3: Commit** + +```bash +git add ui/src/api/queries/executions.ts +git commit -m "feat: add useProcessorSnapshotById hook" +``` + +--- + +### Task 9: useExecutionOverlay and useIterationState hooks + +Create the hooks that map execution data to diagram overlay state and manage per-compound iteration. + +**Files:** +- Create: `ui/src/components/ExecutionDiagram/useExecutionOverlay.ts` +- Create: `ui/src/components/ExecutionDiagram/useIterationState.ts` + +- [ ] **Step 1: Create useExecutionOverlay** + +This hook takes an `ExecutionDetail` and the current iteration state, and produces a `Map` mapping processor IDs to overlay states. + +```typescript +import { useMemo } from 'react'; +import type { NodeExecutionState, IterationInfo } from './types'; +import type { components } from '../../api/schema'; + +type ProcessorNode = components['schemas']['ProcessorNode']; + +export function useExecutionOverlay( + processors: ProcessorNode[] | undefined, + iterationState: Map, +): Map { + return useMemo(() => { + if (!processors) return new Map(); + const overlay = new Map(); + buildOverlay(processors, overlay, iterationState); + return overlay; + }, [processors, iterationState]); +} +``` + +The `buildOverlay` function recursively walks the `ProcessorNode` tree: +- For each processor with a `processorId`, create a `NodeExecutionState` with `status` and `durationMs` +- If the processor is a DIRECT/SEDA type and has status FAILED, set `subRouteFailed: true` (the target sub-route failed) +- For processors with `loopIndex`/`splitIndex`: only include the processor if it matches the current iteration selected in `iterationState` for its parent compound +- Set `hasTraceData: true` if the processor has non-null fields (as a heuristic, or always true since snapshot can be fetched lazily) + +- [ ] **Step 2: Create useIterationState** + +```typescript +import { useCallback, useState } from 'react'; +import type { IterationInfo } from './types'; +import type { components } from '../../api/schema'; + +type ProcessorNode = components['schemas']['ProcessorNode']; + +export function useIterationState(processors: ProcessorNode[] | undefined) { + const [state, setState] = useState>(new Map()); + + // Initialize iteration info from processor tree when processors change + // Walk the tree, find compounds with children that have loopSize/splitSize + // Set default iteration to 0 for each compound + + const setIteration = useCallback((compoundId: string, index: number) => { + setState(prev => { + const next = new Map(prev); + const info = next.get(compoundId); + if (info && index >= 0 && index < info.total) { + next.set(compoundId, { ...info, current: index }); + } + return next; + }); + }, []); + + return { iterationState: state, setIteration }; +} +``` + +Key logic: Walk the `ProcessorNode` tree looking for processors that are children of the same parent and have different `loopIndex` or `splitIndex` values. Group by parent, compute total from `loopSize`/`splitSize`, initialize `current: 0`. + +- [ ] **Step 3: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 4: Commit** + +```bash +git add ui/src/components/ExecutionDiagram/useExecutionOverlay.ts \ + ui/src/components/ExecutionDiagram/useIterationState.ts +git commit -m "feat: useExecutionOverlay and useIterationState hooks" +``` + +--- + +### Task 10: DetailPanel with tab infrastructure + +Create the bottom panel component with tab bar, processor header, and tab content area. + +**Files:** +- Create: `ui/src/components/ExecutionDiagram/DetailPanel.tsx` +- Create: `ui/src/components/ExecutionDiagram/tabs/InfoTab.tsx` +- Create: `ui/src/components/ExecutionDiagram/tabs/HeadersTab.tsx` +- Create: `ui/src/components/ExecutionDiagram/tabs/BodyTab.tsx` +- Create: `ui/src/components/ExecutionDiagram/tabs/ErrorTab.tsx` +- Create: `ui/src/components/ExecutionDiagram/tabs/ConfigTab.tsx` +- Create: `ui/src/components/ExecutionDiagram/tabs/TimelineTab.tsx` +- Modify: `ui/src/components/ExecutionDiagram/ExecutionDiagram.module.css` + +**Reference:** Design spec Section 6 — Detail Panel + +- [ ] **Step 1: Create DetailPanel shell** + +```typescript +interface DetailPanelProps { + /** Currently selected processor (or null for exchange-level view) */ + selectedProcessor: ProcessorNode | null; + /** The full execution detail (for exchange-level tabs and timeline) */ + executionDetail: ExecutionDetail; + /** Execution ID for fetching snapshots */ + executionId: string; + /** Callback to select a processor (from timeline clicks) */ + onSelectProcessor: (processorId: string) => void; +} +``` + +The panel renders: +1. A processor header bar showing selected processor name, status badge, processor ID, duration — or exchange-level info when nothing selected +2. A tab bar with 7 tabs: Info, Headers, Input, Output, Error, Config, Timeline +3. Active tab content area (scrollable) + +Track `activeTab` state internally. When `selectedProcessor` changes, keep the current tab unless it was Error (auto-switch to Info for non-failed processors). + +- [ ] **Step 2: Create InfoTab** + +Grid layout showing: +- Processor: ID, Type, Status, Start/End time, Duration, Endpoint URI +- Attributes: tap-extracted attributes rendered as pill badges +- When no processor selected: show exchange-level metadata (executionId, correlationId, route, app, total duration, engine level, route attributes) + +Data source: `ProcessorNode` fields directly (no API call needed). + +- [ ] **Step 3: Create HeadersTab** + +Side-by-side layout: +- Left: Input headers parsed from JSON string → key/value table +- Right: Output headers parsed from JSON string → key/value table +- Compare and highlight new/changed headers in green + +Data source: `useProcessorSnapshotById(executionId, processorId)` → `inputHeaders`, `outputHeaders`. When no processor selected, use `executionDetail.inputHeaders` / `executionDetail.outputHeaders`. + +- [ ] **Step 4: Create BodyTab (shared by Input and Output)** + +Formatted body display: +- Auto-detect format (try JSON parse → formatted, otherwise plain text) +- Dark-themed code block with monospace font +- Copy button +- Byte size indicator + +Props: `{ body: string | undefined; label: string }`. The Input tab passes `snapshot.inputBody`, the Output tab passes `snapshot.outputBody`. + +- [ ] **Step 5: Create ErrorTab** + +When processor has error: +- Exception type (errorType from attributes if available, otherwise derive from errorMessage) +- Error message +- Root cause (if available in attributes) +- Stack trace in monospace pre block + +When no error: show grayed-out "No error on this processor" message. + +Data source: `ProcessorNode.errorMessage`, `ProcessorNode.errorStackTrace`. + +- [ ] **Step 6: Create ConfigTab (placeholder)** + +Show a centered message: "Processor configuration data is not yet available." Styled with muted text. + +- [ ] **Step 7: Create TimelineTab** + +Gantt-style horizontal bar chart: +- Flatten `ExecutionDetail.processors` tree into execution-order list +- One row per executed processor +- Bar width: `(processor.durationMs / execution.durationMs) * 100%` +- Bar offset: `((processor.startTime - execution.startTime) / execution.durationMs) * 100%` +- Green bars for COMPLETED, red for FAILED +- Click handler: `onSelectProcessor(processorId)` +- Highlight the currently selected processor + +- [ ] **Step 8: Add CSS for detail panel** + +Add to `ExecutionDiagram.module.css`: +- `.detailPanel` — flex column layout +- `.processorHeader` — flex row with processor info +- `.tabBar` — flex row of tab buttons +- `.tabButton`, `.tabButtonActive` — styled tabs +- `.tabContent` — scrollable content area +- Tab-specific styles (`.infoGrid`, `.headersTable`, `.codeBlock`, `.ganttRow`, etc.) + +- [ ] **Step 9: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 10: Commit** + +```bash +git add ui/src/components/ExecutionDiagram/DetailPanel.tsx \ + ui/src/components/ExecutionDiagram/tabs/ \ + ui/src/components/ExecutionDiagram/ExecutionDiagram.module.css +git commit -m "feat: DetailPanel with 7 tabs (Info, Headers, Input, Output, Error, Config, Timeline)" +``` + +--- + +### Task 11: ExecutionDiagram wrapper component + +Create the root wrapper that composes ProcessDiagram with execution overlay data, the exchange summary bar, resizable splitter, and the detail panel. + +**Files:** +- Create: `ui/src/components/ExecutionDiagram/ExecutionDiagram.tsx` +- Create: `ui/src/components/ExecutionDiagram/index.ts` +- Modify: `ui/src/components/ExecutionDiagram/ExecutionDiagram.module.css` + +**Reference:** Design spec Sections 1, 5, 7 + +- [ ] **Step 1: Create ExecutionDiagram component** + +```typescript +interface ExecutionDiagramProps { + executionId: string; + executionDetail?: ExecutionDetail; + direction?: 'LR' | 'TB'; + knownRouteIds?: Set; + onNodeAction?: (nodeId: string, action: NodeAction) => void; + nodeConfigs?: Map; + className?: string; +} +``` + +The component: +1. Fetches `ExecutionDetail` via `useExecutionDetail(executionId)` if not pre-fetched +2. **Loads diagram by content hash** via `useDiagramLayout(detail.diagramContentHash, direction)` — this ensures the diagram version matches the execution, not the latest version +3. Initializes `useIterationState(detail.processors)` +4. Computes `useExecutionOverlay(detail.processors, iterationState)` +5. Manages `selectedProcessorId` state +6. Finds the selected `ProcessorNode` in the tree by walking `detail.processors` + +**RUNNING execution handling:** If `detail.status === 'RUNNING'`, the overlay shows completed processors with green/red and unreached processors as dimmed. No special RUNNING visual is needed — the overlay hook only creates entries for processors that have executed. + +Layout: +```tsx +
+ {/* Exchange summary bar */} +
+ + {detail.status === 'FAILED' && } +
+ + {/* Diagram area */} +
+ +
+ + {/* Resizable splitter */} +
+ + {/* Detail panel */} +
+ +
+
+``` + +- [ ] **Step 2: Implement resizable splitter** + +Track `splitPercent` state (default 60). On pointer down on splitter, capture pointer, update percentage on pointer move based on container height. Minimum panel height ~120px. + +- [ ] **Step 3: Implement "Jump to Error"** + +Find first FAILED `ProcessorNode` in tree. Set it as selected. If it's a DIRECT/SEDA node, trigger drill-down into the sub-route via `ProcessDiagram`'s double-click mechanism. Auto-switch detail panel to Error tab. + +- [ ] **Step 4: Add exchange summary bar CSS** + +```css +.exchangeBar { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 14px; + background: var(--bg-surface, #FFFFFF); + border-bottom: 1px solid var(--border, #E4DFD8); + font-size: 12px; + color: var(--text-secondary, #5C5347); + flex-shrink: 0; +} +``` + +- [ ] **Step 5: Add layout CSS** + +```css +.executionDiagram { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-height: 400px; +} +.diagramArea { + overflow: hidden; + position: relative; +} +.splitter { + height: 4px; + background: var(--border, #E4DFD8); + cursor: row-resize; + flex-shrink: 0; +} +.detailArea { + overflow: hidden; + min-height: 120px; +} +``` + +- [ ] **Step 6: Create index.ts** + +```typescript +export { ExecutionDiagram } from './ExecutionDiagram'; +export type { ExecutionDiagramProps } from './ExecutionDiagram'; +``` + +- [ ] **Step 7: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 8: Commit** + +```bash +git add ui/src/components/ExecutionDiagram/ +git commit -m "feat: ExecutionDiagram wrapper with exchange bar, splitter, and detail panel" +``` + +--- + +### Task 12: Integrate into ExchangeDetail page + +Replace the existing RouteFlow-based "Flow" view in ExchangeDetail with the new ExecutionDiagram component. + +**Files:** +- Modify: `ui/src/pages/ExchangeDetail/ExchangeDetail.tsx` +- Modify: `ui/src/pages/ExchangeDetail/ExchangeDetail.module.css` + +**Reference:** Design spec Section 9; existing Flow view at lines ~486-507 of ExchangeDetail.tsx + +- [ ] **Step 1: Import ExecutionDiagram** + +```typescript +import { ExecutionDiagram } from '../../components/ExecutionDiagram'; +``` + +- [ ] **Step 2: Replace the Flow view section** + +Replace the `RouteFlow` rendering in the "flow" view branch with: + +```tsx +{timelineView === 'flow' && ( +
+ +
+)} +``` + +Build `knownRouteIds` from the route catalog (same pattern as DevDiagram page). Build `nodeConfigs` from existing `tracedMap`. + +- [ ] **Step 3: Remove or simplify redundant sections** + +The ExchangeDetail page currently has its own processor snapshot display (Message IN/OUT panels). With the detail panel inside ExecutionDiagram, these become redundant when in Flow view. Keep them for the Gantt view, but hide when Flow view is active. + +The exchange header card at the top of the page stays — it provides context. The ExecutionDiagram's exchange bar provides a compact summary inside the diagram area. + +- [ ] **Step 4: Add CSS for container** + +```css +.executionDiagramContainer { + height: 600px; + border: 1px solid var(--border, #E4DFD8); + border-radius: var(--radius-md, 8px); + overflow: hidden; +} +``` + +- [ ] **Step 5: Verify types compile** + +Run: `cd ui && npx tsc -p tsconfig.app.json --noEmit` + +- [ ] **Step 6: Manual test** + +Navigate to an exchange detail page for a known execution. Toggle to Flow view. Verify: +- Diagram renders with execution overlay +- Completed nodes are green with checkmarks +- Failed nodes are red with ! badge +- Skipped nodes are dimmed +- Clicking a node updates the detail panel +- Tabs work (Info, Headers, Input, Output, Error, Timeline) +- "Jump to Error" navigates to the failed processor + +- [ ] **Step 7: Commit** + +```bash +git add ui/src/pages/ExchangeDetail/ExchangeDetail.tsx \ + ui/src/pages/ExchangeDetail/ExchangeDetail.module.css +git commit -m "feat: integrate ExecutionDiagram into ExchangeDetail flow view" +``` + +--- + +## Verification + +1. `mvn clean compile -DskipTests` passes (backend changes) +2. `cd ui && npx tsc -p tsconfig.app.json --noEmit` passes (frontend types) +3. ExecutionDiagram renders on ExchangeDetail page for a known failed exchange +4. Completed nodes show green tint + checkmark + duration badge +5. Failed nodes show red tint + ! badge + red duration +6. Skipped nodes are dimmed to 35% opacity +7. Edges between executed nodes turn green; edges to skipped nodes are dashed gray +8. Loop/split compounds show iteration stepper; stepping updates child overlay +9. Nested loops step independently (outer loop at iteration 2, inner split at branch 1) +10. CHOICE compounds highlight taken branch, dim untaken branches +11. Clicking a node shows its data in the detail panel +11. Detail panel tabs: Info shows metadata + attributes, Headers shows side-by-side, Input/Output show formatted body, Error shows exception + stack trace, Timeline shows Gantt chart +12. "Jump to Error" navigates to and selects the failed processor +13. Error tab grayed out for non-failed processors +14. Config tab shows placeholder +15. Resizable splitter between diagram and detail panel works