feat: add interactive ProcessDiagram SVG component (sub-project 1/3)
New interactive route diagram component with SVG rendering using
server-computed ELK layout coordinates. TIBCO BW5-inspired top-bar
card node style with zoom/pan, hover toolbars, config badges, and
error handler sections below the main flow.
Backend: add direction query parameter (LR/TB) to diagram render
endpoints, defaulting to left-to-right layout.
Frontend: 14-file ProcessDiagram component in ui/src/components/
with DiagramNode, CompoundNode, DiagramEdge, ConfigBadge, NodeToolbar,
ErrorSection, ZoomControls, and supporting hooks. Dev test page at
/dev/diagram for validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:55:29 +01:00
|
|
|
.container {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
background: var(--bg-surface, #FFFFFF);
|
|
|
|
|
border: 1px solid var(--border, #E4DFD8);
|
|
|
|
|
border-radius: var(--radius-md, 8px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.svg {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: block;
|
|
|
|
|
outline: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.loading {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
color: var(--text-muted, #9C9184);
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.error {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
color: var(--error, #C0392B);
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.zoomControls {
|
|
|
|
|
position: absolute;
|
|
|
|
|
bottom: 12px;
|
|
|
|
|
right: 12px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
background: var(--bg-surface, #FFFFFF);
|
|
|
|
|
border: 1px solid var(--border, #E4DFD8);
|
|
|
|
|
border-radius: var(--radius-sm, 5px);
|
|
|
|
|
padding: 4px;
|
|
|
|
|
box-shadow: var(--shadow-md, 0 2px 8px rgba(44, 37, 32, 0.08));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.zoomBtn {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 28px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
border: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: var(--text-primary, #1A1612);
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border-radius: var(--radius-sm, 5px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.zoomBtn:hover {
|
|
|
|
|
background: var(--bg-hover, #F5F0EA);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.zoomLevel {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: var(--text-muted, #9C9184);
|
|
|
|
|
min-width: 36px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
}
|
2026-03-27 16:33:24 +01:00
|
|
|
|
|
|
|
|
.nodeToolbar {
|
|
|
|
|
position: absolute;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
padding: 3px 4px;
|
|
|
|
|
background: var(--bg-surface, #FFFFFF);
|
|
|
|
|
border: 1px solid var(--border, #E4DFD8);
|
|
|
|
|
border-radius: var(--radius-sm, 5px);
|
|
|
|
|
box-shadow: var(--shadow-lg, 0 4px 16px rgba(44, 37, 32, 0.10));
|
|
|
|
|
transform: translate(-50%, -100%);
|
|
|
|
|
margin-top: -6px;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
pointer-events: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nodeToolbarBtn {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 26px;
|
|
|
|
|
height: 26px;
|
|
|
|
|
border: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: var(--text-secondary, #5C5347);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border-radius: var(--radius-sm, 5px);
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nodeToolbarBtn:hover {
|
|
|
|
|
background: var(--bg-hover, #F5F0EA);
|
|
|
|
|
color: var(--text-primary, #1A1612);
|
|
|
|
|
}
|