@workflow/web-shared 4.1.0-beta.61 → 4.1.0-beta.63

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 (54) hide show
  1. package/dist/components/event-list-view.d.ts +2 -3
  2. package/dist/components/event-list-view.d.ts.map +1 -1
  3. package/dist/components/event-list-view.js +13 -10
  4. package/dist/components/event-list-view.js.map +1 -1
  5. package/dist/components/run-trace-view.d.ts +1 -3
  6. package/dist/components/run-trace-view.d.ts.map +1 -1
  7. package/dist/components/run-trace-view.js +2 -2
  8. package/dist/components/run-trace-view.js.map +1 -1
  9. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  10. package/dist/components/sidebar/attribute-panel.js +11 -1
  11. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  12. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  13. package/dist/components/sidebar/detail-card.js +4 -2
  14. package/dist/components/sidebar/detail-card.js.map +1 -1
  15. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -3
  16. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  17. package/dist/components/sidebar/entity-detail-panel.js +43 -26
  18. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  19. package/dist/components/trace-viewer/trace-viewer.d.ts +7 -1
  20. package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
  21. package/dist/components/trace-viewer/trace-viewer.js +36 -11
  22. package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
  23. package/dist/components/workflow-trace-view.d.ts +3 -3
  24. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  25. package/dist/components/workflow-trace-view.js +31 -129
  26. package/dist/components/workflow-trace-view.js.map +1 -1
  27. package/dist/components/workflow-traces/trace-span-construction.d.ts +18 -5
  28. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  29. package/dist/components/workflow-traces/trace-span-construction.js +65 -18
  30. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/event-materialization.d.ts +72 -0
  36. package/dist/lib/event-materialization.d.ts.map +1 -0
  37. package/dist/lib/event-materialization.js +171 -0
  38. package/dist/lib/event-materialization.js.map +1 -0
  39. package/dist/lib/trace-builder.d.ts +32 -0
  40. package/dist/lib/trace-builder.d.ts.map +1 -0
  41. package/dist/lib/trace-builder.js +129 -0
  42. package/dist/lib/trace-builder.js.map +1 -0
  43. package/package.json +3 -3
  44. package/src/components/event-list-view.tsx +17 -13
  45. package/src/components/run-trace-view.tsx +0 -6
  46. package/src/components/sidebar/attribute-panel.tsx +17 -2
  47. package/src/components/sidebar/detail-card.tsx +10 -2
  48. package/src/components/sidebar/entity-detail-panel.tsx +59 -21
  49. package/src/components/trace-viewer/trace-viewer.tsx +47 -2
  50. package/src/components/workflow-trace-view.tsx +89 -195
  51. package/src/components/workflow-traces/trace-span-construction.ts +85 -32
  52. package/src/index.ts +13 -0
  53. package/src/lib/event-materialization.ts +243 -0
  54. package/src/lib/trace-builder.ts +201 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Event materialization helpers.
3
+ *
4
+ * These functions convert a flat list of workflow events into entity-like
5
+ * objects (steps, hooks, waits) by grouping events by correlationId and
6
+ * stitching together lifecycle events.
7
+ *
8
+ * This enables a "top-down" data fetching pattern where the client fetches
9
+ * all events for a run once, then materializes entities client-side instead
10
+ * of making separate API calls for each entity type.
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // Helper: group events by correlationId prefix
14
+ // ---------------------------------------------------------------------------
15
+ function groupByCorrelationId(events, prefixes) {
16
+ const groups = new Map();
17
+ for (const event of events) {
18
+ const cid = event.correlationId;
19
+ if (!cid)
20
+ continue;
21
+ if (!prefixes.some((p) => cid.startsWith(p)))
22
+ continue;
23
+ const existing = groups.get(cid);
24
+ if (existing) {
25
+ existing.push(event);
26
+ }
27
+ else {
28
+ groups.set(cid, [event]);
29
+ }
30
+ }
31
+ return groups;
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // materializeSteps
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Group step_* events by correlationId and build Step-like entities.
38
+ *
39
+ * Handles partial event lists gracefully: a step may only have a
40
+ * step_created event with no completion yet.
41
+ */
42
+ export function materializeSteps(events) {
43
+ const groups = groupByCorrelationId(events, ['step_']);
44
+ const steps = [];
45
+ for (const [correlationId, stepEvents] of groups) {
46
+ const created = stepEvents.find((e) => e.eventType === 'step_created');
47
+ if (!created)
48
+ continue;
49
+ let status = 'pending';
50
+ let attempt = 0;
51
+ let startedAt;
52
+ let completedAt;
53
+ let updatedAt = created.createdAt;
54
+ for (const e of stepEvents) {
55
+ switch (e.eventType) {
56
+ case 'step_started':
57
+ status = 'running';
58
+ attempt += 1;
59
+ if (!startedAt)
60
+ startedAt = e.createdAt;
61
+ completedAt = undefined;
62
+ updatedAt = e.createdAt;
63
+ break;
64
+ case 'step_completed':
65
+ status = 'completed';
66
+ completedAt = e.createdAt;
67
+ updatedAt = e.createdAt;
68
+ break;
69
+ case 'step_failed':
70
+ status = 'failed';
71
+ completedAt = e.createdAt;
72
+ updatedAt = e.createdAt;
73
+ break;
74
+ case 'step_retrying':
75
+ status = 'pending';
76
+ completedAt = undefined;
77
+ updatedAt = e.createdAt;
78
+ break;
79
+ }
80
+ }
81
+ steps.push({
82
+ stepId: correlationId,
83
+ runId: created.runId,
84
+ stepName: created.eventType === 'step_created'
85
+ ? (created.eventData?.stepName ?? correlationId)
86
+ : correlationId,
87
+ status,
88
+ attempt,
89
+ createdAt: created.createdAt,
90
+ startedAt,
91
+ completedAt,
92
+ updatedAt,
93
+ events: stepEvents,
94
+ });
95
+ }
96
+ return steps;
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // materializeHooks
100
+ // ---------------------------------------------------------------------------
101
+ /**
102
+ * Group hook_* events by correlationId and build Hook-like entities.
103
+ */
104
+ export function materializeHooks(events) {
105
+ const groups = groupByCorrelationId(events, ['hook_']);
106
+ const hooks = [];
107
+ for (const [correlationId, hookEvents] of groups) {
108
+ const created = hookEvents.find((e) => e.eventType === 'hook_created');
109
+ if (!created)
110
+ continue;
111
+ const receivedEvents = hookEvents.filter((e) => e.eventType === 'hook_received');
112
+ const disposed = hookEvents.find((e) => e.eventType === 'hook_disposed');
113
+ const lastReceived = receivedEvents.at(-1);
114
+ hooks.push({
115
+ hookId: correlationId,
116
+ runId: created.runId,
117
+ token: created.eventType === 'hook_created'
118
+ ? created.eventData?.token
119
+ : undefined,
120
+ createdAt: created.createdAt,
121
+ receivedCount: receivedEvents.length,
122
+ lastReceivedAt: lastReceived?.createdAt,
123
+ disposedAt: disposed?.createdAt,
124
+ events: hookEvents,
125
+ });
126
+ }
127
+ return hooks;
128
+ }
129
+ // ---------------------------------------------------------------------------
130
+ // materializeWaits
131
+ // ---------------------------------------------------------------------------
132
+ /**
133
+ * Group wait_* events by correlationId and build Wait-like entities.
134
+ */
135
+ export function materializeWaits(events) {
136
+ const groups = groupByCorrelationId(events, ['wait_']);
137
+ const waits = [];
138
+ for (const [correlationId, waitEvents] of groups) {
139
+ const created = waitEvents.find((e) => e.eventType === 'wait_created');
140
+ if (!created)
141
+ continue;
142
+ const completed = waitEvents.find((e) => e.eventType === 'wait_completed');
143
+ waits.push({
144
+ waitId: correlationId,
145
+ runId: created.runId,
146
+ status: completed ? 'completed' : 'waiting',
147
+ createdAt: created.createdAt,
148
+ resumeAt: created.eventType === 'wait_created'
149
+ ? created.eventData?.resumeAt
150
+ : undefined,
151
+ completedAt: completed?.createdAt,
152
+ events: waitEvents,
153
+ });
154
+ }
155
+ return waits;
156
+ }
157
+ // ---------------------------------------------------------------------------
158
+ // materializeAll
159
+ // ---------------------------------------------------------------------------
160
+ /**
161
+ * Convenience function that materializes all entity types from a flat
162
+ * event list.
163
+ */
164
+ export function materializeAll(events) {
165
+ return {
166
+ steps: materializeSteps(events),
167
+ hooks: materializeHooks(events),
168
+ waits: materializeWaits(events),
169
+ };
170
+ }
171
+ //# sourceMappingURL=event-materialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-materialization.js","sourceRoot":"","sources":["../../src/lib/event-materialization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Builds a complete trace from a WorkflowRun and its Events.
3
+ *
4
+ * This module groups raw events by correlation ID and entity type,
5
+ * converts each group into an OpenTelemetry-style Span, and returns
6
+ * a fully-formed Trace ready for the trace viewer.
7
+ */
8
+ import type { Event, WorkflowRun } from '@workflow/world';
9
+ import type { Span } from '../components/trace-viewer/types';
10
+ export declare const isStepEvent: (eventType: string) => boolean;
11
+ export declare const isTimerEvent: (eventType: string) => eventType is "wait_created" | "wait_completed";
12
+ export declare const isHookLifecycleEvent: (eventType: string) => eventType is "hook_created" | "hook_received" | "hook_disposed";
13
+ export type GroupedEvents = {
14
+ eventsByStepId: Map<string, Event[]>;
15
+ runLevelEvents: Event[];
16
+ timerEvents: Map<string, Event[]>;
17
+ hookEvents: Map<string, Event[]>;
18
+ };
19
+ export declare function groupEventsByCorrelation(events: Event[]): GroupedEvents;
20
+ export interface TraceWithMeta {
21
+ traceId: string;
22
+ rootSpanId: string;
23
+ spans: Span[];
24
+ resources: {
25
+ name: string;
26
+ attributes: Record<string, string>;
27
+ }[];
28
+ /** Duration in ms from trace start to the latest known event. */
29
+ knownDurationMs: number;
30
+ }
31
+ export declare function buildTrace(run: WorkflowRun, events: Event[], now: Date): TraceWithMeta;
32
+ //# sourceMappingURL=trace-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-builder.d.ts","sourceRoot":"","sources":["../../src/lib/trace-builder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kCAAkC,CAAC;AAc7D,eAAO,MAAM,WAAW,GAAI,WAAW,MAAM,YAAkC,CAAC;AAEhF,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,mDACkB,CAAC;AAEjE,eAAO,MAAM,oBAAoB,GAAI,WAAW,MAAM,oEAGvB,CAAC;AAMhC,MAAM,MAAM,aAAa,GAAG;IAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACrC,cAAc,EAAE,KAAK,EAAE,CAAC;IACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAClC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;CAClC,CAAC;AAeF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,CAgCvE;AAqED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAAE,CAAC;IAClE,iEAAiE;IACjE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,KAAK,EAAE,EACf,GAAG,EAAE,IAAI,GACR,aAAa,CA6Bf"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Builds a complete trace from a WorkflowRun and its Events.
3
+ *
4
+ * This module groups raw events by correlation ID and entity type,
5
+ * converts each group into an OpenTelemetry-style Span, and returns
6
+ * a fully-formed Trace ready for the trace viewer.
7
+ */
8
+ import { hookToSpan, runToSpan, stepToSpan, waitToSpan, WORKFLOW_LIBRARY, } from '../components/workflow-traces/trace-span-construction';
9
+ import { otelTimeToMs } from '../components/workflow-traces/trace-time-utils';
10
+ // ---------------------------------------------------------------------------
11
+ // Event type classifiers
12
+ // ---------------------------------------------------------------------------
13
+ export const isStepEvent = (eventType) => eventType.startsWith('step_');
14
+ export const isTimerEvent = (eventType) => eventType === 'wait_created' || eventType === 'wait_completed';
15
+ export const isHookLifecycleEvent = (eventType) => eventType === 'hook_received' ||
16
+ eventType === 'hook_created' ||
17
+ eventType === 'hook_disposed';
18
+ function pushEvent(map, correlationId, event) {
19
+ const existing = map.get(correlationId);
20
+ if (existing) {
21
+ existing.push(event);
22
+ return;
23
+ }
24
+ map.set(correlationId, [event]);
25
+ }
26
+ export function groupEventsByCorrelation(events) {
27
+ const eventsByStepId = new Map();
28
+ const runLevelEvents = [];
29
+ const timerEvents = new Map();
30
+ const hookEvents = new Map();
31
+ for (const event of events) {
32
+ const correlationId = event.correlationId;
33
+ if (!correlationId) {
34
+ runLevelEvents.push(event);
35
+ continue;
36
+ }
37
+ if (isTimerEvent(event.eventType)) {
38
+ pushEvent(timerEvents, correlationId, event);
39
+ continue;
40
+ }
41
+ if (isHookLifecycleEvent(event.eventType)) {
42
+ pushEvent(hookEvents, correlationId, event);
43
+ continue;
44
+ }
45
+ if (isStepEvent(event.eventType)) {
46
+ pushEvent(eventsByStepId, correlationId, event);
47
+ continue;
48
+ }
49
+ runLevelEvents.push(event);
50
+ }
51
+ return { eventsByStepId, runLevelEvents, timerEvents, hookEvents };
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Trace construction
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Computes the latest known event time from the events list.
58
+ * Active (in-progress) child spans are capped at this time instead of `now`,
59
+ * so they only extend as far as our data goes.
60
+ */
61
+ function computeLatestKnownTime(events, run) {
62
+ let latest = new Date(run.createdAt).getTime();
63
+ for (const event of events) {
64
+ const t = new Date(event.createdAt).getTime();
65
+ if (t > latest)
66
+ latest = t;
67
+ }
68
+ return new Date(latest);
69
+ }
70
+ function buildSpans(run, groupedEvents, now, latestKnownTime) {
71
+ // Active child spans cap at latestKnownTime so they don't extend into
72
+ // unknown territory. Even when the run is completed, we may not have loaded
73
+ // all events yet, so always use the latest event we've actually seen.
74
+ const childMaxEnd = latestKnownTime;
75
+ const stepSpans = Array.from(groupedEvents.eventsByStepId.values())
76
+ .map((events) => stepToSpan(events, childMaxEnd))
77
+ .filter((span) => span !== null);
78
+ const hookSpans = Array.from(groupedEvents.hookEvents.values())
79
+ .map((events) => hookToSpan(events, childMaxEnd))
80
+ .filter((span) => span !== null);
81
+ const waitSpans = Array.from(groupedEvents.timerEvents.values())
82
+ .map((events) => waitToSpan(events, childMaxEnd))
83
+ .filter((span) => span !== null);
84
+ return {
85
+ runSpan: runToSpan(run, groupedEvents.runLevelEvents, now),
86
+ spans: [...stepSpans, ...hookSpans, ...waitSpans],
87
+ };
88
+ }
89
+ function cascadeSpans(runSpan, spans) {
90
+ const sortedSpans = [
91
+ runSpan,
92
+ ...spans.slice().sort((a, b) => {
93
+ const aStart = otelTimeToMs(a.startTime);
94
+ const bStart = otelTimeToMs(b.startTime);
95
+ return aStart - bStart;
96
+ }),
97
+ ];
98
+ return sortedSpans.map((span, index) => {
99
+ const parentSpanId = index === 0 ? undefined : String(sortedSpans[index - 1].spanId);
100
+ return {
101
+ ...span,
102
+ parentSpanId,
103
+ };
104
+ });
105
+ }
106
+ export function buildTrace(run, events, now) {
107
+ const groupedEvents = groupEventsByCorrelation(events);
108
+ const latestKnownTime = computeLatestKnownTime(events, run);
109
+ const { runSpan, spans } = buildSpans(run, groupedEvents, now, latestKnownTime);
110
+ const sortedCascadingSpans = cascadeSpans(runSpan, spans);
111
+ // Compute known duration relative to trace start
112
+ const traceStartMs = otelTimeToMs(runSpan.startTime);
113
+ const knownDurationMs = latestKnownTime.getTime() - traceStartMs;
114
+ return {
115
+ traceId: run.runId,
116
+ rootSpanId: run.runId,
117
+ spans: sortedCascadingSpans,
118
+ resources: [
119
+ {
120
+ name: 'workflow',
121
+ attributes: {
122
+ 'service.name': WORKFLOW_LIBRARY.name,
123
+ },
124
+ },
125
+ ],
126
+ knownDurationMs: Math.max(0, knownDurationMs),
127
+ };
128
+ }
129
+ //# sourceMappingURL=trace-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-builder.js","sourceRoot":"","sources":["../../src/lib/trace-builder.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@workflow/web-shared",
3
3
  "description": "Shared components for Workflow Observability UI",
4
- "version": "4.1.0-beta.61",
4
+ "version": "4.1.0-beta.63",
5
5
  "private": false,
6
6
  "files": [
7
7
  "dist",
@@ -52,9 +52,9 @@
52
52
  "streamdown": "2.3.0",
53
53
  "tailwind-merge": "3.5.0",
54
54
  "tailwindcss": "4",
55
- "@workflow/core": "4.2.0-beta.66",
55
+ "@workflow/core": "4.2.0-beta.68",
56
56
  "@workflow/utils": "4.1.0-beta.13",
57
- "@workflow/world": "4.1.0-beta.10"
57
+ "@workflow/world": "4.1.0-beta.11"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@biomejs/biome": "^2.4.4",
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
- import type { Event, Step, WorkflowRun } from '@workflow/world';
4
+ import type { Event, WorkflowRun } from '@workflow/world';
5
5
  import { Check, ChevronRight, Copy } from 'lucide-react';
6
6
  import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
7
7
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -103,11 +103,11 @@ function getStatusDotColor(eventType: string): string {
103
103
  }
104
104
 
105
105
  /**
106
- * Build a map from correlationId (stepId) → display name using step entities,
107
- * and parse the workflow name from the run.
106
+ * Build a map from correlationId (stepId) → display name using step_created
107
+ * events, and parse the workflow name from the run.
108
108
  */
109
109
  function buildNameMaps(
110
- steps: Step[] | null,
110
+ events: Event[] | null,
111
111
  run: WorkflowRun | null
112
112
  ): {
113
113
  correlationNameMap: Map<string, string>;
@@ -115,11 +115,17 @@ function buildNameMaps(
115
115
  } {
116
116
  const correlationNameMap = new Map<string, string>();
117
117
 
118
- // Map step correlationId (= stepId) → parsed step name
119
- if (steps) {
120
- for (const step of steps) {
121
- const parsed = parseStepName(String(step.stepName));
122
- correlationNameMap.set(step.stepId, parsed?.shortName ?? step.stepName);
118
+ // Map step correlationId (= stepId) → parsed step name from step_created events
119
+ if (events) {
120
+ for (const event of events) {
121
+ if (event.eventType === 'step_created' && event.correlationId) {
122
+ const stepName = event.eventData?.stepName ?? '';
123
+ const parsed = parseStepName(String(stepName));
124
+ correlationNameMap.set(
125
+ event.correlationId,
126
+ parsed?.shortName ?? stepName
127
+ );
128
+ }
123
129
  }
124
130
  }
125
131
 
@@ -583,7 +589,6 @@ function PayloadBlock({
583
589
 
584
590
  interface EventsListProps {
585
591
  events: Event[] | null;
586
- steps?: Step[] | null;
587
592
  run?: WorkflowRun | null;
588
593
  onLoadEventData?: (event: Event) => Promise<unknown | null>;
589
594
  hasMoreEvents?: boolean;
@@ -960,7 +965,6 @@ function EventRow({
960
965
 
961
966
  export function EventListView({
962
967
  events,
963
- steps,
964
968
  run,
965
969
  onLoadEventData,
966
970
  hasMoreEvents = false,
@@ -977,8 +981,8 @@ export function EventListView({
977
981
  }, [events]);
978
982
 
979
983
  const { correlationNameMap, workflowName } = useMemo(
980
- () => buildNameMaps(steps ?? null, run ?? null),
981
- [steps, run]
984
+ () => buildNameMaps(events ?? null, run ?? null),
985
+ [events, run]
982
986
  );
983
987
 
984
988
  const durationMap = useMemo(
@@ -7,8 +7,6 @@ import { WorkflowTraceViewer } from './workflow-trace-view';
7
7
 
8
8
  interface RunTraceViewProps {
9
9
  run: WorkflowRun;
10
- steps: Step[];
11
- hooks: Hook[];
12
10
  events: Event[];
13
11
  isLoading?: boolean;
14
12
  error?: Error | null;
@@ -34,8 +32,6 @@ interface RunTraceViewProps {
34
32
 
35
33
  export function RunTraceView({
36
34
  run,
37
- steps,
38
- hooks,
39
35
  events,
40
36
  isLoading,
41
37
  error,
@@ -65,9 +61,7 @@ export function RunTraceView({
65
61
  <div className="w-full h-full relative">
66
62
  <WorkflowTraceViewer
67
63
  error={error}
68
- steps={steps}
69
64
  events={events}
70
- hooks={hooks}
71
65
  run={run}
72
66
  isLoading={isLoading}
73
67
  spanDetailData={spanDetailData}
@@ -215,7 +215,10 @@ type AttributeKey =
215
215
  | 'eventData'
216
216
  | 'resumeAt'
217
217
  | 'expiredAt'
218
- | 'workflowCoreVersion';
218
+ | 'workflowCoreVersion'
219
+ | 'receivedCount'
220
+ | 'lastReceivedAt'
221
+ | 'disposedAt';
219
222
 
220
223
  const attributeOrder: AttributeKey[] = [
221
224
  'workflowName',
@@ -228,6 +231,9 @@ const attributeOrder: AttributeKey[] = [
228
231
  'runId',
229
232
  'attempt',
230
233
  'token',
234
+ 'receivedCount',
235
+ 'lastReceivedAt',
236
+ 'disposedAt',
231
237
  'correlationId',
232
238
  'eventType',
233
239
  'deploymentId',
@@ -262,6 +268,7 @@ const sortByAttributeOrder = (a: string, b: string): number => {
262
268
  */
263
269
  const attributeDisplayNames: Partial<Record<AttributeKey, string>> = {
264
270
  workflowCoreVersion: '@workflow/core version',
271
+ receivedCount: 'times resolved',
265
272
  };
266
273
 
267
274
  /**
@@ -353,6 +360,9 @@ const attributeToDisplayFn: Record<
353
360
  // Hook details
354
361
  token: (value: unknown) => String(value),
355
362
  isWebhook: (value: unknown) => String(value),
363
+ receivedCount: (value: unknown) => String(value),
364
+ lastReceivedAt: localMillisecondTimeOrNull,
365
+ disposedAt: localMillisecondTimeOrNull,
356
366
  // Event details
357
367
  eventType: (value: unknown) => String(value),
358
368
  correlationId: (value: unknown) => String(value),
@@ -773,13 +783,18 @@ export const AttributePanel = ({
773
783
  typeof displayValue === 'string'
774
784
  ? displayValue
775
785
  : String(displayValue ?? displayData.moduleSpecifier ?? '');
786
+ const shouldCapitalizeLabel = attribute !== 'workflowCoreVersion';
776
787
  const showDivider = index < orderedBasicAttributes.length - 1;
777
788
 
778
789
  return (
779
790
  <div key={attribute} className="py-1">
780
791
  <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
781
792
  <span
782
- className="text-[14px] first-letter:uppercase"
793
+ className={
794
+ shouldCapitalizeLabel
795
+ ? 'text-[14px] first-letter:uppercase'
796
+ : 'text-[14px]'
797
+ }
783
798
  style={{ color: 'var(--ds-gray-700)' }}
784
799
  >
785
800
  {getAttributeDisplayName(attribute)}
@@ -1,3 +1,4 @@
1
+ import { ChevronRight } from 'lucide-react';
1
2
  import type { ReactNode } from 'react';
2
3
 
3
4
  export function DetailCard({
@@ -42,14 +43,21 @@ export function DetailCard({
42
43
  onToggle={(e) => onToggle?.((e.target as HTMLDetailsElement).open)}
43
44
  >
44
45
  <summary
45
- className={`cursor-pointer rounded-md border px-2.5 py-1.5 text-xs hover:brightness-95 ${summaryClassName ?? ''}`}
46
+ className={`cursor-pointer rounded-md border px-2.5 py-1.5 text-xs hover:brightness-95 [&::-webkit-details-marker]:hidden ${summaryClassName ?? ''}`}
46
47
  style={{
47
48
  borderColor: 'var(--ds-gray-300)',
48
49
  backgroundColor: 'var(--ds-gray-100)',
49
50
  color: 'var(--ds-gray-900)',
51
+ listStyle: 'none',
50
52
  }}
51
53
  >
52
- {summary}
54
+ <span className="flex items-center gap-1.5">
55
+ <ChevronRight
56
+ size={14}
57
+ className="shrink-0 transition-transform group-open:rotate-90"
58
+ />
59
+ {summary}
60
+ </span>
53
61
  </summary>
54
62
  {/* Expanded content with connecting line */}
55
63
  <div className={`relative pl-6 mt-3 ${contentClassName ?? ''}`}>