@workflow/web-shared 4.1.0-beta.63 → 4.1.0-beta.64
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.
- package/README.md +4 -0
- package/dist/components/event-list-view.d.ts +8 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +210 -89
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/ui/error-stack-block.d.ts +3 -4
- package/dist/components/ui/error-stack-block.d.ts.map +1 -1
- package/dist/components/ui/error-stack-block.js +18 -9
- package/dist/components/ui/error-stack-block.js.map +1 -1
- package/dist/components/ui/menu-dropdown.d.ts +16 -0
- package/dist/components/ui/menu-dropdown.d.ts.map +1 -0
- package/dist/components/ui/menu-dropdown.js +50 -0
- package/dist/components/ui/menu-dropdown.js.map +1 -0
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +3 -3
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/package.json +3 -3
- package/src/components/event-list-view.tsx +307 -90
- package/src/components/index.ts +1 -0
- package/src/components/ui/error-stack-block.tsx +26 -16
- package/src/components/ui/menu-dropdown.tsx +114 -0
- package/src/components/workflow-trace-view.tsx +9 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Copy } from 'lucide-react';
|
|
3
|
+
import { AlertCircle, Copy } from 'lucide-react';
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -19,10 +19,9 @@ export function isStructuredErrorWithStack(
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Renders an error with a `stack` field as
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* The entire block is copyable via a copy button.
|
|
22
|
+
* Renders an error with a `stack` field as a visually distinct error block.
|
|
23
|
+
* Shows the error message with an alert icon at the top, separated from
|
|
24
|
+
* the stack trace below.
|
|
26
25
|
*/
|
|
27
26
|
export function ErrorStackBlock({
|
|
28
27
|
value,
|
|
@@ -35,15 +34,22 @@ export function ErrorStackBlock({
|
|
|
35
34
|
|
|
36
35
|
return (
|
|
37
36
|
<div
|
|
38
|
-
className="relative overflow-
|
|
39
|
-
style={{
|
|
37
|
+
className="relative overflow-hidden rounded-md border"
|
|
38
|
+
style={{
|
|
39
|
+
borderColor: 'var(--ds-red-400)',
|
|
40
|
+
background: 'var(--ds-red-100)',
|
|
41
|
+
}}
|
|
40
42
|
>
|
|
41
43
|
<button
|
|
42
44
|
type="button"
|
|
43
45
|
aria-label="Copy error"
|
|
44
46
|
title="Copy"
|
|
45
|
-
className="!absolute !right-2 !top-2 !flex !h-6 !w-6 !items-center !justify-center !rounded-md !border
|
|
46
|
-
style={{
|
|
47
|
+
className="!absolute !right-2 !top-2 !flex !h-6 !w-6 !items-center !justify-center !rounded-md !border transition-transform transition-colors duration-100 hover:!bg-[var(--ds-red-200)] active:!scale-95"
|
|
48
|
+
style={{
|
|
49
|
+
borderColor: 'var(--ds-red-400)',
|
|
50
|
+
background: 'var(--ds-red-100)',
|
|
51
|
+
color: 'var(--ds-red-900)',
|
|
52
|
+
}}
|
|
47
53
|
onClick={() => {
|
|
48
54
|
navigator.clipboard
|
|
49
55
|
.writeText(copyText)
|
|
@@ -59,19 +65,23 @@ export function ErrorStackBlock({
|
|
|
59
65
|
</button>
|
|
60
66
|
|
|
61
67
|
{message && (
|
|
62
|
-
<
|
|
63
|
-
className="
|
|
68
|
+
<div
|
|
69
|
+
className="flex items-start gap-2 px-3 py-2.5 pr-10"
|
|
64
70
|
style={{
|
|
65
71
|
color: 'var(--ds-red-900)',
|
|
66
|
-
borderBottom: '1px solid var(--ds-
|
|
72
|
+
borderBottom: '1px solid var(--ds-red-400)',
|
|
67
73
|
}}
|
|
68
74
|
>
|
|
69
|
-
{
|
|
70
|
-
|
|
75
|
+
<AlertCircle className="h-4 w-4 shrink-0" style={{ marginTop: 1 }} />
|
|
76
|
+
<p className="text-xs font-semibold m-0 break-words">{message}</p>
|
|
77
|
+
</div>
|
|
71
78
|
)}
|
|
72
79
|
<pre
|
|
73
|
-
className="text-xs font-mono whitespace-pre-wrap break-words overflow-auto m-0"
|
|
74
|
-
style={{
|
|
80
|
+
className="px-3 py-2.5 text-xs font-mono whitespace-pre-wrap break-words overflow-auto m-0"
|
|
81
|
+
style={{
|
|
82
|
+
color: 'var(--ds-red-900)',
|
|
83
|
+
background: 'var(--ds-red-200)',
|
|
84
|
+
}}
|
|
75
85
|
>
|
|
76
86
|
{stack}
|
|
77
87
|
</pre>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface MenuDropdownOption<T extends string = string> {
|
|
6
|
+
value: T;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface MenuDropdownProps<T extends string = string> {
|
|
11
|
+
options: MenuDropdownOption<T>[];
|
|
12
|
+
value: T;
|
|
13
|
+
onChange: (value: T) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A dropdown menu that matches Geist's MenuButton (secondary) + Menu styling.
|
|
18
|
+
* Uses CSS classes with proper :hover specificity (no inline background).
|
|
19
|
+
*/
|
|
20
|
+
export function MenuDropdown<T extends string = string>({
|
|
21
|
+
options,
|
|
22
|
+
value,
|
|
23
|
+
onChange,
|
|
24
|
+
}: MenuDropdownProps<T>) {
|
|
25
|
+
const [open, setOpen] = useState(false);
|
|
26
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
27
|
+
const label =
|
|
28
|
+
options.find((o) => o.value === value)?.label ?? options[0]?.label ?? '';
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!open) return;
|
|
32
|
+
function handleClickOutside(e: MouseEvent) {
|
|
33
|
+
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
34
|
+
setOpen(false);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
38
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
39
|
+
}, [open]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div ref={ref} style={{ position: 'relative', flexShrink: 0 }}>
|
|
43
|
+
<style>{`
|
|
44
|
+
.wf-menu-btn{appearance:none;-webkit-appearance:none;border:none;display:inline-flex;align-items:center;justify-content:center;height:40px;padding:0 12px;border-radius:6px;font-size:14px;font-weight:500;line-height:20px;color:var(--ds-gray-1000);background:var(--ds-background-100);box-shadow:0 0 0 1px var(--ds-gray-400);cursor:pointer;white-space:nowrap;transition:background 150ms}
|
|
45
|
+
.wf-menu-btn:hover{background:var(--ds-gray-alpha-200)}
|
|
46
|
+
.wf-menu-item{appearance:none;-webkit-appearance:none;border:none;display:flex;align-items:center;width:100%;height:40px;padding:0 8px;border-radius:6px;font-size:14px;color:var(--ds-gray-1000);background:transparent;cursor:pointer;transition:background 150ms}
|
|
47
|
+
.wf-menu-item:hover{background:var(--ds-gray-alpha-100)}
|
|
48
|
+
`}</style>
|
|
49
|
+
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
className="wf-menu-btn"
|
|
53
|
+
onClick={() => setOpen(!open)}
|
|
54
|
+
>
|
|
55
|
+
<span>{label}</span>
|
|
56
|
+
<svg
|
|
57
|
+
width={16}
|
|
58
|
+
height={16}
|
|
59
|
+
viewBox="0 0 16 16"
|
|
60
|
+
fill="none"
|
|
61
|
+
style={{
|
|
62
|
+
marginLeft: 16,
|
|
63
|
+
marginRight: -4,
|
|
64
|
+
color: 'var(--ds-gray-900)',
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<path
|
|
68
|
+
d="M4.5 6L8 9.5L11.5 6"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
strokeWidth="1.5"
|
|
71
|
+
strokeLinecap="round"
|
|
72
|
+
strokeLinejoin="round"
|
|
73
|
+
/>
|
|
74
|
+
</svg>
|
|
75
|
+
</button>
|
|
76
|
+
|
|
77
|
+
{open && (
|
|
78
|
+
<div
|
|
79
|
+
style={{
|
|
80
|
+
position: 'absolute',
|
|
81
|
+
right: 0,
|
|
82
|
+
top: '100%',
|
|
83
|
+
marginTop: 4,
|
|
84
|
+
minWidth: 140,
|
|
85
|
+
padding: 4,
|
|
86
|
+
borderRadius: 12,
|
|
87
|
+
background: 'var(--ds-background-100)',
|
|
88
|
+
boxShadow: 'var(--ds-shadow-menu, var(--ds-shadow-medium))',
|
|
89
|
+
zIndex: 2001,
|
|
90
|
+
}}
|
|
91
|
+
role="menu"
|
|
92
|
+
>
|
|
93
|
+
{options.map((option) => (
|
|
94
|
+
<button
|
|
95
|
+
key={option.value}
|
|
96
|
+
type="button"
|
|
97
|
+
role="menuitem"
|
|
98
|
+
className="wf-menu-item"
|
|
99
|
+
style={{
|
|
100
|
+
fontWeight: option.value === value ? 500 : 400,
|
|
101
|
+
}}
|
|
102
|
+
onClick={() => {
|
|
103
|
+
onChange(option.value);
|
|
104
|
+
setOpen(false);
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
{option.label}
|
|
108
|
+
</button>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -706,12 +706,14 @@ function PanelResizeHandle({
|
|
|
706
706
|
function TraceViewerFooter({
|
|
707
707
|
hasMore,
|
|
708
708
|
isLive,
|
|
709
|
+
isInitialLoading,
|
|
709
710
|
}: {
|
|
710
711
|
hasMore: boolean;
|
|
711
712
|
isLive: boolean;
|
|
713
|
+
isInitialLoading: boolean;
|
|
712
714
|
}): ReactNode {
|
|
713
715
|
const style = { color: 'var(--ds-gray-900)' };
|
|
714
|
-
if (hasMore) {
|
|
716
|
+
if (hasMore || isInitialLoading) {
|
|
715
717
|
return (
|
|
716
718
|
<div
|
|
717
719
|
className="flex items-center justify-center gap-2 py-3 text-xs"
|
|
@@ -997,9 +999,13 @@ export const WorkflowTraceViewer = ({
|
|
|
997
999
|
isLive={isLive}
|
|
998
1000
|
trace={trace}
|
|
999
1001
|
knownDurationMs={traceWithMeta?.knownDurationMs}
|
|
1000
|
-
hasMoreData={hasMoreSpans}
|
|
1002
|
+
hasMoreData={hasMoreSpans || Boolean(isLoading)}
|
|
1001
1003
|
footer={
|
|
1002
|
-
<TraceViewerFooter
|
|
1004
|
+
<TraceViewerFooter
|
|
1005
|
+
hasMore={hasMoreSpans}
|
|
1006
|
+
isLive={isLive}
|
|
1007
|
+
isInitialLoading={Boolean(isLoading)}
|
|
1008
|
+
/>
|
|
1003
1009
|
}
|
|
1004
1010
|
/>
|
|
1005
1011
|
</TraceViewerWithContextMenu>
|