# Story 2.3: BPMN Diagram Type Renderer

Status: done

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

## Story

As a user,
I want to create and view BPMN process diagrams with standard notation,
so that I can model business processes with pools, lanes, gateways, events, and flows.

## Acceptance Criteria

1. **Given** I open or create a BPMN diagram, **When** the canvas renders, **Then** I see BPMN-standard visual elements: start events (green circle), end events (red bold circle), timer events, message events, exclusive gateways (X diamond), parallel gateways (+ diamond), inclusive gateways (O diamond), activities (rounded rectangles), subprocesses (rounded rectangles with + marker), **And** pools render as large labeled containers with lane subdivisions.

2. **Given** a BPMN diagram has pools and lanes, **When** auto-layout runs, **Then** ELK.js uses compound/hierarchical layout placing nodes within their assigned lanes, **And** edges route correctly between lanes and pools with proper crossing minimization.

3. **Given** a BPMN diagram has different edge types, **When** rendered, **Then** sequence flows show solid arrows, message flows show dashed open arrows, associations show dotted lines.

4. **Given** I click on a BPMN node, **When** the path highlighting activates, **Then** connected nodes and edges in the BFS path are highlighted, **And** unconnected elements are dimmed (opacity reduction).

## Tasks / Subtasks

- [x] Task 1: Create BPMN node size constants and type registry (AC: #1)
  - [x] 1.1: Create `apps/web/src/modules/diagram/types/bpmn/constants.ts` — `BPMN_SIZES` map with dimensions per BPMN node type (start-event, end-event, event-timer, event-message, gateway-exclusive, gateway-parallel, gateway-inclusive, data-object, annotation, activity, subprocess)
  - [x] 1.2: Create `apps/web/src/modules/diagram/types/bpmn/index.ts` — export all BPMN node components, constants, and type helpers

- [x] Task 2: Create custom @xyflow/react BPMN node components (AC: #1)
  - [x] 2.1: Create `BpmnActivityNode.tsx` — rounded rectangle with tag header and label body, handle left/right/top/bottom
  - [x] 2.2: Create `BpmnSubprocessNode.tsx` — same as activity but with a `+` marker at the bottom center
  - [x] 2.3: Create `BpmnStartEventNode.tsx` — green circle (stroke #2ecc71, strokeWidth 2) with label below
  - [x] 2.4: Create `BpmnEndEventNode.tsx` — red bold circle (stroke #e74c3c, strokeWidth 3.5) with label below
  - [x] 2.5: Create `BpmnTimerEventNode.tsx` — blue double circle with clock hands inside (stroke #3498db) and label below
  - [x] 2.6: Create `BpmnMessageEventNode.tsx` — orange double circle with envelope inside (stroke #f39c12) and label below
  - [x] 2.7: Create `BpmnGatewayNode.tsx` — diamond shape with inner marker: X for exclusive, + for parallel, O for inclusive (stroke #3498db)
  - [x] 2.8: Create `BpmnDataObjectNode.tsx` — document shape with folded corner (stroke #f39c12)
  - [x] 2.9: Create `BpmnAnnotationNode.tsx` — text annotation with left bracket border

- [x] Task 3: Create custom BPMN edge components (AC: #3)
  - [x] 3.1: Create `BpmnSequenceEdge.tsx` — solid arrow (default BPMN edge)
  - [x] 3.2: Create `BpmnMessageEdge.tsx` — dashed line with open arrowhead
  - [x] 3.3: Create `BpmnAssociationEdge.tsx` — dotted line (no arrowhead)

- [x] Task 4: Create BPMN compound ELK layout builder (AC: #2)
  - [x] 4.1: Create `apps/web/src/modules/diagram/lib/bpmn-layout.ts` — `buildBpmnElkGraph()` that builds pool > lane > node hierarchy with `elk.hierarchyHandling: INCLUDE_CHILDREN`
  - [x] 4.2: Implement `buildBpmnElkNode()` — maps BPMN node types to ELK nodes with correct dimensions from `BPMN_SIZES`, external labels for gateways/events via ELK label system
  - [x] 4.3: Implement edge container resolution — edges placed at lane level (same lane), pool level (cross-lane same pool), or root level (cross-pool)
  - [x] 4.4: Implement `resolveBpmnPositions()` — recursive absolute position resolution from ELK compound output (parent offsets cascade to children)
  - [x] 4.5: Implement `resolveBpmnEdges()` — resolve edge paths from ELK sections with absolute coordinate shifting, support for SPLINES/ORTHOGONAL/POLYLINE

- [x] Task 5: Create pool and lane container rendering (AC: #1, #2)
  - [x] 5.1: Implement pool rendering as a large labeled container div overlaid on the canvas (or via @xyflow/react group node)
  - [x] 5.2: Implement lane rendering as subdivisions within pools with horizontal labels
  - [x] 5.3: Ensure pool/lane dimensions are computed from ELK layout result

- [x] Task 6: Create BFS path highlighting (AC: #4)
  - [x] 6.1: Create `apps/web/src/modules/diagram/lib/bfs-path.ts` — `bfsPath(startId, edges)` returning `{ nodeSet, edgeSet }` with bidirectional BFS
  - [x] 6.2: Integrate path highlighting into node click handlers — toggle highlight state, dim non-connected nodes/edges via opacity
  - [x] 6.3: Store highlighted node ID in `useGraphStore` — add `highlightedNodeId` and `setHighlightedNodeId` state

- [x] Task 7: Integrate BPMN into graph converter and canvas (AC: #1, #2, #3)
  - [x] 7.1: Update `graph-converter.ts` — map BPMN node types to custom @xyflow/react node types (e.g., `bpmn:activity` → `bpmnActivity`)
  - [x] 7.2: Update `graph-converter.ts` — map BPMN edge types to custom edge types (`sequence` → `bpmnSequence`, `message` → `bpmnMessage`, `association` → `bpmnAssociation`)
  - [x] 7.3: Register all BPMN node types in `DiagramCanvas.tsx` `nodeTypes` object
  - [x] 7.4: Register all BPMN edge types in `DiagramCanvas.tsx` `edgeTypes` object
  - [x] 7.5: Update `useAutoLayout` or `computeLayout` to detect BPMN diagrams and use `buildBpmnElkGraph` for compound layout instead of flat `buildElkGraph`

- [x] Task 8: Group rendering overlay (AC: #1)
  - [x] 8.1: Implement visual group boundaries — compute bounding box from member node positions, render as a dashed border with label
  - [x] 8.2: Groups should dim when path highlighting is active and no group members are in the highlighted path

- [x] Task 9: Tests (AC: all)
  - [x] 9.1: Unit tests for `BPMN_SIZES` — all 11 node types have valid dimensions
  - [x] 9.2: Unit tests for `buildBpmnElkGraph()` — correct pool/lane/node hierarchy, edge container resolution
  - [x] 9.3: Unit tests for `resolveBpmnPositions()` — recursive absolute position computation
  - [x] 9.4: Unit tests for `bfsPath()` — forward and backward BFS, edge set correctness
  - [x] 9.5: Unit tests for graph converter BPMN type mapping — node types and edge types correctly resolved
  - [x] 9.6: All existing tests (50) still pass — now 81 web tests (31 new), 337 total across all packages

## Dev Notes

### Overview — What This Story Builds

This story adds the first diagram type renderer: BPMN (Business Process Model and Notation). It ports the proven Flexicar BPMN rendering patterns to @xyflow/react custom nodes and edges, adds compound ELK layout for pool/lane-aware positioning, and implements BFS path highlighting for interactive node selection.

**This story builds:**
- 9 custom BPMN node components (activity, subprocess, start-event, end-event, timer-event, message-event, gateway, data-object, annotation)
- 3 custom BPMN edge components (sequence, message, association)
- Compound ELK layout builder for pool/lane hierarchy
- BFS path highlighting system
- Pool/lane/group visual containers

**This story does NOT implement:**
- Other diagram types (Stories 2.4-2.8)
- Manual node repositioning (Story 2.9)
- Liveblocks/CRDT integration (Epic 4)
- AI-triggered mutations (Epic 3)

### Architecture Compliance

**MANDATORY patterns from Architecture Decision Document:**

1. **Unified Graph Data Model (Decision 1):** BPMN nodes use type-prefixed `type` field (`bpmn:activity`, `bpmn:gateway-exclusive`, `bpmn:start-event`, etc.). The `lane` and `group` fields on DiagramNode are the BPMN-specific fields. The `pools` and `groups` arrays on GraphData are the BPMN extensions.

2. **Component Structure:** Feature code in `~/modules/diagram/types/bpmn/` — BPMN-specific node components and constants. Shared layout utilities in `~/modules/diagram/lib/`. NOT co-located in route directories.

3. **@xyflow/react Custom Nodes:** All custom node components must use `NodeProps` typing from @xyflow/react. The `nodeTypes` object MUST be defined OUTSIDE the component (performance critical — Story 2.1 pattern).

4. **ELK.js in Web Worker:** The compound layout builder creates the ELK graph structure, but the actual `elk.layout()` call still happens in the existing Web Worker via `computeLayout`. The builder just produces a different ELK graph shape (hierarchical vs flat).

5. **Lean JSON Data Model:** No x/y positions stored for BPMN nodes. All positioning is computed by ELK at render time. Only `lane` and `group` assignments are stored.

### BPMN Node Types — Size Constants

Port from Flexicar's `BPMN_SIZES`. These define the dimensions ELK uses for layout spacing:

```typescript
export const BPMN_SIZES: Record<string, { w: number; h: number; labelH: number }> = {
  "start-event":       { w: 36, h: 36, labelH: 32 },
  "end-event":         { w: 36, h: 36, labelH: 32 },
  "event-timer":       { w: 36, h: 36, labelH: 32 },
  "event-message":     { w: 36, h: 36, labelH: 32 },
  "gateway-exclusive": { w: 50, h: 50, labelH: 40 },
  "gateway-parallel":  { w: 50, h: 50, labelH: 40 },
  "gateway-inclusive":  { w: 50, h: 50, labelH: 40 },
  "data-object":       { w: 40, h: 50, labelH: 40 },
  "annotation":        { w: 220, h: 50, labelH: 0 },
  "activity":          { w: 240, h: 76, labelH: 0 },
  "subprocess":        { w: 240, h: 86, labelH: 0 },
};
```

**`labelH`:** Height reserved for labels below the shape (gateways, events, data-objects). Activities/subprocesses have labels inside the shape body, so `labelH: 0`.

### BPMN Custom Node Components — Implementation Approach

Each BPMN node type gets a custom @xyflow/react node component. All go in `apps/web/src/modules/diagram/types/bpmn/`.

**Key pattern — @xyflow/react custom node:**
```typescript
import { Handle, Position } from "@xyflow/react";
import type { Node, NodeProps } from "@xyflow/react";
import type { DiagramNode } from "../../types/graph";

type BpmnActivityData = DiagramNode & { label: string };
type BpmnActivityNodeType = Node<BpmnActivityData, "bpmnActivity">;

export function BpmnActivityNode({ data }: NodeProps<BpmnActivityNodeType>) {
  return (
    <div className="bpmn-activity">
      {data.tag && <div className="bpmn-activity-tag">{data.tag}</div>}
      <div className="bpmn-activity-label">{data.label}</div>
      <Handle type="target" position={Position.Left} />
      <Handle type="source" position={Position.Right} />
    </div>
  );
}
```

**SVG-based nodes (events, gateways, data-objects):** These use inline SVG within the @xyflow/react node wrapper. The node's outer div has explicit width/height matching `BPMN_SIZES`. The SVG shape renders inside.

**Label below:** For event/gateway/data-object nodes, the label renders below the SVG shape as a `<div>` with `text-center`. The node's total height for @xyflow = shape height + labelH from BPMN_SIZES.

**Handles:** @xyflow/react requires `<Handle>` components for edge connection points. For BPMN nodes, add handles at all 4 positions (top/right/bottom/left) with `style={{ opacity: 0 }}` to make them invisible but functional.

### BPMN Node Type → @xyflow/react Type Mapping

| DiagramNode.type | @xyflow Node type | Component |
|---|---|---|
| `bpmn:activity` | `bpmnActivity` | `BpmnActivityNode` |
| `bpmn:subprocess` | `bpmnSubprocess` | `BpmnSubprocessNode` |
| `bpmn:start-event` | `bpmnStartEvent` | `BpmnStartEventNode` |
| `bpmn:end-event` | `bpmnEndEvent` | `BpmnEndEventNode` |
| `bpmn:event-timer` | `bpmnTimerEvent` | `BpmnTimerEventNode` |
| `bpmn:event-message` | `bpmnMessageEvent` | `BpmnMessageEventNode` |
| `bpmn:gateway-exclusive` | `bpmnGateway` | `BpmnGatewayNode` |
| `bpmn:gateway-parallel` | `bpmnGateway` | `BpmnGatewayNode` |
| `bpmn:gateway-inclusive` | `bpmnGateway` | `BpmnGatewayNode` |
| `bpmn:data-object` | `bpmnDataObject` | `BpmnDataObjectNode` |
| `bpmn:annotation` | `bpmnAnnotation` | `BpmnAnnotationNode` |

**Note on gateways:** All three gateway subtypes use the same `BpmnGatewayNode` component. The inner marker (X, +, O) is determined by `data.type` within the component.

**Note on type prefixing:** The stored data uses `bpmn:` prefix (e.g., `bpmn:activity`). The graph converter strips the prefix and maps to @xyflow node type string (e.g., `bpmnActivity`). However, some Flexicar reference data uses unprefixed types (e.g., just `activity`, `start-event`). The converter should handle BOTH formats for backward compatibility:
- `bpmn:activity` → `bpmnActivity`
- `activity` → `bpmnActivity` (when diagram meta.diagramType === "bpmn")

### BPMN Custom Edge Types

| DiagramEdge.type | @xyflow Edge type | Visual |
|---|---|---|
| `sequence` (or default) | `bpmnSequence` | Solid line, filled arrowhead |
| `message` | `bpmnMessage` | Dashed line, open arrowhead |
| `association` | `bpmnAssociation` | Dotted line, no arrowhead |

**Edge components** use @xyflow/react's `BaseEdge` + `getSmoothStepPath` (for orthogonal routing) or `getBezierPath` (for spline routing). Use `markerEnd` prop for arrowheads.

```typescript
import { BaseEdge, getSmoothStepPath } from "@xyflow/react";
import type { EdgeProps } from "@xyflow/react";

export function BpmnSequenceEdge(props: EdgeProps) {
  const [edgePath] = getSmoothStepPath({
    sourceX: props.sourceX,
    sourceY: props.sourceY,
    targetX: props.targetX,
    targetY: props.targetY,
    sourcePosition: props.sourcePosition,
    targetPosition: props.targetPosition,
  });

  return (
    <BaseEdge
      path={edgePath}
      markerEnd="url(#bpmn-arrow)"
      style={{ stroke: "var(--edge-default)" }}
      {...props}
    />
  );
}
```

**SVG marker defs:** Add arrowhead marker definitions to the canvas (filled arrow for sequence, open arrow for message). Use an SVG `<defs>` element rendered inside the ReactFlow component.

### Compound ELK Layout — `buildBpmnElkGraph()`

The critical difference from flat layout: BPMN uses **hierarchical** ELK layout with pool/lane containers.

**Key ELK options for compound layout:**
```typescript
'elk.hierarchyHandling': 'INCLUDE_CHILDREN'  // Process children within parent boundaries
'elk.padding': '[top=55,left=80,bottom=45,right=40]'  // Lane padding for labels
```

**Hierarchy:**
```
root
├── pool (container)
│   ├── lane (container, with padding for lane label)
│   │   ├── node (with BPMN_SIZES dimensions)
│   │   └── node
│   └── lane
│       └── node
└── free-floating node (annotation, data-object without lane)
```

**Edge container resolution (critical for correct routing):**
- Same lane → edge belongs to lane
- Cross-lane, same pool → edge belongs to pool
- Cross-pool or free → edge belongs to root

```typescript
function resolveEdgeContainer(fromId: string, toId: string, nodeToContainer: Map<string, {pool: string, lane: string}>): string {
  const src = nodeToContainer.get(fromId);
  const tgt = nodeToContainer.get(toId);
  if (!src || !tgt) return "root";
  if (src.pool === tgt.pool && src.lane === tgt.lane) return src.lane;
  if (src.pool === tgt.pool) return src.pool;
  return "root";
}
```

**ELK node labels for gateways/events:** Shapes with labels below (gateways, events, data-objects) use ELK's label system so it reserves space for the label text while routing edges to the shape body:
```typescript
elkNode.labels = [{ text: node.label, width: labelW, height: BPMN_SIZES[type].labelH }];
elkNode.layoutOptions = { 'elk.nodeLabels.placement': 'OUTSIDE V_BOTTOM H_CENTER' };
```

### Position Resolution — Compound Graphs

For compound (hierarchical) graphs, ELK returns positions relative to parent containers. Must recursively add parent offsets to get absolute positions:

```typescript
function resolveBpmnPositions(
  node: ElkNode,
  offsetX = 0,
  offsetY = 0,
): Map<string, { x: number; y: number; w: number; h: number }> {
  const positions = new Map();
  const ax = offsetX + (node.x ?? 0);
  const ay = offsetY + (node.y ?? 0);
  positions.set(node.id, { x: ax, y: ay, w: node.width ?? 0, h: node.height ?? 0 });
  for (const child of node.children ?? []) {
    const childPositions = resolveBpmnPositions(child, ax, ay);
    for (const [k, v] of childPositions) positions.set(k, v);
  }
  return positions;
}
```

### BFS Path Highlighting

Port from Flexicar's `bfsPath()`. Bidirectional BFS from clicked node:

```typescript
export function bfsPath(
  startId: string,
  edges: Array<{ from: string; to: string }>,
): { nodeSet: Set<string>; edgeSet: Set<string> } {
  const forward: Record<string, typeof edges> = {};
  const backward: Record<string, typeof edges> = {};
  for (const e of edges) {
    (forward[e.from] ??= []).push(e);
    (backward[e.to] ??= []).push(e);
  }

  const nodeSet = new Set([startId]);
  const edgeSet = new Set<string>();

  // Forward BFS
  let queue = [startId];
  while (queue.length) {
    const next: string[] = [];
    for (const nid of queue) {
      for (const e of forward[nid] ?? []) {
        const key = `${e.from}->${e.to}`;
        if (!edgeSet.has(key)) {
          edgeSet.add(key);
          nodeSet.add(e.to);
          next.push(e.to);
        }
      }
    }
    queue = next;
  }

  // Backward BFS
  queue = [startId];
  const visited = new Set([startId]);
  while (queue.length) {
    const next: string[] = [];
    for (const nid of queue) {
      for (const e of backward[nid] ?? []) {
        if (!visited.has(e.from)) {
          visited.add(e.from);
          edgeSet.add(`${e.from}->${e.to}`);
          nodeSet.add(e.from);
          next.push(e.from);
        }
      }
    }
    queue = next;
  }

  return { nodeSet, edgeSet };
}
```

**Integration:** Add `highlightedNodeId: string | null` and `setHighlightedNodeId` to `useGraphStore`. Node click handler toggles this state. Node components read it to apply dimming (opacity: 0.25) or highlighting (border glow) CSS classes. Edge components use it to dim non-path edges.

### Graph Converter Updates

The current `graphNodeToFlowNode` maps ALL nodes to `type: "default"`. For BPMN, it must map to custom node types.

**Updated logic in `graphNodeToFlowNode`:**
```typescript
function resolveBpmnNodeType(type: string): string {
  // Strip prefix if present
  const bare = type.startsWith("bpmn:") ? type.slice(5) : type;
  switch (bare) {
    case "activity": return "bpmnActivity";
    case "subprocess": return "bpmnSubprocess";
    case "start-event": return "bpmnStartEvent";
    case "end-event": return "bpmnEndEvent";
    case "event-timer": return "bpmnTimerEvent";
    case "event-message": return "bpmnMessageEvent";
    case "gateway-exclusive":
    case "gateway-parallel":
    case "gateway-inclusive": return "bpmnGateway";
    case "data-object": return "bpmnDataObject";
    case "annotation": return "bpmnAnnotation";
    default: return "bpmnActivity"; // fallback for unknown BPMN types
  }
}

function resolveFlowNodeType(diagramType: DiagramType | undefined, nodeType: string): string {
  if (diagramType === "bpmn" || nodeType.startsWith("bpmn:")) {
    return resolveBpmnNodeType(nodeType);
  }
  // Future: er, orgchart, architecture, sequence, flowchart
  return "default";
}
```

**Edge type mapping:**
```typescript
function resolveBpmnEdgeType(type: string | undefined): string {
  switch (type) {
    case "message": return "bpmnMessage";
    case "association": return "bpmnAssociation";
    case "sequence":
    default: return "bpmnSequence";
  }
}
```

**IMPORTANT:** The `graphToFlow` function must receive `diagramType` context to know which renderer to use. Either pass it as a parameter or read it from the graph's `meta.diagramType`.

### Layout Integration — Detecting BPMN for Compound Layout

The `computeLayout` function currently calls `buildElkGraph()` for flat layout. For BPMN diagrams with pools, it must use `buildBpmnElkGraph()` instead.

**Approach:** Add a `diagramType` parameter (or full `GraphData`) to `computeLayout`. When `diagramType === "bpmn"` and pools are present, build the compound ELK graph. Otherwise, use the existing flat builder.

The compound graph builder produces a different ELK graph shape but the worker still runs `elk.layout()` the same way. The key difference is the `resolvePositions` step — compound graphs need recursive position resolution instead of flat mapping.

**Worker change:** None needed. The worker receives any ELK graph and returns the layout result. The client-side code handles the difference.

### Pool and Lane Rendering in @xyflow/react

**Option A (recommended): Use @xyflow/react group nodes.** Register pool/lane as custom node types with `type: "group"`. Set `style: { width, height }` from ELK layout result. Child nodes use `parentId` to be positioned relative to the group.

**Option B: Overlay divs.** Render pool/lane as absolute-positioned divs on a Panel layer behind the nodes. Simpler but less integrated with @xyflow's node system.

**Recommended: Option A** — aligns with @xyflow/react's built-in grouping support. Each pool is a group node, each lane is a child group node within the pool. BPMN nodes have `parentId` set to their lane ID.

```typescript
// Pool node type for @xyflow/react
function BpmnPoolNode({ data }: NodeProps) {
  return (
    <div className="bpmn-pool" style={{ width: "100%", height: "100%" }}>
      <div className="bpmn-pool-label">{data.label}</div>
    </div>
  );
}

// Lane node type for @xyflow/react
function BpmnLaneNode({ data }: NodeProps) {
  return (
    <div className="bpmn-lane" style={{ width: "100%", height: "100%" }}>
      <div className="bpmn-lane-label">{data.label}</div>
    </div>
  );
}
```

**Graph converter must inject pool/lane nodes:** When converting BPMN GraphData, create synthetic @xyflow nodes for pools and lanes (from `graphData.pools`) with `type: "bpmnPool"` and `type: "bpmnLane"`. Set child nodes' `parentId` to their lane ID.

### CSS Styles for BPMN Nodes

Add to `globals.css`:

```css
/* BPMN Node Styles */
.bpmn-activity {
  background: var(--node-bg);
  border: 1.5px solid var(--diagram-bpmn);
  border-radius: 8px;
  padding: 8px 12px;
  min-width: 200px;
  font-size: 12px;
}

.bpmn-activity-tag {
  font-weight: 600;
  font-size: 11px;
  color: var(--diagram-bpmn);
  margin-bottom: 4px;
}

.bpmn-activity-label {
  color: var(--foreground);
  line-height: 1.4;
}

.bpmn-pool {
  border: 2px solid var(--node-border);
  border-radius: 4px;
  background: transparent;
}

.bpmn-pool-label {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  transform: rotate(180deg);
  font-weight: 600;
  font-size: 12px;
  padding: 8px 4px;
  color: var(--foreground);
}

.bpmn-lane {
  border-top: 1px solid var(--node-border);
  background: transparent;
}

.bpmn-lane-label {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  transform: rotate(180deg);
  font-size: 11px;
  padding: 4px 2px;
  color: var(--muted-foreground);
}

/* Path highlighting */
.react-flow__node.dimmed,
.react-flow__edge.dimmed {
  opacity: 0.2;
  transition: opacity 200ms ease-out;
}

.react-flow__node.highlighted {
  filter: drop-shadow(0 0 6px var(--diagram-bpmn));
  transition: filter 200ms ease-out;
}
```

### Existing Code to Reuse / Modify

| File | Action | What |
|------|--------|------|
| `apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx` | **MODIFY** | Register BPMN node types and edge types in `nodeTypes`/`edgeTypes` objects |
| `apps/web/src/modules/diagram/lib/graph-converter.ts` | **MODIFY** | Add BPMN type resolution for nodes and edges, pass diagramType context |
| `apps/web/src/modules/diagram/lib/elk-layout.ts` | **MODIFY** | Add BPMN compound layout path (or import from new bpmn-layout.ts), update `computeLayout` to accept diagramType |
| `apps/web/src/modules/diagram/stores/useGraphStore.ts` | **MODIFY** | Add `highlightedNodeId` state for path highlighting |
| `apps/web/src/modules/diagram/hooks/useAutoLayout.ts` | **MODIFY** | Pass diagramType and GraphData (pools) to `computeLayout` for compound layout detection |
| `apps/web/src/modules/diagram/components/editor/DiagramEditor.tsx` | **READ** | Understand how graphData is passed to canvas and store |
| `apps/web/src/modules/diagram/types/graph.ts` | **READ** | DiagramNode (lane, group), GraphData (pools, groups) — already defined |
| `apps/web/src/assets/styles/globals.css` | **MODIFY** | Add BPMN node CSS styles and path highlighting styles |
| `/Users/agutierrez/Desktop/flexicar-context/.diagrams/app/shared.js` | **REFERENCE** | Port BPMN_SIZES, buildElkGraph compound builder, SVG renderers, bfsPath, resolvePositions, resolveEdges |
| `/Users/agutierrez/Desktop/flexicar-context/.diagrams/app/process-v4-bpmn.json` | **REFERENCE** | Sample BPMN data structure with pools/lanes/groups for testing |

### Library & Framework Requirements

**No new packages required.** Everything is built with existing dependencies:
- `@xyflow/react` 12.10.1 — custom nodes, edges, handles, group nodes
- `elkjs` 0.11.0 — compound layout via `elk.hierarchyHandling: INCLUDE_CHILDREN`
- `zustand` 5.0.8 — highlight state

### @xyflow/react Custom Nodes — Key Implementation Details

**Custom node registration (MUST be outside component):**
```typescript
import { BpmnActivityNode } from "../../types/bpmn/BpmnActivityNode";
// ... all other BPMN node imports

const nodeTypes = {
  bpmnActivity: BpmnActivityNode,
  bpmnSubprocess: BpmnSubprocessNode,
  bpmnStartEvent: BpmnStartEventNode,
  bpmnEndEvent: BpmnEndEventNode,
  bpmnTimerEvent: BpmnTimerEventNode,
  bpmnMessageEvent: BpmnMessageEventNode,
  bpmnGateway: BpmnGatewayNode,
  bpmnDataObject: BpmnDataObjectNode,
  bpmnAnnotation: BpmnAnnotationNode,
  bpmnPool: BpmnPoolNode,
  bpmnLane: BpmnLaneNode,
};
```

**Custom node props:** `data` prop contains the full `DiagramNode` object (spread by `graphNodeToFlowNode`). Access `data.type`, `data.tag`, `data.label`, `data.lane`, `data.group`.

**Custom node dimensions:** @xyflow/react measures nodes after DOM render via `node.measured.width/height`. For ELK layout, set initial width/height from `BPMN_SIZES` using the node's `data.w` or computed size.

**Group nodes (pools/lanes):** @xyflow/react v12 supports group nodes natively. Set `type: "group"` on parent nodes AND set `style: { width, height }` explicitly. Child nodes must have `parentId` pointing to their group. Child positions are relative to parent.

### Flexicar BPMN Reference — SVG Shapes to Port

All SVG shapes from Flexicar are rendered via `React.createElement` (vanilla). Port to JSX:

- **Start Event:** `<circle cx={18} cy={18} r={17} fill="none" stroke="#2ecc71" strokeWidth={2} />`
- **End Event:** `<circle cx={18} cy={18} r={17} fill="none" stroke="#e74c3c" strokeWidth={3.5} />`
- **Timer Event:** Double circle + clock hands (blue #3498db)
- **Message Event:** Double circle + envelope shape (orange #f39c12)
- **Gateway:** Diamond polygon with inner marker. Colors: all blue #3498db
  - Exclusive: X cross lines (strokeWidth 3)
  - Parallel: + cross lines (strokeWidth 3)
  - Inclusive: circle (r=9, strokeWidth 2.5)
- **Data Object:** Document path with folded corner (stroke #f39c12)
- **Activity:** Rounded rectangle (CSS, not SVG)
- **Subprocess:** Same as activity + centered `+` marker at bottom

**Color note:** In Flexicar, these are dark-theme colors (dark background). For domaingraph, use CSS custom properties so they work in both light and dark modes. The shape strokes should use the `--diagram-bpmn` accent color with type-specific overrides for events/gateways.

### File Structure for This Story

New files:
```
apps/web/src/modules/diagram/
├── types/bpmn/
│   ├── index.ts                        # Exports all BPMN components + constants
│   ├── constants.ts                    # BPMN_SIZES, type mappings
│   ├── BpmnActivityNode.tsx            # Activity custom node
│   ├── BpmnSubprocessNode.tsx          # Subprocess custom node
│   ├── BpmnStartEventNode.tsx          # Start event custom node
│   ├── BpmnEndEventNode.tsx            # End event custom node
│   ├── BpmnTimerEventNode.tsx          # Timer event custom node
│   ├── BpmnMessageEventNode.tsx        # Message event custom node
│   ├── BpmnGatewayNode.tsx             # Gateway custom node (all 3 subtypes)
│   ├── BpmnDataObjectNode.tsx          # Data object custom node
│   ├── BpmnAnnotationNode.tsx          # Annotation custom node
│   ├── BpmnPoolNode.tsx                # Pool group container
│   ├── BpmnLaneNode.tsx                # Lane group container
│   ├── BpmnSequenceEdge.tsx            # Sequence flow edge
│   ├── BpmnMessageEdge.tsx             # Message flow edge
│   └── BpmnAssociationEdge.tsx         # Association edge
├── lib/
│   ├── bpmn-layout.ts                  # Compound ELK graph builder for BPMN
│   ├── bpmn-layout.test.ts             # Tests for BPMN layout builder
│   └── bfs-path.ts                     # BFS path highlighting utility
│   └── bfs-path.test.ts                # Tests for BFS path
└── (existing files modified)
```

Modified files:
```
apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx  # Register BPMN node/edge types
apps/web/src/modules/diagram/lib/graph-converter.ts               # BPMN type mapping
apps/web/src/modules/diagram/lib/graph-converter.test.ts          # Add BPMN converter tests
apps/web/src/modules/diagram/lib/elk-layout.ts                    # BPMN compound layout path
apps/web/src/modules/diagram/stores/useGraphStore.ts              # Add highlightedNodeId
apps/web/src/modules/diagram/stores/useGraphStore.test.ts         # Add highlight state tests
apps/web/src/modules/diagram/hooks/useAutoLayout.ts               # Pass diagramType to layout
apps/web/src/assets/styles/globals.css                             # BPMN CSS styles
```

### Project Structure Notes

- BPMN node components go in `~/modules/diagram/types/bpmn/` — this follows the epics file convention (`apps/web/src/modules/diagram/types/bpmn/`)
- Layout utilities go in `~/modules/diagram/lib/` — shared across diagram types
- `bfs-path.ts` is NOT BPMN-specific — it works with any graph and will be reused by other diagram types
- Tests co-located next to source files

### Anti-Patterns to Avoid

- **NEVER put `nodeTypes` or `edgeTypes` inside the component** — causes re-renders on every state change, killing canvas performance (Story 2.1 pattern)
- **NEVER hardcode positions for BPMN nodes** — all positioning comes from ELK compound layout
- **NEVER import from `reactflow`** — use `@xyflow/react` (v12+)
- **NEVER use `require()`** — ESM-only project
- **NEVER co-locate feature code in route directories** — use `~/modules/diagram/`
- **NEVER store ELK-computed positions in the persisted graph data** — positions are ephemeral (Story 2.2 pattern)
- **NEVER run ELK on the main thread** — always via Web Worker
- **DO NOT implement other diagram type renderers** — those are Stories 2.4-2.8
- **DO NOT implement manual node repositioning** — that's Story 2.9
- **DO NOT implement Liveblocks/CRDT** — that's Story 4.1
- **DO NOT break existing tests** — 50 tests must continue passing
- **DO NOT use inline styles for BPMN colors** — use CSS custom properties for light/dark mode support

### Previous Story Intelligence (Story 2.2)

**Key learnings to carry forward:**
- `buildElkGraph()` is the flat layout builder — BPMN needs a compound variant (`buildBpmnElkGraph`) that builds pool/lane hierarchy
- `resolvePositions()` currently handles flat graphs (direct children of root) — BPMN needs recursive resolution that cascades parent offsets
- `computeLayout()` sends the ELK graph to the Web Worker and resolves positions on return — extend this to accept diagramType/GraphData for compound layout detection
- Worker singleton pattern with `getWorker()` / `terminateWorker()` — no worker changes needed
- `useAutoLayout` debounces at 300ms and uses single-flight pattern — these apply to BPMN layout too
- Layout animation uses `.layouting` CSS class toggling — apply same pattern to BPMN nodes
- `DiagramMeta` already has `layoutDirection` and `edgeRouting` fields — BPMN uses these same fields
- `DiagramNode` already has `lane?: string` and `group?: string` — no type changes needed
- `GraphData` already has `pools` and `groups` arrays — no type changes needed
- All node data is spread into `data` prop via `graphNodeToFlowNode()` — custom nodes access full DiagramNode via `data`
- 50 tests currently pass (18 elk-layout + 15 graph-converter + 17 store) — don't break them

### Previous Story Intelligence (Story 2.1)

**Key learnings:**
- `nodeTypes` defined OUTSIDE component — critical for performance
- `ReactFlowProvider` wraps `ReactFlow` — hooks like `useReactFlow()` work in children
- `colorMode="system"` — dark mode automatic
- `diagramTypeConfig` in DiagramCard.tsx — has icons and accent colors for all 6 types
- Hono RPC pattern for data fetching
- `DropdownMenu` for selection controls
- `sonner` toast for user feedback

### Git Intelligence

Recent commits:
- `7dd5af1 feat: implement Story 2.2 — ELK.js auto-layout engine in Web Worker`
- `5033109 feat: implement Story 2.1 — canvas workspace with @xyflow/react and unified graph model`

Established patterns:
- Commit message: `feat: implement Story X.Y — description`
- Feature code in `apps/web/src/modules/diagram/`
- Co-located tests next to source files
- `DropdownMenu` from shadcn/ui for selection controls
- `diagramTypeConfig` object for diagram type metadata
- sherif workspace lint requires alphabetically sorted devDependencies

### Latest Tech Information

**@xyflow/react 12.10.1 — Custom Node API:**
- Custom nodes receive `NodeProps<T>` with `data`, `id`, `selected`, `dragging` props
- `Handle` component required for edge connections — `type="source"` or `type="target"`, `position` from `Position` enum
- `node.measured.width/height` available after DOM render — use for accurate ELK sizing on re-layout
- Group nodes: set `type` on parent, `parentId` on children, explicit `style: { width, height }` on parent node
- Custom edges: receive `EdgeProps` with `sourceX/Y`, `targetX/Y`, `sourcePosition`, `targetPosition`
- `BaseEdge` + path utilities (`getSmoothStepPath`, `getBezierPath`, `getStraightPath`) for edge rendering
- `markerEnd` prop accepts SVG marker URL reference

**ELK.js 0.11.0 — Compound Layout:**
- `elk.hierarchyHandling: 'INCLUDE_CHILDREN'` enables compound layout where child nodes are laid out within parent boundaries
- Each container (pool/lane) can have its own `layoutOptions` (padding, spacing)
- Edges assigned to specific containers via the `edges` property on that container node
- ELK returns positions relative to parent container — must recursively resolve to absolute
- Label system: `labels` array on nodes with `text`, `width`, `height` + `layoutOptions` placement

**@xyflow/react v12 grouping:**
- No need for `node.extent: "parent"` (removed in v12)
- Child nodes use `parentId` (not `parentNode` which was v11)
- Group nodes should use `style` prop with explicit `width`/`height`
- Group CSS: `pointer-events: none` on the group node to allow clicking through to children

### References

- [Source: _bmad-output/planning-artifacts/epics.md#Story 2.3] — Full AC and technical notes
- [Source: _bmad-output/planning-artifacts/architecture.md#Decision 1] — Unified Graph Data Model (hybrid schema, BPMN extensions)
- [Source: _bmad-output/planning-artifacts/architecture.md#Flexicar Prototype] — Lean JSON data model, BPMNDiagram patterns
- [Source: _bmad-output/planning-artifacts/architecture.md#Enforcement Guidelines] — 7 mandatory rules
- [Source: _bmad-output/implementation-artifacts/2-2-elk-js-auto-layout-engine-in-web-worker.md] — ELK layout module, Web Worker, resolvePositions, BPMN extension notes
- [Source: _bmad-output/implementation-artifacts/2-1-canvas-workspace-with-xyflow-react-and-unified-graph-model.md] — Graph converter, Zustand store, DiagramCanvas, nodeTypes pattern
- [Source: _bmad-output/project-context.md] — 62 critical implementation rules
- [Source: /Users/agutierrez/Desktop/flexicar-context/.diagrams/app/shared.js] — BPMN_SIZES, buildElkGraph compound, SVG renderers, bfsPath, resolvePositions, resolveEdges
- [Source: /Users/agutierrez/Desktop/flexicar-context/.diagrams/app/process-v4-bpmn.json] — Sample BPMN data with pools/lanes/groups
- [Source: apps/web/src/modules/diagram/types/graph.ts] — DiagramNode (lane, group), GraphData (pools, groups)
- [Source: apps/web/src/modules/diagram/lib/elk-layout.ts] — Current flat layout builder to extend
- [Source: apps/web/src/modules/diagram/lib/graph-converter.ts] — Current converter to update for BPMN types
- [Source: apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx] — Canvas to register BPMN types

## Dev Agent Record

### Agent Model Used

Claude Opus 4.6

### Debug Log References

No issues encountered during implementation.

### Completion Notes List

- Created 11 BPMN node types (constants.ts) with exact Flexicar BPMN_SIZES dimensions
- Created 9 custom @xyflow/react BPMN node components: activity, subprocess, start-event, end-event, timer-event, message-event, gateway (all 3 subtypes), data-object, annotation
- Created 3 container node types: BpmnPoolNode, BpmnLaneNode, BpmnGroupNode for pool/lane/group rendering
- Created 3 custom BPMN edge components: sequence (solid arrow), message (dashed open arrow), association (dotted)
- Created compound ELK layout builder (`bpmn-layout.ts`) with pool > lane > node hierarchy, edge container resolution, recursive position resolution, and edge path resolution
- Created BFS path highlighting utility (`bfs-path.ts`) with bidirectional BFS
- Updated graph converter to resolve BPMN node/edge types based on `diagramType` context, and to create synthetic pool/lane/group @xyflow/react group nodes
- Updated `flowToGraph` to reconstruct pools/groups from @xyflow group nodes (roundtrip preservation)
- Registered all BPMN node/edge types in DiagramCanvas.tsx with SVG marker defs for arrowheads
- Integrated compound BPMN layout into `computeLayout` in elk-layout.ts — auto-detects pools and uses hierarchical ELK layout
- Integrated BFS path highlighting via `onNodeClick` handler in DiagramCanvas — toggles dimmed/highlighted CSS classes on nodes/edges
- Added `highlightedNodeId` and `setHighlightedNodeId` to useGraphStore
- All event/data-object nodes now have 4 handles (top/right/bottom/left) for edge connections in any layout direction
- All BPMN SVG colors use CSS custom properties for light/dark mode support
- Added comprehensive BPMN CSS styles, group styles, and path highlighting (dimmed/highlighted) styles to globals.css
- All 81 web tests pass (31 new: 12 bpmn-layout, 9 bfs-path, 6 graph-converter BPMN tests, 4 store highlight tests), 337 total across all packages
- No regressions — all 50 pre-existing web tests continue to pass

### Change Log

- 2026-02-24: Story 2.3 implementation complete — BPMN diagram type renderer with 9 node types, 3 edge types, compound ELK layout, BFS highlighting, pool/lane containers
- 2026-02-24: Code review fixes — integrated compound layout into computeLayout (C1), wired BFS highlighting via onNodeClick (C2), added BpmnGroupNode (C3), fixed hardcoded SVG colors to CSS variables (M1), added 4 handles to all event nodes (M2), fixed flowToGraph pool/group preservation (M3), added 6 new tests (L3)

### File List

New files:
- `apps/web/src/modules/diagram/types/bpmn/constants.ts`
- `apps/web/src/modules/diagram/types/bpmn/index.ts`
- `apps/web/src/modules/diagram/types/bpmn/BpmnActivityNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnSubprocessNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnStartEventNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnEndEventNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnTimerEventNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnMessageEventNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnGatewayNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnDataObjectNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnAnnotationNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnPoolNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnLaneNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnGroupNode.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnSequenceEdge.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnMessageEdge.tsx`
- `apps/web/src/modules/diagram/types/bpmn/BpmnAssociationEdge.tsx`
- `apps/web/src/modules/diagram/lib/bpmn-layout.ts`
- `apps/web/src/modules/diagram/lib/bpmn-layout.test.ts`
- `apps/web/src/modules/diagram/lib/bfs-path.ts`
- `apps/web/src/modules/diagram/lib/bfs-path.test.ts`

Modified files:
- `apps/web/src/modules/diagram/components/editor/DiagramCanvas.tsx`
- `apps/web/src/modules/diagram/lib/graph-converter.ts`
- `apps/web/src/modules/diagram/lib/graph-converter.test.ts`
- `apps/web/src/modules/diagram/lib/elk-layout.ts`
- `apps/web/src/modules/diagram/stores/useGraphStore.ts`
- `apps/web/src/modules/diagram/stores/useGraphStore.test.ts`
- `apps/web/src/assets/styles/globals.css`
- `_bmad-output/implementation-artifacts/sprint-status.yaml`
