Compare commits

2 Commits

Author SHA1 Message Date
hsiegeln
30e9b55379 fix: detail panel respects iteration filtering
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m35s
CI / docker (push) Successful in 1m12s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 48s
- findProcessorInTree now skips non-selected iteration wrappers so
  the returned ProcessorNode has data from the correct iteration
- Gate selectedProcessor on overlay presence so processors not
  executed in the current iteration don't show in the detail panel
- Header shows "Exchange Details" or "Processor Details" contextually

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 20:25:28 +02:00
hsiegeln
3091754b0f fix: dim compound containers when no descendants executed in overlay
CompoundNode (circuit breaker, choice, etc.) now renders at 0.35
opacity when the overlay is active but neither the compound itself
nor any of its diagram descendants appear in the execution overlay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 20:13:40 +02:00
3 changed files with 57 additions and 6 deletions

View File

@@ -90,7 +90,7 @@ export function DetailPanel({
<div className={styles.detailPanel}>
{/* Header bar */}
<div className={styles.processorHeader}>
<span className={styles.processorName}>Details</span>
<span className={styles.processorName}>{selectedProcessor ? 'Processor Details' : 'Exchange Details'}</span>
</div>
{/* Tab bar */}

View File

@@ -20,15 +20,46 @@ interface ExecutionDiagramProps {
className?: string;
}
const ITERATION_WRAPPER_TYPES = new Set([
'loopIteration', 'splitIteration', 'multicastBranch',
]);
function wrapperIndex(proc: ProcessorNode): number | undefined {
return proc.loopIndex ?? proc.splitIndex ?? proc.multicastIndex ?? undefined;
}
/**
* Find a processor in the tree, respecting iteration filtering.
* Only recurses into the selected iteration wrapper so the returned
* ProcessorNode has data from the correct iteration.
*/
function findProcessorInTree(
nodes: ProcessorNode[] | undefined,
processorId: string | null,
iterationState?: Map<string, import('./types').IterationInfo>,
parentId?: string,
): ProcessorNode | null {
if (!nodes || !processorId) return null;
for (const n of nodes) {
if (!n.processorId) continue;
// Iteration wrapper: only recurse into the selected iteration
if (ITERATION_WRAPPER_TYPES.has(n.processorType)) {
if (parentId && iterationState?.has(parentId)) {
const info = iterationState.get(parentId)!;
const idx = wrapperIndex(n);
if (idx != null && idx !== info.current) continue;
}
if (n.children) {
const found = findProcessorInTree(n.children, processorId, iterationState, n.processorId);
if (found) return found;
}
continue;
}
if (n.processorId === processorId) return n;
if (n.children) {
const found = findProcessorInTree(n.children, processorId);
const found = findProcessorInTree(n.children, processorId, iterationState, n.processorId);
if (found) return found;
}
}
@@ -204,7 +235,11 @@ export function ExecutionDiagram({
{/* Detail panel */}
<div className={styles.detailArea} style={{ height: `${100 - splitPercent}%` }}>
<DetailPanel
selectedProcessor={findProcessorInTree(detail.processors, selectedProcessorId || null)}
selectedProcessor={
selectedProcessorId && overlay.has(selectedProcessorId)
? findProcessorInTree(detail.processors, selectedProcessorId, iterationState)
: null
}
executionDetail={detail}
executionId={executionId}
onSelectProcessor={setSelectedProcessorId}

View File

@@ -71,6 +71,10 @@ export function CompoundNode({
const isGated = ownState?.filterMatched === false || ownState?.duplicateMessage === true;
const effectiveColor = isGated ? 'var(--amber)' : color;
// Dim compound when overlay is active but neither the compound nor any
// descendant was executed in the current iteration.
const isSkipped = overlayActive && !ownState && !hasExecutedDescendant(node, executionOverlay);
// _TRY_BODY / _CB_MAIN: transparent wrapper — no header, no border, just layout
if (node.type === '_TRY_BODY' || node.type === '_CB_MAIN') {
return (
@@ -85,7 +89,7 @@ export function CompoundNode({
if (node.type === '_CB_FALLBACK') {
const fallbackColor = '#7C3AED'; // EIP purple
return (
<g data-node-id={node.id} transform={`translate(${x}, ${y})`}>
<g data-node-id={node.id} transform={`translate(${x}, ${y})`} opacity={isSkipped ? 0.35 : undefined}>
<rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
fill={fallbackColor} fillOpacity={0.06} />
<rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
@@ -106,7 +110,7 @@ export function CompoundNode({
: (node.label ? `finally: ${node.label}` : 'finally');
return (
<g data-node-id={node.id} transform={`translate(${x}, ${y})`}>
<g data-node-id={node.id} transform={`translate(${x}, ${y})`} opacity={isSkipped ? 0.35 : undefined}>
{/* Tinted background */}
<rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
fill={color} fillOpacity={0.06} />
@@ -126,7 +130,7 @@ export function CompoundNode({
// Default compound rendering (DO_TRY, EIP_CHOICE, EIP_FILTER, EIP_IDEMPOTENT_CONSUMER, etc.)
const containerFill = isGated ? 'var(--amber-bg)' : 'white';
return (
<g data-node-id={node.id} transform={`translate(${x}, ${y})`}>
<g data-node-id={node.id} transform={`translate(${x}, ${y})`} opacity={isSkipped ? 0.35 : undefined}>
{/* Container body */}
<rect
x={0}
@@ -268,3 +272,15 @@ function collectIds(nodes: DiagramNodeType[], set: Set<string>) {
if (n.children) collectIds(n.children, set);
}
}
function hasExecutedDescendant(
node: DiagramNodeType,
overlay?: Map<string, NodeExecutionState>,
): boolean {
if (!overlay || !node.children) return false;
for (const child of node.children) {
if (child.id && overlay.has(child.id)) return true;
if (child.children && hasExecutedDescendant(child, overlay)) return true;
}
return false;
}