@principal-ai/principal-view-react 0.7.11 → 0.7.13

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.
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useMemo } from 'react';
2
2
  import { useTheme } from '@principal-ade/industry-theme';
3
3
  import { HelpCircle } from 'lucide-react';
4
4
 
@@ -20,23 +20,129 @@ interface TestSpan {
20
20
  errorMessage?: string;
21
21
  }
22
22
 
23
+ // OTEL Log types
24
+ export type OtelSeverity = 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
25
+
26
+ export interface OtelLog {
27
+ timestamp: number;
28
+ severity: OtelSeverity;
29
+ body: string | Record<string, unknown>;
30
+ resource: Record<string, string | number>;
31
+ attributes?: Record<string, any>;
32
+ traceId?: string;
33
+ spanId?: string;
34
+ }
35
+
36
+ // Timeline item (event or log)
37
+ interface TimelineItem {
38
+ type: 'event' | 'log';
39
+ time: number;
40
+ // For events
41
+ name?: string;
42
+ attributes?: Record<string, any>;
43
+ // For logs
44
+ severity?: OtelSeverity;
45
+ body?: string | Record<string, unknown>;
46
+ resource?: Record<string, string | number>;
47
+ }
48
+
23
49
  export interface TestEventPanelProps {
24
50
  spans: TestSpan[];
51
+ logs?: OtelLog[]; // Optional for backward compatibility
25
52
  currentSpanIndex: number;
26
53
  currentEventIndex: number;
27
54
  highlightedPhase?: string; // 'setup' | 'execution' | 'assertion'
55
+ onSpanIndexChange?: (index: number) => void;
56
+ }
57
+
58
+ // Helper functions for log severity
59
+ function getSeverityColor(severity: OtelSeverity): string {
60
+ const colors = {
61
+ TRACE: '#6b7280',
62
+ DEBUG: '#60a5fa',
63
+ INFO: '#4ade80',
64
+ WARN: '#fbbf24',
65
+ ERROR: '#f87171',
66
+ FATAL: '#dc2626',
67
+ };
68
+ return colors[severity] || '#9ca3af';
69
+ }
70
+
71
+ function getSeverityIcon(severity: OtelSeverity): string {
72
+ const icons = {
73
+ TRACE: '○',
74
+ DEBUG: '◐',
75
+ INFO: '●',
76
+ WARN: '⚠',
77
+ ERROR: '✕',
78
+ FATAL: '☠',
79
+ };
80
+ return icons[severity] || '•';
28
81
  }
29
82
 
30
83
  export const TestEventPanel: React.FC<TestEventPanelProps> = ({
31
84
  spans,
85
+ logs = [],
32
86
  currentSpanIndex,
33
87
  currentEventIndex,
34
88
  highlightedPhase,
89
+ onSpanIndexChange,
35
90
  }) => {
36
91
  const { theme } = useTheme();
37
92
  const [showHelp, setShowHelp] = useState(false);
93
+ const [viewMode, setViewMode] = useState<'all' | 'events' | 'logs'>('all');
94
+
38
95
  const currentSpan = spans[currentSpanIndex];
39
- const eventsUpToNow = currentSpan?.events.slice(0, currentEventIndex + 1) || [];
96
+
97
+ const handlePrevTest = () => {
98
+ if (currentSpanIndex > 0 && onSpanIndexChange) {
99
+ onSpanIndexChange(currentSpanIndex - 1);
100
+ }
101
+ };
102
+
103
+ const handleNextTest = () => {
104
+ if (currentSpanIndex < spans.length - 1 && onSpanIndexChange) {
105
+ onSpanIndexChange(currentSpanIndex + 1);
106
+ }
107
+ };
108
+
109
+ // Build interleaved timeline
110
+ const timeline = useMemo(() => {
111
+ if (!currentSpan) return [];
112
+
113
+ const items: TimelineItem[] = [
114
+ // Span events
115
+ ...currentSpan.events.slice(0, currentEventIndex + 1).map((event) => ({
116
+ type: 'event' as const,
117
+ time: event.time,
118
+ name: event.name,
119
+ attributes: event.attributes,
120
+ })),
121
+
122
+ // Correlated logs (matching current span's traceId)
123
+ ...logs
124
+ .filter((log) => log.traceId === currentSpan.id)
125
+ .map((log) => ({
126
+ type: 'log' as const,
127
+ time: typeof log.timestamp === 'number' ? log.timestamp : new Date(log.timestamp).getTime(),
128
+ severity: log.severity,
129
+ body: log.body,
130
+ resource: log.resource,
131
+ attributes: log.attributes,
132
+ })),
133
+ ].sort((a, b) => a.time - b.time);
134
+
135
+ return items;
136
+ }, [currentSpan, currentEventIndex, logs]);
137
+
138
+ // Filter timeline based on view mode
139
+ const filteredTimeline = useMemo(() => {
140
+ if (viewMode === 'all') return timeline;
141
+ return timeline.filter((item) => item.type === viewMode.slice(0, -1)); // 'events' -> 'event', 'logs' -> 'log'
142
+ }, [timeline, viewMode]);
143
+
144
+ const eventCount = timeline.filter((i) => i.type === 'event').length;
145
+ const logCount = timeline.filter((i) => i.type === 'log').length;
40
146
 
41
147
  return (
42
148
  <div
@@ -45,40 +151,147 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
45
151
  height: '100%',
46
152
  backgroundColor: theme.colors.background,
47
153
  color: theme.colors.text,
48
- padding: '20px',
49
154
  fontFamily: theme.fonts.monospace,
50
155
  fontSize: '14px',
51
- overflow: 'auto',
52
156
  boxSizing: 'border-box',
157
+ display: 'flex',
158
+ flexDirection: 'column',
53
159
  }}
54
160
  >
55
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '15px' }}>
56
- <div style={{ fontWeight: 'bold', fontSize: '18px' }}>
57
- Wide Event Pattern - Code Journey
161
+ {/* Static Header */}
162
+ <div
163
+ style={{
164
+ padding: '20px 20px 0 20px',
165
+ backgroundColor: theme.colors.background,
166
+ borderBottom: `1px solid ${theme.colors.border}`,
167
+ flexShrink: 0,
168
+ }}
169
+ >
170
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px' }}>
171
+ <div style={{ fontWeight: 'bold', fontSize: '18px' }}>
172
+ Execution Timeline
173
+ </div>
174
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
175
+ <div style={{ fontSize: '13px', color: theme.colors.textMuted }}>
176
+ <span style={{ color: '#4ade80' }}>All Passed ✓</span>
177
+ </div>
178
+ <button
179
+ onClick={() => setShowHelp(true)}
180
+ style={{
181
+ background: 'transparent',
182
+ border: 'none',
183
+ cursor: 'pointer',
184
+ padding: '4px',
185
+ display: 'flex',
186
+ alignItems: 'center',
187
+ color: theme.colors.textMuted,
188
+ }}
189
+ onMouseEnter={(e) => {
190
+ e.currentTarget.style.color = theme.colors.text;
191
+ }}
192
+ onMouseLeave={(e) => {
193
+ e.currentTarget.style.color = theme.colors.textMuted;
194
+ }}
195
+ >
196
+ <HelpCircle size={20} />
197
+ </button>
198
+ </div>
199
+ </div>
200
+
201
+ {/* Test Navigation */}
202
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '12px' }}>
203
+ <button
204
+ onClick={handlePrevTest}
205
+ disabled={currentSpanIndex === 0}
206
+ style={{
207
+ padding: '4px 12px',
208
+ background: theme.colors.surface,
209
+ border: `1px solid ${theme.colors.border}`,
210
+ borderRadius: '4px',
211
+ color: currentSpanIndex === 0 ? theme.colors.textMuted : theme.colors.text,
212
+ cursor: currentSpanIndex === 0 ? 'not-allowed' : 'pointer',
213
+ fontSize: '14px',
214
+ opacity: currentSpanIndex === 0 ? 0.5 : 1,
215
+ }}
216
+ >
217
+ ← Prev
218
+ </button>
219
+ <div style={{ fontSize: '13px', color: theme.colors.textMuted }}>
220
+ Test {currentSpanIndex + 1} of {spans.length}
221
+ </div>
222
+ <button
223
+ onClick={handleNextTest}
224
+ disabled={currentSpanIndex === spans.length - 1}
225
+ style={{
226
+ padding: '4px 12px',
227
+ background: theme.colors.surface,
228
+ border: `1px solid ${theme.colors.border}`,
229
+ borderRadius: '4px',
230
+ color: currentSpanIndex === spans.length - 1 ? theme.colors.textMuted : theme.colors.text,
231
+ cursor: currentSpanIndex === spans.length - 1 ? 'not-allowed' : 'pointer',
232
+ fontSize: '14px',
233
+ opacity: currentSpanIndex === spans.length - 1 ? 0.5 : 1,
234
+ }}
235
+ >
236
+ Next →
237
+ </button>
238
+ <div style={{ fontSize: '12px', color: theme.colors.textMuted, marginLeft: 'auto' }}>
239
+ {eventCount} events, {logCount} logs
240
+ </div>
241
+ </div>
242
+
243
+ {/* Filter Tabs */}
244
+ <div style={{ display: 'flex', gap: '8px', marginBottom: '12px' }}>
245
+ <button
246
+ onClick={() => setViewMode('all')}
247
+ style={{
248
+ padding: '6px 12px',
249
+ background: viewMode === 'all' ? theme.colors.primary : 'transparent',
250
+ border: `1px solid ${theme.colors.border}`,
251
+ borderRadius: '4px',
252
+ color: viewMode === 'all' ? '#ffffff' : theme.colors.text,
253
+ cursor: 'pointer',
254
+ fontSize: '12px',
255
+ fontWeight: 500,
256
+ }}
257
+ >
258
+ All ({timeline.length})
259
+ </button>
260
+ <button
261
+ onClick={() => setViewMode('events')}
262
+ style={{
263
+ padding: '6px 12px',
264
+ background: viewMode === 'events' ? theme.colors.primary : 'transparent',
265
+ border: `1px solid ${theme.colors.border}`,
266
+ borderRadius: '4px',
267
+ color: viewMode === 'events' ? '#ffffff' : theme.colors.text,
268
+ cursor: 'pointer',
269
+ fontSize: '12px',
270
+ fontWeight: 500,
271
+ }}
272
+ >
273
+ Events ({eventCount})
274
+ </button>
275
+ <button
276
+ onClick={() => setViewMode('logs')}
277
+ style={{
278
+ padding: '6px 12px',
279
+ background: viewMode === 'logs' ? theme.colors.primary : 'transparent',
280
+ border: `1px solid ${theme.colors.border}`,
281
+ borderRadius: '4px',
282
+ color: viewMode === 'logs' ? '#ffffff' : theme.colors.text,
283
+ cursor: 'pointer',
284
+ fontSize: '12px',
285
+ fontWeight: 500,
286
+ }}
287
+ >
288
+ Logs ({logCount})
289
+ </button>
290
+ </div>
291
+
292
+ <div style={{ fontSize: '13px', color: theme.colors.textMuted, marginBottom: '15px' }}>
293
+ Test: {currentSpan?.name || 'Loading...'}
58
294
  </div>
59
- <button
60
- onClick={() => setShowHelp(true)}
61
- style={{
62
- background: 'transparent',
63
- border: 'none',
64
- cursor: 'pointer',
65
- padding: '4px',
66
- display: 'flex',
67
- alignItems: 'center',
68
- color: theme.colors.textMuted,
69
- }}
70
- onMouseEnter={(e) => {
71
- e.currentTarget.style.color = theme.colors.text;
72
- }}
73
- onMouseLeave={(e) => {
74
- e.currentTarget.style.color = theme.colors.textMuted;
75
- }}
76
- >
77
- <HelpCircle size={20} />
78
- </button>
79
- </div>
80
- <div style={{ fontSize: '13px', color: theme.colors.textMuted, marginBottom: '15px' }}>
81
- Test: {currentSpan?.name || 'Loading...'}
82
295
  </div>
83
296
 
84
297
  {/* Help Modal */}
@@ -114,9 +327,15 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
114
327
  </div>
115
328
  <div style={{ fontSize: '14px', marginBottom: '16px', lineHeight: '1.6' }}>
116
329
  <p style={{ marginBottom: '12px' }}>
117
- <strong>Watch how execution flows through files:</strong>
330
+ <strong>Timeline shows both events and logs:</strong>
118
331
  </p>
119
332
  <ul style={{ marginLeft: '20px', marginBottom: '16px' }}>
333
+ <li style={{ marginBottom: '8px' }}>
334
+ <span style={{ color: '#f59e0b' }}>🟧 Events</span> - Structured lifecycle points
335
+ </li>
336
+ <li style={{ marginBottom: '8px' }}>
337
+ <span style={{ color: '#4ade80' }}>● Logs</span> - Standalone log records (color = severity)
338
+ </li>
120
339
  <li style={{ marginBottom: '8px' }}>
121
340
  <span style={{ color: '#60a5fa' }}>Blue = Test file</span>
122
341
  </li>
@@ -125,30 +344,20 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
125
344
  </li>
126
345
  </ul>
127
346
  <p style={{ marginBottom: '12px' }}>
128
- <strong>Span Context (Static)</strong>
347
+ <strong>Use filter tabs to focus:</strong>
129
348
  </p>
130
- <pre
131
- style={{
132
- background: theme.colors.surface,
133
- padding: '12px',
134
- borderRadius: '4px',
135
- fontSize: '13px',
136
- overflow: 'auto',
137
- }}
138
- >
139
- {`{
140
- "test.file": "GraphConverter.test.ts",
141
- "test.suite": "GraphConverter",
142
- "test.result": "pass"
143
- }`}
144
- </pre>
349
+ <ul style={{ marginLeft: '20px' }}>
350
+ <li>All - Interleaved timeline</li>
351
+ <li>Events - Span events only</li>
352
+ <li>Logs - OTEL logs only</li>
353
+ </ul>
145
354
  </div>
146
355
  <button
147
356
  onClick={() => setShowHelp(false)}
148
357
  style={{
149
358
  padding: '8px 16px',
150
359
  backgroundColor: theme.colors.primary,
151
- color: theme.colors.background,
360
+ color: '#ffffff',
152
361
  border: 'none',
153
362
  borderRadius: '4px',
154
363
  cursor: 'pointer',
@@ -162,27 +371,26 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
162
371
  </div>
163
372
  )}
164
373
 
165
- {currentSpan && (
166
- <>
167
- {/* Event Timeline (context mutations) */}
374
+ {/* Scrollable Content */}
375
+ <div
376
+ style={{
377
+ flex: 1,
378
+ overflow: 'auto',
379
+ padding: '20px',
380
+ }}
381
+ >
382
+ {/* Timeline Items */}
383
+ {currentSpan && (
168
384
  <div>
169
- <div
170
- style={{
171
- color: '#4ade80',
172
- fontWeight: 'bold',
173
- marginBottom: '8px',
174
- fontSize: '15px',
175
- }}
176
- >
177
- Event Timeline (Context Mutations)
178
- </div>
179
- {eventsUpToNow.map((event, idx) => {
180
- const filepath = event.attributes['code.filepath'] as string;
181
- const lineno = event.attributes['code.lineno'] as number;
385
+ {filteredTimeline.map((item, idx) => {
386
+ if (item.type === 'event') {
387
+ // SPAN EVENT RENDERING
388
+ const filepath = item.attributes?.['code.filepath'] as string;
389
+ const lineno = item.attributes?.['code.lineno'] as number;
182
390
  const isCodeUnderTest = filepath && filepath !== 'GraphConverter.test.ts';
183
391
 
184
392
  // Determine which phase this event belongs to
185
- const eventPhase = event.name.split('.')[0]; // 'setup', 'execution', 'assertion'
393
+ const eventPhase = item.name?.split('.')[0]; // 'setup', 'execution', 'assertion'
186
394
  const isHighlighted = highlightedPhase === eventPhase;
187
395
 
188
396
  return (
@@ -191,12 +399,14 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
191
399
  style={{
192
400
  marginBottom: '12px',
193
401
  paddingBottom: '12px',
194
- borderBottom: idx < eventsUpToNow.length - 1 ? `1px solid ${theme.colors.border}` : 'none',
402
+ paddingLeft: '12px',
403
+ borderBottom: idx < filteredTimeline.length - 1 ? `1px solid ${theme.colors.border}` : 'none',
404
+ borderLeft: '3px solid #f59e0b',
195
405
  opacity: highlightedPhase && !isHighlighted ? 0.4 : 1,
196
406
  transition: 'opacity 0.2s ease',
197
407
  transform: isHighlighted ? 'scale(1.02)' : 'scale(1)',
198
408
  backgroundColor: isHighlighted ? theme.colors.surface : 'transparent',
199
- padding: isHighlighted ? '8px' : '0',
409
+ padding: isHighlighted ? '8px 8px 8px 12px' : '0 0 12px 12px',
200
410
  borderRadius: '4px',
201
411
  }}
202
412
  >
@@ -209,8 +419,8 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
209
419
  gap: '8px',
210
420
  }}
211
421
  >
212
- <div style={{ color: '#f59e0b', fontSize: '13px', flexShrink: 0 }}>
213
- {idx + 1}. {event.name}
422
+ <div style={{ color: '#f59e0b', fontSize: '13px', fontWeight: 'bold', flexShrink: 0 }}>
423
+ EVENT: {item.name}
214
424
  </div>
215
425
  {filepath && (
216
426
  <div
@@ -247,7 +457,7 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
247
457
  >
248
458
  {JSON.stringify(
249
459
  Object.fromEntries(
250
- Object.entries(event.attributes).filter(
460
+ Object.entries(item.attributes || {}).filter(
251
461
  ([key]) => key !== 'code.filepath' && key !== 'code.lineno'
252
462
  )
253
463
  ),
@@ -257,30 +467,97 @@ export const TestEventPanel: React.FC<TestEventPanelProps> = ({
257
467
  </pre>
258
468
  </div>
259
469
  );
260
- })}
261
- </div>
262
- </>
263
- )}
470
+ } else {
471
+ // OTEL LOG RENDERING
472
+ const serviceName = item.resource?.['service.name'];
473
+ const severityColor = getSeverityColor(item.severity!);
264
474
 
265
- <div
266
- style={{
267
- marginTop: '20px',
268
- paddingTop: '15px',
269
- borderTop: `1px solid ${theme.colors.border}`,
270
- fontSize: '13px',
271
- color: theme.colors.textMuted,
272
- }}
273
- >
274
- <div style={{ marginBottom: '8px' }}>
275
- <strong>Total tests:</strong> {spans.length}
276
- </div>
277
- <div style={{ marginBottom: '8px' }}>
278
- <strong>Pattern:</strong> One span per test + event timeline
279
- </div>
280
- <div>
281
- <strong>Status:</strong>{' '}
282
- <span style={{ color: '#4ade80' }}>All Passed ✓</span>
475
+ return (
476
+ <div
477
+ key={idx}
478
+ style={{
479
+ marginBottom: '12px',
480
+ paddingBottom: '12px',
481
+ paddingLeft: '12px',
482
+ borderBottom: idx < filteredTimeline.length - 1 ? `1px solid ${theme.colors.border}` : 'none',
483
+ borderLeft: `3px solid ${severityColor}`,
484
+ }}
485
+ >
486
+ <div
487
+ style={{
488
+ display: 'flex',
489
+ justifyContent: 'space-between',
490
+ alignItems: 'center',
491
+ marginBottom: '4px',
492
+ }}
493
+ >
494
+ <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
495
+ <span style={{ fontSize: '16px' }}>{getSeverityIcon(item.severity!)}</span>
496
+ <span
497
+ style={{
498
+ color: severityColor,
499
+ fontSize: '13px',
500
+ fontWeight: 'bold',
501
+ }}
502
+ >
503
+ LOG: {item.severity}
504
+ </span>
505
+ </div>
506
+ {serviceName && (
507
+ <div
508
+ style={{
509
+ fontSize: '12px',
510
+ color: '#9ca3af',
511
+ background: '#1e293b',
512
+ padding: '2px 6px',
513
+ borderRadius: '3px',
514
+ }}
515
+ >
516
+ {serviceName}
517
+ </div>
518
+ )}
519
+ </div>
520
+
521
+ {/* Log body */}
522
+ <div
523
+ style={{
524
+ background: theme.colors.surface,
525
+ padding: '8px',
526
+ borderRadius: '4px',
527
+ marginBottom: item.attributes && Object.keys(item.attributes).length > 0 ? '8px' : '0',
528
+ fontSize: '13px',
529
+ }}
530
+ >
531
+ {typeof item.body === 'string' ? (
532
+ item.body
533
+ ) : (
534
+ <pre style={{ margin: 0, fontSize: '12px' }}>
535
+ {JSON.stringify(item.body, null, 2)}
536
+ </pre>
537
+ )}
538
+ </div>
539
+
540
+ {/* Log attributes */}
541
+ {item.attributes && Object.keys(item.attributes).length > 0 && (
542
+ <pre
543
+ style={{
544
+ background: theme.colors.surface,
545
+ padding: '8px',
546
+ borderRadius: '4px',
547
+ margin: 0,
548
+ fontSize: '11px',
549
+ opacity: 0.8,
550
+ }}
551
+ >
552
+ {JSON.stringify(item.attributes, null, 2)}
553
+ </pre>
554
+ )}
555
+ </div>
556
+ );
557
+ }
558
+ })}
283
559
  </div>
560
+ )}
284
561
  </div>
285
562
  </div>
286
563
  );
@@ -188,16 +188,33 @@ const testExecutionCanvas: ExtendedCanvas = {
188
188
 
189
189
  const AnimatedTestExecution = () => {
190
190
  const [events] = useState<GraphEvent[]>([]);
191
- const [currentSpanIndex] = useState(0);
191
+ const [currentSpanIndex, setCurrentSpanIndex] = useState(0);
192
192
  // Show all events by default - set to a large number
193
193
  const [currentEventIndex] = useState(999);
194
194
  const [highlightedPhase, setHighlightedPhase] = useState<string | undefined>();
195
195
 
196
+ // Extract spans and logs from test data
197
+ const testData = testSpans as any;
198
+ const spans = Array.isArray(testData) ? testData : testData.spans || testData;
199
+ const logs = testData.logs || [];
200
+
196
201
  return (
197
202
  <div style={{ display: 'flex', width: '100vw', height: '100vh' }}>
198
- {/* Graph Visualization - Left Side */}
203
+ {/* Event Panel - Left Side */}
204
+ <div style={{ flex: '0 0 50%', height: '100%', borderRight: `1px solid #333`, overflow: 'hidden' }}>
205
+ <TestEventPanel
206
+ spans={spans}
207
+ logs={logs}
208
+ currentSpanIndex={currentSpanIndex}
209
+ currentEventIndex={currentEventIndex}
210
+ highlightedPhase={highlightedPhase}
211
+ onSpanIndexChange={setCurrentSpanIndex}
212
+ />
213
+ </div>
214
+
215
+ {/* Graph Visualization - Right Side */}
199
216
  <div
200
- style={{ flex: '0 0 60%', height: '100%', position: 'relative' }}
217
+ style={{ flex: '0 0 50%', height: '100%', position: 'relative' }}
201
218
  onMouseLeave={() => setHighlightedPhase(undefined)}
202
219
  >
203
220
  <div
@@ -218,16 +235,6 @@ const AnimatedTestExecution = () => {
218
235
  />
219
236
  </div>
220
237
  </div>
221
-
222
- {/* Event Panel - Right Side */}
223
- <div style={{ flex: '0 0 40%', height: '100%', borderLeft: `1px solid #333`, overflow: 'hidden' }}>
224
- <TestEventPanel
225
- spans={testSpans as any}
226
- currentSpanIndex={currentSpanIndex}
227
- currentEventIndex={currentEventIndex}
228
- highlightedPhase={highlightedPhase}
229
- />
230
- </div>
231
238
  </div>
232
239
  );
233
240
  };
@@ -263,18 +270,25 @@ export const StaticView: Story = {
263
270
  /**
264
271
  * Event panel component showing test execution narrative with file/line information.
265
272
  *
266
- * Shows how events accumulate context as tests execute, with automatic file/line
267
- * capture from stack traces and manual override for code under test.
273
+ * Shows how events and logs are interleaved in chronological order, with automatic
274
+ * file/line capture from stack traces and severity-based color coding for logs.
268
275
  */
269
276
  export const EventPanelOnly: StoryObj = {
270
- render: () => (
271
- <div style={{ width: '600px', height: '100vh' }}>
272
- <TestEventPanel
273
- spans={testSpans as any}
274
- currentSpanIndex={0}
275
- currentEventIndex={999} // Show all events
276
- highlightedPhase={undefined}
277
- />
278
- </div>
279
- ),
277
+ render: () => {
278
+ const testData = testSpans as any;
279
+ const spans = Array.isArray(testData) ? testData : testData.spans || testData;
280
+ const logs = testData.logs || [];
281
+
282
+ return (
283
+ <div style={{ width: '600px', height: '100vh' }}>
284
+ <TestEventPanel
285
+ spans={spans}
286
+ logs={logs}
287
+ currentSpanIndex={0}
288
+ currentEventIndex={999} // Show all events
289
+ highlightedPhase={undefined}
290
+ />
291
+ </div>
292
+ );
293
+ },
280
294
  };