From 10a72a8cdb7a0e338298cc53a8cfc2ca2ae98d7f Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 11:58:27 +0100 Subject: [PATCH 01/15] [web-shared] Improve spacing, styling, and scroll behavior of trace viewer sidebar Signed-off-by: Peter Wielander --- .changeset/clear-carpets-slide.md | 5 ++ .../src/sidebar/attribute-panel.tsx | 88 +++++++++---------- .../src/sidebar/conversation-view.tsx | 2 +- .../web-shared/src/sidebar/events-list.tsx | 8 +- 4 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 .changeset/clear-carpets-slide.md diff --git a/.changeset/clear-carpets-slide.md b/.changeset/clear-carpets-slide.md new file mode 100644 index 0000000000..72c9369868 --- /dev/null +++ b/.changeset/clear-carpets-slide.md @@ -0,0 +1,5 @@ +--- +"@workflow/web-shared": patch +--- + +Improve spacing, styling and scroll behavior of trace viewer sidebar diff --git a/packages/web-shared/src/sidebar/attribute-panel.tsx b/packages/web-shared/src/sidebar/attribute-panel.tsx index ce0d707ad9..2559119b9a 100644 --- a/packages/web-shared/src/sidebar/attribute-panel.tsx +++ b/packages/web-shared/src/sidebar/attribute-panel.tsx @@ -4,18 +4,40 @@ import { parseStepName, parseWorkflowName } from '@workflow/core/parse-name'; import type { Event, Hook, Step, WorkflowRun } from '@workflow/world'; import type { ModelMessage } from 'ai'; import { AlertCircle } from 'lucide-react'; -import { - createContext, - type ReactNode, - useContext, - useMemo, - useState, -} from 'react'; +import type { ReactNode } from 'react'; +import { createContext, useContext, useMemo, useState } from 'react'; import { Alert, AlertDescription, AlertTitle } from '../components/ui/alert'; import { extractConversation, isDoStreamStep } from '../lib/utils'; import { ConversationView } from './conversation-view'; import { DetailCard } from './detail-card'; +/** + * Tab button for conversation/JSON toggle + */ +function TabButton({ + active, + onClick, + children, +}: { + active: boolean; + onClick: () => void; + children: ReactNode; +}) { + return ( + + ); +} + /** * Tabbed view for conversation and raw JSON */ @@ -36,54 +58,28 @@ function ConversationWithTabs({ className="rounded-md border" style={{ borderColor: 'var(--ds-gray-300)' }} > - {/* Tab buttons */}
- - +
- {/* Tab content */} {activeTab === 'conversation' ? ( ) : ( -
+
{Array.isArray(args) ? args.map((v, i) => (
@@ -178,7 +174,9 @@ const StreamRefDisplay = ({ streamRef }: { streamRef: StreamRef }) => { {streamRef.streamId.length > 40 - ? `${streamRef.streamId.slice(0, 20)}...${streamRef.streamId.slice(-15)}` + ? `${streamRef.streamId.slice(0, 20)}...${streamRef.streamId.slice( + -15 + )}` : streamRef.streamId} ); @@ -687,9 +685,11 @@ export const AttributePanel = ({
)} {error ? ( - - - Failed to load resource details + +
+ + Failed to load resource details +
{error.message} diff --git a/packages/web-shared/src/sidebar/conversation-view.tsx b/packages/web-shared/src/sidebar/conversation-view.tsx index 3011f67bfa..ef303c4c59 100644 --- a/packages/web-shared/src/sidebar/conversation-view.tsx +++ b/packages/web-shared/src/sidebar/conversation-view.tsx @@ -18,7 +18,7 @@ export function ConversationView({ messages }: ConversationViewProps) { } return ( -
+
{messages.map((message, index) => ( ))} diff --git a/packages/web-shared/src/sidebar/events-list.tsx b/packages/web-shared/src/sidebar/events-list.tsx index 4d1ea58c67..bc1fa87e51 100644 --- a/packages/web-shared/src/sidebar/events-list.tsx +++ b/packages/web-shared/src/sidebar/events-list.tsx @@ -63,9 +63,11 @@ export function EventsList({ {/* Events section */} {eventError ? ( - - - Failed to load event data + +
+ + Failed to load event data +
{eventError.message} From 591674a3acf600fa3e37e0618f843e2731f22502 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:05:49 +0100 Subject: [PATCH 02/15] Update packages/web-shared/src/sidebar/events-list.tsx Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Signed-off-by: Peter Wielander --- packages/web-shared/src/sidebar/events-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-shared/src/sidebar/events-list.tsx b/packages/web-shared/src/sidebar/events-list.tsx index bc1fa87e51..67f9fa63d3 100644 --- a/packages/web-shared/src/sidebar/events-list.tsx +++ b/packages/web-shared/src/sidebar/events-list.tsx @@ -64,7 +64,7 @@ export function EventsList({ {/* Events section */} {eventError ? ( -
+
Failed to load event data
From 6f7d6c5b3d41b243913ee5b0aa0ec4b5e807c73e Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:14:39 +0100 Subject: [PATCH 03/15] New error display Signed-off-by: Peter Wielander --- .changeset/clear-carpets-slide.md | 2 +- .../src/components/ui/error-card.tsx | 63 +++++++++++++++++++ packages/web-shared/src/error-boundary.tsx | 42 +++---------- .../src/sidebar/attribute-panel.tsx | 17 ++--- .../web-shared/src/sidebar/events-list.tsx | 18 ++---- 5 files changed, 85 insertions(+), 57 deletions(-) create mode 100644 packages/web-shared/src/components/ui/error-card.tsx diff --git a/.changeset/clear-carpets-slide.md b/.changeset/clear-carpets-slide.md index 72c9369868..5c292a3dee 100644 --- a/.changeset/clear-carpets-slide.md +++ b/.changeset/clear-carpets-slide.md @@ -2,4 +2,4 @@ "@workflow/web-shared": patch --- -Improve spacing, styling and scroll behavior of trace viewer sidebar +Improve styling, error display, and scroll behavior of trace viewer sidebar diff --git a/packages/web-shared/src/components/ui/error-card.tsx b/packages/web-shared/src/components/ui/error-card.tsx new file mode 100644 index 0000000000..76d780fc66 --- /dev/null +++ b/packages/web-shared/src/components/ui/error-card.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { AlertCircle, ChevronDown } from 'lucide-react'; +import { useState } from 'react'; +import { cn } from '../../lib/utils'; + +interface ErrorCardProps { + /** Title shown in the header */ + title: string; + /** Error message or details to show when expanded */ + details?: string; + /** Additional class names */ + className?: string; +} + +/** + * A collapsible error card that shows a title with an error icon, + * and expands to reveal details when clicked. + */ +export function ErrorCard({ + title, + className, + details = 'Unknown error', +}: ErrorCardProps) { + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
+ + + {isExpanded && details && ( +
+
+            {details}
+          
+
+ )} +
+ ); +} diff --git a/packages/web-shared/src/error-boundary.tsx b/packages/web-shared/src/error-boundary.tsx index aff906753f..d751f32bde 100644 --- a/packages/web-shared/src/error-boundary.tsx +++ b/packages/web-shared/src/error-boundary.tsx @@ -1,9 +1,7 @@ 'use client'; -import { AlertCircle } from 'lucide-react'; import React, { type ReactNode } from 'react'; -import { Alert, AlertDescription, AlertTitle } from './components/ui/alert'; -import { Card, CardContent, CardHeader, CardTitle } from './components/ui/card'; +import { ErrorCard } from './components/ui/error-card'; interface ErrorBoundaryProps { children: ReactNode; @@ -66,37 +64,15 @@ export class ErrorBoundary extends React.Component< } // Default error UI - return ( - - - - - {this.props.title || 'Error'} - - - - - - Something went wrong - - {this.props.description || - 'An unexpected error occurred in this section.'} - - + const errorDetails = this.state.error.stack + ? `${this.state.error.message}\n\n${this.state.error.stack}` + : this.state.error.message; -
-
Error details:
-
- {this.state.error.message} -
- {this.state.error.stack && ( -
- {this.state.error.stack} -
- )} -
-
-
+ return ( + ); } diff --git a/packages/web-shared/src/sidebar/attribute-panel.tsx b/packages/web-shared/src/sidebar/attribute-panel.tsx index 2559119b9a..547b7c529e 100644 --- a/packages/web-shared/src/sidebar/attribute-panel.tsx +++ b/packages/web-shared/src/sidebar/attribute-panel.tsx @@ -3,10 +3,9 @@ import { parseStepName, parseWorkflowName } from '@workflow/core/parse-name'; import type { Event, Hook, Step, WorkflowRun } from '@workflow/world'; import type { ModelMessage } from 'ai'; -import { AlertCircle } from 'lucide-react'; import type { ReactNode } from 'react'; import { createContext, useContext, useMemo, useState } from 'react'; -import { Alert, AlertDescription, AlertTitle } from '../components/ui/alert'; +import { ErrorCard } from '../components/ui/error-card'; import { extractConversation, isDoStreamStep } from '../lib/utils'; import { ConversationView } from './conversation-view'; import { DetailCard } from './detail-card'; @@ -685,15 +684,11 @@ export const AttributePanel = ({
)} {error ? ( - -
- - Failed to load resource details -
- - {error.message} - -
+ ) : hasExpired ? ( ) : ( diff --git a/packages/web-shared/src/sidebar/events-list.tsx b/packages/web-shared/src/sidebar/events-list.tsx index 67f9fa63d3..18fb918689 100644 --- a/packages/web-shared/src/sidebar/events-list.tsx +++ b/packages/web-shared/src/sidebar/events-list.tsx @@ -1,13 +1,12 @@ 'use client'; -import { AlertCircle } from 'lucide-react'; import { useCallback } from 'react'; import useSWR from 'swr'; import { type EnvMap, fetchEventsByCorrelationId, } from '../api/workflow-server-actions'; -import { Alert, AlertDescription, AlertTitle } from '../components/ui/alert'; +import { ErrorCard } from '../components/ui/error-card'; import type { SpanEvent } from '../trace-viewer/types'; import { convertEventsToSpanEvents } from '../workflow-traces/trace-span-construction'; import { AttributeBlock, localMillisecondTime } from './attribute-panel'; @@ -61,17 +60,12 @@ export function EventsList({ > Events {!eventsLoading && `(${displayData.length})`} - {/* Events section */} {eventError ? ( - -
- - Failed to load event data -
- - {eventError.message} - -
+ ) : null} {eventsLoading ?
Loading events...
: null} {!eventsLoading && !eventError && displayData.length === 0 && ( From 7ec8fc6c57c1b3abc1d9c4df7a5c61efb5fbabd2 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:19:40 +0100 Subject: [PATCH 04/15] Fix error size Signed-off-by: Peter Wielander --- .../src/components/ui/error-card.tsx | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/web-shared/src/components/ui/error-card.tsx b/packages/web-shared/src/components/ui/error-card.tsx index 76d780fc66..e3d42e24a4 100644 --- a/packages/web-shared/src/components/ui/error-card.tsx +++ b/packages/web-shared/src/components/ui/error-card.tsx @@ -25,39 +25,41 @@ export function ErrorCard({ const [isExpanded, setIsExpanded] = useState(false); return ( -
- + - {isExpanded && details && ( -
-
-            {details}
-          
-
- )} + {isExpanded && details && ( +
+
+              {details}
+            
+
+ )} +
); } From 927ae90ca1ac08e79164b2e4c3501f8b82d21572 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:31:54 +0100 Subject: [PATCH 05/15] Event list Signed-off-by: Peter Wielander --- packages/web-shared/src/events-list.tsx | 153 ++++++++++++++++++ packages/web-shared/src/index.ts | 1 + .../web/src/components/run-detail-view.tsx | 24 ++- 3 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 packages/web-shared/src/events-list.tsx diff --git a/packages/web-shared/src/events-list.tsx b/packages/web-shared/src/events-list.tsx new file mode 100644 index 0000000000..56b8b1bcc5 --- /dev/null +++ b/packages/web-shared/src/events-list.tsx @@ -0,0 +1,153 @@ +'use client'; + +import type { Event } from '@workflow/world'; +import { useMemo } from 'react'; +import { getEventColor } from './workflow-traces/event-colors'; + +/** + * Format a date to a human-readable local time string with milliseconds + */ +function formatEventTime(date: Date): string { + return ( + date.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + }) + + '.' + + date.getMilliseconds().toString().padStart(3, '0') + ); +} + +/** + * Format event type to a more readable label + */ +function formatEventType(eventType: Event['eventType']): string { + return eventType + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +interface EventsListProps { + events: Event[] | null; +} + +/** + * Displays a list of all events for a workflow run as colored cards in a pseudo-table. + * Events are sorted by createdAt (oldest first). + */ +export function EventsList({ events }: EventsListProps) { + // Sort events by createdAt (oldest first) + const sortedEvents = useMemo(() => { + if (!events || events.length === 0) return []; + return [...events].sort( + (a, b) => + new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() + ); + }, [events]); + + if (!events || events.length === 0) { + return ( +
+ No events found +
+ ); + } + + return ( +
+ {/* Header row */} +
+
Time
+
Event Type
+
Correlation ID
+
Event ID
+
+ + {/* Event rows */} +
+ {sortedEvents.map((event) => { + const colors = getEventColor(event.eventType); + const createdAt = new Date(event.createdAt); + + return ( +
+ {/* Time */} +
+ {formatEventTime(createdAt)} +
+ + {/* Event Type */} +
+ + + {formatEventType(event.eventType)} + +
+ + {/* Correlation ID */} +
+ {event.correlationId + ? event.correlationId.slice(0, 12) + '...' + : '-'} +
+ + {/* Event ID */} +
+ {event.eventId.slice(0, 16) + '...'} +
+
+ ); + })} +
+ + {/* Summary */} +
+ {sortedEvents.length} event{sortedEvents.length !== 1 ? 's' : ''} total +
+
+ ); +} diff --git a/packages/web-shared/src/index.ts b/packages/web-shared/src/index.ts index 30ad3827a7..db5006c03b 100644 --- a/packages/web-shared/src/index.ts +++ b/packages/web-shared/src/index.ts @@ -7,6 +7,7 @@ export type { Event, Hook, Step, WorkflowRun } from '@workflow/world'; export * from './api/workflow-api-client'; export type { EnvMap, PublicServerConfig } from './api/workflow-server-actions'; export { ErrorBoundary } from './error-boundary'; +export { EventsList } from './events-list'; export type { HookActionCallbacks, HookActionsDropdownItemProps, diff --git a/packages/web/src/components/run-detail-view.tsx b/packages/web/src/components/run-detail-view.tsx index e3eea07c26..c5179c9018 100644 --- a/packages/web/src/components/run-detail-view.tsx +++ b/packages/web/src/components/run-detail-view.tsx @@ -6,6 +6,7 @@ import { type EnvMap, ErrorBoundary, type Event, + EventsList, recreateRun, type Step, StreamViewer, @@ -154,6 +155,8 @@ interface RunDetailViewProps { selectedId?: string; } +type Tab = 'trace' | 'graph' | 'streams' | 'events'; + export function RunDetailView({ runId, // TODO: This should open the right sidebar within the trace viewer @@ -170,8 +173,7 @@ export function RunDetailView({ const env: EnvMap = useMemo(() => ({}), []); // Read tab and streamId from URL search params - const activeTab = - (searchParams.get('tab') as 'trace' | 'graph' | 'streams') || 'trace'; + const activeTab = (searchParams.get('tab') as Tab) || 'trace'; const selectedStreamId = searchParams.get('streamId'); // Helper to update URL search params @@ -191,7 +193,7 @@ export function RunDetailView({ ); const setActiveTab = useCallback( - (tab: 'trace' | 'graph' | 'streams') => { + (tab: Tab) => { // When switching to trace or graph tab, clear streamId if (tab === 'trace' || tab === 'graph') { updateSearchParams({ tab, streamId: null }); @@ -525,9 +527,7 @@ export function RunDetailView({
- setActiveTab(v as 'trace' | 'graph' | 'streams') - } + onValueChange={(v) => setActiveTab(v as Tab)} className="flex-1 flex flex-col min-h-0" > @@ -541,6 +541,10 @@ export function RunDetailView({ Graph )} + + + Events + Streams @@ -567,6 +571,14 @@ export function RunDetailView({ + + +
+ +
+
+
+ Date: Sun, 18 Jan 2026 12:32:16 +0100 Subject: [PATCH 06/15] Remove dead code Signed-off-by: Peter Wielander --- packages/web-shared/src/error-boundary.tsx | 4 +--- packages/web/src/components/run-detail-view.tsx | 15 +++------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/web-shared/src/error-boundary.tsx b/packages/web-shared/src/error-boundary.tsx index d751f32bde..9bf79a6d49 100644 --- a/packages/web-shared/src/error-boundary.tsx +++ b/packages/web-shared/src/error-boundary.tsx @@ -7,8 +7,6 @@ interface ErrorBoundaryProps { children: ReactNode; /** Optional title for the error message */ title?: string; - /** Optional description for the error message */ - description?: string; /** Optional fallback component to render on error */ fallback?: (error: Error, reset: () => void) => ReactNode; } @@ -70,7 +68,7 @@ export class ErrorBoundary extends React.Component< return ( ); diff --git a/packages/web/src/components/run-detail-view.tsx b/packages/web/src/components/run-detail-view.tsx index e3eea07c26..db4ba6d485 100644 --- a/packages/web/src/components/run-detail-view.tsx +++ b/packages/web/src/components/run-detail-view.tsx @@ -548,10 +548,7 @@ export function RunDetailView({
- +
- +
{/* Stream list sidebar */}
- +
Date: Sun, 18 Jan 2026 12:37:26 +0100 Subject: [PATCH 07/15] Remove unnecessary tooltip Signed-off-by: Peter Wielander --- .../web/src/components/top-nav/docs-link.tsx | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/web/src/components/top-nav/docs-link.tsx b/packages/web/src/components/top-nav/docs-link.tsx index 5e5d16f8e5..eae6654ddc 100644 --- a/packages/web/src/components/top-nav/docs-link.tsx +++ b/packages/web/src/components/top-nav/docs-link.tsx @@ -2,29 +2,19 @@ import { ArrowUpRight } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@/components/ui/tooltip'; export function DocsLink() { return ( - - - - - Open docs - + ); } From cae4305dc35f3e42224ad88ff26fc3a1bed37b40 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:44:53 +0100 Subject: [PATCH 08/15] Add events panel Signed-off-by: Peter Wielander --- .changeset/lazy-pants-cover.md | 6 + packages/web-shared/src/events-list.tsx | 259 ++++++++++++++++++------ 2 files changed, 202 insertions(+), 63 deletions(-) create mode 100644 .changeset/lazy-pants-cover.md diff --git a/.changeset/lazy-pants-cover.md b/.changeset/lazy-pants-cover.md new file mode 100644 index 0000000000..3824d085e7 --- /dev/null +++ b/.changeset/lazy-pants-cover.md @@ -0,0 +1,6 @@ +--- +"@workflow/web-shared": patch +"@workflow/web": patch +--- + +[web] Add view to display a list of all events diff --git a/packages/web-shared/src/events-list.tsx b/packages/web-shared/src/events-list.tsx index 56b8b1bcc5..45aefa9443 100644 --- a/packages/web-shared/src/events-list.tsx +++ b/packages/web-shared/src/events-list.tsx @@ -1,7 +1,8 @@ 'use client'; import type { Event } from '@workflow/world'; -import { useMemo } from 'react'; +import { ChevronRight } from 'lucide-react'; +import { useMemo, useState } from 'react'; import { getEventColor } from './workflow-traces/event-colors'; /** @@ -20,6 +21,21 @@ function formatEventTime(date: Date): string { ); } +/** + * Format a date to full local datetime string with milliseconds + */ +function formatEventDateTime(date: Date): string { + return date.toLocaleString(undefined, { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + fractionalSecondDigits: 3, + }); +} + /** * Format event type to a more readable label */ @@ -34,6 +50,176 @@ interface EventsListProps { events: Event[] | null; } +/** + * Single event row component with expandable details + */ +function EventRow({ event }: { event: Event }) { + const [isExpanded, setIsExpanded] = useState(false); + const colors = getEventColor(event.eventType); + const createdAt = new Date(event.createdAt); + + // Get event data if it exists + const eventData = 'eventData' in event ? event.eventData : null; + + return ( +
+ {/* Clickable row header */} + + + {/* Expanded details */} + {isExpanded && ( +
+ {/* Event attributes in a structured table */} +
+ + + + + +
+ + {/* Event data section */} + {eventData && ( +
+
+ Event Data +
+
+                {JSON.stringify(eventData, null, 2)}
+              
+
+ )} +
+ )} +
+ ); +} + +/** + * Helper component for attribute rows in the expanded details + */ +function AttributeRow({ + label, + value, + mono = false, +}: { + label: string; + value: string; + mono?: boolean; +}) { + return ( +
+ + {label} + + + {value} + +
+ ); +} + /** * Displays a list of all events for a workflow run as colored cards in a pseudo-table. * Events are sorted by createdAt (oldest first). @@ -65,77 +251,24 @@ export function EventsList({ events }: EventsListProps) {
-
Time
-
Event Type
-
Correlation ID
-
Event ID
+
{/* Expand icon column */}
+
Time
+
Event Type
+
Correlation ID
+
Event ID
{/* Event rows */}
- {sortedEvents.map((event) => { - const colors = getEventColor(event.eventType); - const createdAt = new Date(event.createdAt); - - return ( -
- {/* Time */} -
- {formatEventTime(createdAt)} -
- - {/* Event Type */} -
- - - {formatEventType(event.eventType)} - -
- - {/* Correlation ID */} -
- {event.correlationId - ? event.correlationId.slice(0, 12) + '...' - : '-'} -
- - {/* Event ID */} -
- {event.eventId.slice(0, 16) + '...'} -
-
- ); - })} + {sortedEvents.map((event) => ( + + ))}
{/* Summary */} From ebe163024689be58df22d3c3767fc19201018c3b Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:49:07 +0100 Subject: [PATCH 09/15] Full details Signed-off-by: Peter Wielander --- packages/web-shared/src/events-list.tsx | 179 ++++++++++++++++-- .../web/src/components/run-detail-view.tsx | 2 +- 2 files changed, 165 insertions(+), 16 deletions(-) diff --git a/packages/web-shared/src/events-list.tsx b/packages/web-shared/src/events-list.tsx index 45aefa9443..bd71336a62 100644 --- a/packages/web-shared/src/events-list.tsx +++ b/packages/web-shared/src/events-list.tsx @@ -1,8 +1,10 @@ 'use client'; import type { Event } from '@workflow/world'; -import { ChevronRight } from 'lucide-react'; -import { useMemo, useState } from 'react'; +import { ChevronRight, Loader2 } from 'lucide-react'; +import { useCallback, useMemo, useState } from 'react'; +import type { EnvMap } from './api/workflow-server-actions'; +import { fetchEventsByCorrelationId } from './api/workflow-server-actions'; import { getEventColor } from './workflow-traces/event-colors'; /** @@ -48,18 +50,91 @@ function formatEventType(eventType: Event['eventType']): string { interface EventsListProps { events: Event[] | null; + env: EnvMap; } /** * Single event row component with expandable details */ -function EventRow({ event }: { event: Event }) { +function EventRow({ event, env }: { event: Event; env: EnvMap }) { const [isExpanded, setIsExpanded] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [loadedEventData, setLoadedEventData] = useState(null); + const [loadError, setLoadError] = useState(null); + const colors = getEventColor(event.eventType); const createdAt = new Date(event.createdAt); - // Get event data if it exists - const eventData = 'eventData' in event ? event.eventData : null; + // Check if event already has eventData (from initial fetch) + const hasExistingEventData = 'eventData' in event && event.eventData != null; + + // Load full event details when expanding + const loadEventDetails = useCallback(async () => { + // Skip if we already have data or no correlationId + if ( + loadedEventData !== null || + hasExistingEventData || + !event.correlationId + ) { + return; + } + + setIsLoading(true); + setLoadError(null); + + try { + const result = await fetchEventsByCorrelationId( + env, + event.correlationId, + { + sortOrder: 'asc', + limit: 100, + withData: true, + } + ); + + if (!result.success) { + setLoadError(result.error?.message || 'Failed to load event details'); + return; + } + + // Find our specific event in the results + const fullEvent = result.data.data.find( + (e) => e.eventId === event.eventId + ); + if (fullEvent && 'eventData' in fullEvent) { + setLoadedEventData(fullEvent.eventData); + } + } catch (err) { + setLoadError( + err instanceof Error ? err.message : 'Failed to load event details' + ); + } finally { + setIsLoading(false); + } + }, [ + env, + event.correlationId, + event.eventId, + loadedEventData, + hasExistingEventData, + ]); + + // Handle expand/collapse + const handleToggle = useCallback(() => { + const newExpanded = !isExpanded; + setIsExpanded(newExpanded); + + // Load details when expanding for the first time + if (newExpanded && loadedEventData === null && !hasExistingEventData) { + loadEventDetails(); + } + }, [isExpanded, loadedEventData, hasExistingEventData, loadEventDetails]); + + // Get the event data to display (either from initial fetch, loaded data, or null) + const eventData = hasExistingEventData + ? (event as Event & { eventData: unknown }).eventData + : loadedEventData; return (
setIsExpanded(!isExpanded)} + onClick={handleToggle} className="w-full text-left grid gap-3 items-center px-0 py-2 text-xs hover:brightness-[0.98] transition-all cursor-pointer" style={{ gridTemplateColumns: '24px 100px minmax(120px, auto) 1fr 1fr', @@ -161,14 +236,52 @@ function EventRow({ event }: { event: Event }) {
{/* Event data section */} - {eventData && ( -
+
+
+ Event Data +
+ + {/* Loading state */} + {isLoading && ( +
+ + + Loading event details... + +
+ )} + + {/* Error state */} + {loadError && !isLoading && (
- Event Data + {loadError}
+ )} + + {/* Event data display */} + {!isLoading && !loadError && eventData != null && (
                 {JSON.stringify(eventData, null, 2)}
               
-
- )} + )} + + {/* No event data */} + {!isLoading && + !loadError && + eventData == null && + !event.correlationId && ( +
+ No event data available +
+ )} + + {/* No correlation ID - can't load data */} + {!isLoading && + !loadError && + eventData == null && + event.correlationId && + !hasExistingEventData && + loadedEventData === null && ( +
+ No event data for this event type +
+ )} +
)}
@@ -224,7 +373,7 @@ function AttributeRow({ * Displays a list of all events for a workflow run as colored cards in a pseudo-table. * Events are sorted by createdAt (oldest first). */ -export function EventsList({ events }: EventsListProps) { +export function EventsList({ events, env }: EventsListProps) { // Sort events by createdAt (oldest first) const sortedEvents = useMemo(() => { if (!events || events.length === 0) return []; @@ -267,7 +416,7 @@ export function EventsList({ events }: EventsListProps) { {/* Event rows */}
{sortedEvents.map((event) => ( - + ))}
diff --git a/packages/web/src/components/run-detail-view.tsx b/packages/web/src/components/run-detail-view.tsx index 426c63a202..5dda4d8ed8 100644 --- a/packages/web/src/components/run-detail-view.tsx +++ b/packages/web/src/components/run-detail-view.tsx @@ -571,7 +571,7 @@ export function RunDetailView({
- +
From 3fcd21bf985075136232308d997b8a6686d1b32f Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:49:20 +0100 Subject: [PATCH 10/15] Fix Signed-off-by: Peter Wielander --- packages/web-shared/src/workflow-trace-view.tsx | 2 +- packages/web/src/app/page.tsx | 15 +++------------ packages/web/src/app/run/[runId]/page.tsx | 5 +---- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/web-shared/src/workflow-trace-view.tsx b/packages/web-shared/src/workflow-trace-view.tsx index 91cdb5386b..33b164aea6 100644 --- a/packages/web-shared/src/workflow-trace-view.tsx +++ b/packages/web-shared/src/workflow-trace-view.tsx @@ -197,7 +197,7 @@ export const WorkflowTraceViewer = ({ customSpanClassNameFunc={getCustomSpanClassName} customSpanEventClassNameFunc={getCustomSpanEventClassName} customPanelComponent={ - + - + - + {isLocalBackend && ( - + diff --git a/packages/web/src/app/run/[runId]/page.tsx b/packages/web/src/app/run/[runId]/page.tsx index 2170936667..9d56fa0bf3 100644 --- a/packages/web/src/app/run/[runId]/page.tsx +++ b/packages/web/src/app/run/[runId]/page.tsx @@ -19,10 +19,7 @@ export default function RunDetailPage() { const selectedId = stepId || eventId || hookId || undefined; return ( - + ); From 4935b25d7f728b7ad3262dd45e1008c51a2d3530 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 18 Jan 2026 12:49:51 +0100 Subject: [PATCH 11/15] Rename WorkflowDetailPanel -> EntityDetailPanel Signed-off-by: Peter Wielander --- .../{workflow-detail-panel.tsx => entity-detail-panel.tsx} | 4 ++-- packages/web-shared/src/workflow-trace-view.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename packages/web-shared/src/sidebar/{workflow-detail-panel.tsx => entity-detail-panel.tsx} (99%) diff --git a/packages/web-shared/src/sidebar/workflow-detail-panel.tsx b/packages/web-shared/src/sidebar/entity-detail-panel.tsx similarity index 99% rename from packages/web-shared/src/sidebar/workflow-detail-panel.tsx rename to packages/web-shared/src/sidebar/entity-detail-panel.tsx index c50b17c164..9fe5c00a2c 100644 --- a/packages/web-shared/src/sidebar/workflow-detail-panel.tsx +++ b/packages/web-shared/src/sidebar/entity-detail-panel.tsx @@ -11,15 +11,15 @@ import { wakeUpRun, } from '../api/workflow-api-client'; import type { EnvMap } from '../api/workflow-server-actions'; -import { EventsList } from '../sidebar/events-list'; import { useTraceViewer } from '../trace-viewer'; import { AttributePanel } from './attribute-panel'; +import { EventsList } from './events-list'; import { ResolveHookModal } from './resolve-hook-modal'; /** * Custom panel component for workflow traces that displays entity details */ -export function WorkflowDetailPanel({ +export function EntityDetailPanel({ env, run, onStreamClick, diff --git a/packages/web-shared/src/workflow-trace-view.tsx b/packages/web-shared/src/workflow-trace-view.tsx index 33b164aea6..87c0a3a42d 100644 --- a/packages/web-shared/src/workflow-trace-view.tsx +++ b/packages/web-shared/src/workflow-trace-view.tsx @@ -4,7 +4,7 @@ import { toast } from 'sonner'; import type { EnvMap } from './api/workflow-server-actions'; import { Skeleton } from './components/ui/skeleton'; import { ErrorBoundary } from './error-boundary'; -import { WorkflowDetailPanel } from './sidebar/workflow-detail-panel'; +import { EntityDetailPanel } from './sidebar/entity-detail-panel'; import { TraceViewerContextProvider, TraceViewerTimeline, @@ -198,7 +198,7 @@ export const WorkflowTraceViewer = ({ customSpanEventClassNameFunc={getCustomSpanEventClassName} customPanelComponent={ - Date: Sun, 18 Jan 2026 13:00:38 +0100 Subject: [PATCH 12/15] Specify attributes so they won't get overwritten Signed-off-by: Peter Wielander --- .../src/sidebar/attribute-panel.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/web-shared/src/sidebar/attribute-panel.tsx b/packages/web-shared/src/sidebar/attribute-panel.tsx index 547b7c529e..65b1c213d3 100644 --- a/packages/web-shared/src/sidebar/attribute-panel.tsx +++ b/packages/web-shared/src/sidebar/attribute-panel.tsx @@ -28,8 +28,18 @@ function TabButton({ onClick={onClick} className="px-3 py-1.5 text-[11px] font-medium transition-colors -mb-px" style={{ - color: active ? 'var(--ds-gray-1000)' : 'var(--ds-gray-600)', + // Explicit styles to prevent app-level button overrides when web-shared + // is embedded in a self-hosted app. + backgroundColor: 'transparent', + borderTop: 'none', + borderLeft: 'none', + borderRight: 'none', borderBottom: `2px solid ${active ? 'var(--ds-blue-600)' : 'transparent'}`, + borderRadius: 0, + outline: 'none', + boxShadow: 'none', + cursor: 'pointer', + color: active ? 'var(--ds-gray-1000)' : 'var(--ds-gray-600)', }} > {children} @@ -55,11 +65,17 @@ function ConversationWithTabs({
Date: Sun, 18 Jan 2026 14:15:37 +0100 Subject: [PATCH 13/15] Fix typo Signed-off-by: Peter Wielander --- packages/web/src/components/run-detail-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/components/run-detail-view.tsx b/packages/web/src/components/run-detail-view.tsx index db4ba6d485..b65d098278 100644 --- a/packages/web/src/components/run-detail-view.tsx +++ b/packages/web/src/components/run-detail-view.tsx @@ -565,7 +565,7 @@ export function RunDetailView({ - +
{/* Stream list sidebar */}
Date: Sun, 18 Jan 2026 14:26:48 +0100 Subject: [PATCH 14/15] Small styling fix Signed-off-by: Peter Wielander --- packages/web-shared/src/events-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-shared/src/events-list.tsx b/packages/web-shared/src/events-list.tsx index bd71336a62..13a2d03939 100644 --- a/packages/web-shared/src/events-list.tsx +++ b/packages/web-shared/src/events-list.tsx @@ -395,7 +395,7 @@ export function EventsList({ events, env }: EventsListProps) { } return ( -
+
{/* Header row */}
Date: Sun, 18 Jan 2026 14:27:52 +0100 Subject: [PATCH 15/15] Rename Signed-off-by: Peter Wielander --- .../web-shared/src/{events-list.tsx => event-list-view.tsx} | 2 +- packages/web-shared/src/index.ts | 2 +- packages/web/src/components/run-detail-view.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename packages/web-shared/src/{events-list.tsx => event-list-view.tsx} (99%) diff --git a/packages/web-shared/src/events-list.tsx b/packages/web-shared/src/event-list-view.tsx similarity index 99% rename from packages/web-shared/src/events-list.tsx rename to packages/web-shared/src/event-list-view.tsx index 13a2d03939..be44b2c8ec 100644 --- a/packages/web-shared/src/events-list.tsx +++ b/packages/web-shared/src/event-list-view.tsx @@ -373,7 +373,7 @@ function AttributeRow({ * Displays a list of all events for a workflow run as colored cards in a pseudo-table. * Events are sorted by createdAt (oldest first). */ -export function EventsList({ events, env }: EventsListProps) { +export function EventListView({ events, env }: EventsListProps) { // Sort events by createdAt (oldest first) const sortedEvents = useMemo(() => { if (!events || events.length === 0) return []; diff --git a/packages/web-shared/src/index.ts b/packages/web-shared/src/index.ts index db5006c03b..03983a877b 100644 --- a/packages/web-shared/src/index.ts +++ b/packages/web-shared/src/index.ts @@ -7,7 +7,7 @@ export type { Event, Hook, Step, WorkflowRun } from '@workflow/world'; export * from './api/workflow-api-client'; export type { EnvMap, PublicServerConfig } from './api/workflow-server-actions'; export { ErrorBoundary } from './error-boundary'; -export { EventsList } from './events-list'; +export { EventListView } from './event-list-view'; export type { HookActionCallbacks, HookActionsDropdownItemProps, diff --git a/packages/web/src/components/run-detail-view.tsx b/packages/web/src/components/run-detail-view.tsx index 8b8be09410..a4957c0c5f 100644 --- a/packages/web/src/components/run-detail-view.tsx +++ b/packages/web/src/components/run-detail-view.tsx @@ -6,7 +6,7 @@ import { type EnvMap, ErrorBoundary, type Event, - EventsList, + EventListView, recreateRun, type Step, StreamViewer, @@ -571,7 +571,7 @@ export function RunDetailView({
- +