@principal-ai/subsystems-react 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +11 -85
  2. package/dist/index.d.ts +7 -44
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -32
  5. package/dist/index.js.map +1 -1
  6. package/dist/subsystem/SubsystemCarousel.d.ts +36 -0
  7. package/dist/subsystem/SubsystemCarousel.d.ts.map +1 -0
  8. package/dist/subsystem/SubsystemCarousel.js +163 -0
  9. package/dist/subsystem/SubsystemCarousel.js.map +1 -0
  10. package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -0
  11. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  12. package/dist/subsystem/SubsystemComponentGraph.js +329 -68
  13. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  14. package/package.json +4 -4
  15. package/src/index.ts +9 -199
  16. package/src/subsystem/SubsystemComponentGraph.tsx +396 -50
  17. package/src/components/ConfigurationSelector.tsx +0 -133
  18. package/src/components/GraphRenderer.tsx +0 -3195
  19. package/src/components/MultiCanvasRenderer.tsx +0 -295
  20. package/src/components/NodeTooltip.tsx +0 -279
  21. package/src/components/PendingChanges.test.tsx +0 -375
  22. package/src/components/SelectionSidebar.tsx +0 -352
  23. package/src/components/SequenceDiagramRenderer.tsx +0 -1156
  24. package/src/components/WorkflowSequenceDiagram.tsx +0 -270
  25. package/src/components/dashboard/DashboardRenderer.tsx +0 -317
  26. package/src/components/dashboard/MetricPanel.tsx +0 -254
  27. package/src/components/dashboard/MockDataProvider.ts +0 -330
  28. package/src/components/dashboard/components/BarChart.tsx +0 -299
  29. package/src/components/dashboard/components/LineChart.tsx +0 -279
  30. package/src/components/dashboard/components/MetricCard.tsx +0 -270
  31. package/src/components/dashboard/components/SourceLink.tsx +0 -63
  32. package/src/components/dashboard/components/TimeRangeSelector.tsx +0 -280
  33. package/src/components/dashboard/components/index.ts +0 -5
  34. package/src/components/dashboard/index.ts +0 -47
  35. package/src/components/dashboard/types.ts +0 -126
  36. package/src/components/state-view/PipelineView.tsx +0 -347
  37. package/src/components/state-view/index.ts +0 -14
  38. package/src/components/state-view/types.ts +0 -261
  39. package/src/components/state-view/useStateView.ts +0 -205
  40. package/src/contexts/GraphEditContext.tsx +0 -27
  41. package/src/contexts/TooltipPortalContext.ts +0 -8
  42. package/src/edges/CustomEdge.tsx +0 -402
  43. package/src/edges/GenericEdge.tsx +0 -36
  44. package/src/hooks/usePathBasedEvents.ts +0 -192
  45. package/src/hooks/useSequenceLayout.ts +0 -543
  46. package/src/hooks/useUndoRedo.ts +0 -144
  47. package/src/nodes/CustomNode.tsx +0 -1231
  48. package/src/nodes/GenericNode.tsx +0 -55
  49. package/src/nodes/otel/OtelBoundaryNode.tsx +0 -224
  50. package/src/nodes/otel/OtelEventNode.tsx +0 -225
  51. package/src/nodes/otel/OtelResourceNode.tsx +0 -251
  52. package/src/nodes/otel/OtelScopeNode.tsx +0 -221
  53. package/src/nodes/otel/OtelSpanConventionNode.tsx +0 -295
  54. package/src/nodes/otel/index.ts +0 -23
  55. package/src/nodes/otel/shared/NodeBadges.tsx +0 -295
  56. package/src/nodes/otel/shared/NodeContent.tsx +0 -204
  57. package/src/nodes/otel/shared/index.ts +0 -8
  58. package/src/nodes/otel/shared/types.ts +0 -138
  59. package/src/nodes/otel/shared/useNodeBehavior.ts +0 -114
  60. package/src/stories/Audit/BoundaryNodes.stories.tsx +0 -385
  61. package/src/stories/Audit/CanvasEdgeTypes.stories.tsx +0 -1448
  62. package/src/stories/Audit/CanvasNodeTypes.stories.tsx +0 -1049
  63. package/src/stories/Audit/ColorPriority.stories.tsx +0 -895
  64. package/src/stories/Audit/DiamondBadges.stories.tsx +0 -629
  65. package/src/stories/Audit/NodeDimensionsTesting.stories.tsx +0 -724
  66. package/src/stories/Audit/NodeFieldsAudit.stories.tsx +0 -996
  67. package/src/stories/Audit/NodeFontSizeTesting.stories.tsx +0 -715
  68. package/src/stories/Audit/NodeShapes.stories.tsx +0 -1208
  69. package/src/stories/Audit/WideNodesTesting.stories.tsx +0 -1045
  70. package/src/stories/Components/DashboardRenderer.stories.tsx +0 -263
  71. package/src/stories/Components/GraphRenderer.stories.tsx +0 -3236
  72. package/src/stories/Components/MultiCanvasRenderer.stories.tsx +0 -370
  73. package/src/stories/Components/SequenceDiagram.stories.tsx +0 -255
  74. package/src/stories/Components/StateView.stories.tsx +0 -417
  75. package/src/stories/Components/TraceViewer.stories.tsx +0 -442
  76. package/src/stories/Components/WorkflowSequenceDiagram.stories.tsx +0 -802
  77. package/src/stories/Features/AnimationWorkshop.stories.tsx +0 -727
  78. package/src/stories/Features/ChangeTypeVisual.stories.tsx +0 -67
  79. package/src/stories/Features/EventDrivenAnimations.stories.tsx +0 -513
  80. package/src/stories/Features/MultiConfig.stories.tsx +0 -539
  81. package/src/stories/Layout/ElkEdgeRouting.stories.tsx +0 -539
  82. package/src/stories/Layout/GraphOrientation.stories.tsx +0 -500
  83. package/src/stories/Layout/MultiDirectionalConnections.stories.tsx +0 -352
  84. package/src/stories/Nodes/NodeDefinitionComparison.stories.tsx +0 -560
  85. package/src/stories/Nodes/NodeNameFallbacks.stories.tsx +0 -491
  86. package/src/stories/Nodes/NumericColors.stories.tsx +0 -440
  87. package/src/stories/OTEL/EventNameDisplay.stories.tsx +0 -694
  88. package/src/stories/OTEL/EventNameFallback.stories.tsx +0 -578
  89. package/src/stories/OTEL/OtelComponents.stories.tsx +0 -422
  90. package/src/stories/OTEL/OtelNodeTypesPrototype.stories.tsx +0 -806
  91. package/src/stories/OTEL/RawEventCards.stories.tsx +0 -57
  92. package/src/stories/OTEL/SpanBadges.stories.tsx +0 -840
  93. package/src/stories/OTEL/ValidationEventsCanvas.stories.tsx +0 -209
  94. package/src/stories/RealWorld/BacklogMdCanvases.stories.tsx +0 -721
  95. package/src/stories/RealWorld/FileCitySequence.stories.tsx +0 -516
  96. package/src/stories/RealWorld/RealCanvasFiles.stories.tsx +0 -336
  97. package/src/stories/__traces__/test-run.canvas.json +0 -241
  98. package/src/stories/data/activity-feed-analytics.dashboard.json +0 -300
  99. package/src/stories/data/file-city-images-transformed.otel.canvas.json +0 -295
  100. package/src/stories/data/file-city-workflows.json +0 -269
  101. package/src/stories/data/graph-converter-test-execution.json +0 -443
  102. package/src/stories/data/graph-converter-test.workflow.json +0 -105
  103. package/src/stories/data/graph-converter-validated-execution.json +0 -58
  104. package/src/utils/animationMapping.ts +0 -166
  105. package/src/utils/canvasBounds.ts +0 -146
  106. package/src/utils/graphConverter.ts +0 -148
  107. package/src/utils/iconResolver.tsx +0 -53
  108. package/src/utils/orientationUtils.ts +0 -73
@@ -1,1156 +0,0 @@
1
- /**
2
- * Sequence Diagram Renderer
3
- *
4
- * Renders events in swimlane-based sequence diagram layout.
5
- * Uses namespaces to determine swimlanes and event order for vertical positioning.
6
- */
7
-
8
- import React, { useCallback, useMemo, useState } from 'react';
9
- import {
10
- ReactFlow,
11
- Background,
12
- BackgroundVariant,
13
- Controls,
14
- ReactFlowProvider,
15
- useViewport,
16
- useStore,
17
- Handle,
18
- Position,
19
- BaseEdge,
20
- EdgeLabelRenderer,
21
- getBezierPath,
22
- type NodeTypes,
23
- type EdgeTypes,
24
- type NodeProps,
25
- type EdgeProps,
26
- } from '@xyflow/react';
27
- import { useTheme } from '@principal-ade/industry-theme';
28
- // CSS import removed - consumers should import '@xyflow/react/dist/style.css' in their app
29
- import {
30
- useSequenceLayout,
31
- type SequenceEvent,
32
- type SequenceEdge,
33
- type UseSequenceLayoutOptions,
34
- type Swimlane,
35
- type ParentHeader,
36
- } from '../hooks/useSequenceLayout';
37
-
38
- /**
39
- * Minimal marker node for arrow-centric sequence diagrams
40
- * Invisible - just used for positioning, selection is shown on edge labels
41
- * Can optionally show a label for better clickability
42
- */
43
- function SequenceMarkerNode({ data, selected }: NodeProps) {
44
- const { theme } = useTheme();
45
- const showLabel = data.showEventLabels === true; // Only show if explicitly enabled
46
- const label = data.label as string | undefined;
47
-
48
- // If labels are shown on nodes, render them
49
- if (showLabel && label) {
50
- return (
51
- <div
52
- style={{
53
- width: '100%',
54
- height: '100%',
55
- position: 'relative',
56
- cursor: 'pointer',
57
- display: 'flex',
58
- flexDirection: 'column',
59
- alignItems: 'center',
60
- justifyContent: 'center',
61
- }}
62
- title={data.fullName as string}
63
- >
64
- <div
65
- style={{
66
- padding: '6px 12px',
67
- background: selected ? theme.colors.primary : theme.colors.background,
68
- color: selected ? theme.colors.background : theme.colors.text,
69
- border: `2px solid ${selected ? theme.colors.primary : theme.colors.border}`,
70
- borderRadius: 4,
71
- fontSize: theme.fontSizes[1],
72
- fontWeight: selected ? theme.fontWeights.bold : theme.fontWeights.medium,
73
- fontFamily: theme.fonts.body,
74
- whiteSpace: 'nowrap',
75
- pointerEvents: 'none',
76
- userSelect: 'none',
77
- transition: 'all 0.2s ease',
78
- boxShadow: selected ? `0 2px 8px ${theme.colors.primary}40` : 'none',
79
- }}
80
- >
81
- {label}
82
- </div>
83
- <Handle
84
- type="target"
85
- position={Position.Top}
86
- style={{ visibility: 'hidden' }}
87
- />
88
- <Handle
89
- type="source"
90
- position={Position.Bottom}
91
- style={{ visibility: 'hidden' }}
92
- />
93
- </div>
94
- );
95
- }
96
-
97
- // Default: invisible node (selection shown on edge labels)
98
- return (
99
- <div
100
- style={{
101
- width: '100%',
102
- height: '100%',
103
- opacity: 0,
104
- cursor: 'pointer',
105
- }}
106
- title={data.fullName as string}
107
- >
108
- <Handle
109
- type="target"
110
- position={Position.Top}
111
- style={{ visibility: 'hidden' }}
112
- />
113
- <Handle
114
- type="source"
115
- position={Position.Bottom}
116
- style={{ visibility: 'hidden' }}
117
- />
118
- </div>
119
- );
120
- }
121
-
122
- /**
123
- * Sequence arrow edge with label (dot to dot)
124
- */
125
- function SequenceArrowEdge({
126
- id,
127
- sourceX,
128
- sourceY,
129
- targetX,
130
- targetY,
131
- sourcePosition,
132
- targetPosition,
133
- label,
134
- data,
135
- }: EdgeProps) {
136
- const { theme } = useTheme();
137
-
138
- // Use a straight line for same-lane, bezier for cross-lane
139
- const isSameLane = !data?.crossesLanes;
140
- const isSourceSelected = data?.isSourceSelected === true;
141
-
142
- const [edgePath, labelX, labelY] = getBezierPath({
143
- sourceX,
144
- sourceY,
145
- sourcePosition,
146
- targetX,
147
- targetY,
148
- targetPosition,
149
- curvature: isSameLane ? 0.5 : 0.25,
150
- });
151
-
152
- return (
153
- <>
154
- <BaseEdge
155
- id={id}
156
- path={edgePath}
157
- style={{
158
- stroke: theme.colors.primary,
159
- strokeWidth: 2,
160
- }}
161
- markerEnd="url(#sequence-arrow)"
162
- />
163
- {label && (
164
- <EdgeLabelRenderer>
165
- <div
166
- style={{
167
- position: 'absolute',
168
- transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
169
- background: isSourceSelected ? theme.colors.primary : theme.colors.background,
170
- padding: isSourceSelected ? '4px 10px' : '2px 8px',
171
- borderRadius: 4,
172
- fontSize: theme.fontSizes[0],
173
- fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
174
- fontFamily: theme.fonts.body,
175
- color: isSourceSelected ? theme.colors.background : theme.colors.text,
176
- border: `${isSourceSelected ? 2 : 1}px solid ${isSourceSelected ? theme.colors.primary : theme.colors.border}`,
177
- pointerEvents: 'all',
178
- whiteSpace: 'nowrap',
179
- cursor: 'pointer',
180
- transition: 'all 0.2s ease',
181
- boxShadow: isSourceSelected ? `0 2px 8px ${theme.colors.primary}60` : 'none',
182
- }}
183
- className="nodrag nopan"
184
- >
185
- {label}
186
- </div>
187
- </EdgeLabelRenderer>
188
- )}
189
- </>
190
- );
191
- }
192
-
193
- /**
194
- * Participant-to-participant arrow edge (or activation bar for same-lane)
195
- * Draws from source participant lifeline to target participant lifeline
196
- */
197
- function SequenceArrowParticipantEdge({
198
- id,
199
- sourceY,
200
- targetY,
201
- label,
202
- data,
203
- }: EdgeProps) {
204
- const { theme } = useTheme();
205
-
206
- // Use participant X positions from data (swimlane centers)
207
- const sourceX = (data?.sourceParticipantX ?? 0) as number;
208
- const targetX = (data?.targetParticipantX ?? 0) as number;
209
- const safeSourceY = (sourceY ?? 0) as number;
210
- const safeTargetY = (targetY ?? 0) as number;
211
-
212
- // Check if this is same-lane (activation bar) or cross-lane (arrow)
213
- const isSameLane = sourceX === targetX;
214
- const isLastEvent = data?.isLastEvent === true;
215
-
216
- // Style based on whether it's a move event (IPC) or transform event (internal)
217
- const isMoveEvent = data?.isMoveEvent === true;
218
- const isSourceSelected = data?.isSourceSelected === true;
219
- const strokeColor = isMoveEvent ? (theme.colors.accent || '#f48771') : theme.colors.primary;
220
-
221
- // Same lane: render as activation bar
222
- if (isSameLane) {
223
- const barWidth = 12;
224
- const eventSpacing = (data?.eventSpacing ?? 80) as number;
225
-
226
- // For last event, use half the event spacing for bar height
227
- let barHeight: number;
228
- let barY: number;
229
-
230
- if (isLastEvent) {
231
- barHeight = eventSpacing / 2;
232
- barY = safeSourceY; // Start at the event position
233
- } else {
234
- // Normal case: bar from source to target
235
- const calculatedHeight = Math.abs(safeTargetY - safeSourceY);
236
- // Ensure minimum height if events are at same position
237
- barHeight = calculatedHeight > 0 ? calculatedHeight : eventSpacing / 2;
238
- barY = Math.min(safeSourceY, safeTargetY);
239
- }
240
-
241
- const barX = sourceX - barWidth / 2;
242
-
243
- return (
244
- <>
245
- {/* Activation bar */}
246
- <svg>
247
- <rect
248
- x={barX}
249
- y={barY}
250
- width={barWidth}
251
- height={barHeight}
252
- fill={strokeColor}
253
- stroke={strokeColor}
254
- strokeWidth={2}
255
- rx={2}
256
- />
257
- </svg>
258
- {label && (
259
- <EdgeLabelRenderer>
260
- <div
261
- style={{
262
- position: 'absolute',
263
- transform: `translate(-100%, -50%) translate(${sourceX - 7}px,${barY + barHeight / 2}px)`,
264
- background: isSourceSelected ? strokeColor : theme.colors.background,
265
- padding: '2px 8px',
266
- borderTopLeftRadius: 4,
267
- borderTopRightRadius: 0,
268
- borderBottomLeftRadius: 4,
269
- borderBottomRightRadius: 0,
270
- fontSize: theme.fontSizes[0],
271
- fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
272
- fontFamily: theme.fonts.body,
273
- color: isSourceSelected ? theme.colors.background : strokeColor,
274
- borderTop: `${isSourceSelected ? 2 : 1}px solid ${strokeColor}`,
275
- borderLeft: `${isSourceSelected ? 2 : 1}px solid ${strokeColor}`,
276
- borderBottom: `${isSourceSelected ? 2 : 1}px solid ${strokeColor}`,
277
- borderRight: `1px solid ${strokeColor}`,
278
- pointerEvents: 'all',
279
- whiteSpace: 'nowrap',
280
- cursor: 'pointer',
281
- transition: 'all 0.2s ease',
282
- boxShadow: isSourceSelected ? `0 2px 8px ${strokeColor}60` : 'none',
283
- }}
284
- className="nodrag nopan"
285
- >
286
- {label}
287
- </div>
288
- </EdgeLabelRenderer>
289
- )}
290
- </>
291
- );
292
- }
293
-
294
- // Cross-lane: render as horizontal arrow at midpoint
295
- const strokeWidth = isMoveEvent ? 2.5 : 2;
296
- const markerEnd = isMoveEvent ? 'url(#sequence-arrow-move)' : 'url(#sequence-arrow)';
297
-
298
- // Draw horizontal arrow at the midpoint between source and target Y positions
299
- const arrowY = (safeSourceY + safeTargetY) / 2;
300
- // Inset arrow endpoints so they stop just before the lifelines instead of crossing them
301
- const lifelineInset = 6;
302
- const direction = targetX > sourceX ? 1 : -1;
303
- const startX = sourceX + direction * lifelineInset;
304
- const endX = targetX - direction * lifelineInset;
305
- const path = `M ${startX} ${arrowY} L ${endX} ${arrowY}`;
306
- const labelX = (sourceX + targetX) / 2;
307
- const labelY = arrowY;
308
-
309
- return (
310
- <>
311
- <BaseEdge
312
- id={id}
313
- path={path}
314
- style={{
315
- stroke: strokeColor,
316
- strokeWidth: strokeWidth,
317
- }}
318
- markerEnd={markerEnd}
319
- />
320
- {label && (
321
- <EdgeLabelRenderer>
322
- <div
323
- style={{
324
- position: 'absolute',
325
- transform: `translate(-50%, -50%) translate(${labelX}px,${labelY - 12}px)`,
326
- background: isSourceSelected ? strokeColor : theme.colors.background,
327
- padding: isMoveEvent ? '3px 10px' : '2px 8px',
328
- borderTopLeftRadius: 4,
329
- borderTopRightRadius: 4,
330
- borderBottomLeftRadius: 0,
331
- borderBottomRightRadius: 0,
332
- fontSize: theme.fontSizes[0],
333
- fontWeight: isSourceSelected ? theme.fontWeights.bold : (isMoveEvent ? theme.fontWeights.bold : theme.fontWeights.medium),
334
- fontFamily: theme.fonts.body,
335
- color: isSourceSelected ? theme.colors.background : strokeColor,
336
- borderTop: `${isSourceSelected ? 3 : (isMoveEvent ? 2 : 1)}px solid ${strokeColor}`,
337
- borderLeft: `${isSourceSelected ? 3 : (isMoveEvent ? 2 : 1)}px solid ${strokeColor}`,
338
- borderRight: `${isSourceSelected ? 3 : (isMoveEvent ? 2 : 1)}px solid ${strokeColor}`,
339
- borderBottom: `${isMoveEvent ? 2 : 1}px solid ${strokeColor}`,
340
- pointerEvents: 'all',
341
- whiteSpace: 'nowrap',
342
- cursor: 'pointer',
343
- transition: 'all 0.2s ease',
344
- boxShadow: isSourceSelected ? `0 4px 12px ${strokeColor}60` : 'none',
345
- }}
346
- className="nodrag nopan"
347
- >
348
- {label}
349
- </div>
350
- </EdgeLabelRenderer>
351
- )}
352
- </>
353
- );
354
- }
355
-
356
- /**
357
- * Default node types including sequence marker
358
- */
359
- const defaultSequenceNodeTypes: NodeTypes = {
360
- sequenceMarker: SequenceMarkerNode,
361
- };
362
-
363
- /**
364
- * Shared transition for swimlane chrome — interpolates positions and sizes
365
- * when lanes shift due to drill toggles.
366
- */
367
- const swimlaneTransition =
368
- 'top 350ms ease-out, left 350ms ease-out, width 350ms ease-out, height 350ms ease-out';
369
-
370
- /** Duration of the child slide-up exit animation. The close handler waits
371
- * this long before applying the data change so the diagram body doesn't
372
- * shift while the children are still on screen. */
373
- const SWIMLANE_CLOSE_EXIT_MS = 280;
374
-
375
- const swimlaneAnimationStyles = `
376
- @keyframes swimlaneFadeIn {
377
- from { opacity: 0; }
378
- to { opacity: 1; }
379
- }
380
- @keyframes swimlaneFadeOut {
381
- from { opacity: 1; }
382
- to { opacity: 0; }
383
- }
384
- @keyframes swimlaneChildSlideDown {
385
- from { transform: translateY(-100%); opacity: 0; }
386
- to { transform: translateY(0); opacity: 1; }
387
- }
388
- @keyframes swimlaneChildSlideUp {
389
- from { transform: translateY(0); opacity: 1; }
390
- to { transform: translateY(-100%); opacity: 0; }
391
- }
392
- .swimlane-fade-in { animation: swimlaneFadeIn 250ms ease-out both; }
393
- .swimlane-child-bg-in { animation: swimlaneFadeIn 300ms ease-out 250ms both; }
394
- .swimlane-child-in { animation: swimlaneChildSlideDown 300ms ease-out 250ms both; }
395
- .swimlane-child-out { animation: swimlaneChildSlideUp 280ms ease-in both; }
396
- .swimlane-fade-out { animation: swimlaneFadeOut 250ms ease-in both; }
397
- `;
398
-
399
- /**
400
- * Default edge types including sequence arrow and participant arrow
401
- */
402
- const defaultSequenceEdgeTypes: EdgeTypes = {
403
- sequenceArrow: SequenceArrowEdge,
404
- sequenceArrowParticipant: SequenceArrowParticipantEdge,
405
- };
406
-
407
- /**
408
- * Props for the swimlane layer
409
- */
410
- interface SwimlaneLayerProps {
411
- swimlanes: Swimlane[];
412
- parentHeaders: ParentHeader[];
413
- headerRows: number;
414
- laneWidth: number;
415
- /** Per-row height (each row in the header strip is this tall) */
416
- headerHeight: number;
417
- totalHeight: number;
418
- /** Called when the user clicks a chevron or parent header to toggle its drilled state */
419
- onToggleNamespace?: (namespace: string) => void;
420
- /** Namespaces mid-close. Their children are still in the data but render
421
- * with the exit animation so the body can stay put until the animation
422
- * finishes and the data change applies. */
423
- closingNamespaces?: Set<string>;
424
- stickyHeaders?: boolean;
425
- /** When true, render lane and header backgrounds as transparent. */
426
- transparent?: boolean;
427
- }
428
-
429
- /**
430
- * Swimlane layer that renders behind nodes and transforms with viewport
431
- */
432
- function SwimlaneLayer({
433
- swimlanes,
434
- headerRows,
435
- laneWidth,
436
- headerHeight,
437
- totalHeight,
438
- transparent = false,
439
- }: SwimlaneLayerProps) {
440
- const totalHeaderHeight = headerHeight * headerRows;
441
- const { x, y, zoom } = useViewport();
442
- const viewportHeight = useStore((s) => s.height);
443
- const { theme } = useTheme();
444
-
445
- // Extend lanes to cover the viewport bottom even when content is short.
446
- // In flow-coord space, the visible viewport is `viewportHeight / zoom` tall.
447
- const extendedHeight = Math.max(totalHeight + 20, viewportHeight / zoom + 100);
448
-
449
- return (
450
- <div
451
- style={{
452
- position: 'absolute',
453
- top: 0,
454
- left: 0,
455
- transformOrigin: '0 0',
456
- transform: `translate(${x}px, ${y}px) scale(${zoom})`,
457
- pointerEvents: 'none',
458
- zIndex: -1,
459
- }}
460
- >
461
- {/* Lane backgrounds */}
462
- {swimlanes.map((lane, index) => {
463
- const isEven = index % 2 === 0;
464
- const laneBackground = transparent
465
- ? 'transparent'
466
- : isEven
467
- ? theme.colors.muted
468
- : theme.colors.background;
469
- const fadeClass = lane.isParentOpened
470
- ? 'swimlane-child-bg-in'
471
- : 'swimlane-fade-in';
472
- return (
473
- <div
474
- key={`bg-${lane.namespace}`}
475
- className={fadeClass}
476
- style={{
477
- position: 'absolute',
478
- left: lane.x - laneWidth / 2,
479
- top: 0,
480
- width: laneWidth,
481
- height: extendedHeight,
482
- backgroundColor: laneBackground,
483
- borderRight: `1px solid ${theme.colors.border}`,
484
- transition: swimlaneTransition,
485
- }}
486
- />
487
- );
488
- })}
489
-
490
- {/* Vertical lifelines */}
491
- {swimlanes.map((lane) => (
492
- <div
493
- key={`lifeline-${lane.namespace}`}
494
- className={
495
- lane.isParentOpened ? 'swimlane-child-bg-in' : 'swimlane-fade-in'
496
- }
497
- style={{
498
- position: 'absolute',
499
- left: lane.x,
500
- top: totalHeaderHeight,
501
- width: 2,
502
- height: extendedHeight - totalHeaderHeight,
503
- backgroundColor: 'rgba(255, 255, 255, 0.4)',
504
- transform: 'translateX(-1px)',
505
- transition: swimlaneTransition,
506
- }}
507
- />
508
- ))}
509
- </div>
510
- );
511
- }
512
-
513
- /**
514
- * Two offset rounded rectangles signaling that a header has nested lanes
515
- * underneath it. When `opened`, the front layer is filled to indicate the
516
- * stack is currently expanded.
517
- */
518
- function StackIcon({
519
- opened = false,
520
- hovered = false,
521
- accentColor,
522
- }: {
523
- opened?: boolean;
524
- hovered?: boolean;
525
- accentColor?: string;
526
- }) {
527
- // Closed: rects compactly stacked, both outlined in text color.
528
- // Hovered: strokes tint to the accent color (preview of the action).
529
- // Opened: front rect drifts down/right and fades to filled in the accent.
530
- const rectTransition =
531
- 'transform 280ms cubic-bezier(0.2, 0, 0, 1), fill-opacity 280ms ease, stroke 200ms ease';
532
- const useAccent = (opened || hovered) && !!accentColor;
533
- const stroke = useAccent ? accentColor : 'currentColor';
534
- const fill = accentColor || 'currentColor';
535
- return (
536
- <svg
537
- width={22}
538
- height={22}
539
- viewBox="0 0 14 14"
540
- fill="none"
541
- strokeWidth={1.3}
542
- strokeLinejoin="round"
543
- aria-hidden="true"
544
- style={{
545
- flexShrink: 0,
546
- opacity: opened ? 0.95 : hovered ? 0.9 : 0.75,
547
- overflow: 'visible',
548
- transition: 'opacity 200ms ease',
549
- }}
550
- >
551
- <rect
552
- x={2}
553
- y={3}
554
- width={7}
555
- height={5}
556
- rx={1}
557
- stroke={stroke}
558
- style={{
559
- transition: rectTransition,
560
- transform: opened
561
- ? 'translate(-1.5px, -1.5px)'
562
- : 'translate(0px, 0px)',
563
- }}
564
- />
565
- <rect
566
- x={5}
567
- y={6}
568
- width={7}
569
- height={5}
570
- rx={1}
571
- stroke={stroke}
572
- fill={fill}
573
- style={{
574
- transition: rectTransition,
575
- transform: opened
576
- ? 'translate(1.5px, 1.5px)'
577
- : 'translate(0px, 0px)',
578
- fillOpacity: opened ? 0.35 : 0,
579
- }}
580
- />
581
- </svg>
582
- );
583
- }
584
-
585
- /**
586
- * Swimlane headers layer that renders on top of nodes for clickability
587
- */
588
- function SwimlaneHeadersLayer({
589
- swimlanes,
590
- parentHeaders,
591
- laneWidth,
592
- headerHeight,
593
- onToggleNamespace,
594
- closingNamespaces,
595
- stickyHeaders = true,
596
- transparent = false,
597
- }: SwimlaneLayerProps) {
598
- const { x, y, zoom } = useViewport();
599
- const { theme } = useTheme();
600
- const [hoveredNamespace, setHoveredNamespace] = useState<string | null>(null);
601
-
602
- // When sticky headers are enabled, drop vertical translation from the wrapper
603
- // so headers stay locked to the top regardless of vertical pan. The inner
604
- // cells then keep their natural `top` values (no per-scroll recompute), which
605
- // avoids fighting the CSS transition on `top` used for drill-toggle animations.
606
- const wrapperY = stickyHeaders ? 0 : y;
607
-
608
- // Build a unified header list: each namespace currently in view (whether a
609
- // leaf or an opened ancestor) gets ONE DOM element keyed by namespace, so
610
- // clicking ▶ smoothly morphs the same cell from leaf-shape to parent-shape
611
- // (wider, possibly across multiple lanes) instead of unmounting+remounting.
612
- type HeaderCell = {
613
- namespace: string;
614
- label: string;
615
- x: number;
616
- width: number;
617
- depth: number;
618
- isOpened: boolean; // currently in `openedNamespaces`
619
- isParentOpened: boolean;
620
- canExpand: boolean; // only meaningful when !isOpened
621
- };
622
-
623
- const headers: HeaderCell[] = useMemo(
624
- () => [
625
- ...parentHeaders.map((h) => ({
626
- namespace: h.namespace,
627
- label: h.label,
628
- x: h.x,
629
- width: h.width,
630
- depth: h.depth,
631
- isOpened: true,
632
- isParentOpened: h.depth > 1,
633
- canExpand: false,
634
- })),
635
- ...swimlanes.map((lane) => ({
636
- namespace: lane.namespace,
637
- label: lane.label,
638
- x: lane.x,
639
- width: laneWidth,
640
- depth: lane.namespace.split('.').length,
641
- isOpened: false,
642
- isParentOpened: lane.isParentOpened,
643
- canExpand: lane.canExpand,
644
- })),
645
- ],
646
- [parentHeaders, swimlanes, laneWidth]
647
- );
648
-
649
-
650
- return (
651
- <div
652
- style={{
653
- position: 'absolute',
654
- top: 0,
655
- left: 0,
656
- transformOrigin: '0 0',
657
- transform: `translate(${x}px, ${wrapperY}px) scale(${zoom})`,
658
- pointerEvents: 'none',
659
- zIndex: 10,
660
- }}
661
- >
662
- {headers.map((header) => {
663
- const rowTop = (header.depth - 1) * headerHeight;
664
- // Child leaves (under an opened parent) get the differentiated, lighter
665
- // styling. Top-level leaves AND opened parents share the original
666
- // header look — so the cell you clicked doesn't change appearance, it
667
- // just grows to span its children.
668
- const isChild = header.isParentOpened && !header.isOpened;
669
- const showOpen = header.canExpand && !header.isOpened;
670
- const isClickable = header.isOpened || showOpen;
671
- // If this child's parent is mid-close, play the exit animation
672
- // instead of the entry. After SWIMLANE_CLOSE_EXIT_MS the data
673
- // change applies and the child unmounts.
674
- const parentNs =
675
- header.depth > 1
676
- ? header.namespace.split('.').slice(0, -1).join('.')
677
- : undefined;
678
- const isExiting =
679
- isChild && !!parentNs && !!closingNamespaces?.has(parentNs);
680
- const cellClassName = isExiting
681
- ? 'swimlane-child-out'
682
- : isChild
683
- ? 'swimlane-child-in'
684
- : 'swimlane-fade-in';
685
- return (
686
- <div
687
- key={header.namespace}
688
- className={cellClassName}
689
- role={isClickable ? 'button' : undefined}
690
- aria-label={
691
- header.isOpened
692
- ? `Close ${header.namespace}`
693
- : showOpen
694
- ? `Open ${header.namespace}`
695
- : undefined
696
- }
697
- title={header.namespace}
698
- style={{
699
- position: 'absolute',
700
- left: header.x - header.width / 2,
701
- top: rowTop,
702
- width: header.width,
703
- height: headerHeight,
704
- display: 'flex',
705
- alignItems: 'center',
706
- justifyContent: 'center',
707
- padding: '0 8px',
708
- boxSizing: 'border-box',
709
- backgroundColor: transparent
710
- ? 'transparent'
711
- : isChild
712
- ? theme.colors.background
713
- : theme.colors.muted,
714
- transition: swimlaneTransition,
715
- borderBottom: isChild
716
- ? `1px solid ${theme.colors.border}`
717
- : `2px solid ${theme.colors.border}`,
718
- borderLeft: isChild
719
- ? `1px solid ${theme.colors.border}`
720
- : 'none',
721
- borderRight: isChild
722
- ? `1px solid ${theme.colors.border}`
723
- : 'none',
724
- fontWeight: isChild
725
- ? theme.fontWeights.medium
726
- : theme.fontWeights.semibold,
727
- fontSize: isChild ? theme.fontSizes[1] : theme.fontSizes[2],
728
- fontFamily: theme.fonts.heading,
729
- color: isChild ? theme.colors.textSecondary : theme.colors.text,
730
- pointerEvents: 'auto',
731
- userSelect: 'none',
732
- cursor: isClickable ? 'pointer' : 'default',
733
- gap: 6,
734
- // Parent (opened) cells sit above leaves so children slide out
735
- // from behind them rather than over the top.
736
- zIndex: header.isOpened ? 2 : 1,
737
- }}
738
- onClick={
739
- isClickable
740
- ? (e) => {
741
- e.stopPropagation();
742
- onToggleNamespace?.(header.namespace);
743
- }
744
- : undefined
745
- }
746
- onMouseEnter={
747
- isClickable
748
- ? () => setHoveredNamespace(header.namespace)
749
- : undefined
750
- }
751
- onMouseLeave={
752
- isClickable
753
- ? () =>
754
- setHoveredNamespace((current) =>
755
- current === header.namespace ? null : current
756
- )
757
- : undefined
758
- }
759
- >
760
- <span
761
- style={{
762
- overflowWrap: 'anywhere',
763
- wordBreak: 'break-word',
764
- lineHeight: 1.2,
765
- textAlign: 'center',
766
- flex: 1,
767
- }}
768
- >
769
- {header.label}
770
- </span>
771
- {(header.isOpened || showOpen) && (
772
- <StackIcon
773
- opened={header.isOpened}
774
- hovered={hoveredNamespace === header.namespace}
775
- accentColor={theme.colors.primary}
776
- />
777
- )}
778
- </div>
779
- );
780
- })}
781
-
782
- </div>
783
- );
784
- }
785
-
786
- /**
787
- * Props for SequenceDiagramRenderer
788
- */
789
- export interface SequenceDiagramRendererProps {
790
- /** Events to display in the diagram */
791
- events: SequenceEvent[];
792
-
793
- /** Edges connecting events */
794
- edges: SequenceEdge[];
795
-
796
- /** Layout options */
797
- layoutOptions?: UseSequenceLayoutOptions;
798
-
799
- /** Optional custom node types */
800
- nodeTypes?: NodeTypes;
801
-
802
- /** Optional custom edge types */
803
- edgeTypes?: EdgeTypes;
804
-
805
- /**
806
- * Called when the user toggles a lane's drill state via the header
807
- * chevrons. Update `layoutOptions.openedNamespaces` in response to
808
- * open/close the lane.
809
- */
810
- onToggleNamespace?: (namespace: string) => void;
811
-
812
- /** Callback when a node is clicked */
813
- onNodeClick?: (nodeId: string, event: React.MouseEvent) => void;
814
-
815
- /** ID of the currently selected node (for visual highlighting) */
816
- selectedNodeId?: string;
817
-
818
- /** Optional class name */
819
- className?: string;
820
-
821
- /** Optional width */
822
- width?: number | string;
823
-
824
- /** Optional height */
825
- height?: number | string;
826
-
827
- /** Whether to show controls */
828
- showControls?: boolean;
829
-
830
- /** Whether to show background grid */
831
- showBackground?: boolean;
832
-
833
- /** Whether swimlane headers should stick to the top when scrolling vertically (default: true) */
834
- stickyHeaders?: boolean;
835
-
836
- /** Whether to show event labels on nodes (default: false, labels already shown on edges) */
837
- showEventLabels?: boolean;
838
-
839
- /**
840
- * When true, render the diagram chrome (canvas background, swimlane fills,
841
- * header backgrounds) as transparent so the diagram can be composited over
842
- * an arbitrary backdrop. Lifelines, borders, edges, and label pills keep
843
- * their theme colors so the diagram stays legible. Defaults to `false`.
844
- */
845
- transparent?: boolean;
846
- }
847
-
848
- /**
849
- * Inner component that uses React Flow hooks
850
- */
851
- function SequenceDiagramInner({
852
- events,
853
- edges: sequenceEdges,
854
- layoutOptions = {},
855
- nodeTypes: customNodeTypes,
856
- edgeTypes: customEdgeTypes,
857
- onToggleNamespace,
858
- onNodeClick,
859
- showControls = true,
860
- showBackground = false, // Default to false since swimlanes provide visual structure
861
- stickyHeaders = true,
862
- selectedNodeId,
863
- showEventLabels = false, // Default to false - labels already shown on edges
864
- transparent = false,
865
- }: SequenceDiagramRendererProps) {
866
- const { theme } = useTheme();
867
-
868
- // Extract layout params
869
- const { laneWidth = 250, headerHeight = 60 } = layoutOptions;
870
-
871
- // openedNamespaces is controlled if provided in layoutOptions, otherwise
872
- // we manage it internally so chevrons work out of the box.
873
- const isOpenedControlled = layoutOptions.openedNamespaces !== undefined;
874
- const [internalOpened, setInternalOpened] = useState<string[]>([]);
875
- const effectiveOpened = isOpenedControlled
876
- ? layoutOptions.openedNamespaces
877
- : internalOpened;
878
-
879
- // Namespaces currently mid-close. While one is here, the children still
880
- // render in the data (lifelines, events, edges unchanged) but their header
881
- // cells flip to the exit animation. After the exit completes we apply the
882
- // real data change, so the diagram body shifts in sync with the parent
883
- // header shrink instead of ahead of it.
884
- const [closingNamespaces, setClosingNamespaces] = useState<Set<string>>(
885
- () => new Set()
886
- );
887
-
888
- const handleToggleNamespace = useCallback(
889
- (namespace: string) => {
890
- const openedSet =
891
- effectiveOpened instanceof Set
892
- ? effectiveOpened
893
- : new Set(effectiveOpened ?? []);
894
- const isCurrentlyOpened = openedSet.has(namespace);
895
-
896
- if (isCurrentlyOpened) {
897
- // Stage the close: animate children out first, then apply data change.
898
- setClosingNamespaces((prev) => {
899
- if (prev.has(namespace)) return prev;
900
- const next = new Set(prev);
901
- next.add(namespace);
902
- return next;
903
- });
904
- setTimeout(() => {
905
- if (!isOpenedControlled) {
906
- setInternalOpened((prev) => prev.filter((n) => n !== namespace));
907
- }
908
- onToggleNamespace?.(namespace);
909
- setClosingNamespaces((prev) => {
910
- if (!prev.has(namespace)) return prev;
911
- const next = new Set(prev);
912
- next.delete(namespace);
913
- return next;
914
- });
915
- }, SWIMLANE_CLOSE_EXIT_MS);
916
- } else {
917
- // Open: data change is immediate; children animate in via CSS.
918
- if (!isOpenedControlled) {
919
- setInternalOpened((prev) => [...prev, namespace]);
920
- }
921
- onToggleNamespace?.(namespace);
922
- }
923
- },
924
- [effectiveOpened, isOpenedControlled, onToggleNamespace]
925
- );
926
-
927
- const effectiveLayoutOptions = useMemo(
928
- () => ({ ...layoutOptions, openedNamespaces: effectiveOpened }),
929
- [layoutOptions, effectiveOpened]
930
- );
931
-
932
- // Merge custom node/edge types with sequence defaults
933
- const nodeTypes = useMemo(
934
- () => ({ ...defaultSequenceNodeTypes, ...customNodeTypes }),
935
- [customNodeTypes]
936
- );
937
- const edgeTypes = useMemo(
938
- () => ({ ...defaultSequenceEdgeTypes, ...customEdgeTypes }),
939
- [customEdgeTypes]
940
- );
941
-
942
- // Compute layout
943
- const {
944
- nodes: layoutNodes,
945
- edges,
946
- swimlanes,
947
- parentHeaders,
948
- headerRows,
949
- totalWidth,
950
- totalHeight,
951
- } = useSequenceLayout(events, sequenceEdges, effectiveLayoutOptions);
952
-
953
- // Mark selected node and add showEventLabels to node data
954
- const nodes = useMemo(() => {
955
- return layoutNodes.map(node => ({
956
- ...node,
957
- selected: node.id === selectedNodeId,
958
- data: {
959
- ...node.data,
960
- showEventLabels,
961
- },
962
- }));
963
- }, [layoutNodes, selectedNodeId, showEventLabels]);
964
-
965
- // Add selectedNodeId to edge data so edges can highlight their labels
966
- const edgesWithSelection = useMemo(() => {
967
- return edges.map(edge => ({
968
- ...edge,
969
- data: {
970
- ...edge.data,
971
- isSourceSelected: edge.source === selectedNodeId,
972
- },
973
- }));
974
- }, [edges, selectedNodeId]);
975
-
976
- // Handle node click
977
- const handleNodeClick = useCallback(
978
- (_event: React.MouseEvent, node: { id: string }) => {
979
- onNodeClick?.(node.id, _event);
980
- },
981
- [onNodeClick]
982
- );
983
-
984
- // Handle edge click - extract source event from edge and trigger node selection
985
- const handleEdgeClick = useCallback(
986
- (_event: React.MouseEvent, edge: { id: string; source: string; target: string }) => {
987
- // When clicking an edge, select the source event (the label describes the source)
988
- // The edge label comes from the source event, so clicking it should select that event
989
- onNodeClick?.(edge.source, _event);
990
- },
991
- [onNodeClick]
992
- );
993
-
994
- // Clamp horizontal and vertical panning to content bounds so swimlanes and
995
- // headers stay in view. When sticky headers are disabled, allow a small
996
- // negative top buffer for visual breathing room.
997
- const translateExtent = useMemo(() => {
998
- const xMin = 0;
999
- const xMax = totalWidth;
1000
- const yMin = stickyHeaders ? 0 : -20;
1001
- const yMax = stickyHeaders ? totalHeight + 1000 : totalHeight + 20;
1002
- return [
1003
- [xMin, yMin],
1004
- [xMax, yMax],
1005
- ] as [[number, number], [number, number]];
1006
- }, [stickyHeaders, totalWidth, totalHeight]);
1007
-
1008
- // When sticky headers are enabled, use defaultViewport to ensure we start at y=0
1009
- // This prevents the snap-to-position issue on initial load
1010
- const viewportConfig = useMemo(() => {
1011
- if (stickyHeaders) {
1012
- // Start with top-aligned view (y=0) and reasonable zoom
1013
- return {
1014
- fitView: false,
1015
- defaultViewport: { x: 0, y: 0, zoom: 0.8 },
1016
- };
1017
- }
1018
- // Use fitView for non-sticky mode
1019
- return {
1020
- fitView: true,
1021
- fitViewOptions: {
1022
- padding: 0.1,
1023
- minZoom: 0.5,
1024
- maxZoom: 1.5,
1025
- },
1026
- };
1027
- }, [stickyHeaders]);
1028
-
1029
- return (
1030
- <ReactFlow
1031
- nodes={nodes}
1032
- edges={edgesWithSelection}
1033
- nodeTypes={nodeTypes}
1034
- edgeTypes={edgeTypes}
1035
- onNodeClick={handleNodeClick}
1036
- onEdgeClick={handleEdgeClick}
1037
- {...viewportConfig}
1038
- minZoom={0.1}
1039
- maxZoom={2}
1040
- nodesDraggable={false}
1041
- nodesConnectable={false}
1042
- elementsSelectable={true}
1043
- panOnScroll
1044
- zoomOnScroll
1045
- translateExtent={translateExtent}
1046
- style={{ background: transparent ? 'transparent' : theme.colors.background }}
1047
- >
1048
- <style>{swimlaneAnimationStyles}</style>
1049
- {/* SVG defs for arrow markers */}
1050
- <svg style={{ position: 'absolute', width: 0, height: 0 }}>
1051
- <defs>
1052
- {/* Standard arrow for transform events */}
1053
- <marker
1054
- id="sequence-arrow"
1055
- viewBox="0 0 10 10"
1056
- refX="8"
1057
- refY="5"
1058
- markerWidth="6"
1059
- markerHeight="6"
1060
- orient="auto-start-reverse"
1061
- >
1062
- <path
1063
- d="M 0 0 L 10 5 L 0 10 z"
1064
- fill={theme.colors.primary}
1065
- />
1066
- </marker>
1067
- {/* Accent arrow for move events (IPC calls) */}
1068
- <marker
1069
- id="sequence-arrow-move"
1070
- viewBox="0 0 10 10"
1071
- refX="8"
1072
- refY="5"
1073
- markerWidth="7"
1074
- markerHeight="7"
1075
- orient="auto-start-reverse"
1076
- >
1077
- <path
1078
- d="M 0 0 L 10 5 L 0 10 z"
1079
- fill={theme.colors.accent || '#f48771'}
1080
- />
1081
- </marker>
1082
- </defs>
1083
- </svg>
1084
-
1085
- {showBackground && (
1086
- <Background variant={BackgroundVariant.Dots} gap={16} size={1} />
1087
- )}
1088
- {showControls && <Controls />}
1089
-
1090
- {/* Swimlane layer - renders behind nodes */}
1091
- <SwimlaneLayer
1092
- swimlanes={swimlanes}
1093
- parentHeaders={parentHeaders}
1094
- headerRows={headerRows}
1095
- laneWidth={laneWidth}
1096
- headerHeight={headerHeight}
1097
- totalHeight={totalHeight}
1098
- transparent={transparent}
1099
- />
1100
-
1101
- {/* Swimlane headers layer - renders on top for clickability */}
1102
- <SwimlaneHeadersLayer
1103
- swimlanes={swimlanes}
1104
- parentHeaders={parentHeaders}
1105
- headerRows={headerRows}
1106
- laneWidth={laneWidth}
1107
- headerHeight={headerHeight}
1108
- totalHeight={totalHeight}
1109
- onToggleNamespace={handleToggleNamespace}
1110
- closingNamespaces={closingNamespaces}
1111
- stickyHeaders={stickyHeaders}
1112
- transparent={transparent}
1113
- />
1114
-
1115
- </ReactFlow>
1116
- );
1117
- }
1118
-
1119
- /**
1120
- * Sequence Diagram Renderer
1121
- *
1122
- * Renders events as a sequence diagram with swimlanes based on namespaces.
1123
- *
1124
- * @example
1125
- * ```tsx
1126
- * <SequenceDiagramRenderer
1127
- * events={[
1128
- * { id: '1', name: 'auth.validation.started' },
1129
- * { id: '2', name: 'auth.validation.completed' },
1130
- * { id: '3', name: 'database.query.executed' },
1131
- * ]}
1132
- * edges={[
1133
- * { id: 'e1', fromEvent: '1', toEvent: '2' },
1134
- * { id: 'e2', fromEvent: '2', toEvent: '3' },
1135
- * ]}
1136
- * />
1137
- * ```
1138
- */
1139
- export function SequenceDiagramRenderer(props: SequenceDiagramRendererProps) {
1140
- const { className, width = '100%', height = 600 } = props;
1141
-
1142
- return (
1143
- <div
1144
- className={className}
1145
- style={{
1146
- width,
1147
- height,
1148
- position: 'relative',
1149
- }}
1150
- >
1151
- <ReactFlowProvider>
1152
- <SequenceDiagramInner {...props} />
1153
- </ReactFlowProvider>
1154
- </div>
1155
- );
1156
- }