# 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 | `cameleer-server-app/src/main/resources/db/migration/V8__processor_iteration_fields.sql` | Add iteration columns to processor_executions | | Modify | `cameleer-server-core/.../storage/ExecutionStore.java` | Extend ProcessorRecord with iteration fields | | Modify | `cameleer-server-app/.../storage/PostgresExecutionStore.java` | Update SQL queries for new columns | | Modify | `cameleer-server-core/.../ingestion/IngestionService.java` | Store iteration fields during ingestion | | Modify | `cameleer-server-core/.../detail/ProcessorNode.java` | Add iteration fields to detail model | | Modify | `cameleer-server-core/.../detail/DetailService.java` | Pass iteration fields through tree builder | | Modify | `cameleer-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 `cameleer-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 `cameleer-common` and a new SNAPSHOT or release published before Task 1 Step 4 can work. Check `cameleer/cameleer-common/src/main/java/com/cameleer/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: `cameleer-server-app/src/main/resources/db/migration/V8__processor_iteration_fields.sql` - Modify: `cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionStore.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresExecutionStore.java` - Modify: `cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java` - Modify: `cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ProcessorNode.java` - Modify: `cameleer-server-core/src/main/java/com/cameleer/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 cameleer-server-app/src/main/resources/db/migration/V8__processor_iteration_fields.sql \ cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionStore.java \ cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresExecutionStore.java \ cameleer-server-core/src/main/java/com/cameleer/server/core/ingestion/IngestionService.java \ cameleer-server-core/src/main/java/com/cameleer/server/core/detail/ProcessorNode.java \ cameleer-server-core/src/main/java/com/cameleer/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: `cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionStore.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresExecutionStore.java` - Modify: `cameleer-server-core/src/main/java/com/cameleer/server/core/detail/DetailService.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/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 cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ExecutionStore.java \ cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresExecutionStore.java \ cameleer-server-core/src/main/java/com/cameleer/server/core/detail/DetailService.java \ cameleer-server-app/src/main/java/com/cameleer/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 cameleer-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