# Interactive Process Diagram — Design Spec

**Sub-project:** 1 of 3 (Component → Execution Overlay → Page Integration)
**Scope:** Interactive SVG diagram component with zoom/pan, node interactions, config badges, and a configurable layout direction. Does NOT include execution overlay or page replacement — those are sub-projects 2 and 3.

---

## Problem

The current RouteFlow component renders Camel routes as a flat vertical list of nodes. It cannot show compound structures (choice branches, split fan-out, try-catch nesting), does not support zoom/pan, and has no interactive controls beyond click-to-select. Routes with 10+ processors become hard to follow, and the relationship between processors is not visually clear.

## Goal

Build an interactive process diagram component styled after MuleSoft / TIBCO BusinessWorks 5, rendering Camel routes as left-to-right flow diagrams using server-computed ELK layout coordinates. The component supports zoom/pan, node hover toolbars for tracing/tap configuration, config badge indicators, and a collapsible detail side-panel.

---

## Decisions

| Decision | Choice | Rationale |
|----------|--------|-----------|
| Rendering | SVG + custom React | Full control over styling, no heavy deps. Server owns layout. |
| Node style | Top-Bar Cards | TIBCO BW5-inspired white cards with colored top accent bar. Professional, clean. |
| Flow direction | Left-to-right (default) | Matches MuleSoft/BW5 conventions. Query param for flexibility. |
| Component location | `ui/src/components/ProcessDiagram/` | Tightly coupled to Cameleer data model, no design-system abstraction needed. |
| Interactions | Hover floating toolbar + click-to-select | Discoverable, no right-click dependency. |
| Error handlers | Below main flow | Clear visual separation, labeled divider. |
| Selection behavior | Side panel with config info; execution data only with overlay | Keeps base diagram focused on topology. |

---

## 1. Backend: Layout Direction Parameter

### Change

Add optional `direction` query parameter to diagram render endpoints.

### Files

- `cameleer-server-app/.../diagram/ElkDiagramRenderer.java` — accept direction param, map to ELK `Direction.RIGHT` (LR) or `Direction.DOWN` (TB)
- `cameleer-server-core/.../diagram/DiagramRenderer.java` — update interface to accept direction
- `cameleer-server-app/.../controller/DiagramRenderController.java` — add `@RequestParam(defaultValue = "LR") String direction` to render endpoints
- `ui/src/api/queries/diagrams.ts` — pass `direction` query param to API calls; also update `DiagramLayout` edge type to match backend `PositionedEdge` serialization: `{ sourceId, targetId, label?, points: number[][] }` (currently defines `{ from?, to? }` which is missing `points` and `label`)

### Behavior

- `GET /diagrams/{contentHash}/render?direction=LR` → left-to-right layout (default)
- `GET /diagrams/{contentHash}/render?direction=TB` → top-to-bottom layout
- `GET /diagrams?application=X&routeId=Y&direction=LR` → same for by-route endpoint

### Compound Node Direction

The direction parameter applies to the **root** layout only. Compound nodes (CHOICE, SPLIT, TRY_CATCH, etc.) keep their internal layout direction as **top-to-bottom** regardless of the root direction. This matches how MuleSoft/BW5 render branching patterns: the main flow goes left-to-right, but branches within a choice or split fan out vertically inside their container.

---

## 2. Frontend: ProcessDiagram Component

### File Structure

```
ui/src/components/ProcessDiagram/
├── ProcessDiagram.tsx          # Root: SVG container, zoom/pan, section layout
├── ProcessDiagram.module.css   # Styles using design system tokens
├── DiagramNode.tsx             # Individual node: top-bar card rendering
├── DiagramEdge.tsx             # Edge: cubic Bezier path with arrowhead
├── CompoundNode.tsx            # Container for compound types (choice, split)
├── NodeToolbar.tsx             # Floating action toolbar on hover
├── ConfigBadge.tsx             # Indicator badges (TRACE, TAP) on nodes
├── ErrorSection.tsx            # Visual separator + error handler flow section
├── ZoomControls.tsx            # HTML overlay: zoom in/out/fit buttons
├── useZoomPan.ts               # Hook: viewBox transform, wheel zoom, drag pan
├── useDiagramData.ts           # Hook: fetch + separate layout into sections
├── node-colors.ts              # NodeType → design system color token mapping
├── types.ts                    # Shared TypeScript interfaces
└── index.ts                    # Public exports
```

### Props API

```typescript
interface ProcessDiagramProps {
  application: string;
  routeId: string;
  direction?: 'LR' | 'TB';                        // default 'LR'
  selectedNodeId?: string;                          // controlled selection
  onNodeSelect?: (nodeId: string) => void;
  onNodeAction?: (nodeId: string, action: NodeAction) => void;
  nodeConfigs?: Map<string, NodeConfig>;            // active taps/tracing per processor
  className?: string;
}

type NodeAction = 'inspect' | 'toggle-trace' | 'configure-tap' | 'copy-id';

interface NodeConfig {
  traceEnabled?: boolean;
  tapExpression?: string;
}

// ExecutionOverlay types will be added in sub-project 2 when needed.
// No forward-declared types here to avoid drift.
```

### SVG Structure

```
<div class="process-diagram">
  <svg viewBox="...">                    // zoom = viewBox transform
    <defs>                               // arrowhead markers, filters
      <marker id="arrow">...</marker>
    </defs>
    <g class="diagram-content">          // pan offset transform

      <!-- Main Route section -->
      <g class="section section--main">
        <g class="edges">               // rendered first (behind nodes)
          <path d="M ... C ..." />       // cubic bezier from ELK waypoints
        </g>
        <g class="nodes">
          <g transform="translate(x,y)"> // ELK-computed position
            <!-- DiagramNode: top-bar card -->
            <!-- ConfigBadge: top-right corner pills -->
            <!-- NodeToolbar: foreignObject on hover -->
          </g>
          <g class="compound">           // CompoundNode: dashed border container
            <g transform="translate(...)"> <!-- children inside -->
          </g>
        </g>
      </g>

      <!-- Error Handler section(s) -->
      <g class="section section--error"
         transform="translate(0, mainHeight + gap)">
        <text>onException: java.lang.Exception</text>
        <line ... />                      // divider
        <g class="edges">...</g>
        <g class="nodes">...</g>
      </g>

    </g>
  </svg>
  <div class="zoom-controls">...</div>   // HTML overlay, bottom-right
</div>
```

---

## 3. Node Visual States

### Base States

| State | Visual |
|-------|--------|
| Normal | White card, `--border` (#E4DFD8), colored top bar per type |
| Hovered | Warm tint background (`--bg-hover` / #F5F0EA), stronger border, floating toolbar appears above |
| Selected | Amber selection ring (2.5px solid `--amber`), side panel opens |

### Config Badges

Small colored pill badges positioned at the top-right corner of the node card, always visible:
- **TRACE** — teal (`--running`) pill, shown when tracing is enabled
- **TAP** — purple (`--purple`) pill, shown when a tap expression is configured

### Execution Overlay States (sub-project 2 — node must support these props)

| State | Visual |
|-------|--------|
| Executed (OK) | Green left border or subtle green tint |
| Failed (caused error handler) | Red border (2px `--error`), red marker icon |
| Not executed | Dimmed (reduced opacity) |
| Has trace data | Small "data available" indicator icon |
| No trace data | No indicator (or grayed-out data icon) |

### Node Type Colors

| Category | Token | Hex | Types |
|----------|-------|-----|-------|
| Endpoints | `--running` | #1A7F8E teal | ENDPOINT |
| Processors | `--amber` | #C6820E | PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL |
| Targets | `--success` | #3D7C47 green | TO, TO_DYNAMIC, DIRECT, SEDA |
| EIP Patterns | `--purple` | #7C3AED | EIP_CHOICE, EIP_WHEN, EIP_OTHERWISE, EIP_SPLIT, EIP_MULTICAST, EIP_LOOP, EIP_AGGREGATE, EIP_FILTER, etc. |
| Error Handling | `--error` | #C0392B red | ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY |
| Cross-Route | (hardcoded) | #06B6D4 cyan | EIP_WIRE_TAP, EIP_ENRICH, EIP_POLL_ENRICH |

Note: This frontend color mapping intentionally differs from the backend `ElkDiagramRenderer` SVG colors (which use blue for endpoints, green for processors). The frontend uses design system tokens for consistency with the rest of the UI. The backend SVG renderer is not changed.

### Compound Node Rendering

Compound types (CHOICE, SPLIT, TRY_CATCH, LOOP, etc.) render as:
- Full-width colored header bar with white text label (type name)
- White body area with subtle border matching the type color
- Children rendered inside at their ELK-relative positions
- Children have their own hover/select/badge behavior

---

## 4. Interactions

### Hover Floating Toolbar

On mouse enter over a node, a dark floating toolbar appears above the node (centered). Uses `<foreignObject>` for HTML accessibility.

| Icon | Action | Callback |
|------|--------|----------|
| Search | Inspect | `onNodeAction(id, 'inspect')` — selects node, opens side panel |
| T | Toggle Trace | `onNodeAction(id, 'toggle-trace')` — enables/disables tracing |
| Pencil | Configure Tap | `onNodeAction(id, 'configure-tap')` — opens tap config |
| ... | More | `onNodeAction(id, 'copy-id')` — copies processor ID |

Toolbar hides on mouse leave after a short delay (150ms) to prevent flicker when moving between node and toolbar.

### Click-to-Select

Click on a node → calls `onNodeSelect(nodeId)`. Parent controls `selectedNodeId` prop. Selected node shows amber ring.

### Zoom & Pan

**`useZoomPan` hook manages:**
- Mouse wheel → zoom centered on cursor
- Click+drag on background → pan
- Pinch gesture → zoom (trackpad/touch)
- State: `{ scale, translateX, translateY }`
- Applied to SVG `viewBox` attribute

**`ZoomControls` component:**
- Three buttons: `+` (zoom in), `−` (zoom out), fit-to-view icon
- Positioned as HTML overlay at bottom-right of diagram container
- Fit-to-view calculates viewBox to show entire diagram with 40px padding

**Zoom limits:** 25% to 400%.

### Keyboard Navigation

**Required:**

| Key | Action |
|-----|--------|
| Escape | Deselect / close panel |
| +/- | Zoom in/out |
| 0 | Fit to view |

**Stretch (implement if time permits):**

| Key | Action |
|-----|--------|
| Arrow keys | Move selection between connected nodes |
| Tab | Cycle through nodes in flow order |
| Enter | Open detail panel for selected node |

---

## 5. Error Handler Sections

Error handler compounds (ON_EXCEPTION, ERROR_HANDLER) render as separate sections below the main flow:

1. **Divider:** Horizontal line with label text (e.g., "onException: java.lang.Exception")
2. **Gap:** 40px vertical gap between main section and error section
3. **Layout:** Error section gets its own ELK-computed layout (compound node children already have relative coordinates)
4. **Styling:** Same node rendering as main section, but the section background has a subtle red tint
5. **Multiple handlers:** Each ON_EXCEPTION becomes its own section, stacked vertically

The `useDiagramData` hook separates top-level compound error nodes from regular nodes, computing the Y offset for each error section based on accumulated heights.

---

## 6. Data Flow

```
useDiagramByRoute(app, routeId)
  → contentHash
  → useDiagramLayout(contentHash, direction)
    → DiagramLayout { nodes[], edges[], width, height }

useDiagramData hook:
  1. Separate nodes into mainNodes[] and errorSections[]
     (reuses logic from buildFlowSegments: error-handler compounds with children → error sections)
  2. Filter edges: mainEdges (between main nodes), errorEdges (within each error section)
  3. Compute total SVG dimensions: max(mainWidth, errorWidths) × (mainHeight + gap + errorHeights)
  4. Return { mainNodes, mainEdges, errorSections, totalWidth, totalHeight }
```

The existing `diagram-mapping.ts` `buildFlowSegments` function handles the separation logic. The new `useDiagramData` hook adapts this for SVG coordinate-based rendering instead of RouteFlow's FlowSegment format.

---

## 7. Side Panel (Detail Panel)

When a node is selected, a collapsible side panel slides in from the right of the diagram container.

**Base mode (no execution overlay):**
- Processor ID
- Processor type
- Endpoint URI (if applicable)
- Active configuration: tracing status, tap expression
- Node metadata from the diagram

**With execution overlay (sub-project 2):**
- Execution status + duration
- Input/output body (if trace data captured)
- Input/output headers
- Error message + stack trace (if failed)
- Loop iteration selector (if inside a loop)

For sub-project 1, the side panel shows config info only. The component accepts an `onNodeSelect` callback — the parent page controls what appears in the panel.

The side panel is NOT part of the ProcessDiagram component itself. It is rendered by the parent page and controlled via the `selectedNodeId` / `onNodeSelect` props. This keeps the diagram component focused on visualization.

**Dev test page (`/dev/diagram`):** In sub-project 1, the test page renders the ProcessDiagram with a simple stub side panel that shows the selected node's ID, type, label, and any `nodeConfigs` entry. This validates the selection interaction without needing full page integration.

---

## 8. Non-Goals (Sub-project 2 & 3)

These are explicitly out of scope for sub-project 1:

- **Execution overlay rendering** — animated flow, per-node status/duration, dimming non-executed nodes
- **Loop/split iteration stepping** — "debugger" UI with iteration tabs
- **Page integration** — replacing RouteFlow on RouteDetail, ExchangeDetail, Dashboard
- **Minimap** — small overview for large diagrams (stretch goal, not v1)
- **Drag to rearrange** — nodes are server-positioned, not user-movable

---

## Verification

1. **Backend:** `mvn clean verify -DskipITs` passes after direction param addition
2. **Frontend types:** `npx tsc -p tsconfig.app.json --noEmit` passes
3. **Manual test:** Create a temporary test page or Storybook-like route (`/dev/diagram`) that renders the ProcessDiagram component with a known route
4. **Zoom/pan:** Mouse wheel zooms, drag pans, fit-to-view works
5. **Node interaction:** Hover shows toolbar, click selects with amber ring
6. **Config badges:** Pass mock `nodeConfigs` and verify TRACE/TAP pills render
7. **Error sections:** Route with ON_EXCEPTION renders error handler below main flow
8. **Compound nodes:** Route with CHOICE renders children inside dashed container
9. **Keyboard (required):** Escape deselects, +/- zooms, 0 fits to view
10. **Direction:** `?direction=TB` renders top-to-bottom layout

---

## Implementation Notes (post-spec additions)

The following features were added during implementation beyond the original spec:

### Recursive compound nesting
EIP_WHEN, EIP_OTHERWISE, DO_CATCH, DO_FINALLY added to COMPOUND_TYPES on both backend and frontend. CompoundNode recursively renders children that are themselves compound (e.g., CHOICE → WHEN → processors).

### Edge z-ordering
Edges are distributed to their containing compound and rendered inside the compound's SVG group (after background, before children). Top-level edges stay in the main edges group. This prevents compound backgrounds from hiding edges.

### ON_COMPLETION handler sections
ON_COMPLETION nodes render as teal-tinted sections between the main flow and error handler sections. Structurally parallel to ON_EXCEPTION.

### Drill-down navigation
Double-click on DIRECT or SEDA nodes navigates into the target route's diagram. A breadcrumb bar shows the route stack and supports clicking back to any level. Escape key goes back one level. Route ID resolution handles camelCase endpoint URIs → kebab-case route IDs using the catalog's known route IDs.

### Zoom via CSS transform
The original spec proposed SVG viewBox manipulation. Implementation uses CSS `transform: translate() scale()` on the content `<g>` element instead, which is simpler and more predictable. Default zoom is 100%.

### Toolbar as HTML overlay
The original spec proposed SVG `<foreignObject>`. Implementation renders the toolbar as an absolute-positioned HTML div outside the SVG, so it maintains fixed size regardless of zoom level. Styled with design system tokens.
