feat: meaningful labels by status

This commit is contained in:
2026-03-13 23:35:34 +01:00
parent fc265266c7
commit 353c2db335
8 changed files with 36 additions and 16 deletions

View File

@@ -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 !== '' && (
<g transform={`translate(${edgeLabelX}, ${edgeLabelY})`} className="nodrag nopan">
<rect
x={-32}
@@ -109,7 +113,7 @@ function AnimatedEdgeInner({
dominantBaseline="middle"
className="fill-foreground text-[10px] font-medium"
>
{label}
{displayLabel}
</text>
</g>
)}
@@ -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
)
}

View File

@@ -17,7 +17,7 @@ export function getAgentNodeDescriptor(): NodeTypeDescriptor {
.allowedTargetTypes(['render'])
.help(NODE_HELP.agent)
.menu('Agent', <Bot className={ICON_CLASS} />)
.connectionLabel('prompt/context')
.connectionLabelByStatus({ default: 'prompt/context', updating: 'running', paused: 'pending', error: 'corrupted' })
.withFullscreen()
.sourceRenderingLogic(agentRenderingLogic)
.outputMenuContent(() => null)

View File

@@ -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',

View File

@@ -20,7 +20,7 @@ export function getDataNodeDescriptor(): NodeTypeDescriptor {
.allowedTargetTypes(['config', 'agent'])
.help(NODE_HELP.data)
.menu('Data', <Database className={ICON_CLASS} />)
.connectionLabel('data source')
.connectionLabelByStatus({ default: 'data source' })
.withFullscreen()
.build()
}

View File

@@ -21,7 +21,7 @@ export function getFunctionNodeDescriptor(): NodeTypeDescriptor {
.help(NODE_HELP.function)
.menu('Function', <Code2 className={ICON_CLASS} />)
.getResetData(() => ({ body: '' }))
.connectionLabel('adding input')
.connectionLabelByStatus({ default: 'adding input', updating: 'running', paused: 'pending', error: 'corrupted' })
.withFullscreen()
.build()
}

View File

@@ -20,7 +20,7 @@ export function getRenderNodeDescriptor(): NodeTypeDescriptor {
.allowedSourceTypes(['config', 'agent'])
.help(NODE_HELP.render)
.menu('Renderer', <Sparkles className={ICON_CLASS} />)
.connectionLabel('rendering')
.connectionLabelByStatus({ default: 'listening', updating: 'giving life', paused: 'pending', error: 'corrupted' })
.withFullscreen()
.build()
}

View File

@@ -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<string, unknown>
/** Optional: data for Reset action; if omitted, getDefaultData(nodeId) or defaultData is used. */
getResetData?: (nodeId?: string) => Record<string, unknown>
/** 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<Record<ConnectionStatus, string>>
/** 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. */

View File

@@ -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<Record<ConnectionStatus, string>>): this {
this.partial.connectionLabelByStatus = partial
return this
}