@principal-ai/principal-view-react 0.14.24 → 0.14.26

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 (31) hide show
  1. package/dist/components/GraphRenderer.d.ts.map +1 -1
  2. package/dist/components/GraphRenderer.js +2 -1
  3. package/dist/components/GraphRenderer.js.map +1 -1
  4. package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -1
  5. package/dist/components/SequenceDiagramRenderer.js +83 -7
  6. package/dist/components/SequenceDiagramRenderer.js.map +1 -1
  7. package/dist/components/WorkflowSequenceDiagram.d.ts +52 -0
  8. package/dist/components/WorkflowSequenceDiagram.d.ts.map +1 -0
  9. package/dist/components/WorkflowSequenceDiagram.js +127 -0
  10. package/dist/components/WorkflowSequenceDiagram.js.map +1 -0
  11. package/dist/hooks/useSequenceLayout.d.ts +4 -0
  12. package/dist/hooks/useSequenceLayout.d.ts.map +1 -1
  13. package/dist/hooks/useSequenceLayout.js +66 -25
  14. package/dist/hooks/useSequenceLayout.js.map +1 -1
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/utils/graphConverter.d.ts.map +1 -1
  20. package/dist/utils/graphConverter.js +15 -1
  21. package/dist/utils/graphConverter.js.map +1 -1
  22. package/package.json +3 -3
  23. package/src/components/GraphRenderer.tsx +2 -1
  24. package/src/components/SequenceDiagramRenderer.tsx +162 -7
  25. package/src/components/WorkflowSequenceDiagram.tsx +222 -0
  26. package/src/hooks/useSequenceLayout.ts +73 -28
  27. package/src/index.ts +3 -0
  28. package/src/stories/FileCitySequence.stories.tsx +544 -0
  29. package/src/stories/data/file-city-images-transformed.otel.canvas.json +295 -0
  30. package/src/stories/data/file-city-workflows.json +269 -0
  31. package/src/utils/graphConverter.ts +14 -1
@@ -0,0 +1,544 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { SequenceDiagramRenderer } from '../components/SequenceDiagramRenderer';
4
+ import { WorkflowSequenceDiagram } from '../components/WorkflowSequenceDiagram';
5
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
6
+ import workflowData from './data/file-city-workflows.json';
7
+ import type { WorkflowScenario } from '@principal-ai/principal-view-core';
8
+
9
+ const meta = {
10
+ title: 'Workflows/File City Image Generation',
11
+ component: SequenceDiagramRenderer,
12
+ parameters: {
13
+ layout: 'fullscreen',
14
+ docs: {
15
+ description: {
16
+ component: `
17
+ # File City Image Generation Workflows
18
+
19
+ This demonstrates the **graph-to-sequence** concepts applied to the File City image generation system.
20
+
21
+ ## Participant Model
22
+
23
+ Following the sequence-first architecture:
24
+
25
+ - **Nodes** = Participants (Main Process, Renderer Process)
26
+ - **Edges** = Move Events (IPC calls crossing process boundaries)
27
+ - **Transform Events** = Internal processing within each participant
28
+
29
+ ## Key Concepts
30
+
31
+ - **Sequence diagrams are primary** - They show actual execution traces
32
+ - **Graphs aggregate sequences** - The canvas shows all possible paths
33
+ - **Move vs Transform Events**:
34
+ - **Move**: Data crossing participant boundaries (IPC calls)
35
+ - **Transform**: Internal processing within a participant
36
+
37
+ See \`/docs/GRAPH_TO_SEQUENCE_CONCEPTS.md\` for the full conceptual model.
38
+ `,
39
+ },
40
+ },
41
+ },
42
+ tags: ['autodocs'],
43
+ decorators: [
44
+ (Story) => (
45
+ <ThemeProvider theme={defaultEditorTheme}>
46
+ <div style={{ height: '100vh', boxSizing: 'border-box' }}>
47
+ <Story />
48
+ </div>
49
+ </ThemeProvider>
50
+ ),
51
+ ],
52
+ } satisfies Meta<typeof SequenceDiagramRenderer>;
53
+
54
+ export default meta;
55
+ type Story = StoryObj<typeof meta>;
56
+
57
+ // ============================================================================
58
+ // Helper Components
59
+ // ============================================================================
60
+
61
+ interface SequenceWithEventsProps {
62
+ events: SequenceEvent[];
63
+ edges: SequenceEdge[];
64
+ height?: number;
65
+ layoutOptions?: any;
66
+ }
67
+
68
+ const SequenceWithEventsList: React.FC<SequenceWithEventsProps> = ({
69
+ events,
70
+ edges,
71
+ height = 500,
72
+ layoutOptions,
73
+ }) => {
74
+ return (
75
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
76
+ {/* Top Half - Sequence Diagram */}
77
+ <div style={{ flex: 1, overflow: 'hidden' }}>
78
+ <SequenceDiagramRenderer
79
+ events={events}
80
+ edges={edges}
81
+ height={height}
82
+ layoutOptions={layoutOptions}
83
+ />
84
+ </div>
85
+
86
+ {/* Bottom Half - Event List */}
87
+ <div style={{
88
+ flex: 1,
89
+ overflow: 'auto',
90
+ background: '#1e1e1e',
91
+ color: '#d4d4d4',
92
+ fontFamily: 'monospace',
93
+ fontSize: 13,
94
+ }}>
95
+ <div style={{ padding: '16px' }}>
96
+ <h3 style={{ margin: '0 0 12px 0', color: '#4ec9b0' }}>Event Sequence</h3>
97
+ <table style={{ width: '100%', borderCollapse: 'collapse' }}>
98
+ <thead>
99
+ <tr style={{ borderBottom: '1px solid #3e3e3e' }}>
100
+ <th style={{ textAlign: 'left', padding: '8px', color: '#9cdcfe' }}>Order</th>
101
+ <th style={{ textAlign: 'left', padding: '8px', color: '#9cdcfe' }}>Event Name</th>
102
+ <th style={{ textAlign: 'left', padding: '8px', color: '#9cdcfe' }}>Label</th>
103
+ <th style={{ textAlign: 'left', padding: '8px', color: '#9cdcfe' }}>Type</th>
104
+ </tr>
105
+ </thead>
106
+ <tbody>
107
+ {events.map((event, index) => {
108
+ const isMoveEvent = (event as any).moveEvent;
109
+ const participant = event.name.split('.')[0];
110
+ return (
111
+ <tr key={event.id} style={{ borderBottom: '1px solid #2a2a2a' }}>
112
+ <td style={{ padding: '8px', color: '#b5cea8' }}>{index + 1}</td>
113
+ <td style={{
114
+ padding: '8px',
115
+ color: isMoveEvent ? '#ce9178' : '#dcdcaa',
116
+ fontWeight: isMoveEvent ? 'bold' : 'normal'
117
+ }}>
118
+ {event.name}
119
+ </td>
120
+ <td style={{ padding: '8px', color: '#d4d4d4' }}>{event.label}</td>
121
+ <td style={{
122
+ padding: '8px',
123
+ color: isMoveEvent ? '#f48771' : '#569cd6'
124
+ }}>
125
+ {isMoveEvent ? '→ Move Event (IPC)' : `Transform (${participant})`}
126
+ </td>
127
+ </tr>
128
+ );
129
+ })}
130
+ </tbody>
131
+ </table>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ );
136
+ };
137
+
138
+ // ============================================================================
139
+ // Workflow Stories
140
+ // ============================================================================
141
+
142
+ /**
143
+ * **Happy Path - Cache Hit**
144
+ *
145
+ * Optimal execution path where a cached image already exists.
146
+ *
147
+ * **Flow:**
148
+ * 1. Renderer requests image via IPC
149
+ * 2. Main process checks cache → Hit!
150
+ * 3. Returns cached URL immediately
151
+ * 4. Renderer displays image
152
+ *
153
+ * **Move Events (cross-boundary):**
154
+ * - `main.image_requested` (Renderer → Main)
155
+ * - `renderer.cache_hit` (Main → Renderer)
156
+ */
157
+ export const CacheHitFlow: Story = {
158
+ render: () => (
159
+ <SequenceWithEventsList
160
+ events={workflowData.workflows[0].events}
161
+ edges={workflowData.workflows[0].edges}
162
+ height={600}
163
+ layoutOptions={{
164
+ namespaceStrategy: 'first',
165
+ laneWidth: 200,
166
+ eventSpacing: 80,
167
+ }}
168
+ />
169
+ ),
170
+ parameters: {
171
+ docs: {
172
+ description: {
173
+ story: 'Fast path when cached image exists - only 2 IPC calls needed.',
174
+ },
175
+ },
176
+ },
177
+ };
178
+
179
+ /**
180
+ * **Full Generation Flow**
181
+ *
182
+ * Complete execution path when no cached image exists.
183
+ *
184
+ * **Flow:**
185
+ * 1. Renderer requests image via IPC
186
+ * 2. Main process checks cache → Miss
187
+ * 3. Fetches file tree from repository cache
188
+ * 4. Generates new File City visualization
189
+ * 5. Saves to cache
190
+ * 6. Returns generated URL via IPC
191
+ * 7. Renderer displays image
192
+ *
193
+ * **Move Events (cross-boundary):**
194
+ * - `main.image_requested` (Renderer → Main)
195
+ * - `renderer.generation_complete` (Main → Renderer)
196
+ *
197
+ * **Transform Events (internal to Main):**
198
+ * - `cache_checked`, `filetree_fetched`, `generation_started`
199
+ */
200
+ export const GenerateNewFlow: Story = {
201
+ render: () => (
202
+ <SequenceWithEventsList
203
+ events={workflowData.workflows[1].events}
204
+ edges={workflowData.workflows[1].edges}
205
+ height={700}
206
+ layoutOptions={{
207
+ namespaceStrategy: 'first',
208
+ laneWidth: 200,
209
+ eventSpacing: 80,
210
+ }}
211
+ />
212
+ ),
213
+ parameters: {
214
+ docs: {
215
+ description: {
216
+ story: 'Full generation path with cache miss - includes image generation.',
217
+ },
218
+ },
219
+ },
220
+ };
221
+
222
+ /**
223
+ * **Error Path - No FileTree**
224
+ *
225
+ * Error handling when file tree is not available.
226
+ *
227
+ * **Flow:**
228
+ * 1. Renderer requests image via IPC
229
+ * 2. Main process checks cache → Miss
230
+ * 3. Attempts to fetch file tree → Not available
231
+ * 4. Skips generation, returns null
232
+ * 5. Renderer displays fallback UI
233
+ *
234
+ * **Move Events:**
235
+ * - `main.image_requested` (Renderer → Main)
236
+ * - `renderer.generation_skipped` (Main → Renderer)
237
+ */
238
+ export const NoFileTreeFlow: Story = {
239
+ render: () => (
240
+ <SequenceWithEventsList
241
+ events={workflowData.workflows[2].events}
242
+ edges={workflowData.workflows[2].edges}
243
+ height={600}
244
+ layoutOptions={{
245
+ namespaceStrategy: 'first',
246
+ laneWidth: 200,
247
+ eventSpacing: 80,
248
+ }}
249
+ />
250
+ ),
251
+ parameters: {
252
+ docs: {
253
+ description: {
254
+ story: 'Error handling when repository file tree is unavailable.',
255
+ },
256
+ },
257
+ },
258
+ };
259
+
260
+ /**
261
+ * **Error During Generation**
262
+ *
263
+ * Error handling when image generation fails.
264
+ *
265
+ * **Flow:**
266
+ * 1. Renderer requests image via IPC
267
+ * 2. Main process checks cache → Miss
268
+ * 3. Fetches file tree successfully
269
+ * 4. Starts generation → Error occurs
270
+ * 5. Returns error via IPC
271
+ * 6. Renderer displays fallback UI
272
+ *
273
+ * **Move Events:**
274
+ * - `main.image_requested` (Renderer → Main)
275
+ * - `renderer.generation_error` (Main → Renderer)
276
+ */
277
+ export const ErrorFlow: Story = {
278
+ render: () => (
279
+ <SequenceWithEventsList
280
+ events={workflowData.workflows[3].events}
281
+ edges={workflowData.workflows[3].edges}
282
+ height={650}
283
+ layoutOptions={{
284
+ namespaceStrategy: 'first',
285
+ laneWidth: 200,
286
+ eventSpacing: 80,
287
+ }}
288
+ />
289
+ ),
290
+ parameters: {
291
+ docs: {
292
+ description: {
293
+ story: 'Error handling when image generation fails unexpectedly.',
294
+ },
295
+ },
296
+ },
297
+ };
298
+
299
+ /**
300
+ * **Comparison View**
301
+ *
302
+ * Side-by-side comparison of cache hit vs full generation.
303
+ * Shows how the same canvas structure supports multiple execution paths.
304
+ */
305
+ export const ComparisonView: Story = {
306
+ render: () => (
307
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', height: '100vh' }}>
308
+ <div style={{ display: 'flex', flexDirection: 'column', borderRight: '1px solid #3e3e3e' }}>
309
+ <h3 style={{ margin: '12px', color: '#d4d4d4' }}>Cache Hit (Fast)</h3>
310
+ <div style={{ flex: 1 }}>
311
+ <SequenceDiagramRenderer
312
+ events={workflowData.workflows[0].events}
313
+ edges={workflowData.workflows[0].edges}
314
+ height={600}
315
+ layoutOptions={{
316
+ namespaceStrategy: 'first',
317
+ laneWidth: 180,
318
+ eventSpacing: 70,
319
+ }}
320
+ />
321
+ </div>
322
+ </div>
323
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
324
+ <h3 style={{ margin: '12px', color: '#d4d4d4' }}>Generate New (Full)</h3>
325
+ <div style={{ flex: 1 }}>
326
+ <SequenceDiagramRenderer
327
+ events={workflowData.workflows[1].events}
328
+ edges={workflowData.workflows[1].edges}
329
+ height={600}
330
+ layoutOptions={{
331
+ namespaceStrategy: 'first',
332
+ laneWidth: 180,
333
+ eventSpacing: 70,
334
+ }}
335
+ />
336
+ </div>
337
+ </div>
338
+ </div>
339
+ ),
340
+ parameters: {
341
+ docs: {
342
+ description: {
343
+ story: 'Visual comparison showing branching execution paths from the same canvas.',
344
+ },
345
+ },
346
+ },
347
+ };
348
+
349
+ /**
350
+ * **All Paths Overview**
351
+ *
352
+ * Grid view of all possible execution paths through the system.
353
+ * Demonstrates the canvas as an "aggregation of sequences" - showing
354
+ * the full possibility space that the canvas represents.
355
+ */
356
+ export const AllPathsOverview: Story = {
357
+ render: () => (
358
+ <div style={{
359
+ display: 'grid',
360
+ gridTemplateColumns: '1fr 1fr',
361
+ height: '100vh',
362
+ overflow: 'auto',
363
+ background: '#1e1e1e'
364
+ }}>
365
+ {workflowData.workflows.map((workflow, index) => (
366
+ <div
367
+ key={workflow.id}
368
+ style={{
369
+ display: 'flex',
370
+ flexDirection: 'column',
371
+ borderRight: index % 2 === 0 ? '1px solid #3e3e3e' : 'none',
372
+ borderBottom: index < 2 ? '1px solid #3e3e3e' : 'none'
373
+ }}
374
+ >
375
+ <div style={{ padding: '12px', borderBottom: '1px solid #3e3e3e' }}>
376
+ <h3 style={{ margin: '0 0 4px 0', color: '#d4d4d4' }}>{workflow.name}</h3>
377
+ <p style={{ fontSize: 13, color: '#858585', margin: 0 }}>
378
+ {workflow.description}
379
+ </p>
380
+ </div>
381
+ <div style={{ flex: 1 }}>
382
+ <SequenceDiagramRenderer
383
+ events={workflow.events}
384
+ edges={workflow.edges}
385
+ height={400}
386
+ layoutOptions={{
387
+ namespaceStrategy: 'first',
388
+ laneWidth: 160,
389
+ eventSpacing: 60,
390
+ laneGap: 40,
391
+ }}
392
+ />
393
+ </div>
394
+ </div>
395
+ ))}
396
+ </div>
397
+ ),
398
+ parameters: {
399
+ docs: {
400
+ description: {
401
+ story: 'Complete overview of all execution paths - the "library of possibilities".',
402
+ },
403
+ },
404
+ },
405
+ };
406
+
407
+ /**
408
+ * **Using WorkflowSequenceDiagram Wrapper**
409
+ *
410
+ * Demonstrates the simplified API using the WorkflowSequenceDiagram component.
411
+ * This component automatically handles the conversion from workflow to sequence format.
412
+ */
413
+ export const UsingWorkflowWrapper: Story = {
414
+ render: () => {
415
+ // Convert our workflow data to the expected WorkflowScenario format
416
+ const scenario: WorkflowScenario = {
417
+ id: 'cache-hit-flow',
418
+ name: 'Cache Hit Flow',
419
+ description: 'Optimal path where cached image exists',
420
+ events: workflowData.workflows[1].events.map(e => ({
421
+ name: e.name,
422
+ label: e.label,
423
+ moveEvent: e.moveEvent,
424
+ participant: e.participant,
425
+ })),
426
+ edges: workflowData.workflows[1].edges,
427
+ template: {
428
+ events: {},
429
+ },
430
+ };
431
+
432
+ return (
433
+ <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
434
+ <div style={{
435
+ padding: '12px',
436
+ background: '#1e1e1e',
437
+ borderBottom: '1px solid #3e3e3e',
438
+ color: '#d4d4d4'
439
+ }}>
440
+ <h3 style={{ margin: '0 0 4px 0' }}>WorkflowSequenceDiagram Component</h3>
441
+ <p style={{ fontSize: 13, color: '#858585', margin: 0 }}>
442
+ Cleaner API - just pass the workflow scenario and optional canvas
443
+ </p>
444
+ </div>
445
+ <div style={{ flex: 1 }}>
446
+ <WorkflowSequenceDiagram
447
+ scenario={scenario}
448
+ height="100%"
449
+ layoutOptions={{
450
+ namespaceStrategy: 'first',
451
+ laneWidth: 200,
452
+ eventSpacing: 80,
453
+ }}
454
+ />
455
+ </div>
456
+ </div>
457
+ );
458
+ },
459
+ parameters: {
460
+ docs: {
461
+ description: {
462
+ story: 'Simplified API using the WorkflowSequenceDiagram wrapper component.',
463
+ },
464
+ },
465
+ },
466
+ };
467
+
468
+ /**
469
+ * **Interactive Namespace Strategy**
470
+ *
471
+ * Demonstrates different namespace grouping strategies.
472
+ * Toggle between showing full event namespaces vs collapsed participants.
473
+ */
474
+ export const InteractiveNamespaceStrategy: Story = {
475
+ render: () => {
476
+ const [strategy, setStrategy] = React.useState<'first' | 'all-but-last'>('first');
477
+
478
+ return (
479
+ <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
480
+ <div style={{
481
+ padding: '12px',
482
+ background: '#1e1e1e',
483
+ borderBottom: '1px solid #3e3e3e'
484
+ }}>
485
+ <label style={{ marginRight: 16, color: '#d4d4d4' }}>
486
+ <strong>Namespace Strategy:</strong>
487
+ </label>
488
+ <button
489
+ onClick={() => setStrategy('first')}
490
+ style={{
491
+ padding: '8px 16px',
492
+ marginRight: 8,
493
+ background: strategy === 'first' ? '#3B82F6' : '#2a2a2a',
494
+ color: '#d4d4d4',
495
+ border: '1px solid #3e3e3e',
496
+ borderRadius: 4,
497
+ cursor: 'pointer',
498
+ }}
499
+ >
500
+ First Segment (Participants)
501
+ </button>
502
+ <button
503
+ onClick={() => setStrategy('all-but-last')}
504
+ style={{
505
+ padding: '8px 16px',
506
+ background: strategy === 'all-but-last' ? '#3B82F6' : '#2a2a2a',
507
+ color: '#d4d4d4',
508
+ border: '1px solid #3e3e3e',
509
+ borderRadius: 4,
510
+ cursor: 'pointer',
511
+ }}
512
+ >
513
+ Detailed Namespaces
514
+ </button>
515
+ <p style={{ fontSize: 13, color: '#858585', marginTop: 8, marginBottom: 0 }}>
516
+ {strategy === 'first'
517
+ ? 'Showing high-level participants (renderer.*, main.*) - participant-focused view'
518
+ : 'Showing detailed event namespaces - event-focused view'
519
+ }
520
+ </p>
521
+ </div>
522
+ <div style={{ flex: 1 }}>
523
+ <SequenceDiagramRenderer
524
+ events={workflowData.workflows[1].events}
525
+ edges={workflowData.workflows[1].edges}
526
+ height={700}
527
+ layoutOptions={{
528
+ namespaceStrategy: strategy,
529
+ laneWidth: 200,
530
+ eventSpacing: 80,
531
+ }}
532
+ />
533
+ </div>
534
+ </div>
535
+ );
536
+ },
537
+ parameters: {
538
+ docs: {
539
+ description: {
540
+ story: 'Toggle between participant-level and detailed namespace views.',
541
+ },
542
+ },
543
+ },
544
+ };