--- phase: 02-transaction-search-diagrams plan: 02 type: execute wave: 1 depends_on: [] files_modified: - cameleer-server-app/pom.xml - cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java - cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramLayout.java - cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedNode.java - cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedEdge.java - cameleer-server-app/src/main/java/com/cameleer/server/app/diagram/ElkDiagramRenderer.java - cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramRenderController.java - cameleer-server-app/src/main/java/com/cameleer/server/app/config/DiagramBeanConfig.java - cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramRenderControllerIT.java - cameleer-server-app/src/test/java/com/cameleer/server/app/diagram/ElkDiagramRendererTest.java autonomous: true requirements: - DIAG-03 must_haves: truths: - "GET /api/v1/diagrams/{hash} with Accept: image/svg+xml returns an SVG document with color-coded nodes" - "GET /api/v1/diagrams/{hash} with Accept: application/json returns a JSON layout with node positions" - "Nodes are laid out top-to-bottom using ELK layered algorithm" - "Node colors match the route-diagram-example.html style: blue endpoints, green processors, red error handlers, purple EIPs" - "Nested processors (inside split, choice, try-catch) are rendered in compound/swimlane groups" artifacts: - path: "cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java" provides: "Renderer interface for SVG and JSON layout output" exports: ["DiagramRenderer"] - path: "cameleer-server-app/src/main/java/com/cameleer/server/app/diagram/ElkDiagramRenderer.java" provides: "ELK + JFreeSVG implementation of DiagramRenderer" min_lines: 100 - path: "cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramRenderController.java" provides: "GET /api/v1/diagrams/{hash} with content negotiation" exports: ["DiagramRenderController"] key_links: - from: "DiagramRenderController" to: "DiagramRepository" via: "findByContentHash to load RouteGraph" pattern: "findByContentHash" - from: "DiagramRenderController" to: "DiagramRenderer" via: "renderSvg or layoutJson based on Accept header" pattern: "renderSvg|layoutJson" - from: "ElkDiagramRenderer" to: "ELK RecursiveGraphLayoutEngine" via: "layout computation" pattern: "RecursiveGraphLayoutEngine" --- Implement route diagram rendering with Eclipse ELK for layout and JFreeSVG for SVG output, exposed via a REST endpoint with content negotiation. Purpose: Users need to visualize route diagrams from stored RouteGraph definitions. The server renders color-coded, top-to-bottom SVG diagrams or returns JSON layout data for client-side rendering. This is independent of the search work and can run in parallel. Output: DiagramRenderer interface in core, ElkDiagramRenderer implementation in app, DiagramRenderController with Accept header content negotiation, integration and unit tests. @C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/02-transaction-search-diagrams/02-CONTEXT.md @.planning/phases/02-transaction-search-diagrams/02-RESEARCH.md @cameleer-server-core/src/main/java/com/cameleer/server/core/storage/DiagramRepository.java @cameleer-server-app/src/main/java/com/cameleer/server/app/storage/ClickHouseDiagramRepository.java @cameleer-server-app/pom.xml From cameleer-server-core/.../storage/DiagramRepository.java: ```java public interface DiagramRepository { void store(RouteGraph graph); Optional findByContentHash(String contentHash); Optional findContentHashForRoute(String routeId, String agentId); } ``` From cameleer-common (decompiled — diagram models): ```java // RouteGraph: routeId (String), nodes (List), edges (List), // processorNodeMapping (Map) // RouteNode: id (String), label (String), type (NodeType), properties (Map) // RouteEdge: source (String), target (String), label (String) // NodeType enum: ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA, PROCESSOR, BEAN, LOG, // SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL, CHOICE, WHEN, OTHERWISE, // SPLIT, AGGREGATE, MULTICAST, FILTER, RECIPIENT_LIST, ROUTING_SLIP, DYNAMIC_ROUTER, // LOAD_BALANCE, THROTTLE, DELAY, ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, // DO_CATCH, DO_FINALLY, WIRE_TAP, ENRICH, POLL_ENRICH, SORT, RESEQUENCE, // IDEMPOTENT_CONSUMER, CIRCUIT_BREAKER, SAGA, LOOP ``` NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html): - Blue (#3B82F6): ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA (endpoints) - Green (#22C55E): PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL (processors) - Red (#EF4444): ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY (error handling) - Purple (#A855F7): CHOICE, WHEN, OTHERWISE, SPLIT, AGGREGATE, MULTICAST, FILTER, etc. (EIP patterns) - Cyan (#06B6D4): WIRE_TAP, ENRICH, POLL_ENRICH (cross-route) Task 1: Add ELK/JFreeSVG dependencies and create core diagram rendering interfaces cameleer-server-app/pom.xml, cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramRenderer.java, cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/DiagramLayout.java, cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedNode.java, cameleer-server-core/src/main/java/com/cameleer/server/core/diagram/PositionedEdge.java 1. Add Maven dependencies to `cameleer-server-app/pom.xml`: ```xml org.eclipse.elk org.eclipse.elk.core 0.11.0 org.eclipse.elk org.eclipse.elk.alg.layered 0.11.0 org.jfree org.jfree.svg 5.0.7 ``` 2. Create core diagram rendering interfaces in `com.cameleer.server.core.diagram`: - `PositionedNode` record: id (String), label (String), type (String — NodeType name), x (double), y (double), width (double), height (double), children (List — for compound/swimlane groups). JSON-serializable for the JSON layout response. - `PositionedEdge` record: sourceId (String), targetId (String), label (String), points (List — waypoints for edge routing). The points list contains [x,y] pairs from source to target. - `DiagramLayout` record: width (double), height (double), nodes (List), edges (List). This is the JSON layout response format. - `DiagramRenderer` interface with two methods: - `String renderSvg(RouteGraph graph)` — returns SVG XML string - `DiagramLayout layoutJson(RouteGraph graph)` — returns positioned layout data Both methods take a RouteGraph and produce output. The interface lives in core so it can be swapped (e.g., for a different renderer). cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn compile -pl cameleer-server-core && mvn dependency:resolve -pl cameleer-server-app -q ELK and JFreeSVG dependencies resolve, DiagramRenderer interface and layout DTOs compile in core module Task 2: Implement ElkDiagramRenderer, DiagramRenderController, and integration tests cameleer-server-app/src/main/java/com/cameleer/server/app/diagram/ElkDiagramRenderer.java, cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DiagramRenderController.java, cameleer-server-app/src/main/java/com/cameleer/server/app/config/DiagramBeanConfig.java, cameleer-server-app/src/test/java/com/cameleer/server/app/diagram/ElkDiagramRendererTest.java, cameleer-server-app/src/test/java/com/cameleer/server/app/controller/DiagramRenderControllerIT.java - Unit test: ElkDiagramRenderer.renderSvg with a simple 3-node graph (from->process->to) produces valid SVG containing svg element, rect elements for nodes, line/path elements for edges - Unit test: ElkDiagramRenderer.renderSvg produces SVG where endpoint nodes have blue fill (#3B82F6 or rgb equivalent) - Unit test: ElkDiagramRenderer.layoutJson returns DiagramLayout with correct node count and positive coordinates - Unit test: Nested processors (e.g., CHOICE with WHEN children) are laid out as compound nodes with children inside parent bounds - Integration test: GET /api/v1/diagrams/{hash} with Accept: image/svg+xml returns 200 with content-type image/svg+xml and body starting with ' 1. Create `ElkDiagramRenderer` implementing `DiagramRenderer` in `com.cameleer.server.app.diagram`: **Layout phase (shared by both SVG and JSON):** - Convert RouteGraph to ELK graph: create ElkNode root, set properties for LayeredOptions.ALGORITHM_ID, Direction.DOWN (top-to-bottom per user decision), spacing 40px node-node, 20px edge-node. - For each RouteNode: create ElkNode with estimated width (based on label length * 8 + 32, min 80) and height (40). Set identifier to node.id. - For compound/nesting nodes (CHOICE, SPLIT, TRY_CATCH, DO_TRY, LOOP, MULTICAST, AGGREGATE): create these as compound ElkNodes. Identify children by examining RouteEdge topology — nodes whose only incoming edge is from the compound node AND have no outgoing edge leaving the compound scope are children. Alternatively, use the NodeType hierarchy: WHEN/OTHERWISE are children of CHOICE, DO_CATCH/DO_FINALLY are children of DO_TRY. Create child ElkNodes inside the parent compound node. Set compound node padding (top: 30 for label, sides: 10). - For each RouteEdge: create ElkEdge connecting source to target ElkNodes. - Run layout: `new RecursiveGraphLayoutEngine().layout(rootNode, new BasicProgressMonitor())`. - Extract positions from computed layout into DiagramLayout (nodes with x/y/w/h, edges with routed waypoints). **SVG rendering (renderSvg):** - Run layout phase to get DiagramLayout. - Create `SVGGraphics2D` with layout width + margins and layout height + margins (add 20px padding each side). - Draw edges first (behind nodes): gray (#9CA3AF) lines with 2px stroke following edge waypoints. Draw arrowheads at endpoints. - Draw nodes: rounded rectangles (corner radius 8) filled with type-based colors: - Blue (#3B82F6): ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA - Green (#22C55E): PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL - Red (#EF4444): ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY - Purple (#A855F7): CHOICE, WHEN, OTHERWISE, SPLIT, AGGREGATE, MULTICAST, FILTER, RECIPIENT_LIST, ROUTING_SLIP, DYNAMIC_ROUTER, LOAD_BALANCE, THROTTLE, DELAY, SORT, RESEQUENCE, IDEMPOTENT_CONSUMER, CIRCUIT_BREAKER, SAGA, LOOP - Cyan (#06B6D4): WIRE_TAP, ENRICH, POLL_ENRICH - Draw node labels: white text, centered horizontally, vertically positioned at node.y + 24. - For compound nodes: draw a lighter-fill (alpha 0.15) rounded rectangle for the swimlane container with a label at the top. Draw child nodes inside. - Return `g2.getSVGDocument()`. **JSON layout (layoutJson):** - Run layout phase, return DiagramLayout directly. Jackson will serialize it to JSON. 2. Create `DiagramBeanConfig` in `com.cameleer.server.app.config`: - @Configuration class that creates DiagramRenderer bean (ElkDiagramRenderer) and SearchService bean wiring (prepare for Plan 03). 3. Create `DiagramRenderController` in `com.cameleer.server.app.controller`: - `GET /api/v1/diagrams/{contentHash}/render` — renders the diagram - Inject DiagramRepository and DiagramRenderer. - Look up RouteGraph via `diagramRepository.findByContentHash(contentHash)`. If empty, return 404. - Content negotiation via Accept header: - `image/svg+xml` or `*/*` or no Accept: call `renderer.renderSvg(graph)`, return ResponseEntity with content-type `image/svg+xml` and SVG body. - `application/json`: call `renderer.layoutJson(graph)`, return ResponseEntity with content-type `application/json`. - Use `@RequestMapping(produces = {...})` or manual Accept header parsing to handle content negotiation. Manual parsing is simpler: read `request.getHeader("Accept")`, check for "application/json", default to SVG. 4. Create `ElkDiagramRendererTest` (unit test, no Spring context): - Build a simple RouteGraph with 3 nodes (from-endpoint, process-bean, to-endpoint) and 2 edges. - Test renderSvg produces valid SVG string containing ` WHEN, OTHERWISE compound structure. Verify compound node layout has children. 5. Create `DiagramRenderControllerIT` (extends AbstractClickHouseIT): - Seed a RouteGraph into ClickHouse via the /api/v1/data/diagrams endpoint, wait for flush. - Look up the content hash (compute SHA-256 of the JSON-serialized RouteGraph, same as ClickHouseDiagramRepository.sha256Hex). - GET /api/v1/diagrams/{hash}/render with Accept: image/svg+xml -> assert 200, content-type contains "svg", body contains " assert 200, body contains "nodes", "edges". - GET /api/v1/diagrams/nonexistent/render -> assert 404. - GET /api/v1/diagrams/{hash}/render with no Accept header -> assert SVG response (default). cd C:/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest="ElkDiagramRendererTest,DiagramRenderControllerIT" Diagram rendering produces color-coded top-to-bottom SVG and JSON layout, content negotiation works via Accept header, compound nodes group nested processors, all tests pass - `mvn test -pl cameleer-server-app -Dtest=ElkDiagramRendererTest` passes (unit tests for layout and SVG) - `mvn test -pl cameleer-server-app -Dtest=DiagramRenderControllerIT` passes (integration tests for REST endpoint) - `mvn clean verify` passes (all existing tests still green) - SVG output contains color-coded nodes matching the NodeType color scheme - GET /api/v1/diagrams/{hash}/render returns SVG with color-coded nodes (blue endpoints, green processors, red error handlers, purple EIPs, cyan cross-route) - GET /api/v1/diagrams/{hash}/render with Accept: application/json returns JSON layout with node positions - Nodes laid out top-to-bottom via ELK layered algorithm - Compound nodes group nested processors (CHOICE/WHEN, TRY/CATCH) in swimlane containers - Non-existent hash returns 404 - Default (no Accept header) returns SVG After completion, create `.planning/phases/02-transaction-search-diagrams/02-02-SUMMARY.md`