# 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.
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.
- [ ]**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.
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.
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.
**Note:** The spec defines `iterationState` as `Map<string, number>`. This plan uses `Map<string, IterationInfo>` 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 */
- [ ]**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:
**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.
- [ ]**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: 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.
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`:
**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.
This hook takes an `ExecutionDetail` and the current iteration state, and produces a `Map<string, NodeExecutionState>` 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<string, IterationInfo>,
): Map<string, NodeExecutionState> {
return useMemo(() => {
if (!processors) return new Map();
const overlay = new Map<string, NodeExecutionState>();
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) {
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`.
/** 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).
- 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)**
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
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.
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';
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.