@principal-ai/principal-view-react 0.14.30 → 0.14.32
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.
- package/dist/components/SequenceDiagramRenderer.d.ts +2 -0
- package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -1
- package/dist/components/SequenceDiagramRenderer.js +85 -39
- package/dist/components/SequenceDiagramRenderer.js.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts +3 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.js +3 -2
- package/dist/components/WorkflowSequenceDiagram.js.map +1 -1
- package/dist/nodes/otel/OtelEventNode.d.ts.map +1 -1
- package/dist/nodes/otel/OtelEventNode.js +4 -3
- package/dist/nodes/otel/OtelEventNode.js.map +1 -1
- package/package.json +3 -3
- package/src/components/SequenceDiagramRenderer.tsx +114 -43
- package/src/components/WorkflowSequenceDiagram.tsx +5 -0
- package/src/nodes/otel/OtelEventNode.tsx +5 -3
- package/src/stories/ValidationEventsCanvas.stories.tsx +209 -0
- package/src/stories/WorkflowSequenceDiagram.stories.tsx +777 -0
|
@@ -36,42 +36,74 @@ import {
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Minimal marker node for arrow-centric sequence diagrams
|
|
39
|
-
* Invisible - just used for positioning,
|
|
40
|
-
*
|
|
39
|
+
* Invisible - just used for positioning, selection is shown on edge labels
|
|
40
|
+
* Can optionally show a label for better clickability
|
|
41
41
|
*/
|
|
42
42
|
function SequenceMarkerNode({ data, selected }: NodeProps) {
|
|
43
43
|
const { theme } = useTheme();
|
|
44
|
+
const showLabel = data.showEventLabels === true; // Only show if explicitly enabled
|
|
45
|
+
const label = data.label as string | undefined;
|
|
44
46
|
|
|
47
|
+
// If labels are shown on nodes, render them
|
|
48
|
+
if (showLabel && label) {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
style={{
|
|
52
|
+
width: '100%',
|
|
53
|
+
height: '100%',
|
|
54
|
+
position: 'relative',
|
|
55
|
+
cursor: 'pointer',
|
|
56
|
+
display: 'flex',
|
|
57
|
+
flexDirection: 'column',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
justifyContent: 'center',
|
|
60
|
+
}}
|
|
61
|
+
title={data.fullName as string}
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
style={{
|
|
65
|
+
padding: '6px 12px',
|
|
66
|
+
background: selected ? theme.colors.primary : theme.colors.background,
|
|
67
|
+
color: selected ? theme.colors.background : theme.colors.text,
|
|
68
|
+
border: `2px solid ${selected ? theme.colors.primary : theme.colors.border}`,
|
|
69
|
+
borderRadius: 4,
|
|
70
|
+
fontSize: theme.fontSizes[1],
|
|
71
|
+
fontWeight: selected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
72
|
+
fontFamily: theme.fonts.body,
|
|
73
|
+
whiteSpace: 'nowrap',
|
|
74
|
+
pointerEvents: 'none',
|
|
75
|
+
userSelect: 'none',
|
|
76
|
+
transition: 'all 0.2s ease',
|
|
77
|
+
boxShadow: selected ? `0 2px 8px ${theme.colors.primary}40` : 'none',
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{label}
|
|
81
|
+
</div>
|
|
82
|
+
<Handle
|
|
83
|
+
type="target"
|
|
84
|
+
position={Position.Top}
|
|
85
|
+
style={{ visibility: 'hidden' }}
|
|
86
|
+
/>
|
|
87
|
+
<Handle
|
|
88
|
+
type="source"
|
|
89
|
+
position={Position.Bottom}
|
|
90
|
+
style={{ visibility: 'hidden' }}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Default: invisible node (selection shown on edge labels)
|
|
45
97
|
return (
|
|
46
98
|
<div
|
|
47
99
|
style={{
|
|
48
100
|
width: '100%',
|
|
49
101
|
height: '100%',
|
|
50
102
|
opacity: 0,
|
|
51
|
-
|
|
103
|
+
cursor: 'pointer',
|
|
52
104
|
}}
|
|
53
105
|
title={data.fullName as string}
|
|
54
106
|
>
|
|
55
|
-
{/* Show indicator when selected */}
|
|
56
|
-
{selected && (
|
|
57
|
-
<div
|
|
58
|
-
style={{
|
|
59
|
-
position: 'absolute',
|
|
60
|
-
top: '50%',
|
|
61
|
-
left: '50%',
|
|
62
|
-
transform: 'translate(-50%, -50%)',
|
|
63
|
-
width: '12px',
|
|
64
|
-
height: '12px',
|
|
65
|
-
borderRadius: '50%',
|
|
66
|
-
backgroundColor: theme.colors.primary,
|
|
67
|
-
border: `2px solid ${theme.colors.background}`,
|
|
68
|
-
boxShadow: `0 0 0 2px ${theme.colors.primary}40`,
|
|
69
|
-
opacity: 1,
|
|
70
|
-
zIndex: 10,
|
|
71
|
-
pointerEvents: 'none',
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
-
)}
|
|
75
107
|
<Handle
|
|
76
108
|
type="target"
|
|
77
109
|
position={Position.Top}
|
|
@@ -104,6 +136,7 @@ function SequenceArrowEdge({
|
|
|
104
136
|
|
|
105
137
|
// Use a straight line for same-lane, bezier for cross-lane
|
|
106
138
|
const isSameLane = !data?.crossesLanes;
|
|
139
|
+
const isSourceSelected = data?.isSourceSelected === true;
|
|
107
140
|
|
|
108
141
|
const [edgePath, labelX, labelY] = getBezierPath({
|
|
109
142
|
sourceX,
|
|
@@ -132,16 +165,19 @@ function SequenceArrowEdge({
|
|
|
132
165
|
style={{
|
|
133
166
|
position: 'absolute',
|
|
134
167
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
135
|
-
background: theme.colors.background,
|
|
136
|
-
padding: '2px 8px',
|
|
168
|
+
background: isSourceSelected ? theme.colors.primary : theme.colors.background,
|
|
169
|
+
padding: isSourceSelected ? '4px 10px' : '2px 8px',
|
|
137
170
|
borderRadius: 4,
|
|
138
171
|
fontSize: theme.fontSizes[0],
|
|
139
|
-
fontWeight: theme.fontWeights.medium,
|
|
172
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
140
173
|
fontFamily: theme.fonts.body,
|
|
141
|
-
color: theme.colors.text,
|
|
142
|
-
border:
|
|
174
|
+
color: isSourceSelected ? theme.colors.background : theme.colors.text,
|
|
175
|
+
border: `${isSourceSelected ? 2 : 1}px solid ${isSourceSelected ? theme.colors.primary : theme.colors.border}`,
|
|
143
176
|
pointerEvents: 'all',
|
|
144
177
|
whiteSpace: 'nowrap',
|
|
178
|
+
cursor: 'pointer',
|
|
179
|
+
transition: 'all 0.2s ease',
|
|
180
|
+
boxShadow: isSourceSelected ? `0 2px 8px ${theme.colors.primary}60` : 'none',
|
|
145
181
|
}}
|
|
146
182
|
className="nodrag nopan"
|
|
147
183
|
>
|
|
@@ -178,6 +214,7 @@ function SequenceArrowParticipantEdge({
|
|
|
178
214
|
|
|
179
215
|
// Style based on whether it's a move event (IPC) or transform event (internal)
|
|
180
216
|
const isMoveEvent = data?.isMoveEvent === true;
|
|
217
|
+
const isSourceSelected = data?.isSourceSelected === true;
|
|
181
218
|
const strokeColor = isMoveEvent ? (theme.colors.accent || '#f48771') : theme.colors.primary;
|
|
182
219
|
|
|
183
220
|
// Same lane: render as activation bar
|
|
@@ -224,16 +261,19 @@ function SequenceArrowParticipantEdge({
|
|
|
224
261
|
style={{
|
|
225
262
|
position: 'absolute',
|
|
226
263
|
transform: `translate(0, -50%) translate(${sourceX + 15}px,${barY + barHeight / 2}px)`,
|
|
227
|
-
background: theme.colors.background,
|
|
228
|
-
padding: '2px 8px',
|
|
264
|
+
background: isSourceSelected ? strokeColor : theme.colors.background,
|
|
265
|
+
padding: isSourceSelected ? '4px 10px' : '2px 8px',
|
|
229
266
|
borderRadius: 4,
|
|
230
267
|
fontSize: theme.fontSizes[0],
|
|
231
|
-
fontWeight: theme.fontWeights.medium,
|
|
268
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
232
269
|
fontFamily: theme.fonts.body,
|
|
233
|
-
color: strokeColor,
|
|
234
|
-
border:
|
|
270
|
+
color: isSourceSelected ? theme.colors.background : strokeColor,
|
|
271
|
+
border: `${isSourceSelected ? 2 : 1}px solid ${strokeColor}`,
|
|
235
272
|
pointerEvents: 'all',
|
|
236
273
|
whiteSpace: 'nowrap',
|
|
274
|
+
cursor: 'pointer',
|
|
275
|
+
transition: 'all 0.2s ease',
|
|
276
|
+
boxShadow: isSourceSelected ? `0 2px 8px ${strokeColor}60` : 'none',
|
|
237
277
|
}}
|
|
238
278
|
className="nodrag nopan"
|
|
239
279
|
>
|
|
@@ -272,16 +312,19 @@ function SequenceArrowParticipantEdge({
|
|
|
272
312
|
style={{
|
|
273
313
|
position: 'absolute',
|
|
274
314
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY - 12}px)`,
|
|
275
|
-
background: theme.colors.background,
|
|
276
|
-
padding: isMoveEvent ? '3px 10px' : '2px 8px',
|
|
315
|
+
background: isSourceSelected ? strokeColor : theme.colors.background,
|
|
316
|
+
padding: isSourceSelected ? '5px 12px' : (isMoveEvent ? '3px 10px' : '2px 8px'),
|
|
277
317
|
borderRadius: 4,
|
|
278
318
|
fontSize: theme.fontSizes[0],
|
|
279
|
-
fontWeight: isMoveEvent ? theme.fontWeights.bold : theme.fontWeights.medium,
|
|
319
|
+
fontWeight: isSourceSelected ? theme.fontWeights.bold : (isMoveEvent ? theme.fontWeights.bold : theme.fontWeights.medium),
|
|
280
320
|
fontFamily: theme.fonts.body,
|
|
281
|
-
color: strokeColor,
|
|
282
|
-
border:
|
|
321
|
+
color: isSourceSelected ? theme.colors.background : strokeColor,
|
|
322
|
+
border: `${isSourceSelected ? 3 : (isMoveEvent ? 2 : 1)}px solid ${strokeColor}`,
|
|
283
323
|
pointerEvents: 'all',
|
|
284
324
|
whiteSpace: 'nowrap',
|
|
325
|
+
cursor: 'pointer',
|
|
326
|
+
transition: 'all 0.2s ease',
|
|
327
|
+
boxShadow: isSourceSelected ? `0 4px 12px ${strokeColor}60` : 'none',
|
|
285
328
|
}}
|
|
286
329
|
className="nodrag nopan"
|
|
287
330
|
>
|
|
@@ -501,6 +544,9 @@ export interface SequenceDiagramRendererProps {
|
|
|
501
544
|
|
|
502
545
|
/** Whether swimlane headers should stick to the top when scrolling vertically (default: true) */
|
|
503
546
|
stickyHeaders?: boolean;
|
|
547
|
+
|
|
548
|
+
/** Whether to show event labels on nodes (default: false, labels already shown on edges) */
|
|
549
|
+
showEventLabels?: boolean;
|
|
504
550
|
}
|
|
505
551
|
|
|
506
552
|
/**
|
|
@@ -518,6 +564,7 @@ function SequenceDiagramInner({
|
|
|
518
564
|
showBackground = false, // Default to false since swimlanes provide visual structure
|
|
519
565
|
stickyHeaders = true,
|
|
520
566
|
selectedNodeId,
|
|
567
|
+
showEventLabels = false, // Default to false - labels already shown on edges
|
|
521
568
|
}: SequenceDiagramRendererProps) {
|
|
522
569
|
const { theme } = useTheme();
|
|
523
570
|
|
|
@@ -541,15 +588,28 @@ function SequenceDiagramInner({
|
|
|
541
588
|
layoutOptions
|
|
542
589
|
);
|
|
543
590
|
|
|
544
|
-
// Mark selected node
|
|
591
|
+
// Mark selected node and add showEventLabels to node data
|
|
545
592
|
const nodes = useMemo(() => {
|
|
546
|
-
if (!selectedNodeId) return layoutNodes;
|
|
547
|
-
|
|
548
593
|
return layoutNodes.map(node => ({
|
|
549
594
|
...node,
|
|
550
595
|
selected: node.id === selectedNodeId,
|
|
596
|
+
data: {
|
|
597
|
+
...node.data,
|
|
598
|
+
showEventLabels,
|
|
599
|
+
},
|
|
600
|
+
}));
|
|
601
|
+
}, [layoutNodes, selectedNodeId, showEventLabels]);
|
|
602
|
+
|
|
603
|
+
// Add selectedNodeId to edge data so edges can highlight their labels
|
|
604
|
+
const edgesWithSelection = useMemo(() => {
|
|
605
|
+
return edges.map(edge => ({
|
|
606
|
+
...edge,
|
|
607
|
+
data: {
|
|
608
|
+
...edge.data,
|
|
609
|
+
isSourceSelected: edge.source === selectedNodeId,
|
|
610
|
+
},
|
|
551
611
|
}));
|
|
552
|
-
}, [
|
|
612
|
+
}, [edges, selectedNodeId]);
|
|
553
613
|
|
|
554
614
|
// Handle node click
|
|
555
615
|
const handleNodeClick = useCallback(
|
|
@@ -559,6 +619,16 @@ function SequenceDiagramInner({
|
|
|
559
619
|
[onNodeClick]
|
|
560
620
|
);
|
|
561
621
|
|
|
622
|
+
// Handle edge click - extract source event from edge and trigger node selection
|
|
623
|
+
const handleEdgeClick = useCallback(
|
|
624
|
+
(_event: React.MouseEvent, edge: { id: string; source: string; target: string }) => {
|
|
625
|
+
// When clicking an edge, select the source event (the label describes the source)
|
|
626
|
+
// The edge label comes from the source event, so clicking it should select that event
|
|
627
|
+
onNodeClick?.(edge.source, _event);
|
|
628
|
+
},
|
|
629
|
+
[onNodeClick]
|
|
630
|
+
);
|
|
631
|
+
|
|
562
632
|
// When sticky headers are enabled, limit upward panning to prevent headers from disconnecting
|
|
563
633
|
// from swimlane backgrounds. Allow downward panning to see all content.
|
|
564
634
|
const translateExtent = useMemo(() => {
|
|
@@ -600,10 +670,11 @@ function SequenceDiagramInner({
|
|
|
600
670
|
return (
|
|
601
671
|
<ReactFlow
|
|
602
672
|
nodes={nodes}
|
|
603
|
-
edges={
|
|
673
|
+
edges={edgesWithSelection}
|
|
604
674
|
nodeTypes={nodeTypes}
|
|
605
675
|
edgeTypes={edgeTypes}
|
|
606
676
|
onNodeClick={handleNodeClick}
|
|
677
|
+
onEdgeClick={handleEdgeClick}
|
|
607
678
|
{...viewportConfig}
|
|
608
679
|
minZoom={0.1}
|
|
609
680
|
maxZoom={2}
|
|
@@ -45,6 +45,9 @@ export interface WorkflowSequenceDiagramProps {
|
|
|
45
45
|
|
|
46
46
|
/** Currently selected event index (0-based) for visual highlighting */
|
|
47
47
|
selectedEventIndex?: number;
|
|
48
|
+
|
|
49
|
+
/** Whether to show event labels on nodes (default: false, labels already shown on edges) */
|
|
50
|
+
showEventLabels?: boolean;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/**
|
|
@@ -188,6 +191,7 @@ export function WorkflowSequenceDiagram({
|
|
|
188
191
|
className,
|
|
189
192
|
onEventIndexChange,
|
|
190
193
|
selectedEventIndex,
|
|
194
|
+
showEventLabels = false, // Default to false - labels already on edges
|
|
191
195
|
}: WorkflowSequenceDiagramProps) {
|
|
192
196
|
// Convert workflow to sequence format
|
|
193
197
|
const { events, edges } = useMemo(
|
|
@@ -248,6 +252,7 @@ export function WorkflowSequenceDiagram({
|
|
|
248
252
|
className={className}
|
|
249
253
|
onNodeClick={onEventIndexChange ? handleNodeClick : undefined}
|
|
250
254
|
selectedNodeId={selectedNodeId}
|
|
255
|
+
showEventLabels={showEventLabels}
|
|
251
256
|
/>
|
|
252
257
|
);
|
|
253
258
|
}
|
|
@@ -99,9 +99,11 @@ export const OtelEventNode: React.FC<NodeProps<Node<OtelEventNodeData>>> = ({
|
|
|
99
99
|
// Color resolution
|
|
100
100
|
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
101
101
|
const spanColor = nodeData.spanColor as string | undefined;
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
102
|
+
|
|
103
|
+
// OTEL nodes use scope-based coloring exclusively
|
|
104
|
+
// Priority: scopeColor (from library.yaml) → typeDefinition.color → default
|
|
105
|
+
// Note: node.fill and node.color fields are intentionally ignored (validation enforces this)
|
|
106
|
+
const baseFillColor = scopeColor || typeDefinition.color || '#3b82f6';
|
|
105
107
|
const fillColor = baseFillColor;
|
|
106
108
|
// Stroke color priority: explicit stroke > span color (workflow context) > fill color
|
|
107
109
|
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import '@xyflow/react/dist/style.css';
|
|
4
|
+
import { GraphRenderer } from '../components/GraphRenderer';
|
|
5
|
+
import type { ExtendedCanvas } from '@principal-ai/principal-view-core';
|
|
6
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
7
|
+
|
|
8
|
+
// Import the validation events canvas
|
|
9
|
+
import validationEventsCanvasUrl from '../../../../.principal-views/validation/validation.events.canvas?url';
|
|
10
|
+
|
|
11
|
+
// Helper to load canvas from URL
|
|
12
|
+
async function loadCanvas(url: string): Promise<ExtendedCanvas> {
|
|
13
|
+
const response = await fetch(url);
|
|
14
|
+
return await response.json();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Wrapper component that loads canvas data
|
|
18
|
+
function CanvasLoader({ url, children }: { url: string; children: (canvas: ExtendedCanvas) => React.ReactNode }) {
|
|
19
|
+
const [canvas, setCanvas] = useState<ExtendedCanvas | null>(null);
|
|
20
|
+
const [error, setError] = useState<string | null>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
loadCanvas(url)
|
|
24
|
+
.then(setCanvas)
|
|
25
|
+
.catch((err) => setError(err.message));
|
|
26
|
+
}, [url]);
|
|
27
|
+
|
|
28
|
+
if (error) {
|
|
29
|
+
return <div style={{ padding: 20, color: 'red' }}>Error loading canvas: {error}</div>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!canvas) {
|
|
33
|
+
return <div style={{ padding: 20 }}>Loading canvas...</div>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
38
|
+
{children(canvas)}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const meta = {
|
|
44
|
+
title: 'Validation/Event Namespace Canvas',
|
|
45
|
+
component: GraphRenderer,
|
|
46
|
+
parameters: {
|
|
47
|
+
layout: 'fullscreen',
|
|
48
|
+
},
|
|
49
|
+
tags: ['autodocs'],
|
|
50
|
+
decorators: [
|
|
51
|
+
(Story) => (
|
|
52
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
53
|
+
<div style={{ width: '100vw', height: '100vh' }}>
|
|
54
|
+
<Story />
|
|
55
|
+
</div>
|
|
56
|
+
</ThemeProvider>
|
|
57
|
+
),
|
|
58
|
+
],
|
|
59
|
+
} satisfies Meta<typeof GraphRenderer>;
|
|
60
|
+
|
|
61
|
+
export default meta;
|
|
62
|
+
type Story = StoryObj<typeof meta>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validation Event Namespace Canvas
|
|
66
|
+
*
|
|
67
|
+
* File: .principal-views/validation/validation.events.canvas
|
|
68
|
+
* Type: event-namespace nodes
|
|
69
|
+
* Purpose: Shows the vocabulary of events emitted during validation workflows
|
|
70
|
+
*/
|
|
71
|
+
export const EventNamespaceVocabulary: Story = {
|
|
72
|
+
render: () => (
|
|
73
|
+
<CanvasLoader url={validationEventsCanvasUrl}>
|
|
74
|
+
{(canvas) => <GraphRenderer canvas={canvas} showBackground={false} editable={true} />}
|
|
75
|
+
</CanvasLoader>
|
|
76
|
+
),
|
|
77
|
+
parameters: {
|
|
78
|
+
docs: {
|
|
79
|
+
description: {
|
|
80
|
+
story: `
|
|
81
|
+
Renders the **validation.events.canvas** file - an event namespace vocabulary canvas.
|
|
82
|
+
|
|
83
|
+
## Event Namespace Canvas Concept
|
|
84
|
+
|
|
85
|
+
Unlike span canvases (which show user workflows) or OTEL event canvases (which show individual events),
|
|
86
|
+
this canvas shows the **vocabulary of event namespaces** and their adjacency relationships.
|
|
87
|
+
|
|
88
|
+
### Structure
|
|
89
|
+
|
|
90
|
+
**Nodes = Event Namespaces**
|
|
91
|
+
- \`analysis\` - Analysis pipeline lifecycle events
|
|
92
|
+
- \`validation\` - Validation lifecycle events (started, complete, error)
|
|
93
|
+
- \`file\` - File parsing events
|
|
94
|
+
- \`type\` - Type detection events
|
|
95
|
+
- etc.
|
|
96
|
+
|
|
97
|
+
Each namespace node documents all events within that namespace with their attributes.
|
|
98
|
+
|
|
99
|
+
**Edges = Adjacency**
|
|
100
|
+
- Edges represent when events from one namespace appear adjacent to events from another namespace in workflow scenarios
|
|
101
|
+
- Example: \`analysis → filetree\` because \`analysis.started → filetree.built\` in scenarios
|
|
102
|
+
- Example: \`file → validation\` because \`file.parsed → validation.error\` in scenarios
|
|
103
|
+
|
|
104
|
+
### Purpose
|
|
105
|
+
|
|
106
|
+
This canvas serves as:
|
|
107
|
+
1. **Event vocabulary documentation** - What events can be emitted
|
|
108
|
+
2. **Flow visualization** - How events from different namespaces connect
|
|
109
|
+
3. **Implementation guide** - What attributes each event requires
|
|
110
|
+
4. **Validation source** - Canvas edges must match actual workflow scenario adjacencies
|
|
111
|
+
|
|
112
|
+
### Conventions
|
|
113
|
+
|
|
114
|
+
As defined in \`.principal-views/architecture.events.md\`:
|
|
115
|
+
- Events follow \`{namespace}.{action}.{detail}\` naming
|
|
116
|
+
- Edges represent direct adjacency only (not transitive)
|
|
117
|
+
- Canvas is validated against workflow scenario files
|
|
118
|
+
- Namespaces group code-level implementation events (vs user-level workflow spans)
|
|
119
|
+
|
|
120
|
+
**Use this to:**
|
|
121
|
+
- Preview event namespace canvas rendering
|
|
122
|
+
- Understand event flow architecture
|
|
123
|
+
- Document which events your code emits
|
|
124
|
+
- Validate event relationships match actual code
|
|
125
|
+
`,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Event Namespace Canvas with Annotations
|
|
133
|
+
*
|
|
134
|
+
* Shows the canvas with additional context about what each namespace contains
|
|
135
|
+
*/
|
|
136
|
+
export const WithNamespaceDetails: Story = {
|
|
137
|
+
render: () => (
|
|
138
|
+
<CanvasLoader url={validationEventsCanvasUrl}>
|
|
139
|
+
{(canvas) => (
|
|
140
|
+
<div style={{ display: 'flex', height: '100vh' }}>
|
|
141
|
+
<div style={{ flex: 1 }}>
|
|
142
|
+
<GraphRenderer canvas={canvas} showBackground={false} editable={true} />
|
|
143
|
+
</div>
|
|
144
|
+
<div style={{
|
|
145
|
+
width: '350px',
|
|
146
|
+
padding: '20px',
|
|
147
|
+
backgroundColor: '#f5f5f5',
|
|
148
|
+
overflowY: 'auto',
|
|
149
|
+
fontFamily: 'system-ui, -apple-system, sans-serif'
|
|
150
|
+
}}>
|
|
151
|
+
<h2 style={{ marginTop: 0, fontSize: '18px' }}>Event Namespaces</h2>
|
|
152
|
+
<p style={{ fontSize: '14px', color: '#666' }}>
|
|
153
|
+
Each node represents a namespace grouping related events.
|
|
154
|
+
</p>
|
|
155
|
+
|
|
156
|
+
<div style={{ marginTop: '20px' }}>
|
|
157
|
+
<h3 style={{ fontSize: '14px', marginBottom: '8px' }}>Discovery Phase</h3>
|
|
158
|
+
<ul style={{ fontSize: '13px', lineHeight: '1.6', paddingLeft: '20px' }}>
|
|
159
|
+
<li><strong>analysis</strong> - Pipeline start</li>
|
|
160
|
+
<li><strong>filetree</strong> - Repository scanning</li>
|
|
161
|
+
<li><strong>packages</strong> - Package discovery</li>
|
|
162
|
+
<li><strong>canvases</strong> - Canvas file discovery</li>
|
|
163
|
+
<li><strong>executions</strong> - Execution file discovery</li>
|
|
164
|
+
<li><strong>library</strong> - Component library loading</li>
|
|
165
|
+
<li><strong>discovery</strong> - Phase completion</li>
|
|
166
|
+
</ul>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div style={{ marginTop: '20px' }}>
|
|
170
|
+
<h3 style={{ fontSize: '14px', marginBottom: '8px' }}>Validation Phase</h3>
|
|
171
|
+
<ul style={{ fontSize: '13px', lineHeight: '1.6', paddingLeft: '20px' }}>
|
|
172
|
+
<li><strong>validation</strong> - Lifecycle (started, complete, error)</li>
|
|
173
|
+
<li><strong>file</strong> - Parsing operations</li>
|
|
174
|
+
<li><strong>type</strong> - Type detection</li>
|
|
175
|
+
<li><strong>canvas</strong> - Canvas validation</li>
|
|
176
|
+
<li><strong>workflow</strong> - Workflow validation</li>
|
|
177
|
+
<li><strong>execution</strong> - Execution validation</li>
|
|
178
|
+
<li><strong>rules</strong> - Lint rule execution</li>
|
|
179
|
+
<li><strong>results</strong> - Result aggregation</li>
|
|
180
|
+
</ul>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div style={{ marginTop: '20px', padding: '12px', backgroundColor: '#fff3cd', borderRadius: '4px' }}>
|
|
184
|
+
<h4 style={{ fontSize: '13px', marginTop: 0 }}>Adjacency Edges</h4>
|
|
185
|
+
<p style={{ fontSize: '12px', margin: 0, color: '#856404' }}>
|
|
186
|
+
Edges show when events from one namespace appear next to events from another namespace
|
|
187
|
+
in workflow scenarios. These are validated against actual execution traces.
|
|
188
|
+
</p>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</CanvasLoader>
|
|
194
|
+
),
|
|
195
|
+
parameters: {
|
|
196
|
+
docs: {
|
|
197
|
+
description: {
|
|
198
|
+
story: `
|
|
199
|
+
Same canvas with a side panel showing namespace details and explanations.
|
|
200
|
+
|
|
201
|
+
This view helps:
|
|
202
|
+
- Understand what each namespace contains
|
|
203
|
+
- See the phase groupings (discovery vs validation)
|
|
204
|
+
- Learn the event namespace canvas concept
|
|
205
|
+
`,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
};
|