diff --git a/frontend/src/components/graph/AnimatedEdge.tsx b/frontend/src/components/graph/AnimatedEdge.tsx
index 742c25a..65ceaf5 100644
--- a/frontend/src/components/graph/AnimatedEdge.tsx
+++ b/frontend/src/components/graph/AnimatedEdge.tsx
@@ -7,6 +7,7 @@ import {
import { useCanvasStore } from '@/app/canvas/canvasStore'
import { selectConnectionStatusForEdge } from '@/app/canvas/canvasStore.selectors'
import { CONNECTION_STATUS_CLASS } from '@/lib/graph/connectionStatus'
+import { getConnectionLabelForTargetAndStatus } from '@/lib/graph/nodeRegistry'
const EDGE_STROKE_WIDTH = 2
const DOT_MARKER_R = 1.5
@@ -31,7 +32,10 @@ function AnimatedEdgeInner({
)
const statusClass = CONNECTION_STATUS_CLASS[connectionStatus]
- const label = labelProp ?? (data as { connectionLabel?: string } | undefined)?.connectionLabel
+ const targetType = (data as { targetType?: string } | undefined)?.targetType ?? ''
+ const dataLabel = (data as { connectionLabel?: string } | undefined)?.connectionLabel
+ const displayLabel =
+ getConnectionLabelForTargetAndStatus(targetType, connectionStatus) ?? labelProp ?? dataLabel
const [edgePath, edgeLabelX, edgeLabelY] = getBezierPath({
sourceX,
@@ -92,7 +96,7 @@ function AnimatedEdgeInner({
className={`animated-edge-path${statusClass ? ` ${statusClass}` : ''}`}
interactionWidth={interactionWidth}
/>
- {label != null && (
+ {displayLabel != null && displayLabel !== '' && (
- {label}
+ {displayLabel}
)}
@@ -130,8 +134,10 @@ function edgePropsAreEqual(prev: EdgeProps, next: EdgeProps): boolean {
prev.targetPosition === next.targetPosition &&
prev.style === next.style &&
prev.label === next.label &&
- (prev.data as { connectionLabel?: string } | undefined)?.connectionLabel ===
- (next.data as { connectionLabel?: string } | undefined)?.connectionLabel
+ (prev.data as { connectionLabel?: string; targetType?: string } | undefined)?.connectionLabel ===
+ (next.data as { connectionLabel?: string; targetType?: string } | undefined)?.connectionLabel &&
+ (prev.data as { targetType?: string } | undefined)?.targetType ===
+ (next.data as { targetType?: string } | undefined)?.targetType
)
}
diff --git a/frontend/src/components/nodes/agent/descriptor.tsx b/frontend/src/components/nodes/agent/descriptor.tsx
index 26f9c5a..50065f4 100644
--- a/frontend/src/components/nodes/agent/descriptor.tsx
+++ b/frontend/src/components/nodes/agent/descriptor.tsx
@@ -17,7 +17,7 @@ export function getAgentNodeDescriptor(): NodeTypeDescriptor {
.allowedTargetTypes(['render'])
.help(NODE_HELP.agent)
.menu('Agent', )
- .connectionLabel('prompt/context')
+ .connectionLabelByStatus({ default: 'prompt/context', updating: 'running', paused: 'pending', error: 'corrupted' })
.withFullscreen()
.sourceRenderingLogic(agentRenderingLogic)
.outputMenuContent(() => null)
diff --git a/frontend/src/components/nodes/config/descriptor.tsx b/frontend/src/components/nodes/config/descriptor.tsx
index 348e7f4..e745c5f 100644
--- a/frontend/src/components/nodes/config/descriptor.tsx
+++ b/frontend/src/components/nodes/config/descriptor.tsx
@@ -82,7 +82,7 @@ export function getConfigNodeDescriptor(): NodeTypeDescriptor {
content: '@startuml\n\n@enduml\n',
title: nodeId ?? '',
}))
- .connectionLabel('adding input')
+ .connectionLabelByStatus({ default: 'adding input', updating: 'pushing new form', paused: 'pending', error: 'corrupted' })
.withFullscreen()
.sourceRenderingLogic({
defaultUpdateMode: 'auto',
diff --git a/frontend/src/components/nodes/data/descriptor.tsx b/frontend/src/components/nodes/data/descriptor.tsx
index 12e0d4e..a58be1e 100644
--- a/frontend/src/components/nodes/data/descriptor.tsx
+++ b/frontend/src/components/nodes/data/descriptor.tsx
@@ -20,7 +20,7 @@ export function getDataNodeDescriptor(): NodeTypeDescriptor {
.allowedTargetTypes(['config', 'agent'])
.help(NODE_HELP.data)
.menu('Data', )
- .connectionLabel('data source')
+ .connectionLabelByStatus({ default: 'data source' })
.withFullscreen()
.build()
}
diff --git a/frontend/src/components/nodes/function/descriptor.tsx b/frontend/src/components/nodes/function/descriptor.tsx
index 6bac6cb..2504cff 100644
--- a/frontend/src/components/nodes/function/descriptor.tsx
+++ b/frontend/src/components/nodes/function/descriptor.tsx
@@ -21,7 +21,7 @@ export function getFunctionNodeDescriptor(): NodeTypeDescriptor {
.help(NODE_HELP.function)
.menu('Function', )
.getResetData(() => ({ body: '' }))
- .connectionLabel('adding input')
+ .connectionLabelByStatus({ default: 'adding input', updating: 'running', paused: 'pending', error: 'corrupted' })
.withFullscreen()
.build()
}
diff --git a/frontend/src/components/nodes/render/descriptor.tsx b/frontend/src/components/nodes/render/descriptor.tsx
index e5d5202..e756823 100644
--- a/frontend/src/components/nodes/render/descriptor.tsx
+++ b/frontend/src/components/nodes/render/descriptor.tsx
@@ -20,7 +20,7 @@ export function getRenderNodeDescriptor(): NodeTypeDescriptor {
.allowedSourceTypes(['config', 'agent'])
.help(NODE_HELP.render)
.menu('Renderer', )
- .connectionLabel('rendering')
+ .connectionLabelByStatus({ default: 'listening', updating: 'giving life', paused: 'pending', error: 'corrupted' })
.withFullscreen()
.build()
}
diff --git a/frontend/src/lib/graph/nodeRegistry.ts b/frontend/src/lib/graph/nodeRegistry.ts
index b292de2..b7b2b14 100644
--- a/frontend/src/lib/graph/nodeRegistry.ts
+++ b/frontend/src/lib/graph/nodeRegistry.ts
@@ -5,6 +5,7 @@
*/
import type React from 'react'
+import type { ConnectionStatus } from './connectionStatus'
import { registerSourceRenderingLogic } from './sourceRenderingLogic'
import type { SourceRenderingLogic } from './sourceRenderingLogic'
@@ -44,8 +45,8 @@ export type NodeTypeDescriptor = {
getDefaultData?: (newId?: string) => Record
/** Optional: data for Reset action; if omitted, getDefaultData(nodeId) or defaultData is used. */
getResetData?: (nodeId?: string) => Record
- /** Optional: label shown on edge when this type is the target (e.g. "render", "add input"). */
- connectionLabel?: string
+ /** Optional: label per connection status shown on edge when this type is the target. */
+ connectionLabelByStatus?: Partial>
/** When true, double-clicking the node header opens a fullscreen dialog for this node. */
supportsFullscreen?: boolean
/**
@@ -157,9 +158,21 @@ export function isConnectionAllowed(
return true
}
-/** Edge label when target is of this type (for AnimatedEdge). */
+/** Edge label when target is of this type (default status only; for edge data fallback). */
export function getConnectionLabelForTarget(targetType: string): string | undefined {
- return getNodeType(targetType)?.connectionLabel
+ return getNodeType(targetType)?.connectionLabelByStatus?.['default']
+}
+
+/** Edge label for target type and connection status; falls back to default when status not in map. */
+export function getConnectionLabelForTargetAndStatus(
+ targetType: string,
+ status: ConnectionStatus
+): string | undefined {
+ const desc = getNodeType(targetType)
+ if (!desc?.connectionLabelByStatus) return undefined
+ const byStatus = desc.connectionLabelByStatus[status]
+ if (byStatus !== undefined) return byStatus
+ return desc.connectionLabelByStatus['default']
}
/** Help entry for a node type. Use in NodeHelpPopover. */
diff --git a/frontend/src/lib/graph/nodeTypeBuilder.ts b/frontend/src/lib/graph/nodeTypeBuilder.ts
index 0f6a447..b952c12 100644
--- a/frontend/src/lib/graph/nodeTypeBuilder.ts
+++ b/frontend/src/lib/graph/nodeTypeBuilder.ts
@@ -14,6 +14,7 @@
*/
import type React from 'react'
+import type { ConnectionStatus } from './connectionStatus'
import type { NodeTypeDescriptor, NodeClassification, NodeHelpEntry } from './nodeRegistry'
import type { SourceRenderingLogic } from './sourceRenderingLogic'
@@ -97,8 +98,8 @@ export class NodeTypeBuilder {
return this
}
- connectionLabel(label: string): this {
- this.partial.connectionLabel = label
+ connectionLabelByStatus(partial: Partial>): this {
+ this.partial.connectionLabelByStatus = partial
return this
}