@workflow/web-shared 5.0.0-beta.13 → 5.0.0-beta.15

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 (38) hide show
  1. package/dist/components/new-trace-viewer/components/event-list.js +3 -3
  2. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  3. package/dist/components/new-trace-viewer/components/timeline.js +26 -14
  4. package/dist/components/new-trace-viewer/components/timeline.module.css +31 -0
  5. package/dist/components/new-trace-viewer/utils.d.ts +1 -1
  6. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  7. package/dist/components/new-trace-viewer/utils.js +12 -5
  8. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  9. package/dist/components/sidebar/attribute-panel.js +8 -4
  10. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  11. package/dist/components/sidebar/entity-detail-panel.js +12 -11
  12. package/dist/components/sidebar/events-list.d.ts +3 -1
  13. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  14. package/dist/components/sidebar/events-list.js +4 -3
  15. package/dist/components/sidebar/span-detail-merge.d.ts +16 -0
  16. package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
  17. package/dist/components/sidebar/span-detail-merge.js +53 -0
  18. package/dist/components/trace-viewer/util/timing.d.ts +2 -2
  19. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  20. package/dist/components/trace-viewer/util/timing.js +3 -3
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -2
  24. package/dist/lib/utils.d.ts +11 -0
  25. package/dist/lib/utils.d.ts.map +1 -1
  26. package/dist/lib/utils.js +49 -1
  27. package/package.json +4 -4
  28. package/src/components/new-trace-viewer/components/event-list.tsx +2 -2
  29. package/src/components/new-trace-viewer/components/timeline.module.css +31 -0
  30. package/src/components/new-trace-viewer/components/timeline.tsx +60 -12
  31. package/src/components/new-trace-viewer/utils.ts +12 -4
  32. package/src/components/sidebar/attribute-panel.tsx +7 -3
  33. package/src/components/sidebar/entity-detail-panel.tsx +28 -13
  34. package/src/components/sidebar/events-list.tsx +34 -25
  35. package/src/components/sidebar/span-detail-merge.ts +65 -0
  36. package/src/components/trace-viewer/util/timing.ts +2 -2
  37. package/src/index.ts +1 -0
  38. package/src/lib/utils.ts +57 -0
@@ -0,0 +1,65 @@
1
+ const hasField = (
2
+ value: object,
3
+ key: string
4
+ ): value is Record<string, unknown> => key in value;
5
+
6
+ /**
7
+ * Returns true when the fetched `detail` belongs to the current selection.
8
+ * The fetch lags selection, so it can briefly hold a previously selected span
9
+ * (even a different resource type); reject it before merging or its fields
10
+ * union into the wrong panel. Steps/hooks/waits carry their parent `runId`, so
11
+ * a `run` selection excludes objects identifiable as a child resource.
12
+ */
13
+ export function spanDetailMatchesSelection(
14
+ detail: unknown,
15
+ resource: string | undefined,
16
+ resourceId: string | undefined
17
+ ): boolean {
18
+ if (!detail || typeof detail !== 'object' || !resource || !resourceId) {
19
+ return false;
20
+ }
21
+ switch (resource) {
22
+ case 'step':
23
+ return hasField(detail, 'stepId') && detail.stepId === resourceId;
24
+ case 'hook':
25
+ return hasField(detail, 'hookId') && detail.hookId === resourceId;
26
+ case 'sleep':
27
+ return hasField(detail, 'waitId') && detail.waitId === resourceId;
28
+ case 'run':
29
+ return (
30
+ hasField(detail, 'runId') &&
31
+ !('stepId' in detail) &&
32
+ !('hookId' in detail) &&
33
+ !('waitId' in detail) &&
34
+ detail.runId === resourceId
35
+ );
36
+ default:
37
+ return false;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Merges fetched `detail` over the span's own data. The detail supplies the
43
+ * heavy fields the trace strips (input/output/error/metadata, sleep resumeAt);
44
+ * the span's identity, status, and event-derived timestamps stay authoritative
45
+ * so they don't flicker to the backend row's millisecond-different values.
46
+ */
47
+ export function mergeSpanDetail(spanData: unknown, detail: unknown): unknown {
48
+ if (!detail || typeof detail !== 'object') {
49
+ return spanData;
50
+ }
51
+ if (!spanData || typeof spanData !== 'object') {
52
+ return detail;
53
+ }
54
+ // Skip `undefined` span fields so they don't clobber a value the detail
55
+ // legitimately provides (e.g. a step's optional startedAt).
56
+ const merged: Record<string, unknown> = { ...detail };
57
+ for (const [key, value] of Object.entries(
58
+ spanData as Record<string, unknown>
59
+ )) {
60
+ if (value !== undefined) {
61
+ merged[key] = value;
62
+ }
63
+ }
64
+ return merged;
65
+ }
@@ -1,6 +1,6 @@
1
- import { formatDuration } from '../../../lib/utils';
1
+ import { formatDuration, formatDurationPrecise } from '../../../lib/utils';
2
2
 
3
- export { formatDuration };
3
+ export { formatDuration, formatDurationPrecise };
4
4
 
5
5
  export function getHighResInMs([seconds, nanoseconds]: [
6
6
  number,
package/src/index.ts CHANGED
@@ -64,6 +64,7 @@ export type { StreamStep } from './lib/utils';
64
64
  export {
65
65
  extractConversation,
66
66
  formatDuration,
67
+ formatDurationPrecise,
67
68
  identifyStreamSteps,
68
69
  isDoStreamStep,
69
70
  } from './lib/utils';
package/src/lib/utils.ts CHANGED
@@ -80,6 +80,63 @@ export function formatDuration(ms: number, compact = false): string {
80
80
  return parts.join(' ');
81
81
  }
82
82
 
83
+ // Locale-aware formatter that always renders exactly two fraction digits.
84
+ // Used for the seconds component of precise durations so values are never
85
+ // snapped to a whole second (and thousands separators stay correct).
86
+ const preciseSecondsFormatter = new Intl.NumberFormat(undefined, {
87
+ minimumFractionDigits: 2,
88
+ maximumFractionDigits: 2,
89
+ });
90
+
91
+ /**
92
+ * Formats a duration in milliseconds without snapping to whole seconds.
93
+ *
94
+ * Unlike {@link formatDuration}, this never rounds a sub-minute value up to
95
+ * the next whole second (e.g. 1626ms renders as "1.63s", not "2s").
96
+ *
97
+ * - < 1s: shows whole milliseconds (e.g. "626ms")
98
+ * - 1s – 1m: shows seconds with two decimals (e.g. "1.63s", "45.20s")
99
+ * - >= 1m: decomposes into d/h/m with two-decimal seconds (e.g. "1m 13.45s")
100
+ */
101
+ export function formatDurationPrecise(ms: number): string {
102
+ if (ms === 0) {
103
+ return '0s';
104
+ }
105
+
106
+ if (ms < MS_IN_SECOND) {
107
+ const roundedMs = Math.round(ms);
108
+ if (roundedMs < MS_IN_SECOND) {
109
+ return `${roundedMs}ms`;
110
+ }
111
+ }
112
+
113
+ const normalizedMs = Math.round(ms / 10) * 10;
114
+
115
+ if (normalizedMs < MS_IN_MINUTE) {
116
+ return `${preciseSecondsFormatter.format(normalizedMs / MS_IN_SECOND)}s`;
117
+ }
118
+
119
+ const days = Math.floor(normalizedMs / MS_IN_DAY);
120
+ const hours = Math.floor((normalizedMs % MS_IN_DAY) / MS_IN_HOUR);
121
+ const minutes = Math.floor((normalizedMs % MS_IN_HOUR) / MS_IN_MINUTE);
122
+ const seconds = (normalizedMs % MS_IN_MINUTE) / MS_IN_SECOND;
123
+
124
+ const parts: string[] = [];
125
+
126
+ if (days > 0) {
127
+ parts.push(`${days}d`);
128
+ }
129
+ if (hours > 0) {
130
+ parts.push(`${hours}h`);
131
+ }
132
+ if (minutes > 0) {
133
+ parts.push(`${minutes}m`);
134
+ }
135
+ parts.push(`${preciseSecondsFormatter.format(seconds)}s`);
136
+
137
+ return parts.join(' ');
138
+ }
139
+
83
140
  /**
84
141
  * Returns a formatted pagination display string
85
142
  * @param currentPage - The current page number