fix: ENDPOINT node execution state, badge position, and edge traversal

- Synthesize COMPLETED state for ENDPOINT nodes when overlay is active
  (endpoints are route entry points, not in the processor execution tree)
- Move status badge (check/error) inside the card (top-right, below top bar)
  to avoid collision with ConfigBadge (TRACE/TAP) badges
- Include ENDPOINT nodes in edge traversal check so the edge from
  endpoint to first processor renders as green/traversed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-27 19:29:30 +01:00
parent f675451384
commit 3d5d462de0
2 changed files with 45 additions and 12 deletions

View File

@@ -129,16 +129,16 @@ export function DiagramNode({
{/* Config badges */}
{config && <ConfigBadge nodeWidth={w} config={config} />}
{/* Execution overlay: status badge at top-right */}
{/* Execution overlay: status badge inside card, top-right corner */}
{isCompleted && (
<>
<circle cx={w - 8} cy={-8} r={8} fill="#3D7C47" />
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="#3D7C47" />
<text
x={w - 8}
y={-4}
x={w - 10}
y={TOP_BAR_HEIGHT + 11}
textAnchor="middle"
fill="white"
fontSize={11}
fontSize={9}
fontWeight={700}
>
&#x2713;
@@ -147,13 +147,13 @@ export function DiagramNode({
)}
{isFailed && (
<>
<circle cx={w - 8} cy={-8} r={8} fill="#C0392B" />
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="#C0392B" />
<text
x={w - 8}
y={-4}
x={w - 10}
y={TOP_BAR_HEIGHT + 11}
textAnchor="middle"
fill="white"
fontSize={12}
fontSize={9}
fontWeight={700}
>
!

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ProcessDiagramProps } from './types';
import type { DiagramNode as DiagramNodeType } from '../../api/queries/diagrams';
import { useDiagramData } from './useDiagramData';
@@ -58,6 +58,7 @@ export function ProcessDiagram({
onIterationChange,
}: ProcessDiagramProps) {
const overlayActive = !!executionOverlay;
// Route stack for drill-down navigation
const [routeStack, setRouteStack] = useState<string[]>([routeId]);
@@ -72,6 +73,19 @@ export function ProcessDiagram({
application, currentRouteId, direction, diagramLayout,
);
// Collect ENDPOINT node IDs — these are always "traversed" when overlay is active
// because the endpoint is the route entry point (not in the processor execution tree).
const endpointNodeIds = useMemo(() => {
const ids = new Set<string>();
if (!overlayActive || !sections.length) return ids;
for (const section of sections) {
for (const node of section.nodes) {
if (node.type === 'ENDPOINT' && node.id) ids.add(node.id);
}
}
return ids;
}, [overlayActive, sections]);
const zoom = useZoomPan();
const toolbar = useToolbarHover();
@@ -85,6 +99,23 @@ export function ProcessDiagram({
}
}, [totalWidth, totalHeight, currentRouteId]); // eslint-disable-line react-hooks/exhaustive-deps
// Resolve execution state for a node. ENDPOINT nodes (the route's "from:")
// don't appear in the processor execution tree, but should be marked as
// COMPLETED when the route executed (i.e., overlay has any entries).
const getNodeExecutionState = useCallback(
(nodeId: string | undefined, nodeType: string | undefined) => {
if (!nodeId || !executionOverlay) return undefined;
const state = executionOverlay.get(nodeId);
if (state) return state;
// Synthesize COMPLETED for ENDPOINT nodes when overlay is active
if (nodeType === 'ENDPOINT' && executionOverlay.size > 0) {
return { status: 'COMPLETED' as const, durationMs: 0, hasTraceData: false };
}
return undefined;
},
[executionOverlay],
);
const handleNodeClick = useCallback(
(nodeId: string) => { onNodeSelect?.(nodeId); },
[onNodeSelect],
@@ -229,8 +260,10 @@ export function ProcessDiagram({
{/* Main section top-level edges (not inside compounds) */}
<g className="edges">
{mainSection.edges.filter(e => topLevelEdge(e, mainSection.nodes)).map((edge, i) => {
const sourceHasState = executionOverlay?.has(edge.sourceId) || endpointNodeIds.has(edge.sourceId);
const targetHasState = executionOverlay?.has(edge.targetId) || endpointNodeIds.has(edge.targetId);
const isTraversed = executionOverlay
? (executionOverlay.has(edge.sourceId) && executionOverlay.has(edge.targetId))
? (!!sourceHasState && !!targetHasState)
: undefined;
return (
<DiagramEdge key={`${edge.sourceId}-${edge.targetId}-${i}`} edge={edge} traversed={isTraversed} />
@@ -268,7 +301,7 @@ export function ProcessDiagram({
isHovered={toolbar.hoveredNodeId === node.id}
isSelected={selectedNodeId === node.id}
config={node.id ? nodeConfigs?.get(node.id) : undefined}
executionState={executionOverlay?.get(node.id ?? '')}
executionState={getNodeExecutionState(node.id, node.type)}
overlayActive={overlayActive}
onClick={() => node.id && handleNodeClick(node.id)}
onDoubleClick={() => node.id && handleNodeDoubleClick(node.id)}